第一章/第二章 分析Hello World!程序
OD基本命令
-
Restart Ctrl+F2 重新开始调试
-
Step Into F7 执行语句会进入函数内部
-
Step Over F8 执行语句不会进入函数内部
-
Execute till Return Ctrl+F9 一直在函数代码内部运行,直到遇到retn跳出
(跳出该命令函数)
OD中右边注释中的红字部分是 代码调用的API函数名称 是VS为了保证程序正常运行自动添加的,源码中并没有,是VS的启动函数,可以暂时忽略。
RETN指令,它用于返回函数调用者的下一条命令,一般是被调用的函数的最后一句
●找到Helloworld!的main()主函数 未完成 没找到
OD调试器指令 | ||
---|---|---|
Go to | Ctrl+G | 移动到指定地址,用来查看代码或者内存,运行时不能用 |
Execute till Cursor | F4 | 执行到光标位置,即直接转到要调试的地址 |
Comment | ; | 添加注释 |
User-defined commet | 右键菜单Search for User-defined comment | |
Label | : | 添加标签 |
User-defined label | 右键菜单Search for User-defined label | |
Set/Reset BreakPoint | F2 | 设置或取消断点(BP)(BP就是断点 |
Run | F9 | 运行(若设置了断点,则执行至断点处) |
Show the current EIP | * | 显示当前EIP(命令指针)位置 |
Show the previous Cursor | - | 显示上一个光标的位置 |
Preview CALL/JMP address | Enter | 若光标处有CALL/JMP等指令,则跟踪并显示相关地址(运行时不可用) |
在菜单栏选择View-Breakpoints选项(快捷键ALT+B)打开Breakpoints窗口,该窗口列出代码中设置的断点。双击会跳到相应位置。
右键菜单Search for User-defined comment。可以看见所有添加的注释。双击会跳到相应位置。
找主函数的方法
<1>
1、 从头到尾地执行 直到消息窗口弹出 弹出消息窗口的就是主函数 F8
2、 第二遍 一直执行到弹出消息窗口的函数前 F7 进入 函数内部
<2>查找字符串
Tips
VC++中,static字符串会被默认保存为Unicode码形式,static字符串是指在程序内部被硬编码的字符串。
代码与数据所在的区域是彼此分开的。
<3>API检索
Helloworld!.exe这个程序会出弹窗
弹窗调用了user32.MessageBoxW()API
所以我们直接 右键 查找所有模块间的调用(All intermodular calls)该窗口会列出程序中调用的所有API
找 MessageBoxW()
<4>
上法的缺点: OD不能为所有.exe都列出API函数调用列表。使用压缩器/保护器工具对软件进行压缩或者保护后,文件结构机会改变。因此OD就不能列出所有API函数列表了。
Tips
压缩器:压缩软件的代码、数据、资源等,压缩后仍是.exe
保护器:不仅具有压缩功能还能反调试、反模拟、反储存..能够有效保护进程
DLL代码库被加载到进程内存后,可直接向DLL代码库添加断点。看不懂
API是操作系统对用户应用程序提供的一系列函数,实现于系统文件夹中的*.dll文件内部。
我们编写的应用程序执行某种操作时,必须使用OS提供的API向OS提出请求,然后与被调用API对应的系统DLL文件就会被加载到应用程序的进程内存。
该过程可以通过,OD菜单中的View-Memory(Alt+M),打开内存映射窗口,观察到。
若HelloWorld.exe应用程序中调用了MessageBoxW()API,则调用时程序运行到该处就会停止。
1、 OD中的Name in all modules(所有模块名称) 命令可以列出被加载的DLL文件中提供的所有API。名称排序。找到MessageBoxW。
2、 下断点 F2
3、 继续执行 F9 直到遇见MessageBoxW()代码的断点 自动停
4、 此时的ESP值对应一个返回地址 HelloWorld.exe的main()函数调用完MessageBoxW()之后,程序会回到该地址处。Ctrl+F9可以直接让MessageBoxW()到RETN处。F7也可以。
用打补丁的方式修改字符串
打补丁 可以修BUG 还可以加新功能
<1>直接修改字符串 缓冲区
右下 要选HEX/ASCII
直接智能搜索到 相应的字符串处 数据窗口跟随—>立即数
在ASCII区域选中 Ctrl+E 直接更改 保存到可执行文件 保存文件
此法的缺点在于不能比原字符串长,最好别
<2>程序中未被使用的NULL填充区域
找一块空地儿 Ctrl+E 修改
找到原字符串的地方修改汇编指令 空格键修改
把地址改成新地方的地址
保存
汇编语言基础指令 | |
---|---|
CALL XXXX | 调用XXXX地址处的函数 |
JMP XXXX | 跳转到XXXX地址处 |
PUSH XXXX | 保存XXXX到栈 |
RETN | 跳转到栈中保持的地址 |
术语和说明
VA | 进程的虚拟地址 |
---|---|
OP code | CPU指令 |
PE | windows可执行文件(EXE、DLL、SYS等) |
第三章 小端序标记法
字节序 是多字节数据在计算机内存中存放的字节顺序 主要分为 小端序 和 大端序
BYTE | b= | 0x12 |
---|---|---|
WOED | w= | 0x1234 |
DWORO | dw= | 0x12345678 |
char | str[]= | “abcde” |
大端序与小端序的不同比较
TYPE | NAME | SIZE | 大端序类型 | 小端序类型 |
---|---|---|---|---|
BYTE | b | 1 | [12] | [12] |
WOED | w | 2 | [12][34] | [34][12] |
DWORD | dw | 4 | [12][34][56][78] | [78][56][34][12] |
char[] | str | 6 | [61][62][63][64][65][00] | [61][62][63][64][65][00] |
Tip
字符串最后是以NULL结尾的
大端序存储数据时,内存地址低位 存储 数据的高位
小端序存储数据时,内存地址高位 存储 数据的高位
小端序是高位存高位 这是一直逆序存储的方式 保持的字节顺序被倒转
字符串在字符数组里存着,字符数组在内存中是连续的,所以这个时候无论是大小端序,存储的顺序都是一样的。
第四章IA-32寄存器基本讲解
寄存器是CPU内部用来存放数据的一些小型存储区域
基本程序运行寄存器
1、 通用寄存器8 通常用来保存常量与地址
2、 段寄存器6
3、 程序状态与控制寄存器1
4、 指令指针寄存器1 EIP
16-bit | 32-bit | |||
---|---|---|---|---|
AH | AL | AX | EAX | 累加器(针对操作数和结果数据) |
BH | BL | BX | EBX | 基址寄存器(DS段中的数据指针) |
CH | CL | CX | ECX | 计数器(字符串和循环操作) |
DH | DL | DX | EDX | 数据寄存器(I/O指针) |
BP | EBP | 拓展基址指针寄存器(SS段中栈内数据指针) | ||
SI | ESI | 源变址寄存器(字符串操作源指针) | ||
DI | EDI | 目的变址寄存器(字符串操作目标指针) | ||
SP | ESP | 栈指针寄存器(SS段中栈指针) |
<1>通用寄存器↓
ESP指示栈区域的栈顶地址,PUSH、POP、CALL、RET指令可以直接用来操作ESP
EBP表示栈区域的基地址,函数被调用时,保存ESP的值,函数返回时再把值返回ESP,保证栈不会崩溃。(栈帧技术)。
ESI和EDI与特定指令(LODS、STOS、REP、MOVS等)一起使用,主要用于内存复制。
装串 保存串 串传送
<2>段寄存器
段是一种内存保护技术,它把内存分段,保护内存。它还同
一起用于将虚拟内存变更为实际物理内存。段内存记录在SDT中,而段寄存器就持有这些SDT的索引。
段寄存器
CS | 代码段寄存器 | 存放应用程序代码所在段的段基址 |
---|---|---|
SS | 栈段寄存器 | 存放栈段的段基址 |
DS | 数据段寄存器 | 存放数据段的段基址 |
ES | 附加(数据)段寄存器 | 存放程序使用的附加数据段的段基址 |
FS | 数据段寄存器 | 同上 |
GS | 数据段寄存器 | 同上 |
FS常在程序调试中用到,用于计算SEH(结构化异常处理)、TEB(线程环境块)、PEB(进程环境块)等地址。
<3>标志寄存器EFLAGS
EFLAGS(32位)是由FLAGS(16位)扩展来的。
每位值都有意义。
初级阶段掌握 ZF(零标志)、OF(溢出标志)、CF(进位标志)即可。
ZF 运算结果0或1,True或False
OF 有符号整数溢出时,OF为1。MSB改变时,其值也为1。
CF 无符号整数溢出时,CF为1。
Tip
Jcc(条件跳转)指令要检查这3个标志的值,并根据其值决定是否执行某个动作。
<4>EIP指针指令寄存器
保存着CPU要执行的指令地址,由IP寄存器拓展的
EIP是不能直接修改的
程序运行时CPU读EIP中的一条指令的地址,EIP寄存器的值自己增加,CPU每次执行完一条指令,就会通过EIP寄存器读取并执行下一条指令。
第五章 栈
栈内存的作用
暂存函数内的局部变量、调用函数时传递函数参数、保存函数返回后的地址
栈是后进先出的
栈顶指针ESP初始状态指向栈底端
第六章 分析abex’crackme#1
大多数crackme小程序都让我们猜测序列号
如果直接用汇编语言写程序,汇编代码会直接变成反汇编代码
INC | 值加1 |
---|---|
DEC | 值减1 |
JMP | 跳转到指定地址 |
CMP | 比较给定的两个操作数*与SUB命令类似,但操作数的值不会改变,仅改变EFLAGS寄存器(若2个操作数的值一致,SUB结果为0,ZF被置为1) |
JE | 条件跳转*若ZF为1就跳转 |
第七章 栈帧
栈帧:利用EBP寄存器访问栈内局部变量、参数、函数返回地址等手段
调用函数时,先把用作基准点的ESP值保存到EBP,并维持在函数内部。所以是以EBP的值作为基准编写程序的。
在执行完
PUSH EBP
MOV EBP ESP
两条命令后函数main()的栈帧就生成好了(设置好EBP了)
调用某一个函数之前要先使用PUSH把参数a、b压入栈,要清理栈就是让ESP+参数的字节数,就能把它们从栈中清理掉。
Tips
被调函数执行完毕后,函数的调用者Caller负责清理存储在栈中的参数,这被称为cdecl方式
Caller复制清理保存在栈中的参数,这被称为stdcall方式
这些规则统称为调用约定Calling Convention
Return 0的汇编写法可以为XOR EAX,EAX
两个相同的值进行异或运算结果为0
这样写比MOV EAX,0更快,所以常用来初始化寄存器
第八章abex’crackme#2
VB文件的特点 VB文件使用名为MSVBVM60.dll的VB专用引擎
可以编译为本地代码(N code)和伪代码(P code)
VB主要用来编写GUI程序
VB使用的各种信息以结构体的形式保存在文件内部
VC++、VB编译器通常用间接调用的方法,调用ThunRTMain()函数,这个函数在它的专用引擎中
TEST:逻辑比较
与AND一样(仅改变EFLAGS寄存器而不改变操作数的值),若2个操作数中一个为0,则AND运算结果被置为0->ZF=1
TEST AX,AX 是为了检测AX是否为0,这是汇编语法的特征
例子:
TEST AX,AX
JE 403408
If(AX==0)
Goto 403408
JE:条件跳转
若ZF=1,则跳转
第九章 进程管理工具
Process explorer
Sysinternals 上面那个的迷你版
第十章 函数调用约定
函数调用约定:对函数调用时如何传递参数的一种约定。
调用函数前要先把参数压入栈再传递给函数,栈就是定义在进程中的一小段内存空间。进程运行时,栈内存大小才确定。
函数执行完毕后,栈中的参数不必管。因为存在栈里的是临时存的,下一次再用这个栈存东西的时候,会覆盖掉之前的参数。
函数执行完毕后,ESP的值要恢复到函数调用之前。ESP是用来指示栈当前的位置的,栈内存是固定的,所以ESP不恢复初始的话,这个栈就不能用了。
主要函数的约定有如下三种:
优点在于
Cdecl | 调用者处理栈 | 可以向被调用函数传递长度可变的参数 |
---|---|---|
Stdcall | 被调用者清理栈 | 被调用者函数内部存在着栈清理代码,使得代码尺寸更小 |
Fastcall | 与上类似,不同在于该方式通常会使用寄存器去传参 | 可以实现对函数的快速调用 |
C语言默认使用Cdecl的方式
Stdcall常用于Win32 API Win32 API虽然是c语言写的库但用的是Stdcall的方式,这可以使C语言之外的其他语言也能直接调用API
第十一章 视频讲座
www.tuts4you.com 的公示板上有40个crackme讲座可供练习
要去除消息框
可以清理栈 但要注意调用rtcMsgBox()函数后返回值要是1(表示确定的按钮)
找到跳出该弹窗的命令 CALL XXXX修改
正确命令如下
ADD ESP,14 14是传递参数的大小
MOV EAX,1
还可以从根头跳过rtcMsgBox()函数 直接改函数开头
PUSH EBP
MOV EBP,ESP
修改成
RETN 4
此处根据要传递的参数大小调整栈 RETN XX
__vbaStrCmp()API 是VB中比较字符串的函数