使用fgets导致缓冲区溢出

arn*_*app 11 c stack-overflow buffer overflow fortify-source

我正在尝试缓冲区溢出,并尝试使用一定的fgets输入覆盖堆栈的返回地址

这是代码:

void foo()
{
    fprintf(stderr, "You did it.\n");
}

void bar()
{
    char buf[20];
    puts("Input:");
    fgets(buf, 24, stdin);
    printf("Your input:.\n", strlen(buf));
}


int main(int argc, char **argv)
{
    bar();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在正常执行时,程序只返回您的输入.我希望它输出foo()而不修改代码.

我的想法是buf通过进入20 'A'秒来溢出缓冲区.这会起作用并导致分段错误.我的下一个想法是找出其中的地址并将foo()\x4006cd附加到20 'A'秒.

根据我的理解,这应该覆盖堆栈的返回地址并使其跳转到foo.但它只会导致段错误.

我究竟做错了什么?

更新:汇编程序转储主要

    Dump of assembler code for function main:
   0x000000000040073b <+0>: push   %rbp
   0x000000000040073c <+1>: mov    %rsp,%rbp
   0x000000000040073f <+4>: sub    $0x10,%rsp
   0x0000000000400743 <+8>: mov    %edi,-0x4(%rbp)
   0x0000000000400746 <+11>:    mov    %rsi,-0x10(%rbp)
   0x000000000040074a <+15>:    mov    $0x0,%eax
   0x000000000040074f <+20>:    callq  0x4006f1 <bar>
   0x0000000000400754 <+25>:    mov    $0x0,%eax
   0x0000000000400759 <+30>:    leaveq 
   0x000000000040075a <+31>:    retq   
   End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

FOO

Dump of assembler code for function foo:
   0x00000000004006cd <+0>: push   %rbp
   0x00000000004006ce <+1>: mov    %rsp,%rbp
   0x00000000004006d1 <+4>: mov    0x200990(%rip),%rax        # 0x601068 <stderr@@GLIBC_2.2.5>
   0x00000000004006d8 <+11>:    mov    %rax,%rcx
   0x00000000004006db <+14>:    mov    $0x15,%edx
   0x00000000004006e0 <+19>:    mov    $0x1,%esi
   0x00000000004006e5 <+24>:    mov    $0x400804,%edi
   0x00000000004006ea <+29>:    callq  0x4005d0 <fwrite@plt>
   0x00000000004006ef <+34>:    pop    %rbp
   0x00000000004006f0 <+35>:    retq   
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

酒吧:

Dump of assembler code for function bar:
   0x00000000004006f1 <+0>: push   %rbp
   0x00000000004006f2 <+1>: mov    %rsp,%rbp
   0x00000000004006f5 <+4>: sub    $0x20,%rsp
   0x00000000004006f9 <+8>: mov    $0x40081a,%edi
   0x00000000004006fe <+13>:    callq  0x400570 <puts@plt>
   0x0000000000400703 <+18>:    mov    0x200956(%rip),%rdx        # 0x601060 <stdin@@GLIBC_2.2.5>
   0x000000000040070a <+25>:    lea    -0x20(%rbp),%rax
   0x000000000040070e <+29>:    mov    $0x18,%esi
   0x0000000000400713 <+34>:    mov    %rax,%rdi
   0x0000000000400716 <+37>:    callq  0x4005b0 <fgets@plt>
   0x000000000040071b <+42>:    lea    -0x20(%rbp),%rax
   0x000000000040071f <+46>:    mov    %rax,%rdi
   0x0000000000400722 <+49>:    callq  0x400580 <strlen@plt>
   0x0000000000400727 <+54>:    mov    %rax,%rsi
   0x000000000040072a <+57>:    mov    $0x400821,%edi
   0x000000000040072f <+62>:    mov    $0x0,%eax
   0x0000000000400734 <+67>:    callq  0x400590 <printf@plt>
   0x0000000000400739 <+72>:    leaveq 
   0x000000000040073a <+73>:    retq   
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

nme*_*zes 3

你没有计算内存对齐。我稍微更改了代码,以便更容易找到正确的位置。

\n\n
#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\nint **x;\nint z;\n\nvoid foo()\n{\n    fprintf(stderr, "You did it.\\n");\n}\n\nvoid bar()\n{\n    char buf[2];\n    //puts("Input:");\n    //fgets(buf, 70, stdin);\n    x = (int**) buf;\n    for(z=0;z<8;z++)\n            printf("%d X=%x\\n", z, *(x+z));\n    *(x+3) = foo;\n    printf("Your input: %d %s\\n", strlen(buf), buf);\n}\n\n\nint main(int argc, char **argv)\n{\n        printf("Foo: %x\\n", foo);\n        printf("Main: %x\\n", main);\n        bar();\n        return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

对于较小的缓冲区(在我的示例中为 2),我发现返回地址距缓冲区开头 24 个字节(x+3,对于 8 字节指针;64 位,无调试,无优化...)。该位置可以根据缓冲区大小、体系结构等进行更改。在本示例中,我设法将 bar 的返回地址更改为 foo。无论如何,您将在 foo 返回时遇到分段错误,因为它没有正确设置为返回到 main。

\n\n

我将 x 和 z 添加为全局变量,以不更改 bar 的堆栈大小。该代码将显示一个类似指针的值数组,从 buf[0] 开始。就我而言,我在 main 中的位置 3 中找到了地址。这就是为什么最终代码有 *(x+3) = foo。正如我所说,这个位置可以根据编译选项、机器等而改变。要找到正确的位置,请在地址列表上找到 main 的地址(在调用 bar 之前打印)。

\n\n

需要注意的是,我说的是 main 中的地址,而不是 main 的地址,因为返回地址被设置为调用 bar 之后的行,而不是 main 的开头。所以,就我而言,它是 0x4006af 而不是 0x400668。

\n\n

在您的示例中,据我所知,对于 20 字节缓冲区,它与 32 字节(0x20)对齐。

\n\n

如果你想对 fgets 做同样的事情,你必须弄清楚如何输入 foo 的地址,但如果你运行的是 x86/x64 机器,请记住将其添加到小尾数中。您可以更改代码以按字节显示值,这样您就可以按正确的顺序获取它们并使用 ALT+数字键入它们。请记住,按住 ALT 时键入的数字是十进制数字。有些终端不会友好地处理 0x00。

\n\n

我的输出看起来像:

\n\n
$ gcc test.c -o test\ntest.c: In function \xe2\x80\x98bar\xe2\x80\x99:\ntest.c:21: warning: assignment from incompatible pointer type\n$ ./test\nFoo: 400594\nMain: 400668\n0 X=9560e9f0\n1 X=95821188\n2 X=889350f0\n3 X=4006af\n4 X=889351d8\n5 X=0\n6 X=0\n7 X=95a1ed1d\nYour input: 5 \xe2\x96\x92\xe2\x96\x92`\xe2\x96\x929\nYou did it.\nSegmentation fault\n
Run Code Online (Sandbox Code Playgroud)\n