章节大纲

      • 要了解如何进行 Return-to-libc 攻击,我们需要理解堆栈的工作原理。 我们使用一个小型 C 程序来理解函数调用对堆栈的影响。更详细的解释可以在SEED 书籍和 SEED 课程中找到。

        #include<stdio.h>
        void foo(int x)
        {
          printf("Hello world: %d\n", x);
        }
        
        int main()
        {
          foo(1);
          return 0;
        }

        我们可以使用 "gcc -m32 -S foobar.c" 将这个程序编译成汇编代码。 生成的文件 foobar.s 将如下所示:

        ......
          8 foo:
          9         pushl   %ebp
         10         movl    %esp, %ebp
         11         subl    $8, %esp
         12         movl    8(%ebp), %eax   
         13         movl    %eax, 4(%esp)
         14         movl    $.LC0, (%esp)
         15         call    printf
         16         leave
         17         ret
            ......
         21 main:
         22         leal    4(%esp), %ecx
         23         andl    $-16, %esp
         24         pushl   -4(%ecx)
         25         pushl   %ebp
         26         movl    %esp, %ebp
         27         pushl   %ecx
         28         subl    $4, %esp
         29         movl    $1, (%esp)
         30         call    foo
         31         movl    $0, %eax
         32         addl    $4, %esp
         33         popl    %ecx
         34         popl    %ebp
         35         leal    -4(%ecx), %esp
         36         ret
      • 让我们关注调用 foo() 时的堆栈。我们可以忽略之前的堆栈。请注意,本解释中使用的是行号而不是指令地址。
         
        • 第 28-29 行:这两个语句将值 1,即 foo() 的参数,推入堆栈。这个操作使 %esp 增加 4。这两个语句之后的堆栈如图 (a) 所示。

        • 第 30 行:call foo:该语句将紧随 call 语句之后的下一条指令的地址推入堆栈(即返回地址)然后跳转到 foo() 的代码。当前堆栈如图 (b) 所示。

        • 第 9-10 行:函数 foo() 的第一行将 %ebp 推入堆栈,以保存先前的栈帧指针。第二行让 %ebp 指向当前栈帧。当前堆栈如图 (c) 所示。

        • 第 11 行:subl $8, %esp:堆栈指针被修改,以便为局部变量和传递给 printf 的两个参数分配空间(共 8 字节)。 由于函数 foo 中没有局部变量,这 8 字节仅用于参数传递。 见图 (d)。

        图 1: 进入和离开 foo()

      • 现在控制权已经传递给函数foo()。让我们看看函数返回时堆栈会发生什么。

        • 第16行:leave:这条指令其实执行了两个指令(它在早期 x86 版本中是一个宏,但后来被制作成了指令):

        mov  %ebp, %esp
        pop  %ebp

        第一条语句释放为函数分配的堆栈空间; 第二条语句恢复先前的框架指针。 当前堆栈如图 (e) 所示。

        • 第17行:ret:这 条指令从堆栈中弹出返回地址,然后跳转到返回地址。 当前堆栈如图(f)所示。

        • 第32行:addl $4, %esp:进一步释放为 foo 分配的内存。 如你所见,堆栈现在的状态与进入函数 foo 之前完全相同(即,第28行之前)。