我试图在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的方法找出了返回地址。
答案 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
中的值是否符合您的期望。