基本缓冲区溢出教程

3

我正在学习基本的缓冲区溢出,以下是我的 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语句的比较部分,以便我能够“获胜”。只有your_fcn()可以更改,不能更改main()
我的方法是通过缓冲区溢出覆盖返回地址。在这种情况下,我确定返回地址应该离buffer8个字节的距离,因为buffer4个字节,而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 <+43>: call 0x51d <your_fcn>0x00000583 <+57>: 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  

2
既然你在使用汇编语言,你应该诊断一下你的目标CPU是什么,以及你是否正在构建32位或64位程序,还有你使用的操作系统,因为布局可能不同。如果你包括"-fomit -frame-pointer"选项,并且你使用的是GCC编译器,你可能会得到不同的结果。好的优化编译器可以检测到未定义行为并安排重新格式化你的磁盘,在你造成任何真正的损害之前——也许你会很幸运,你的编译器会让你的硬盘活着看到另一天。 - Jonathan Leffler
1
为什么gdb中给出的地址不是每次4个字节?因为许多处理器具有可变长度指令。 - Weather Vane
1
call 本身并不重要,因为您正在覆盖返回地址。因此,您应该相对于 0x0000057a <+48> 计算偏移量。假设您已经在堆栈上找到了正确的位置,那么这应该意味着一个偏移量为 9 - Jester
1
你需要发布 your_func 的反汇编。可能你的 ret = buffer + 8; 偏移量是错误的。 - Jester
1
编译器将ret分配在ebp-4处,将buffer分配在ebp-8处,因此返回地址位于buffer+12处。 - prl
显示剩余14条评论
1个回答

2

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

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

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

这样做会跳过对yoursmine的赋值。由于它们都初始化为0,因此将打印“你赢了”。


现在意思清楚了。我也意识到我的语法错误覆盖了返回地址,应该是ret =&buffer [4] +8;而不是ret = buffer + 8;。谢谢! - user6843746

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