[原创]逆向学习笔记之汇编
初学逆向,学习滴水的课程整理出的笔记,不足之处敬请各位老师傅指正
十六进制可看作二进制的一种简写形式
数据宽度
计算机中数据有长度限制,超过最高宽度的数据会被丢弃。
1 2 3 4
|
1. 位(bit):一个 0 或 1 2. 字节(byte): 8 位 3. 字(word): 16 位 4. 双字(doubleword): 32 位 |
存储范围
1 2 3
|
1. 字节: 0 ~ 0xFF 2. 字: 0 ~ 0xFFFF 3. 双字: 0 ~ 0xFFFFFFFF |
有符号数和无符号数
无符号数的编码规则
无符号数没有负数,全为正数,这个数是多少就存多少
例:1001 1010=0x9A
有符号数的编码规则
若有符号数最高位是0时为正数,最高位是1时为负数。
1 2 3
|
1. 原码:最高位为符号位,其余各位为数值本身的绝对值 2. 反码:正数:反码与原码相同。负数:符号位为 1 ,其余位对原码取反 3. 补码:正数:补码与原码相同。负数:符号位为 1 ,其余位对原码取反后加 1 |
总结:正数原码存储,负数补码存储。
这个圆,从下面的点开始,逆时针数右半圆为从0到127,顺时针数左半圆为-1到-128
对于一个字节
无符号数:0~FF
有符号数:正数:0~7F,负数:FF~80(因为正数最高位必为0,负数最高位必为1)
计算机运算
位运算
1 2 3 4 5 6 7 8
|
与运算( and &):两个位都为 1 时,结果才为 1 或运算( or |):只要有一个为 1 就是 1 异或运算(xor ^):不一样的时候是 1 非运算( not ~): 0 就是 1 , 1 就是 0 左移(shl <<):各二进制位全部左移若干位,高位丢弃,低位补 0 右移:各二进制位全部右移若干位,低位丢弃,高位补 0 或者补符号位 汇编:shr - - 高位补 0 ,sar - - 高位补符号位 C语言:>>,通过数据类型判断,无符号数补 0 ,有符号数补符号位 |
四则运算
加法
1 2 3 4 5
|
1. 异或运算(无进位时结果与按位加一样) 2. 与运算(判断是否有进位,因为只有两个 1 才会进位) 3. 异或运算( 2 的结果左移 1 位后和 1 的结果进行异或,左移是因为 2 判断了哪里需要进位) 4. 与运算(再次判断是否有进位) 5. 如此重复直到与运算的结果为 0 ,最终结果即为最后一次异或运算的结果 |
减法
1
|
减法就是加法,例如 4 - 5 = 4 + ( - 5 ) |
乘法
1
|
乘法本质就是循环的加法 |
除法
1
|
除法的本质是减法,就是看一个数能减去一个数多少次 |
寄存器
寄存器就是CPU内的存储器(cache)
存储格式:
32位CPU:8 16 32
64位CPU:8 16 32 64
通用寄存器
1 2 3 4 5 6 7 8 9 10
|
16 位是 32 位的低位,两个 8 位组成一个 16 位。 32 位 16 位 8 位 EAX AX AH + AL ECX CX CH + CL EDX DX DH + DL EBX BX BH + BL ESP SP EBP BP ESI SI EDI DI |
MOV指令
1 2
|
1. 把数存到寄存器。例:MOV EAX, 1 2. 把寄存器的值存到另一寄存器。例:MOV EDX,EAX(注意是后面的值赋给前面) |
内存
内存地址
每个内存地址对应1Byte,内存地址大小为32位,前面的0可以省略,表示时用十六进制。
每个应用程序都有独立的4GB空间就是这个大小,但是这并非是完全可用的,使用前需要先申请。
从0x00000000到0xFFFFFFFF
MOV指令
1 2 3 4 5 6 7
|
注意前后数据宽度一致,大于一个字节的数据是从给的编号开始,向后存储。 1. 数到内存,例如:MOV BYTE PTR DS:[ 0018FFF0 ], 1 注:内存空间是要被申请的才行 2. 寄存器到内存,例如:MOV DWORD PTR DS:[ 0018FFFC ],EAX 注:EAX是 4 个字节(DWORD),AX是 2 个字节(WORD),AL是 1 个字节(BYTE) 3. 内存到寄存器,例如:MOV EAX,DWORD PTR DS:[ 0018FFF8 ] 4.MOV WORD PTR DS:[xxxxxxxx], 1 在这个命令中,WORD PTR的作用是指明了要访问的内存单元的大小为 2 字节 DS为内存单元的段地址,因为命令中的内存地址实质上是偏移地址,仅靠偏移地址无法读取,还需要段地址。 |
汇编中绝大多数指令不允许从内存到内存。
内存地址的五种形式
1 2 3 4 5 6
|
前面MOV指令中[]的内容 1. 立即数,例如: 0x13FFC4 2.reg ,reg为 8 个通用寄存器中的任意一个,例如:EAX,此时就是把EAX中的数据当做内存地址 3.reg + 立即数,例如:EAX + 4 4.reg + reg * { 1 , 2 , 4 , 8 },数组赋值的汇编一般是这种形式,例如:EAX + ECX * 4 5.reg + reg * { 1 , 2 , 4 , 8 } + 立即数,例如:EAX + ECX * 4 + 4 |
数据的存储模式
1 2 3
|
大端模式:数据高位存低位,数据低位存高位 小端模式:数据高位存高位,数据低位存低位 X86大多采用小端模式存储,ARM大多采用大端模式存储 |
DTdebug命令
1 2 3
|
db / dw / dd xxxxxxxx db是一个字节为单位查看,dw是两个字节为单位查看,dd是四个字节为单位查看。后跟内存地址 注意:想查看存储模式时,要用db,直接用dd看不出来 |
常用汇编指令
MOV指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
r代表通用寄存器,m代表内存,imm代表立即数,即r8代表 8 位通用寄存器,m8代表 8 位内存,imm8代表 8 位立即数 MOV 目标操作数,源操作数 MOV r / m8,r8 MOV r / m16,r16 MOV r / m32,r32 MOV r8,r / m8 MOV r16,r / m16 MOV r32,r / m32 MOV r8,imm8 MOV r16,imm16 MOV r16,imm32 源操作数:立即数,通用寄存器,段寄存器,内存单元 目标操作数:通用寄存器,段寄存器,内存单元 二者数据宽度要保持一致且不能同时为内存单元 |
ADD指令
1 2 3 4 5 6 7 8 9 10 11 12
|
加法指令,最后结果存在前者中 ADD r / m8,imm8 ADD r / m16,imm16 ADD r / m32,imm32 ADD r / m16,imm8 ADD r / m32,imm8 ADD r / m8,r8 ADD r / m16,r16 ADD r / m32,r32 ADD r8,r / m8 ADD r16,r / m16 ADD r32,r / m32 |
SUB指令
1 2 3 4 5 6 7 8 9 10 11 12
|
减法指令,前者减去后者,结果存入前者 SUB r / m8,imm8 SUB r / m16,imm16 SUB r / m32,imm32 SUB r / m16,imm8 SUB r / m32,imm8 SUB r / m8,r8 SUB r / m16,r16 SUB r / m32,r32 SUB r8,r / m8 SUB r16,r / m16 SUB r32,r / m32 |
AND指令
1 2 3 4 5 6 7 8 9 10 11 12
|
与运算,结果保存到前者 AND r / m8,imm8 AND r / m16,imm16 AND r / m32,imm32 AND r / m16,imm8 AND r / m32,imm8 AND r / m8,r8 AND r / m16,r16 AND r / m32,r32 AND r8,r / m8 AND r16,r / m16 AND r32,r / m32 |
OR指令
1 2 3 4 5 6 7 8 9 10 11 12
|
或运算,结果保留到前者 OR r / m8,imm8 OR r / m16,imm16 OR r / m32,imm32 OR r / m16,imm8 OR r / m32,imm8 OR r / m8,r8 OR r / m16,r16 OR r / m32,r32 OR r8,r / m8 OR r16,r / m16 OR r32,r / m32 |
XOR指令
1 2 3 4 5 6 7 8 9 10 11 12
|
异或运算,结果保留到前者 XOR r / m8,imm8 XOR r / m16,imm16 XOR r / m32,imm32 XOR r / m16,imm8 XOR r / m32,imm8 XOR r / m8,r8 XOR r / m16,r16 XOR r / m32,r32 XOR r8,r / m8 XOR r16,r / m16 XOR r32,r / m32 |
NOT指令
1 2 3 4
|
非运算 NOT r / m8 NOT r / m16 NOT r / m32 |
MOVS指令
1 2 3 4
|
内存与内存间移动数据,使用ESI和EDI寄存器,其中保存的是两个需要操作的内存地址。每次执行后,ESI与EDI都会改变。此改变是加还是减取决于DF为是 0 还是 1 ,加减数取决于指令操作位数。 MOVS BYTE PTR ES:[EDI],BYTE PTR ES:[ESI] 简写为:MOVSB MOVS WORD PTR ES:[EDI],WORD PTR ES:[ESI] 简写为:MOVSW MOVS DWORD PTR ES:[EDI],DWORD PTR ES:[ESI] 简写为:MOVSD |
STOS指令
1 2 3 4
|
将AL / AX / EAX中的值存储到[EDI]指定的内存单元 STOS BYTE PTR ES:[EDI] 简写为:STOSB,对应AL STOS WORD PTR ES:[EDI] 简写为:STOSW,对应AX STOS DWORD PTR ES:[EDI] 简写为:STOSD,对应EAX |
REP指令
1 2 3 4
|
按计数寄存器(ECX)中指定的次数重复执行字符串命令 例如: MOV ECX, 10 REP MOVSD |
堆栈
什么是堆栈?
1 2
|
程序执行的过程中由操作系统分配可使用的一块内存,要是用超了就是堆栈溢出。 ESP寄存器被称为栈指针寄存器,存储了当前的堆栈用到了哪里。 |
堆栈的使用
1
|
堆栈使用时是大地址往小地址用,每次使用后要修改栈顶指针ESP寄存器,防止数据被顶掉 |
PUSH指令
1 2 3 4 5 6 7 8
|
向堆栈压入数据,修改栈顶指针ESP寄存器(减) PUSH imm8 / imm16 / imm32 PUSH r16 / r32 PUSH m16 / m32 例如:PUSH EAX等于 SUB ESP, 4 MOV DWORD PTR DS:[ESP],EAX |
POP指令
1 2 3 4 5 6 7
|
将栈顶数据存到寄存器或内存,修改栈顶指针ESP寄存器(加) POP r16 / r32 POP m16 / m32 例如:POP EAX等于 MOV EAX,DWORD PTR DS:[ESP] ADD ESP, 4 |
修改EIP的指令
EIP中记录的是CPU下一次要执行的地址,不可用普通的MOV指令修改
JMP指令
1 2
|
修改EIP的值 JMP imm32 / r32 / m32 |
CALL指令
1 2
|
修改EIP的值,并把当前指令的下一行地址存入堆栈中(修改了堆栈,所以ESP的值也会改动)。 CALL imm32 / r32 / m32 |
RET指令
1 2 3 4
|
本质上就是 ADD ESP, 4 MOV EIP,[ESP - 4 ] RET后面可以跟一个imm,表示ESP加上这个imm,即在函数返回后再修改一次栈顶指针寄存器,用途为释放函数数据占用的堆栈。 |
反调试
1 2
|
单步步入(F7)单步步过(F8) CALL指令时F7是一步一步执行,F8是全执行完 |
调试器实现原理
1 2 3 4 5 6 7 8
|
断点: 0xCC 单步步入:设置EFLAGS的TF位 单步步过:在下一行设置断点,即无论你CALL有多少指令,最后都要停在CALL的下一行 小小的一个反调试的方法(仅对单步步过有用): 在CALL跳转的位置写: MOV DWORD PTR DS:[ESP], 004183D7 (内存地址随意) RET 这样的话,相当于修改了栈顶的值,使得RET返回时不会回到CALL的下一行,而是跳转到你输入的那个内存地址。 |
反调试思路
1
|
写大量的CALL,大量的无意义代码,使得单步步入非常消耗耐心。 |
汇编眼中的函数
什么是函数?
函数就是一系列指令的集合,为了完成某个会重复使用的特定功能
调用函数:JMP和CALL指令,一般用CALL,因为用RET就能很方便的回来
参数:保存到堆栈,一般调用函数完的返回值保存到EAX
堆栈平衡
1 2
|
1. 如果要返回父程序,则当我们在堆栈中进行堆栈的操作时,一定要保证在RET这条指令之前,ESP指向的是我们压入栈的地址,即RET能正确返回。 2. 如果通过堆栈传递参数了,那么在函数执行完毕后,要平衡常数导致的堆栈变化。例如:先向堆栈压入了函数所用的数据,但是函数执行完成后却没有释放数据占用的堆栈,浪费空间。 |
ESP寻址
1 2 3 4 5
|
ESP - - 栈顶指针寄存器,存储了堆栈现在用到的地方 优点:调用方便 缺点:如果函数同时使用多个寄存器,则寄存器的值要保存入栈,此时ESP的值会发生改变 所以在读取堆栈中所需数据时,要灵活使用ESP + 4 , 8 , 16 等 最后注意堆栈平衡! |
EBP寻址
1 2 3 4 5 6 7 8 9 10 11 12
|
EBP - - 栈底指针寄存器 具体为在进入函数前让EBP的值等于ESP,这样后续存入的数据影响ESP的值 但是我们使用未受影响的EBP进行寻址,同时让ESP减去一个自然数,相当于开辟了一块空间专供此函数使用 PUSH EBP MOV EBP,ESP SUB ESP, 10 XXXXXXXXXXXXXXXXX(函数内容) MOV ESP,EBP POP EBP RET 优点:EBP不会随着你向堆栈添加数据而改变,位置相对固定。 注意:最后也要堆栈平衡。 |
标志寄存器(EFL)
几个重要的标志寄存器
进位标志寄存器(CF carry Flag):如果运算结果最高位产生了一个进位或借位,那么其值位1,否则为0,这个标志通常用来指示无符号整型运算的溢出状态
奇偶标志寄存器(PF Parity Flag):奇偶编制PF用于反映运算结果中“1”的个数的奇偶性,“1”为偶数个则该位置为1,奇数个则为0。通常用于传输数据过程中的奇偶校验。
辅助进位标志寄存器(AF Auxiliary Carry Flag)
一般用于BCD运算中
如果算数操作在结果的第三位发生进位或者错位,则辅助进位标志AF的值
被置为1,否则其值为0:零标志寄存器(ZF Zero Flag):零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为0。在判断运算结果是否为0时,可使用此标志位。(经常与CMP或TEST等指令一起使用)
附:CMP指令相当于SUB指令,但结果仅修改标志寄存器而不把结果保存到第一个操作数中,常用与判断两个值是否相同。TEST指令相当于AND指令,但结果仅修改标志寄存器而不把结果保存到第一个操作数中,常用与判断某个值是否为0.
符号标志寄存器(SF Sign Flag):符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同
溢出标志寄存器(OF Overflow Flag):溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算为数所能表示的范围,则成为溢出,OF的值被置为1,否则,OF的值被清为0
进位标志表示无符号数运算结果是否超出范围
溢出标志表示有符号位运算结果是否超出范围
溢出主要给有符号运算使用的,在有符号的运算中,有如下规律:
正 + 正 = 正,如果结果为负数,则说明有溢出
负 + 负 = 负,如果结果为正数,则说明有溢出
正 + 负 永远都不会有溢出
无符号、有符号都不溢出
最高位进位与溢出的区别:
方向标志寄存器(DF Direction Flag):设置DF标志使得串指令自动递减(从高地址向低地址方向处理字符串),清除该标志则使得串指令自动递增。涉及指令:MOVS,CMPS,SCAS,LODS,STOS。STD及CLD指令分别用于设置及清除DF标志。
JCC指令
[培训] 优秀毕业生寄语:恭喜id咸鱼炒白菜拿到远超3W月薪的offer,《安卓高级研修班》火热招生!!!