逆向工程核心原理——第三十七章
x64处理器
64位系统中的内存地址为64位(8个字节),所以使用64位大小的指针。所以含有的绝对地址(AV)的指令大小比原来增加了4个字节。
x64系统中,通用寄存器的大小扩展到了64位(8个字节),数量也增加到了18个(新增加了R8~R15寄存器)。x64系统下的所有通用寄存器的名称均以字母“R”开头(x86以字母“E”开头)。
call/jmp指令
在原来x86系统中,FF15 XXXXXXXX指令用于调用API,其中XXXXXXXX表示绝对地址,指向IAT区域的某个位置。
x64系统指令地址从原来的4字节变为现在8个字节,在x86中FF15(CALL)指令后跟着4个字节的绝对地址。若x64中也在FF15后跟随绝对地址,这样在FF15垢面应该跟着8个字节的绝对地址,这样指令的长度就增加。为了防止地址增加,x64系统中指令后面仍然跟着4个字节大小的地址,不过是这4字节大小的地址被解析成“相对地址(RVA)”。
绝对地址和相对地址转化方法为:
函数调用约定
在32位系统中,主要有三种cdecl、stdcall、fastcall,但64位系统中它们都统一为一种变形的fastcall。64位fastcall中可以把4个参数存储到寄存器中,如果超过4个参数,则会将从第5个开始的参数通过栈传递。
栈和栈帧
windows 64 os中使用栈和栈帧的方式也发生了变化。调用子函数时,不再使用PUSH命令,而是通过MOV指令操作寄存器与预定的栈来传递。
练习:
我们通过将下面的代码分别编译成x64、x86,并对其进行观察。
Stack32.cpp\Stack64.cpp
#include <stdio.h>
#include<Windows.h>
void main()
{
HANDLE hFile = INVALID_HANDLE_VALUE;
hFile = CreateFileA(
"c:\\work\\ReverseCore.txt",
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
}
}
Stack32.cpp
首先我们进入main函数可以看到我们传递的参数:
可以看到其中的特征:
1.不使用栈帧。
2.调用子函数时使用栈传递参数。
3.使用PUSH指令压入栈的函数参数不需要main()函数清理。
我们知道stdcall方式调用win32 API时,是由被调用者清理栈。因此我们运行到调用CreateFileA函数,查看栈:
可以看到,这些参数全被压入了栈,然后我们进入CreateFileA():
可以看到,在CreateFileA()中,使用了栈帧,在调用CreateFileW()之前将参数压入了栈,并且在返回之前清理了栈。
我们可以看到CreateFileA()和CreateFileW()参数是相同的,但是占了两份的栈。
Stack64.cpp
这里我们看到,在开始时,分配了48H的栈空间,而在ret之前,关闭了48H的栈空间,这就相当于是使用了一个变形的栈帧。
我们可以看到,64位中不像32位经常使用push/pop指令,第14个参数使用寄存器传递(RCX,RDX,R8,R9),第57个参数使用栈。
我们运行到CreateFileA(),就可以看到参数被传递进了栈: