了解汇编中堆栈上的程序参数

逆向工程 部件 C amd64 争论
2021-07-01 02:20:12

为了自学逆向工程,我正在编写小型 C 程序并逆向它们,以便了解编译器如何看待我的代码。但是,我很难理解堆栈概念wrt 命令行参数。

所以对于像这样的基本 c 代码:

int main(int argc, char** argv){

    if(argc < 2){
        printf("1 argument needed!");   
    } else {
        printf("\n -- %s Entered", argv[1]);
        printf("\n%s -- is the first argument\n", argv[0]);
    }
    return 0;
}

转换如下:

IDA Pro 免费 DSM 代码

评论部分是我遇到困难的地方:

var_10= qword ptr -10h
var_4= dword ptr -4
push    rbp ; understandable due to convention
mov     rbp, rsp ; same as above
sub     rsp, 10h ; why the space allocation? 
mov     [rbp+var_4], edi ; ?
mov     [rbp+var_10], rsi ; ?
1个回答

首先,您必须了解所有这些事情都有一个规范。这些规范从一种汇编语言到另一种,从一种操作系统到另一种不同。

这些全局规范被称为应用程序二进制接口 (ABI),其中定义了我们称之为函数的“调用约定”。IDAPro 似乎发现您的程序遵循cdecl约定,但我怀疑它是否正确,我认为此处使用的调用约定fastcallamd64SystemV ABI 中(请参阅有关调用约定的维基百科页面)。我的猜测是您正在使用 Linux ...

因此,对rpb的操作rsp只是为了保存堆栈帧的先前状态,以便在您离开函数时恢复它(您将 的内容堆叠rbp在堆栈上,希望您能够在你最后离开了这个功能)。但是,你已经明白了这一点。

空间分配 ( sub rsp, 10h) 来自这样一个事实,即您必须存储4 个字节的一个int( argc) 和一个指向8 个字节char*( argv) 的指针我知道,添加时它只有 12 个字节而不是 16 个字节 ( 10h)。但是,当它们对齐时,CPU 在内存中的访问得到了优化(意味着它们从 2 的幂的地址开始,或者,如果考虑十六进制表示,地址必须以 结尾0)。因此,编译器决定四舍五入对齐数据所需的内存并更有效地获取它。

然后,您在堆栈上有可用的内存空间,现在让我们去获取数据。因此,为此您必须在启动我们当前正在查看的函数之前知道调用者的功能是什么。

事实上,前面的函数在调用我们所在的函数之前,已经将我们函数的参数存储在一些寄存器中。在这里,重要的是要同意所有函数(调用者和被调用者)将使用相同的寄存器集将参数从调用者传递给被调用者。

所以,通常,第一个整数参数存储在rdi,第二个参数存储在rsi(寄存器的完整列表这里列出。这里,因为第一个参数是argcan int,一个 32 位寄存器就足够了(4 个字节),所以我们使用ediin的位置rdi并且,由于第二个参数是一个指针 ( argv),我们需要完整的 64 位寄存器rsi(8 个字节)。

我们该怎么办?好了,我们储存珍贵argcargv我们在我们之前刚刚分配的内存堆栈帧中。

请注意,存储在rbp当前堆栈帧的整个生命周期中的地址不会改变(因为我们在最后需要它来执行 aleave并恢复rbp前一个堆栈帧的地址)。因此,大多数编译器将rbp用作参考点来调用当前堆栈帧的局部变量。因此,从现在开始rbp+var_4参考argcrbp+var_10参考argv

嗯,这就是您真正需要了解的有关fastcallLinux 中约定的全部内容现在,您应该更容易理解该程序。

希望这有帮助!