【问题标题】:Basic buffer overflow tutorial基本缓冲区溢出教程
【发布时间】:2018-02-18 21:46:34
【问题描述】:

我正在学习基本的缓冲区溢出,并且我有以下 C 代码:

int your_fcn()
{
    char buffer[4];
    int *ret;

    ret = buffer + 8;
    (*ret) += 16;

    return 1;
}

int main()
{
    int mine = 0;
    int yours = 0;

    yours = your_fcn();
    mine = yours + 1;

    if(mine > yours)
        printf("You lost!\n");
    else
        printf("You won!\n");

    return EXIT_SUCCESS;
}

我的目标是绕过mine = yours + 1;这一行,直接跳到if语句比较,这样我就能“赢”。 main()不能碰,只有your_fcn()可以。

我的方法是用缓冲区溢出覆盖返回地址。所以在这种情况下,我确定返回地址应该是远离buffer8 字节,因为缓冲区是4 字节,而EBP4 字节。然后我使用gdb 来确定我要跳转到的行是16 距离函数调用的字节。这是 gdb 的结果:

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000054a <+0>:     lea    0x4(%esp),%ecx
   0x0000054e <+4>:     and    $0xfffffff0,%esp
   0x00000551 <+7>:     pushl  -0x4(%ecx)
   0x00000554 <+10>:    push   %ebp
   0x00000555 <+11>:    mov    %esp,%ebp
   0x00000557 <+13>:    push   %ebx
   0x00000558 <+14>:    push   %ecx
   0x00000559 <+15>:    sub    $0x10,%esp
   0x0000055c <+18>:    call   0x420 <__x86.get_pc_thunk.bx>
   0x00000561 <+23>:    add    $0x1a77,%ebx
   0x00000567 <+29>:    movl   $0x0,-0xc(%ebp)
   0x0000056e <+36>:    movl   $0x0,-0x10(%ebp)
   0x00000575 <+43>:    call   0x51d <your_fcn>
   0x0000057a <+48>:    mov    %eax,-0x10(%ebp)
   0x0000057d <+51>:    mov    -0x10(%ebp),%eax
   0x00000580 <+54>:    add    $0x1,%eax
   0x00000583 <+57>:    mov    %eax,-0xc(%ebp)
   0x00000586 <+60>:    mov    -0xc(%ebp),%eax
   0x00000589 <+63>:    cmp    -0x10(%ebp),%eax
   0x0000058c <+66>:    jle    0x5a2 <main+88>
   0x0000058e <+68>:    sub    $0xc,%esp
   0x00000591 <+71>:    lea    -0x1988(%ebx),%eax

我看到0x00000575 &lt;+43&gt;: call 0x51d &lt;your_fcn&gt;0x00000583 &lt;+57&gt;: mov %eax,-0xc(%ebp) 线相距四行,这告诉我应该将ret 偏移16 字节。但是来自 gdb 的地址说明了一些不同的内容。也就是说,函数调用从0x00000575开始,而我要跳转到的那一行在0x00000583,也就是说它们在15字节之外?

无论如何,无论我使用16 字节还是15 字节,我都会收到segmentation fault 错误,但我仍然“失败”。

问题:我做错了什么?为什么 gdb 中给出的地址一次不增加 4 个字节,这里实际发生了什么。如何正确跳转到我想要的行?


澄清:这是在运行 linux Ubuntu 的 VM 上的 x32 机器上完成的。我正在使用命令gcc -fno-stack-protector -z execstack -m32 -g guesser.c -o guesser.o 进行编译,该命令会关闭堆栈保护器并强制进行 x32 编译。


your_fcn() 的 gdb 按要求:

(gdb) disassemble your_fcn
Dump of assembler code for function your_fcn:
   0x0000051d <+0>: push   %ebp
   0x0000051e <+1>: mov    %esp,%ebp
   0x00000520 <+3>: sub    $0x10,%esp
   0x00000523 <+6>: call   0x5c3 <__x86.get_pc_thunk.ax>
   0x00000528 <+11>:    add    $0x1ab0,%eax
   0x0000052d <+16>:    lea    -0x8(%ebp),%eax
   0x00000530 <+19>:    add    $0x8,%eax
   0x00000533 <+22>:    mov    %eax,-0x4(%ebp)
   0x00000536 <+25>:    mov    -0x4(%ebp),%eax
   0x00000539 <+28>:    mov    (%eax),%eax
   0x0000053b <+30>:    lea    0xc(%eax),%edx
   0x0000053e <+33>:    mov    -0x4(%ebp),%eax
   0x00000541 <+36>:    mov    %edx,(%eax)
   0x00000543 <+38>:    mov    $0x1,%eax
   0x00000548 <+43>:    leave  
   0x00000549 <+44>:    ret  

【问题讨论】:

  • 既然你在玩汇编程序,你应该诊断你的目标是哪个 CPU,你是在构建 32-but 还是 64-but,可能还有哪些 o/s,因为布局可能不同。如果您包含 -fomit -frame-pointer 并且您正在使用 GCC,您可能会得到不同的结果。还有一个很好的优化编译器之夜发现 UB 并安排在您造成任何真正损坏之前重新格式化您的磁盘 - 或者也许您会很幸运,您的编译器会让您的硬盘驱动器活着看到另一天
  • “为什么 gdb 中给出的地址一次不走 4 个字节?” 因为很多处理器都有可变长度指令?
  • call 本身不相关,因为您正在覆盖返回地址。因此,您应该计算相对于0x0000057a &lt;+48&gt; 的偏移量。假设您在堆栈上找到了正确的位置,这应该意味着 9 的偏移量。
  • 你需要发布your_func的反汇编。大概你的ret = buffer + 8; 的偏移量是错误的。
  • 编译器在ebp-4分配ret,在ebp-8分配buffer,所以返回地址在buffer+12。

标签: c assembly buffer-overflow


【解决方案1】:

x86 具有可变长度指令,因此您不能简单地计算指令并乘以 4。由于您有来自 gdb 的输出,因此请相信它可以确定每条指令的地址。

函数的返回地址是调用指令之后的地址。在显示的代码中,这将是 main+48。

if 语句从 main+60 开始,而不是 main+57。 main+57 处的指令将yours+1 存储到mine 中。所以要调整返回地址返回if语句,应该加12(即60-48)。

这样做会跳过对yoursmine 的分配。由于它们都被初始化为 0,它会打印“You won”。

【讨论】:

  • 现在有意义了。我还意识到我用错误的语法覆盖了返回地址,它应该是ret = &amp;buffer[4] + 8; 而不是ret = buffer + 8;。谢谢!
猜你喜欢
  • 1970-01-01
  • 2011-12-19
  • 1970-01-01
  • 2015-12-16
  • 1970-01-01
  • 2010-11-11
  • 1970-01-01
  • 2015-12-31
  • 1970-01-01
相关资源
最近更新 更多