第4章:逆向分析技术--32位软件逆向技术
启动函数
Win32 应用程序源码中,都会有一个 WinMain 函数.但 Windows 程序的执行并非是从 WinMain 函数开始的.这段代码由编译器生成,完成进程初始化.
函数返回值后,进行一些必要的处理,最后调用 ExitProcess() 函数.
函数
call 指令会奖其之后的指令地址压栈, ret 指令用于结束函数的执行,但是并不是所有的 ret 都标志着函数的结束.
①利用栈传递参数:
C/C++ 和 MFC 程序默认使用 __cdecl ,由调用者负责清除栈.
stdcall 是 Win32 API 的默认方式,由被调用者清理栈.部分 API ( wsprintf )采用 __cdecl 方式.
esp 是栈指针,所以一般使用 ebp 来存取栈,此时 [ ebp + xx ] 是使用参数, [ ebp - xx ] 是使用局部变量.若编译器开启了优化,则会直接使用 esp 来存取栈中数据.使用指令 add esp,8 即可清除局部变量,最后使用 ret 8 (相当于 ret ; add esp, 8 ),平栈.
enter 指令 == push ebp ; mov ebp, esp ; sub esp, xxx .
leave 指令 == add esp, xxx ; pop ebp .
②利用寄存器传递参数
大多数都采用 Fastcall 规范. Visual C++ 在编译时, 左边两个不大于4字节的参数放入 edx 和 ecx . 其余的从右至左压栈.浮点值, 远指针, __int64 类型使用栈传递.
thiscall 调用规范是 C++ 非静态类成员函数的默认调用约定,同样使用寄存器传递参数,并由被调用者平栈. 每个对象隐含接受一个通过 ecx 传递的额外参数-- this 指针.
注意此处 this 指针传递的时机,不要看错。
③名称修饰约定
为了允许使用操作符和函数重载, C++编译器会改写函数名称.
通过传值调用的参数,会创建副本传入函数;通过传引用的参数,会直接传入参数的地址。
数据结构
①局部变量
1#. 利用栈存放
通过命令 push ecx 和 sub esp, n 都可以实现。
2#. 通过寄存器存放
寄存器不够用时就会使用栈。
②全局变量
一般存放在 .data 节区,通过硬编码进行寻址,放在可以读写的区块里(在只读区块则为常量)。
③数组
一般是通过 “基址+变址 ” 寻址。
虚函数
C++ 中的虚函数,在程序运行时定义的函数,在编译时不能确定,在调用时才确定。
所有对虚函数的引用通常放在一个专用数组 —— 虚函数表( Virtual Table ,VTBL )。调用虚函数时,先取出虚函数表指针(Virtual Talble Pointer,VPTR)。
C++ 中 this 指针的传递是隐含的。
控制语句
1# .IF —— Else语句
在编译代码时,选择优化,不会使用 cmp 指令。
2#. Switch - Case 语句
如果 case 的取值表示一个算术级数,那么编译器会利用一个 跳转表(Jump Table)来实现。
注意跳转地址的计算。
neg 指令改变 CF 位, sbb 指令带 CF 位进行减法运算。
数学运算符
①加减法
lea 指令允许用户在一个时钟内完成加减法运算。
②乘法
③除法
除法的运算代价很高,比乘法大约多出10倍的CPU时钟。
2E8BA2E9 = 2 • ( 1/11 • 232 ). 232 代表了寄存器大小 方便其溢出。
而多乘一个 2 是为了防止计算时,数值刚好小了一点,避免在计算 11/11 时出现 0.9xxxxx 的情况。
得出结果后,只需将存放溢出的寄存器的值 /2 即可。