软件安全逆向——寻址方式
寻址方式
[!IMPORTANT]
对于汇编主要指令的复习要会判断指令的合法性和判断基本运算(主要考察选择题)
立即数:直接嵌入在汇编指令中的常数
可理解
寻址方式就是处理器根据指令中给出的地址信息来寻找有效地址的方式,是确定本条指令的数据地址(操作数寻址)以及下一条要执行的指令地址(指令寻址)的方法。
指令寻址
顺序寻址:由于指令地址在内存中按顺序安排,当执行一段程序时,通常是一条一条指令地顺序进行。
跳跃寻址:当程序执行转移指令时,指令的寻址就采取跳跃寻址方式。所谓跳跃,就是指下条指令的地址由本条转移指令给出。
操作数寻址(会判别)
形成操作数的有效地址的方法称为操作数的寻址方式。【汇编中表示例如:MOV 目的操作数,源操作数】
- 立即寻址:指令给出操作数本身 【MOV CL,05H(表示将数值存到CL存储器中)】
- 寄存器寻址:操作数存在寄存器中【MOV CL,AL】
- 直接寻址:指令中直接给出操作数的有效地址【MOV AL,[2100H]】(地址默认来自DS存储器,也可以指定ES:[2100H])
- 寄存器间接寻址:操作数的有效地址是寄存器的值【MOV AX,[BX]】
- 寄存器相对寻址:源操作数位置是寄存器值加指令给定的偏移【MOV AX,[DI+122H]或MOV AX,122H[DI]】
- 基址变址寻址:基址地址加变址地址【MOV EAX,[EBX+ESI]】
- 相对基址变址寻址:前一方式基础上增加偏移【MOV EAX,[EBX+ESI+1000H]】
主要指令
数据传送指令
MOV语法:【MOV 目的操作数,源操作数】
[!CAUTION]
目的操作数和源操作数的长度要一致;(盒子大小一致)
目的操作数不能为立即数;
目的操作数和源操作数不能都为内存。(不能在两个箱子之间搬,得有一个放手里(寄存器))
MOVSX语法:【MOVSX 目的操作数,源操作数】(带符号扩展)
[!CAUTION]
目的操作数长度必须大于源操作数;(小盒子的东西放大盒子里)
目的操作数必须为寄存器,例mov bx,0x800;movsx eax,bx →eax=0xffff800
源操作数不能为立即数。
MOVZX语法:【MOVZX 目的操作数,源操作数】(高位补0扩展,当作无符号数处理)
注意事项同上
LEA语法:【LEA 目的操作数,源操作数】
取地址送入寄存器
[!CAUTION]
目的操作数必须为寄存器;
源操作数必须为内存
XCHG语法:【XCHG 目的操作数,源操作数】
将源操作数和目的操作数交换
[!CAUTION]
目的操作数和源操作数长度一致;
目的操作数和源操作数都不能为立即数;
目的操作数和源操作数不能都为内存。
PUSH语法:【PUSH 操作数】
将操作数压入栈顶:对于32位操作数,先修改ESP=ESP-4,再修改[ESP]=操作数
[!IMPORTANT]
对于PUSH ESP,要先修改[ESP-4]=ESP,再修改ESP=ESP-4(先保存原栈顶地址,再扩展放进去)
POP语法:【POP 操作数】
将操作数弹出栈顶:对于32位操作数,先修改操作数=[ESP],再修改ESP=ESP+4
[!IMPORTANT]
同样的,POP ESP时,要先ESP=ESP+4 再修改ESP=[ESP-4];
操作数为内存时必须指明类型(byte ptr/word ptr/dword ptr)【操作数不得为立即数】
PUSHFD/POPFD【保存/恢复CPU工作状态】
将EFlags(x86的状态标志寄存器,存储了 CPU 的运行状态)入/出栈
PUSHAD语法:
按照EAX,ECX,EDX,EBX,ESP(取PUSHAD之前的值),,EBP,ESI,EDI顺序入栈
POPAD语法:
与前一个相反,不过ESP(是恢复到PUSHAD之前的值)
算术运算指令
加法指令:ADD,ADC 减法指令:SUB,SBB 将操作数的值加1/减1 :INC,DEC
乘法指令: MUL,IMUL 除法指令: DIV,IDIV 将操作数符号反向:NEG
ADD/SUB语法:将目的操作数加上/减去源操作数
ADC/SBB语法:在前一条基础上需要加上/减去CF位的当前值(带进位)
[!NOTE]
此类加减指令会影响ZF,OF,CF,SF的值。目的操作数和源操作数不能都为内存,目的操作数不能为立即数
INC/DEC语法:将操作数加1/减1
此类指令不会影响到CF。操作数不能为立即数。
NEG语法:
操作数不能为立即数,对四个值都有影响。
MUL语法:
计算无符号数乘法,如果乘积的高半部分不为0,则MUL将CF和OF置为1(可用于决定是否忽略乘积的高半部分);
操作数不能为立即数,且为内存时需指明类型。
IMUL语法1:【IMUL 操作数】
有符号数乘法,若乘积的高半部不是低半部分的符号扩展,则IMUL将CF和OF置1(可用于决定是否忽略高半部分);
操作数不能为立即数,且为内存时需指明类型。
IMUL语法2:【IMUL 操作数1,操作数2】(三操作数同理)
双操作数格式会按照操作数1的大小来截取乘积,如果被丢弃的部分不是乘积的符号扩展,则OF和CF置1;因此在执行双操作数的指令后必须检查这些标志位中的1个
类似的,DIV/IDIV语法:【做除法】
位运算指令
- AND/OR/XOR运算
对两个数进行逻辑与/或/异或运算,结果保存到目的操作数,运算后会清空OF,CF,根据结果设置SF和ZF
- NOT语法
逻辑非运算,结果保存到操作数,不影响标志位
- TEST语法
相较于前者,也是对两个数进行逻辑与运算,仅是不保存结果
- SHR语法
对目的操作数进行逻辑右移运算,将目的操作数向右移动源操作数次,最高位用0填充【源操作数为8bit/CL,将最后一位写入CF】
- SHL/SAL语法
对目的操作数进行逻辑/算术左移运算,最低为0填充,同样的最后一位写入CF。
- SAR语法
算术右移,最高位用最高位填充,其他的同上
- ROR语法
循环右移,移出的位填充到最高位,对于源操作数为8bit/CL,最后移动的1位写入CF
- ROL
循环左移,移出的位填充到最低位,对于源操作数为8bit/CL,最后移动的1位写入CF
- RCR
循环右移,高位用CF补充(移出去的放CF里,CF的拿去填充,最后剩下的留作CF)
- RCL
循环左移,低位用CF补充(移出去的放CF里,CF的拿去填充,最后剩下的留作CF)
程序流程控制指令
- CMP
将目的操作数减去源操作数,结果不保存,对4个标志位都有影响;操作数不能都为内存,目的操作数不能为立即数
- JMP
转到操作数指定的地址执行(把操作数赋值给EIP)【无条件转移】
- LOOP
已不建议使用,ECX-1,值与0比较,不等于0则跳转到操作数指定的地址执行,否则不跳转(-128~127)
- CALL
先将EIP的值压入栈顶,然后转到操作数指定的地址执行
- RET
将栈顶弹出到EIP(返回)
需掌握
x64指令集的函数调用
- 调用函数之前,在RSP和RBP之间预留空间:
4个参数以内:32字节
4个参数以上:参数个数*8字节
- 调用函数时传递参数:
第1、2、3、4个函数使用寄存器传递,分别为RCX、RDX、R8、R9;
超出4个的直接用mov指令保存到RSP和RBP之间预留的空间【例如将第5个指令x保存:mov [rsp+20h],x】
栈帧状态判断
EBP指栈底(高地址),ESP指栈顶(低地址),从右到左把参数入栈【因此对于f(int a,int b),a的地址比b的高】;
返回地址通过call压入EIP的值来实现,用于跳转回函数入口处的地址。
在保存主函数栈帧EBP的值的条件下,通过噶癌变EBP和ESP寄存器的值来为add函数分配了栈帧空间,此时[x]=[ebp+4];
如果要恢复,则
mov esp,ebp ;栈顶指向主函数的EBP
pop ebp ;恢复到主函数的EBP
ret ;根据返回地址恢复EIP的值,相当于pop EIP
add esp,8 ; 栈平衡
mov dword ptr[n],eax ;返回值初始化
第三章 结

