为了自学逆向工程,我正在编写小型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;
}


转换如下:



评论的部分是我度过难关的地方:

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约定,但我怀疑它是正确的,我认为此处使用的调用约定是fastcall SystemV ABI中的amd64(请参阅有关调用约定的Wikipedia页面)。我的猜测是您正在使用Linux ...

因此,对rpbrsp的操作仅是在此处保存堆栈帧的先前状态,以便在退出该功能时将其恢复(您将rbp的内容堆栈在堆栈上,希望在最后保留该功能时能够将其还原)。但是,您已经了解了。

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

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

实际上,在调用我们所在的函数之前,先前的函数已将我们函数的参数存储在某些寄存器中。在这里,重要的是要同意所有功能(调用方和被调用方)将使用同一组寄存器将参数从调用方传递给被调用方。

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

我们该怎么做呢?好吧,我们宝贵地存储了argc和我们刚刚分配的内存中的堆栈帧中的argv

请注意,存储在rbp中的地址在当前堆栈帧的整个生命周期内都不会改变(因为我们最终需要它执行leave并恢复先前堆栈帧的rbp的地址。因此,大多数编译器将使用q4 312079q作为参考点,以调用当前堆栈框架本地的变量。因此,从现在开始,rbp指的是rbp+var_4argc指的是rbp+var_10

这就是您真正需要了解的Linux中argv约定的全部内容。现在,该程序应该对您更易懂了。

希望对您有所帮助!

评论


很棒的解释!

–托马斯
18年7月22日在15:57

* nix世界中使用了fastcall吗?

– Biswapriyo
18年7月22日在19:08

我在应有的更广泛的范围内使用术语fastcall。我的意思是:一种调用约定比寄存器更好地使用寄存器来传递参数。我之所以说fastcall是因为它是最早使用注册人的应用程序之一,也是使用最广泛的应用程序之一。但是,用于传递参数的寄存器在Microsoft ABI和SysemV ABI之间发生了变化,因此我猜想Unix不会使用fastcall,即使使用了fastcall的基本原理(通过寄存器传递)。

–恐怖
18年7月22日在19:20