预测函数内参数的内存位置

逆向工程 调试 风袋
2021-06-22 11:23:49

我正在尝试编写一个脚本来帮助显示在函数中传递的参数的内存内容。例如,在下面的函数中,第一个参数从 EAX 寄存器开始。打印 EAX 的内容为我们提供了第一个参数的值。我们如何打印以下所有参数?

BOOLAPI FtpCommandA(

HINTERNET hConnect,

BOOL fExpectResponse,

DWORD dwFlags,

LPCSTR lpsz命令,

DWORD_PTR dwContext,

HINTERNET *phFtp命令

);

谢谢!

2个回答

根据Microsoft Docsx64Windows的 ABI如下所示:

默认情况下,x64 应用程序二进制接口 (ABI) 使用四寄存器快速调用调用约定。在调用堆栈上分配空间作为被调用者保存这些寄存器的影子存储。函数调用的参数和用于这些参数的寄存器之间存在严格的一一对应关系。任何不适合 8 个字节或不是 1、2、4 或 8 个字节的参数都必须通过引用传递。单个参数永远不会分布在多个寄存器中。

前四个整数参数在寄存器中传递。整数值分别在 RCX、RDX、R8 和 R9 中按从左到右的顺序传递。参数 5 和更高的参数在堆栈上传递。所有参数在寄存器中都是右对齐的,因此被调用者可以忽略寄存器的高位并只访问寄存器的必要部分。

前四个参数中的任何浮点和双精度参数都在 XMM0 - XMM3 中传递,具体取决于位置。通常用于这些位置的整数寄存器 RCX、RDX、R8 和 R9 将被忽略,除了 varargs 参数的情况。

__m128 类型、数组和字符串永远不会通过立即值传递。相反,一个指针被传递给调用者分配的内存。大小为 8、16、32 或 64 位以及 __m64 类型的结构和联合被传递,就好像它们是相同大小的整数一样。其他大小的结构或联合作为指向调用者分配的内存的指针传递。

您还可以在该网站上找到更全面的描述和示例。

为了了解它在实践中是如何工作的,我们可以使用该FtpCommandA函数编写简单的程序并像这样调用它:

FtpCommandA(NULL, false, 1, "command", 2, NULL);

反汇编这个简单的程序后,你会看到: x64CallingConvention

您可以注意到,确实,前四个参数使用寄存器传递rcx而后两个参数使用堆栈传递。rdxr8r9

另一种可以实现的方法是使用 Stacktrace 函数,然后打印参数的位置 - 像这样:

dprintf( "%08p %08p %08p %08p %08p %s",
             stk[i].FramePointer,
             stk[i].ReturnAddress,
             stk[i].Args[0],
             stk[i].Args[1],
             stk[i].Args[2],
             Buffer
             );