C中的缓冲区溢出

时间:2011-06-02 20:48:25

标签: c buffer-overflow

我试图在Mac OS X 10.6 64位上使用C编写一个简单的缓冲区溢出。这是概念:

void function() {
    char buffer[64];
    buffer[offset] += 7;    // i'm not sure how large offset needs to be, or if
                            // 7 is correct.
}

int main() {

    int x = 0;
    function();
    x += 1;
    printf("%d\n", x);      // the idea is to modify the return address so that
                            // the x += 1 expression is not executed and 0 gets
                            // printed

    return 0;
}

这是main的汇编程序转储的一部分:

...
0x0000000100000ebe <main+30>:   callq  0x100000e30 <function>
0x0000000100000ec3 <main+35>:   movl   $0x1,-0x8(%rbp)
0x0000000100000eca <main+42>:   mov    -0x8(%rbp),%esi
0x0000000100000ecd <main+45>:   xor    %al,%al
0x0000000100000ecf <main+47>:   lea    0x56(%rip),%rdi        # 0x100000f2c
0x0000000100000ed6 <main+54>:   callq  0x100000ef4 <dyld_stub_printf>
...

我想跳过movl指令,这意味着我需要将返回地址递增42 - 35 = 7(正确吗?)。现在我需要知道返回地址的存储位置,这样我才能计算出正确的偏移量。

我已经尝试手动搜索正确的值,但是1被打印或者我得到abort trap - 是否有某种缓冲区溢出保护正在进行?


在我的机器上使用88的偏移量。我用Nemo的方法找出了返回地址。

5 个答案:

答案 0 :(得分:11)

这个 32位示例说明了如何解决这个问题,请参阅下面的64位:

#include <stdio.h>

void function() {
    char buffer[64];
    char *p;
    asm("lea 4(%%ebp),%0" : "=r" (p));  // loads address of return address
    printf("%d\n", p - buffer);         // computes offset
    buffer[p - buffer] += 9;            // 9 from disassembling main
}

int main() {
    volatile int x = 7;
    function();
    x++;
    printf("x = %d\n", x); // prints 7, not 8
}

在我的系统上,偏移量是76.这是缓冲区的64个字节(记住,堆栈增长了,因此缓冲区的起点远离返回地址)加上其他任何碎屑。

显然,如果你正在攻击现有的程序,你不能指望它为你计算答案,但我认为这说明了原则。

(另外,我们很幸运+9没有执行另一个字节。否则单字节增量不会设置我们预期的返回地址。如果你不满意返回地址,这个例子可能会中断在main

之内

我以某种方式忽略了原始问题的64位。 x86-64的等价物是8(%rbp),因为指针长度为8个字节。在这种情况下,我的测试构建恰好产生104的偏移量。在上面的代码中,使用双8(%%rbp)替换%%以在输出程序集中获得单个%。这在this ABI document中有所描述。搜索8(%rbp)

评论中抱怨4(%ebp)76或其他任意数字一样神奇。事实上,寄存器%ebp(也称为“帧指针”)的含义及其与堆栈上返回地址位置的关系是标准化的。我快速用Google搜索的一个例子是here。该文使用术语“基指针”。如果您想在其他体系结构上利用缓冲区溢出,则需要详细了解该CPU的调用约定。

答案 1 :(得分:4)

罗迪是对的,你需要操作指针大小的值。

我会先从你的漏洞利用函数中读取值(并打印它们),而不是它们。当您爬过数组的末尾时,您应该开始从堆栈中查看值。不久之后,您应该找到返回地址,并能够将其与反汇编程序转储对齐。

答案 2 :(得分:1)

反汇编function(),看看它是什么样的。

偏移需要正,可能是64 + 8,因为它是64位地址。此外,您应该在指针大小的对象上执行'+7',而不是在char上。否则,如果两个地址跨越256字节边界,您将利用您的漏洞....

答案 3 :(得分:0)

您可以尝试在调试器中运行代码,一次步进每个装配线,并检查堆栈的内存空间和寄存器。

答案 4 :(得分:0)

我总是喜欢操作不错的数据类型,比如这个:

struct stackframe {
    char *sf_bp;
    char *sf_return_address;
};

void function() {
    /* the following code is dirty. */
    char *dummy;
    dummy = (char *)&dummy;
    struct stackframe *stackframe = dummy + 24; /* try multiples of 4 here. */

    /* here starts the beautiful code. */    
    stackframe->sf_return_address += 7;
}

使用此代码,您可以轻松地与调试器核实stackframe->sf_return_address中的值是否符合您的期望。