You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GCC编译二进制冗余寄存器值问题及二进制修改故障咨询

关于x64汇编中GCC编译逻辑与radare2二进制修改的疑惑

问题描述

我正在学习逆向工程与x64汇编,编写了如下C测试程序:

#include <stdio.h>
int square(int num) {
    return num * num;
}
int main() {
    int ans = square(5);
    printf("%d", ans);
}

该程序中square函数的编译汇编代码(GCC默认优化等级)为:

push rbp
mov rbp,rsp
mov DWORD PTR [rbp-0x4],edi
mov eax,DWORD PTR [rbp-0x4]
imul eax,eax
pop rbp
ret

我有两个疑惑:

  1. 为何GCC要将参数edi先存入栈中再加载回eax计算,而非直接执行imul edi, edi?
  2. 使用radare2修改二进制后程序返回随机值,恳请解释原因及修改错误点。

一、为什么GCC要将edi存入栈再加载回eax计算?

这主要和GCC的默认优化策略、调试友好性以及x86-64 System V ABI的规范有关:

  • 默认优化等级(-O0)的特性:GCC在默认-O0优化下,会生成便于调试的代码,而非最精简的执行代码。它会把函数参数保存到栈帧的局部变量区域(这里的[rbp-0x4]就是square函数的局部变量槽),这样在调试时,你可以通过查看栈帧直接看到传入的num参数值,而不需要依赖寄存器的状态。
  • 栈帧的标准化结构:即使函数不需要局部变量,GCC也会建立完整的栈帧(push rbp + mov rbp,rsp)并保存参数到栈上,这是为了保持函数调用栈的一致性,方便栈回溯和调试器解析调用栈信息。
  • 寄存器返回值的要求:x86-64 System V ABI规定整数返回值必须存在eax寄存器中。如果直接执行imul edi, edi,结果会存在edi里,之后还需要额外的mov eax, edi指令把结果移到返回寄存器。在-O0下,GCC选择了更直观的“保存参数→加载参数→计算→返回”流程,而非追求指令数量最少。

如果你开启-O1及以上优化等级,GCC会生成更精简的代码,比如直接:

push rbp
mov rbp,rsp
mov eax,edi
imul eax,eax
pop rbp
ret

甚至在更高优化等级下,会省略栈帧建立的指令,直接用寄存器完成计算。


二、radare2修改二进制后返回随机值的原因及错误点

出现随机值的核心问题是指令长度不匹配导致的代码偏移错误,或者栈帧/寄存器状态被破坏,具体分析如下:

常见错误点

  1. 指令字节数不匹配,未填充NOP
    原来的三条指令:

    mov DWORD PTR [rbp-0x4],edi  ; 3字节机器码:89 7D FC
    mov eax,DWORD PTR [rbp-0x4]  ; 3字节机器码:8B 45 FC
    imul eax,eax                 ; 3字节机器码:0F AF C0
    

    总长度是9字节。如果你直接替换成imul edi, edi + mov eax, edi,这两条指令的总长度是5字节(0F AF FF + 8B C7),剩下的4字节会变成“垃圾指令”,这些指令会被CPU错误执行,破坏寄存器或栈的状态,最终导致程序返回随机值。

  2. 破坏了栈帧或返回流程
    如果修改时误改了push rbp、mov rbp,rsp、pop rbp或ret这些栈帧相关指令,会导致栈指针(rsp)和基指针(rbp)的状态混乱。比如pop rbp被改成其他指令,会导致rsp没有正确恢复,ret指令执行时会从错误的栈地址读取返回地址,进而跳转到随机内存区域,返回不可预测的值。

正确的修改方法

要确保修改后的指令总长度和原指令一致,多余的字节用nop(机器码0x90)填充:

  1. 在radare2中定位到square函数的汇编代码,计算原指令的总字节数。
  2. 将原三条指令替换为:
    mov eax,edi       ; 2字节:8B C7
    imul eax,eax      ; 3字节:0F AF C0
    nop               ; 1字节:90
    nop               ; 1字节:90
    nop               ; 1字节:90
    nop               ; 1字节:90
    
    这样总长度还是9字节,不会影响后续的pop rbp和ret指令的执行位置。
  3. 保存修改后的二进制文件,重新运行即可得到正确结果。

内容的提问来源于stack exchange,提问作者Julia van der Kris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 17:22:46