滴水-反汇编
数据宽度
1、定义
数学上的数字,是没有大小限制的,可以无限的大。但在计算机中,由于受硬件的制约,数据都是有长度限制的(我们称为数据宽度),超过最多宽度的数据会被丢弃。
2、计算机中常见的数据宽度
1)位(BIT): 0 //1
2)字节(Byte): 00000000 //8
3)字(Word): 0000000000000000 //16
4)双字(Doubleword):00000000000000000000000000000000 //32
3、存储范围
字节(Byte): 0 ~ 0xFF
字(Word): 0 ~ 0xFFFF
双字(Doubleword): 0 ~ 0xFFFFFFFF
无符号数与有符号数
1、无符号数的编码规则
| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 |
|---|
0x9A
154
2、有符号数的正数编码规则
| 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 |
|---|
有符号数的首位为符号位,0为正数,1为负数
0x1A
26
原码反码补码
1、有符号数的编码规则
- 原码:最高位为符号位,其余各位为数值本身的绝对值
- 反码:
- 正数:反码与原码相同
- 负数:符号位为1,其余位对原码取反
- 补码:
- 正数:补码与原码相同
- 复数:符号位为1,其余位对原码取反+1
2、举例说明
-1
1 0 0 0 0 0 0 1 原码
1 1 1 1 1 1 1 0 反码
1 1 1 1 1 1 1 1 补码 //FF
-7
1 0 0 0 0 1 1 1 原码
1 1 1 1 1 0 0 0 反码
1 1 1 1 1 0 0 1 补码 //F9
3、总结
1)正数原(补)码存储
2)负数补码存储
- 假设数据宽度为1 BYTE(8 BIT)
无符号数:0 1 2 3 … FF(10进制255)
有符号数: - 正数:0 … 7F
- 负数:FF … 80
计算机运算
1、与运算
两个位都为1时,结果才为1
例如:
1011 0001
1101 1000 and(&)
-—————
1001 0000
2、或运算
只要有一个为1就是1
例如:
1011 0001
1101 1000 or( | )
-—————
1111 1001
3、异或运算
不一样时为1
例如:
1011 0001
1101 1000 xor(^)
-—————
0110 1001
4、非运算
0就是1 1就是0
例如:
1101 1000 not(~)
-—————
0010 0111
5、左移
各二进制位全部左移若干位,高位丢弃,低位补0
例如:
shl(<<) 1101 1000 左移2位:0110 0000
6、右移
各二进制位全部右移若干位,低位丢弃,高位补0或符号位
例如:
shr(>>) 1101 0101 右移2位补0:0011 0101
C语言:
1 | unsigned int a=10; |
sar(>>) 1101 0101 右移2位补符号位:1111 0101
C语言:
1 | int a=10; |
7、总结
计算机只会做位运算
计算机加法
4+5=?的运算过程
0000 0100
0000 0101
-——–加
0000 1001
计算机无法做加法,只能进行位运算:
1)异或
0000 0100
0000 0101 xor(^)
-————–
0000 0001
2)判断是否有进位
0000 0100
0000 0101 and(&)
-—————
0000 0100
3)继续异或
0000 0001
0000 1000 xor(^) //and运算后的结果shl一位
-—————
0000 1001
4)判断是否有进位
0000 0001
0000 1000 and(&)
-—————
0000 0000
结果:0000 1001
通用寄存器
1、寄存器
存储数据:
CPU > 内存 > 硬盘
32位CPU:8 16 32
64位CPU:8 16 32 64
2、通用寄存器
32位通用寄存器:
EAX ESP
ECX EBP
EDX ESI
EBX EDI
3、MOV指令
<1>立即数到寄存器
<2>寄存器到寄存器
mov 位置1,位置2 //将位置2的数据复制到位置1
4、通用寄存储器
| 通用寄存储器 | ||
|---|---|---|
| 32位 | 16位 | 8位 |
| EAX | AX | AL |
| ECX | CX | CL |
| EDX | DX | DL |
| EBX | BX | BL |
| ESP | SP | AH |
| EBP | BP | CH |
| ESI | SI | DH |
| EDI | DI | BH |
8位寄存器,L(low)低八位,H(high)高八位
同位寄存储器之间才可以相互复制(MOV)
内存
1、每个应用程序都会有自己的独立的4GB内存空间
进程A/B均为虚拟内存,真正使用时需投射到物理内存上发挥作用
1 Byte=8Bit 1KB=1024Byte 1MB=1024KB 1GB=1024MB
2、内存地址
<1>内存太大只能使用编号,当我们向内存中存储数据,或者从内存中读取数据时,必须用到这个编号
<2>这个编号又称为内存地址(32位,前面0可以省略)
[0x00000000]—[0xFFFFFFFF]
<3>一个内存地址通常只能存储一个字节(1 Byte=8 BIT)
<4>内存地址从小到大是从上到下排列,成一倒置的梯形玻璃杯状
3、MOV指令
<1>立即数到内存
<2>寄存器到内存
<3>内存到寄存器
//<4>内存到内存
MOV 数据宽度 (PTR DS) : 内存地址,位置1 //将相同宽度的位置1的数据复制到内存中
- 内存地址是连续使用的
内存地址的5种形式
1、形式一[立即数]
- 读取内存的值:
MOV EAX,DWORD PTR DS:[0x13FFC4]
//从内存地址0x13FFC4开始读取一个DWORD大小的数据存入EAX寄存器中 - 向内存中写入数据:
MOV DWORD PTR DS:[0x13FFC24],EAX
2、形式二[reg]
[reg]代表寄存器 可以是8个通用寄存器中的任意一个
寄存器中存储着内存地址
- 读取内存的值:
MOV ECX,0x13FFD0
MOV EAX,DWORD PTR DS:[ECX] - 向内存中写入数据:
MOV EDX,0x13FFD8
MOV DWORD PTR DS:[EDX],0x87654321
3、形式三[reg+立即数]
- 读取内存的值:
MOV ECX,0x13FFD0
MOV EAX,DWORD PTR DS:[ECX+4] - 向内存中写入数据:
MOV EDX,0x13FFD8
MOV DWORD PTR DS:[EDX+0xC],0x87654321
4、形式四[reg+reg*{1,2,4,8}]
- 读取内存的值:
MOV EAX,13FFC4
MOV ECX,2
MOV EDX,DWORD PTR DS:[EAX+ECX*4] - 向内存中写入数据:
MOV EAX,13FFC4
MOV ECX,2
MOV EDX,DOWORD PTR DS:[EAX+ECX*4],87654321
5、形式五[reg+reg*{1,2,4,8}+立即数]
- 读取内存的值:
MOV EAX,13FFC4
MOV ECX,2
MOV EDX,DWORD PTR DS:[EAX+ECX*4+4] - 向内存中写入数据:
MOV EAX,13FFC4
MOV ECX,2
MOV DWORD PTR DS:[EAX+ECX*4+4],87654321
小端存储模式
1、存储模式
- 大端存储:数据高位在低位(内存地址),数据低位在高位
0x1A2C3E4F→1A 2C 3E 4F - 小端存储:数据低位在低位(内存地址),数据高位在高位
0x1A2C3E4F→4F 3E 2C 1A
常用汇编指令
字母含义表:
| 字母 | 含义 |
|---|---|
| r | 通用寄存器 |
| m | 内存 |
| imm | 立即数 |
| r8 | 8位通用寄存器 |
| m8 | 8位内存 |
| imm8 | 8位立即数 |
1、MOV指令
1 | 1.MOV r/m8,r8 |
2、ADD指令
1 | 1.ADD r/m8,imm8 |
3、SUB指令
1 | 1.SUB r/m8,imm8 |
4、AND指令
1 | 1.AND r/m8,imm8 |
5、OR指令
1 | 1.OR r/m8,imm8 |
6、XOR指令
1 | 1.XOR r/m8,imm8 |
7、NOT指令
[!note]
对操作数中的每一位进行逻辑“非”运算
1 | 1.NOT r/m8 |
8、MOVS指令
移动数据 内存-内存
固定为 将ESI存储的内存地址中的数据移动到EDI对应的内存地址
1 | MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 简写:MOVSB |
MOVS指令每执行一次,两个目标地址都会自增1/2/4 (Byte)
EFLAGS第10位:DF位为方向标志,==0时为+,1时为-==
9、STOS指令
将寄存器EAX对应数据大小的值存储到[EDI]指定的内存单元
1 | STOS BYTE PTR ES:[EDI] 简写:stosb |
STOS指令每执行一次,目标地址都会自增
//与MOVS指令修改的寄存器相同,均为EDI寄存器
10、REP指令
按计数寄存器(ECX)中指定的次数重复执行字符串指令
REP + 指令
堆栈相关指令
1、什么是堆栈
1)就是一块内存,操作系统在程序启动的时候已经分配好的,供程序执行时使用
//本质上就是内存,只不过是系统规划好的更加便于操作的内存区域
2)和数据结构的堆栈无关
3)查看堆栈
2、栈指针寄存器
ESP存储了当前的堆栈用到哪里
3、堆栈的使用
<1>存储数据
<2>修改栈顶指针(栈顶指针指向当前元素)
堆栈使用时是从大地址到小地址
- 局部变量一定要赋初始值
4、PUSH指令
<1>向堆栈中压入数据
<2>修改栈顶指针ESP寄存器(变小,即向上移动)
1 | 1、PUSH r32 |
5、POP指令
<1>将栈顶数据存储到寄存器/内存
<2>修改栈顶指针ESP寄存器(增加,即向下移动)
1 | 1、POP r32 |
修改EIP指令
- EIP中存储的值是CPU执行下一次命令的地址
1、JMP指令
1 | MOV EIP,r/m/imm |
简写为:JMP r/m/imm
//不影响堆栈
2、CALL指令
1 | push下一行地址 |
简写为:CALL r/m/imm
- 与JMP唯一区别:
在堆栈中存储 反汇编窗口处CALL指令下一行的地址
//可以结合RET指令,实现随时随地调用目标函数
3、RET指令
1 | ADD ESP,4 |
简写为RET //相当于POP EIPRET imm效果等价于
1 | RET |
*反调试之Fake F8
1、单步步入/过
单步步入(F7) 单步步过(F8)
<1>单步步入与单步步过的区别:
单步步入:设置EFLAGS的TF位,每一次执行设置一个断点
单步步过:在下一行设置断点
<2>调试器实现原理:
断点:0xCC(int 3)
2、反调试思路
堆大量CALL指令,CALL指令中塞满无用代码(花指令)
汇编眼中的函数
1、什么是函数
函数就是一系列指令的集合,为了完成某个会重复使用的特定功能
2、函数的调用
<1>用JMP来执行函数
<2>用CALL来执行函数
3、参数与返回值
一般用EAX存储返回值
//参数就是需要操作的数据,返回值就是操作后的结果数据
堆栈传参_堆栈平衡
1 | MOV EAX,DWORD PTR DS:[ESP+4] |
EAX一定会取得堆栈中的参数
1 | MOV EAX,DWORD PTR DS:[ESP+14] |
实现多个参数的相加
堆栈平衡
1)如果要返回父程序,则当我们在堆栈中进行堆栈的操作的时候,一定要保证在RET这条指令之前,ESP指向的是我们压入栈中的地址(如CALL指令存储的地址)
2)如果通过堆栈传递参数了,那么在函数执行完毕后,要平衡参数导致的堆栈变化
ESP寻址
1、寄存器传参与堆栈传参
举例:
寄存器传参:MOV r,r
堆栈传参:push imm
2、ESP寻址
1 | push 1 |
3、ESP寻址的缺点
由于ESP指向栈顶的值,而堆栈会被频繁使用,受各种指令影响
导致ESP寻址不适用于过于复杂的代码
EBP寻址
ESP栈顶指针、EBP栈底指针
利用EBP存储原先ESP的值,再利用相对固定的EBP寻址,不会受到函数指令影响
1 | push 1 |
JCC
1、标志寄存器EFLAGS
2、CF(bit 0)
[Carry flag]
若算术操作产生的结果在最高有效位发生进位或借位则将其置1,反之清零
[!tip] 这个标志通常用来指示无符号整型运算的溢出状态
[!example]+
MOV AL,0xFE
ADD AL,2
或
MOV AL,0x7F
SUB AL,0xFF
3、PF(bit 2)
[Parity flag]
如果结果的最低有效字节(1 Byte)包含偶数个1位则该位置1,否则清零
[!tip] 利用PF可进行奇偶校验检查
[!example]+
MOV AL,0CE
ADD AL,0
4、AF(bit 4)
[Auxiliary Carry flag]
如果算术操作在结果的第3位发生进位或借位则将该标志置1,否则清零
[!tip] 这个标志在BCD算术运算中被使用
5、ZF(bit 6)
[Zero flag]
若结果为0则将其置1,反之清零
[!tip] 经常与CMP或者TEST等指令一起使用
[!example]+
判断两个值是否相等
MOV EAX,100
MOV ECX,100
CMP EAX,ECX
(CMP指令相当于SUB指令,但相减结果并不保存到第一个操作数中)
[!example]+
判断某个值是否为0
TEST EAX,EAX
(TEST指令相当于AND指令,但是与的结果并不保存到第一个操作数中)
6、SF(bit 7)
[sign flag]
该标志被设置为有符号整型的最高有效位
[!tip] 0为正,1为负
[!example]+
MOV AL,0x7F
ADD AL,2
或
MOV AL,0xFE
ADD AL,2
7、OF(bit 11)
[Overflow flag]
溢出标志OF用于反映有符号数加减运算所得结果是否溢出
[!tip]
如果是无符号数运算,是否溢出看CF位
如果是有符号数运算,是否溢出看OF位
[!example]+
MOV AL,0x7F
ADD AL,2
8、DF(bit 10)
[Direction flag]
这个方向标志控制串指令。设置DF标志使得串指令自动递减(从高地址向低地址方向处理字符串),清楚该标志则使得串指令自动递增
[!tip] STD以及CLD指令分别用于设置以及清楚DF标志
9、JCC指令
JCC指令仅看当前EFLAGS的值,与其他指令无关联
只要EFLAGS的值符合JCC指令条件,则跳转
| JCC指令 | 条件 | EFLAGS |
|---|---|---|
| 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 |


