基础汇编知识以及 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