基本缓冲区溢出教程

Basic buffer overflow tutorial

我正在学习基本的缓冲区溢出,我有以下 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 语句比较,这样我就可以 "win"。 main()不能碰,只有your_fcn()可以。

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

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000054a <+0>:     lea    0x4(%esp),%ecx
   0x0000054e <+4>:     and    [=11=]xfffffff0,%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    [=11=]x10,%esp
   0x0000055c <+18>:    call   0x420 <__x86.get_pc_thunk.bx>
   0x00000561 <+23>:    add    [=11=]x1a77,%ebx
   0x00000567 <+29>:    movl   [=11=]x0,-0xc(%ebp)
   0x0000056e <+36>:    movl   [=11=]x0,-0x10(%ebp)
   0x00000575 <+43>:    call   0x51d <your_fcn>
   0x0000057a <+48>:    mov    %eax,-0x10(%ebp)
   0x0000057d <+51>:    mov    -0x10(%ebp),%eax
   0x00000580 <+54>:    add    [=11=]x1,%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    [=11=]xc,%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 错误并且我仍然 "lose".

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


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


gdb of your_fcn() 根据要求:

(gdb) disassemble your_fcn
Dump of assembler code for function your_fcn:
   0x0000051d <+0>: push   %ebp
   0x0000051e <+1>: mov    %esp,%ebp
   0x00000520 <+3>: sub    [=12=]x10,%esp
   0x00000523 <+6>: call   0x5c3 <__x86.get_pc_thunk.ax>
   0x00000528 <+11>:    add    [=12=]x1ab0,%eax
   0x0000052d <+16>:    lea    -0x8(%ebp),%eax
   0x00000530 <+19>:    add    [=12=]x8,%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    [=12=]x1,%eax
   0x00000548 <+43>:    leave  
   0x00000549 <+44>:    ret  

x86 有可变长度的指令,所以你不能简单地计算指令数并乘以 4。既然你有 gdb 的输出,请相信它来确定每条指令的地址。

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

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

这样做会跳过对 yoursmine 的赋值。由于它们都被初始化为 0,所以它会打印 "You won".