汇编
汇编语言
简介
工具
- DTDebug
位运算
- 进制
操作符
| 位运算 | |
|---|---|
| and | 与 |
| or | 或 |
| not | 非 |
| xor | 异或 |
| shl | 左移 |
| shr | 右移,高位补0,C语言使用unsigned类型时,就会补0 |
| sar | 右移,高位补符号位,signed |
存储
通用寄存器
- 计算机可以在三个地方存储数据,cpu、内存、硬盘。寄存器就是用于cpu存储数据的。
寄存器组成
- 包含8个通用寄存器可以存储任意数据。
- 16位、32位的寄存器都指向了同一个地址,只是赋值时,影响的位数不同。
- 8位寄存器,近用了ABCD四个寄存器,将高8位和第八位拆成了AL、BL、CL、DL、AH、BH、CH、DH,8个寄存器。
| 32位通用寄存器 | 16位通用寄存器 | 8位通用寄存器 | 用途 |
|---|---|---|---|
| EAX | AX | AL | 作为返回值 |
| EBX | BX | BL | |
| ECX | CX | CL | 计数寄存器 |
| EDX | DX | DL | |
| ESP | SP | AH | 栈指针寄存器,栈顶指针 |
| EBP | BP | BH | 栈底指针 |
| ESI | SI | CH | 串赋值的源地址 |
| EDI | DI | DH | 出赋值的目的地址 |
寄存器读写
| 指令 | |
|---|---|
| 立即数到寄存器 | MOV EAX,1 |
| 寄存器到寄存器 | MOV EAX,EBX |
内存
- 对于32位寻址空间的计算机,内存最大可支持4GB
- 由于内存太大了,没办法起名字,只能用地址编号代替
内存读写
- 汇编指令中,绝大多数指令,不允许从内存到内存。可以通过寄存器进行存储。
- MOVS指令可以实现内存到内存的移动。
| 指令 | |
|---|---|
| 立即数到内存 | MOV BYTE ptr ds:[0018FFF0], 1 |
| 寄存器到内存 | MOV DWORD ptr ds:[0018FFF0], EAX |
| 内存到寄存器 | MOV EAX,DWORD ptr ds:[0018FFF0] |
| 写入的宽度 | |
|---|---|
| BYTE | 一字节 |
| WORD | 二字节 |
| DWORD | 四字节 |
内存地址的表示
- 5中形式
| 表达形式 | ||
|---|---|---|
| 立即数 | 0x13FFC4 | MOV EAX,DWORD PTR DS:[0x13FFC4] |
[reg] |
八个寄存器的任意一个,读取里面的地址 | MOV EAX,DWORD PTR DS:[ECX] |
[reg+立即数] |
寄存器+立即数,立即数表示了偏移量 | MOV EAX,DWORD PTR DS:[ECX+4] |
[reg+reg*{1,2,4,8}] |
寄存器+寄存器*2的倍数,后者也代表偏移量 | MOV EAX,DWORD PTR DS:[ECX+EAX*4] |
[reg+reg*{1,2,4,8}]+立即数 |
寄存器+寄存器*2的倍数+立即数 | MOV EAX,DWORD PTR DS:[ECX+EAX*4+4] |
内存存储模式
- 大端模式:数据高位在低位,数据低位在高位
>>> 0x1A2C 高位-->低位 |
- 小端模式:数据高位在高位,数据低位在低位
>>> 0x1A2C |
常用指令
写指令
| 指令 | ||
|---|---|---|
| MOV a,b | b存储到a | |
| ADD a,b | 加法,a += b | |
| SUB a,b | 减法,a -= b | |
| AND a,b | 与,a &= b | |
| OR a,b | 或,a | = b |
| NOT a,b | 非,a != b | |
| XOR a,b | 异或,a ^= b | |
| MOVS a,b | 内存b存储到内存a,并a和b的指针自动+1或者-1(取决于标志位D),适合拷贝MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] |
|
| MOVSB a,b | 简写形式,移动目标为byte | |
| MOVSW a,b | 简写形式,移动目标为word | |
| MOVSD a,b | 简写形式,移动目标为dword | |
| STOS BYTE PTR ES:[EDI] | 将AI、AX、EAX的值存储到EDI指定内存单元,指针自动+1或者-1 | |
| STOSB | 简写形式,移动目标为byte | |
| STOSW | 简写形式,移动目标为word | |
| STOSD | 简写形式,移动目标为dword | |
| REP | 重复执行,执行次数取决于ECX的值,16进制:MOV ECX 10REP MOVSD a,b |
|
| CMP a,b | 相当于SUB,判断是否为0,只是CMP不对a进行更改 | |
| TEST a,b | 相当于AND,判断是否为0,只是TEST不对a进行更改 |
堆栈
- 程序开启后,系统会分配堆栈空间,用于存储中间状态的值。这块空间如果程序用完,就会发生栈溢出异常。
- 堆栈从高地址开始使用,直到低地址。
- ESP寄存器保存了栈指针。
| 指令 | ||
|---|---|---|
| PUSH mem/reg/立即数 | 将reg或者立即数存入堆栈,ESP减4,根据压入的是什么 | |
| POP reg/mem | 弹出,ESP加4 |
EIP寄存器
- cpu下一次执行的地址,EIP修改只能通过JMP、CALL修改
| 指令 | ||
|---|---|---|
| JMP reg、立即数、mem | EIP更改值,mem必须是dword | |
| CALL reg、立即数、mem | EIP更改值,并保护现场,放入堆栈,栈顶-4 | |
| RET | EIP更改为堆栈,恢复现场,栈顶+4 |
反调试
Fake F8
-
调式中F7单步步入,F8是单步步过
-
调试原理:
- 在某一行下断点,实际是将对应的语句改为了
int 3,对应的十六进制是CC,中断,停到调试器 - 单步执行的原因是在EFL的TF标志位为1,cpu就会进入单步执行模式
- 在某一行下断点,实际是将对应的语句改为了
-
F7是单步执行,而F8在遇到CALL指令时,会在下一个语句改为
int 3,并将当前指令存到堆栈中。 -
Fake F8欺骗的是进入到CALL中时,修改了ESP的值,此时返回时,F8就失效了
-
在程序中CALL比较多的时候,想跳过CALL就无法实现了,这样给反调试带来困难。
函数
函数调用
- 一般不用JMP,无法返回原来的位置
| 指令 | ||
|---|---|---|
| CALL reg、立即数、mem | EIP更改值,并保护现场,放入堆栈,栈顶-4 | |
| RET | EIP更改为堆栈,恢复现场,栈顶+4 |
函数参数
- 参数放到寄存器中,返回值放在EAX中
- 参数也可以放到堆栈中,要注意堆栈平衡
ESP寻址
- 数据放入堆栈后的寻址,由于存储的栈指针,所以计算相对麻烦
EBP寻址
- 通过改变栈底指针,可以避免新增数据导致的地址便宜计算。
PUSH 1 |
JCC指令
标记寄存器
- 标记寄存器状态的变化是由于数据计算引起的。

- CF:最高有效位发生借位,则置1。用于判断无符号数运算是否溢出
- PF:结果的最低有效字节包含偶数个1,则置1。用于奇偶校验检查
- AF:辅助进位操作,在结果的第3位发生进位或借位时置1。在BCD算数运算中使用
- ZF:运算结果为0,则置1。与CMP和TEST指令一起使用,查看是不是0
- SF:符号位,有符号整型的最高位
- OF:溢出标志位。用于判断有符号数运算是否溢出。
- DF:方向标志位,地址由低向高,则为0
JCC指令
| 指令 | 说明 | 判断条件 |
|---|---|---|
| JE JZ | 结果为零则跳转(相等时跳转) | ZF=1 |
| JNE JNZ | 结果不为零则跳转(不相等时跳转) | ZF=0 |
| JS | 结果为负则跳转 | SF=1 |
| JNS | 结果不为负则跳转 | SF=0 |
| JP JPE | 结果中1的个数为偶数则跳转 | PF=1 |
| JNP JPO | 结果中1的个数为奇数则跳转 | PF=0 |
| JO | 结果溢出了则跳转 | OF=1 |
| JNO | 结果没有溢出则跳转 | OF=0 |
| JB JNAE | 小于则跳转(无符号数) | CF=1 |
| JNB JAE | 大于等于则跳转(无符号数) | CF=0 |
| JBE JNA | 小于等于则跳转(无符号数) | CF=1 OR ZF=1 |
| JNBE JA | 大于则跳转(无符号数) | CF=0 AND ZF=0 |
| JL JNGE | 小于则跳转(有符号数) | SF!=OF |
| JNL JGE | 大于等于则跳转(有符号数) | SF=OF |
| JLE JNG | 小于等于则跳转(有符号数) | ZF=1 OR SF!=OF |
| JNLE JG | 大于则跳转(有符号数) | ZF=0 AND SF=OF |
