【问题标题】:Causing a buffer Overflow with fgets使用 fgets 导致缓冲区溢出
【发布时间】:2013-12-18 17:10:49
【问题描述】:

我正在试验缓冲区溢出,并尝试用 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;
}

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

我的想法是通过输入 20 'A's 来溢出 buf 的缓冲区。这有效并导致分段错误。 我的下一个想法是找出foo() 的地址\x4006cd 并将其附加到20 'A's。

据我了解,这应该覆盖堆栈的返回地址并使其跳转到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.

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.

酒吧:

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.

【问题讨论】:

  • 您是否查看过此代码的相应汇编程序(以及堆栈布局)?
  • 要是这么简单就好了……
  • 您应该查看相应的程序集。有时编译器会执行超过 20 个字节的对齐操作。您还应该查看堆栈的布局以及如何保存寄存器/保存的 ret addr 以及以什么顺序保存。继续发布功能的组装,我们可以为您提供进一步的帮助。
  • 如果不分析程序集,您将不会走得太远。您使用的是哪个编译器和环境?
  • @AzzUrr1 如果你看一下这条指令sub $0x20,%rsp,你可以看到编译器从堆栈指针中减去 32 个字节。这是你缓冲区的大小。

标签: c buffer overflow stack-overflow fortify-source


【解决方案1】:

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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int **x;
int z;

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

void bar()
{
    char buf[2];
    //puts("Input:");
    //fgets(buf, 70, stdin);
    x = (int**) buf;
    for(z=0;z<8;z++)
            printf("%d X=%x\n", z, *(x+z));
    *(x+3) = foo;
    printf("Your input: %d %s\n", strlen(buf), buf);
}


int main(int argc, char **argv)
{
        printf("Foo: %x\n", foo);
        printf("Main: %x\n", main);
        bar();
        return 0;
}

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

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

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

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

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

我的输出如下:

$ gcc test.c -o test
test.c: In function ‘bar’:
test.c:21: warning: assignment from incompatible pointer type
$ ./test
Foo: 400594
Main: 400668
0 X=9560e9f0
1 X=95821188
2 X=889350f0
3 X=4006af
4 X=889351d8
5 X=0
6 X=0
7 X=95a1ed1d
Your input: 5 ▒▒`▒9
You did it.
Segmentation fault

【讨论】:

    【解决方案2】:
    void bar()
    {
        char buf[20];
        puts("Input:");
        fgets(buf, 24, stdin);
        printf("Your input:.\n", strlen(buf));
    }
    

    ...这有效并导致分段错误...

    编译器可能正在将fgets 替换为更安全的变体,其中包括对目标缓冲区大小的检查。如果检查失败,prgram无条件调用abort()

    在这种特殊情况下,您应该使用-U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0 编译程序。

    【讨论】:

      猜你喜欢
      • 2021-06-15
      • 1970-01-01
      • 2013-02-17
      • 2010-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多