第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 即可。

原文地址:https://www.cnblogs.com/Rev-omi/p/13541045.html