返回值传递
在 32 位(x86)逆向工程中,理解返回值的传递机制是分析函数功能的关键。虽然不同的调用约定(Calling Convention)在参数压栈顺序上有所不同,但在如何返回结果这一点上,通常遵循非常一致的硬件规范。
1. 核心通道:寄存器传递
对于大多数基本数据类型,返回值并不通过栈传递,而是直接存储在特定的寄存器中。
-
整数与指针(32位):这是最常见的情况。函数执行完毕后,返回值会存储在
EAX寄存器中。 -
64 位整数(long long):由于 32 位寄存器放不下 64 位数据,编译器会联合使用两个寄存器:
EDX:EAX。其中EDX存储高 32 位,EAX存储低 32 位。 -
浮点数:通常通过浮点寄存器栈的栈顶
ST(0)传递。
2. 特殊情况:大对象(结构体)的返回
如果函数返回一个很大的结构体(超过 8 字节),寄存器放不下,传递机制会发生显著变化:
-
调用者预留空间:调用者(Caller)会在自己的栈帧上预先开辟一块足够大的临时空间。
-
隐藏参数:调用者会将这块空间的地址作为一个“隐藏参数”压入栈中。
-
被调用者填充:被调用者(Callee)执行逻辑后,将结果拷贝到该地址指向的内存。
-
返回地址:最后,
EAX依然会被使用,但它存储的是指向那块内存的地址,而不是具体数值。
3. 逆向中的实战观察
当你使用 x32dbg 调试你编写的程序时,可以按照以下步骤观察返回值:
-
观察
CALL之后:在调试器中,单步执行(F8)跳过一个函数调用后,立刻观察右侧寄存器窗口的EAX。此时EAX的值就是该函数的return结果。 (依据:用户使用 x32dbg 进行逆向练习) -
识别布尔值:如果一个函数返回布尔值(bool),你会发现执行完后
EAX通常是1(true) 或0(false)。 -
函数指针回溯:有时你会看到
mov eax, [ebp-4]紧跟着ret,这说明函数将局部变量的值移动到了EAX中准备返回。 (依据:用户对局部变量压栈顺序的疑问)
4. 总结对比表
| 返回值类型 | 存放位置 | 逆向关注点 |
|---|---|---|
| int / char / 指针 | EAX |
执行 ret 前最后一次对 EAX 的赋值 |
| long long (64位) | EDX:EAX |
同时观察这两个寄存器的组合 |
| 大型结构体 (struct) | 内存 + EAX (地址) |
寻找调用前压入的“隐藏”指针参数 |
| void (无返回) | 无 (EAX 保持原样) | EAX 的值通常是函数内最后一次运算的残余 |