学习完PE格式在32bit OS和64bit OS的差异之后,本节探讨64bit & 32bit处理器在逆向工程中一些需要了解的差异性,方便在你想过程中根据实际场景做分析思路的变动。
0x00
32位系统和64位系统有什么区别? 32位和64位表示CPU一次能处理的最大位数,理论上来说,64位系统处理的数据效率比32位更高,相当于单车道和双车道开车似得,双车道单位时间可以有更多的车辆通行。 但需要内存跟上,而且程序本身也是64位编译才能发挥64位系统的优势。
那么问题来了32位编译的程序 & 64位编译出来的程序有些什么需要掌握的差异呢?
差异
CALL|JMP指令
32位的x86系统中,CALL/JMP指令的使用形式为“地址 指令 CALL/JMP”
注意到0x00401000 的FF15 XXXXXXXX指令用于调用API,其中XXXXXXXX位绝对地址,指向IAT区域的某个位置。这在x64中形式一致,但指令解析不同:
- 首先在指令地址的部分,由x86中的4字节变为8个字节
- X86中
FF15
之后跟的4个字节的绝对地址,若在64位中若也采取一样的操作则会造成指令长度的增加(根据64位处理器索引范围正常也应该是8字节),故为了避免指令长度的增加,64处理器在FF15
后的内容也是采取4字节**,不过为了兼容索引范围,将这四个字节的内容解析为相对地址
**,相对地址换算的过程:0x0000000100401000 FF15FA3F0000
0x0000000100401000+3FFA+6 = 0x0000000100405000,计算的是相对于当前指令地址的,指令地址➕偏移➕指令长度(6)
(PS:截图取自《逆向工程核心原理》书中内容)
函数调用约定*
32bit os
32位下的函数调用包含:cdecl
、stdcall
、fastcall
cdecl方式:C/C++默认方式
,参数从右往左依次入栈,主调函数负责栈平衡
stdcall方式:Windows API默认方式
,参数从右往左依次入栈,被调函数负责栈平衡
fastcall方式:这种方式优先选择将参数传入ECX,EDX,多的参数从右往左依次入栈,由于栈位于内存区域,寄存器则是硬件存取,故称fastcall
64bit os
在64位系统中将他们归为一种变形后的fastcall,至多可将4个参数存储到寄存器中传递
若参数个数超过4个,则从第五个开始会存入栈中,此外函数返回时传递参数过程中使用的栈由调用者清理。ps:尽管函数前四个参数由寄存器传递,可在栈中依旧会保留着四个参数对应的空间。(32bytes)
栈&栈桢
(这块理解还不是很深)
64bit OS中栈的大小比函数实际使用的空间要大得多。在调用子函数(Sub Function)时,不再使用PUSH命令来传递参数,而是通过MOV指令
操作寄存器与预定的栈位置来传递。
使用**VC++**创建的x64程序代码中几乎看不到PUSH/POP指令,并且创建栈桢时也不再使用RBP寄存器,而是直接使用RSP,优点是:调用子函数时不需要改变栈指针(RSP),函数返回时也不需要清理栈指针,如此可大幅提升程序的运行速度。
处于深化对二者不同的理解,将如下源代码编译成不同的版本可执行程序
1 |
|
32bit version
前边提到32位系统下,调用windows API默认使用的stdcall方式,并且栈由被调用API处理。这里需要关注的过程是API调用过程中的32位调用特征:一是栈桢的设置,二是函数的参数使用过程中栈的清理方。
main函数的入口在0x0040116A特征是main需要三个参数,找到连续三个push,在此之前的指令是系统在设置程序需要的环境,只有完成这一步,才能由main接管程序执行,否则会出现程序致命性错误。
跟进main里边
首先可以看到指令地址还是属于用户区域的,另外API CreateFIleA是ascii的版本,所用的7个参数都使用栈来传递。
push指令进行过后的栈情况
跟进0x00401017,来到0x765D3F40,该区域不属于用户区,是核心区
跟进
1 | 75E2F9D2 55 push ebp |
接着后边的指令调用了CreateFileW API这是unicode版本,参数一致,还是那7个,不同的是存储方式使用unicode
并且相比第一次入栈的方式不同,这里使用的是第一次入栈后用栈桢来定位的,ss:[ebp+0xXX],运行至0x0075E2F9FC,观察栈的情况:
随后在API进行retn前有个leave指令,用于清理栈,恢复栈桢到调用函数前的状态
64bit version
Windbg 64bit下的视图:
从CreateFile API的位置看得到栈桢操作并不是32bit下的操作ebp的方式:
1 | ;32bit OD下视图 |
64bit是直接操作RSP栈顶指针的,使用sub rsp, 48h
开辟的栈空间大小是48H,也就是72bytes,在函数retn前使用平栈操作 add rsp, 48h
,在观察传参的行为:xor r9d,r9d
清空r9寄存器的低四字节内容。
ps:64处理器中对于通用寄存器R8-R15,使用在寄存器后加flag标识高低位,b表示一字节,w表示二字节,d表示四字节。
从入栈的位置可以看出即使前四个参数使用寄存器传递,但栈中依旧保留了空间,rsp~rsp+20h