C语言中的缓冲区溢出问题

19

我正在尝试在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的方法找到返回地址。

如果您的堆栈没有得到清理和保存寄存器恢复,那么在主函数中您的寄存器可能会出现垃圾值。谁负责这个取决于编译器使用的函数调用约定。http://en.wikipedia.org/wiki/X86_calling_conventions - x4u
你应该将这个标记为作业或其他什么吗?你可能不希望人们认为你做这个的目的是为了其他事情而不是学习。 - filipe
1
@filipe:已经这样做了,我最初没有这样做,因为这真的很基础(你在大学的第一年就学过它)。 - ryyst
5个回答

13

这个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)”以获得输出汇编中的单个“%”。这在ABI文档中有描述。搜索“8(%rbp)”。
评论中有一个抱怨说,“4(%ebp)”与“76”或任何其他任意数一样神秘。实际上,寄存器%ebp(也称为“帧指针”)的含义及其与堆栈上返回地址的位置的关系已标准化。我快速搜索的一个例子是这里。该文章使用术语“基指针”。如果你想利用其他体系结构上的缓冲区溢出,就需要类似详细了解那个CPU的调用约定。

1
这又增加了一个神奇的数字 :-) 解释为什么是 4(%%ebp) 而不是 42(%%ebp) 会很有用! - Roddy
x86_64 GCC默认省略帧指针(仅使用堆栈指针的偏移量),因此这将无法工作。 - Nemo
当然不会默认,因为我使用了GCC,而且它工作得很好。但是,如果你增加优化,它会的。我的回答试图引导问者朝着更深入的理解方向前进,以帮助他回答自己的问题。然而,他接受了另一个答案,并评论说他尝试了一些随机值直到找到可行的方案。 - Ben Jackson
@Ben Jackson:我刚试了一下你的解决方案(因为它显然比随机尝试值要好),结果我得到了一个偏移量为104。显然,88和104都可以作为偏移量。你有什么解释吗? - ryyst
1
@ryyst:当你对一个函数进行溢出攻击时,它将只针对于一个确切的编译版本。能够针对 我的 function() 生效的值可能与那个能够针对你问题中的 function() 生效的值不同。如果你把 char *p 在我的代码中移到 char buffer 之前,它可能会再次改变。请参考这篇关于 激活记录 的解释:http://en.wikipedia.org/wiki/Call_stack - Ben Jackson
显示剩余2条评论

4

Roddy 是正确的,你需要操作指针大小的值。

我建议在你的漏洞函数中首先 读取 值(并打印它们),而不是 写入 值。当你越过数组的末尾时,你应该开始看到来自堆栈的值。不久之后,你应该能够找到返回地址,并将其与汇编代码转储对齐。


不错的方法,我刚刚找到了偏移量(在我的情况下是88)。 - ryyst

1

反汇编 function() 并查看其外观。

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


不,不是负数。由于堆栈向下增长,一旦超过缓冲区的高端,他将开始破坏堆栈数据。 - Dave Rager
我认为偏移量不需要是负数... 在x86上,堆栈向下增长。 - Nemo

0
你可以尝试在调试器中运行你的代码,逐行执行每个汇编指令,并检查堆栈的内存空间以及寄存器。

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中的值是否符合您的期望。

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