基础汇编知识以及 OD 的使用。
数值表示
二进制-b(binary)、十进制-d(decimalism)、十六进制-h(hexadecimal)
字长:
- bit:位
- byte:字节–1byte=8bit
- word:字–1word=2byte=16bit
- dword:双字–1dword=2word=4byte=32bit
- qword:四字–1qword=2dword=4word=8byte=64bit
通用寄存器
- EAX:累加器(accumulator),是加法乘法指令的缺省寄存器。还可以用来存储函数返回值
- ECX:重复 REP 和 LOOP 指令的计数器(counter)
- EDX:用于存放整数除法产生的余数
- EBX:在内存寻址时用来存放基地址(base)
- ESP:当前线程的栈顶指针,压入栈的数据越多,ESP 越小,每入栈一次减小 4 字节
- EBP:当前线程的栈底指针
- ESI/EDI:源/目标索引寄存器,字符串操作中,DS:ESI 指向源串,ES:EDI 指向目标串。
- EIP:存放下一个 CPU 指令的内存地址,执行完后读取下一指令
标志寄存器
- CF:进位标志(可检查无符号操作是否溢出)
- OF:零标志
- SF:符号标志
- PF:溢出标志(补码溢出)
寄存器寻址
- 立即数寻址:
MOV EAX, 123H - 寄存器寻址:
MOV EAX, EBX(EBX 中存放操作数) - 直接寻址:
MOV EAX, [12345678H](操作数以[]为地址) - 寄存器间接寻址:
MOV EAX, [EBX](操作数的地址为 EBX 中存储的值)
基本汇编指令
数据传输
| 指令 | 作用 |
|---|---|
| MOV | 赋值 |
| PUSH | 入栈 |
| POP | 出栈 |
| LEA | 取地址 |
| MOVSX | 符号传送 |
| PUSHAD | 将所有 32 位通用寄存器压入栈 |
| POPAD | 将所有 32 位通用寄存器取出栈 |
算术运算
| 指令 | 作用 |
|---|---|
| ADD | 加法 |
| INC | 自加 |
| SUB | 减法 |
| DEC | 自减 |
| CMP | 比较 |
| MUL | 乘法 |
| DIV | 除法 |
| IDIV | 符号整除 |
| IMUL | 符号乘法 |
| NEG | 求补 |
逻辑运算
| 指令 | 作用 |
|---|---|
| AND | 与运算 |
| OR | 或运算 |
| NOT | 非运算 |
| XOR | 异或运算 |
| TEST | 与运算(只对标志位修改,对操作数没有影响) |
| SHL | 逻辑左移 |
| SAL | 算术左移 |
| SHR | 逻辑右移 |
| SAR | 算术右移 |
| ROL | 循环左移 |
| ROR | 循环右移 |
| RCL | 进位循环左移 |
| RCR | 进位循环右移 |
转移指令
| 指令 | 作用 |
|---|---|
| JMP | 跳转 |
| JA | 大于时跳转(>) |
| JNA | 不大于时跳转(<=) |
| JAE | 大于等于时跳转(>=) |
| JB | 小于时跳转(<) |
| JNB | 不小于时跳转(>=) |
| JBE | 小于等于时跳转(<=) |
| JE | 相等时跳转(==) |
| JNE | 不等于时跳转(!=) |
| JNBE | 不小于等于时跳转(>) |
| JG | 大于时跳转(有符号)(>) |
| JNG | 不大于时跳转(有符号)(<=) |
| JGE | 大于等于时跳转(有符号)(>=) |
| JL | 小于时跳转(有符号)(<) |
| JNL | 不小于时跳转(有符号)(>=) |
| JLE | 小于等于时跳转(有符号)(<=) |
| JNGE | 不大于等于时跳转(有符号)(<) |
| JNLE | 不小于等于时跳转(有符号)(>) |
| JZ | ZF 为 0 时跳转 |
| JNZ | ZF 不为 0 时跳转 |
| JS | 有符号时跳转 |
| JNS | 无符号时跳转 |
JGE:Jump if Greater or Equal
循环指令
| 指令 | 作用 |
|---|---|
| LOOP | 循环(改变 ECX 的值) |
| JCXZ | 循环(不改变 ECX 的值) |
串指令
| 指令 | 作用 |
|---|---|
| MOVS[B/W/D] | 传送字节串/字串/双字串 |
| CMPS[B/W/D] | 比较字节串/字串/双字串 |
| SCAS[B/W/D] | 扫描字节串/字串/双字串 |
| LODS[B/W/D] | 加载源变址字节串/字串/双字串 |
| STOS[B/W/D] | 保存字节串/字串/双字串 |
| REP | 重复 |
其他指令
| 指令 | 作用 |
|---|---|
| INT | 终止程序 |
| CALL | 调用函数 |
| RET | 过程返回 |
| NOP | 空 |
| CLD | 方向清零 |
OD 初探
静态调试与动态调试:
静态调试就是在不执行程序的情况下,人工地对源代码的语法和逻辑分析;动态调试则是在编译、链接、运行的整个过程中,观察如寄存器内容、函数执行情况等状态来分析调试
- L(og):日志信息
- E(xecute modules):模块信息
- M(emory map):内存映射信息
- T(hreads):线程信息
- W(indows):窗口信息
- H(andles):句柄信息
- C:当前线程上下文
- K:调用链信息
- ……
OD 调试快捷键
| 快捷键 | 功能 |
|---|---|
| ctrl+g | 跳转到指定位置 |
| ctrl+e | 编辑指定区域 |
| space | 编辑汇编代码 |
| f4 | 执行到光标位置处 |
| f2 | 断点(Int3) |
| ; | 添加注释 |
| : | 添加标签名 |
| * | 返回到正在运行的地方 |
| -(+) | 返回到上(下)一个光标处 |
| enter | 跟随跳转/跟入调用内部 |
| f3 | 打开一个新的可执行程序 |
| ctrl+f2 | 重新运行当前调试的程序 |
| f9 | 运行选定的程序进行调试 |
| f12 | 暂时停止被调试程序的执行 |
| f7 | 单步进入被调试程序的 call 中 |
| f8 | 步过被调试程序的 call |
| ctrl+f9 | 执行直到返回 |
第一次调试(helloworld.exe)
关于 PE 文件的 EntryPoint。打开 CFF,将 PE 文件拖入:

其中,ImageBase 和 AddressOfEntryPoint 指向的地址即为 EntryPoint
找到 main 函数
- 代码执行:通过 F7 单步步入,一步一步执行到弹框跳出时,进入 main 函数
- 层层推进:通过 F8 单步步过,快速跳过每个函数,跳出弹框后,进入 main 函数
- 字符串检索:根据弹框上方的字符串,右键智能搜索查找,找到后直接进入 main 函数
- API 检索法:通过运行程序可以判断文件句柄是 MessageBox,在模块中找到后依次设置断点,运行程序,在主函数断电处停下
- 特征法:根据 C 语言的反汇编特征进行判断
修改内容




Fact.exe

jge 表示大于或等于时跳转。当输入数字 n 时,EDX 存放值 n,ECX 中的值初始化为 1,[local.3] 中的值也为 1,每次循环时,ECX 中的值自加一,[local.3] 的值为自身再乘上 ECX 中的值。当 ECX 等于 EDX 时跳出循环,而不再进行下一次乘法,最后 [local.3] 中的值为(n-1)!,故应该将 jge 修改为 jg 即可
参考网站
https://www.cnblogs.com/qq78292959/archive/2012/07/20/2600865.html
https://blog.csdn.net/qq_34717555/article/details/77727176
https://blog.csdn.net/hanchaoman/article/details/9187093