如何在GCC,Windows XP,x86中编写缓冲区溢出漏洞?

void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; int *ret; ret = buffer1 + 12; (*ret) += 8;//why is it 8?? } void main() { int x; x = 0; function(1,2,3); x = 1; printf("%d\n",x); } 

上面的演示来自这里:

http://insecure.org/stf/smashstack.html

但它在这里不起作用:

 D:\test>gcc -Wall -Wextra hw.cpp && a.exe hw.cpp: In function `void function(int, int, int)': hw.cpp:6: warning: unused variable 'buffer2' hw.cpp: At global scope: hw.cpp:4: warning: unused parameter 'a' hw.cpp:4: warning: unused parameter 'b' hw.cpp:4: warning: unused parameter 'c' 1 

我不明白为什么它是8,尽管作者认为:

一个小数学告诉我们距离是8个字节。

我调用的gdb转储:

 Dump of assembler code for function main: 0x004012ee : push %ebp 0x004012ef : mov %esp,%ebp 0x004012f1 : sub $0x18,%esp 0x004012f4 : and $0xfffffff0,%esp 0x004012f7 : mov $0x0,%eax 0x004012fc : add $0xf,%eax 0x004012ff : add $0xf,%eax 0x00401302 : shr $0x4,%eax 0x00401305 : shl $0x4,%eax 0x00401308 : mov %eax,0xfffffff8(%ebp) 0x0040130b : mov 0xfffffff8(%ebp),%eax 0x0040130e : call 0x401b00  0x00401313 : call 0x4017b0  0x00401318 : movl $0x0,0xfffffffc(%ebp) 0x0040131f : movl $0x3,0x8(%esp) 0x00401327 : movl $0x2,0x4(%esp) 0x0040132f : movl $0x1,(%esp) 0x00401336 : call 0x4012d0  0x0040133b : movl $0x1,0xfffffffc(%ebp) 0x00401342 : mov 0xfffffffc(%ebp),%eax 0x00401345 : mov %eax,0x4(%esp) 0x00401349 : movl $0x403000,(%esp) 0x00401350 : call 0x401b60  0x00401355 : leave 0x00401356 : ret 0x00401357 : nop 0x00401358 : add %al,(%eax) 0x0040135a : add %al,(%eax) 0x0040135c : add %al,(%eax) 0x0040135e : add %al,(%eax) End of assembler dump. Dump of assembler code for function function: 0x004012d0 : push %ebp 0x004012d1 : mov %esp,%ebp 0x004012d3 : sub $0x38,%esp 0x004012d6 : lea 0xffffffe8(%ebp),%eax 0x004012d9 : add $0xc,%eax 0x004012dc : mov %eax,0xffffffd4(%ebp) 0x004012df : mov 0xffffffd4(%ebp),%edx 0x004012e2 : mov 0xffffffd4(%ebp),%eax 0x004012e5 : movzbl (%eax),%eax 0x004012e8 : add $0x5,%al 0x004012ea : mov %al,(%edx) 0x004012ec : leave 0x004012ed : ret 

在我的情况下,距离应该是 – = 5,对吗?但它似乎不起作用..

为什么function需要56个字节的局部变量?( sub $0x38,%esp

正如joveha指出的那样 , call指令保存在堆栈(返回地址)上的EIP值需要增加7个字节( 0x004013420x0040133b = 7 )才能跳过x = 1; 指令( movl $0x1,0xfffffffc(%ebp) )。

你是正确的56个字节是为局部变量保留的( sub $0x38,%esp ),所以缺少的部分是堆栈上buffer1多少字节是保存的EIP。


一些测试代码和内联汇编告诉我,我的测试的魔术值是28 。 我无法提供关于为什么它是28的明确答案,但我认为编译器正在添加填充和/或堆栈金丝雀 。

以下代码使用GCC 3.4.5(MinGW)编译并在Windows XP SP3(x86)上测试。

 unsigned long get_ebp() { __asm__("pop %ebp\n\t" "movl %ebp,%eax\n\t" "push %ebp\n\t"); } void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; int *ret; /* distance in bytes from buffer1 to return address on the stack */ printf("test %d\n", ((get_ebp() + 4) - (unsigned long)&buffer1)); ret = (int *)(buffer1 + 28); (*ret) += 7; } void main() { int x; x = 0; function(1,2,3); x = 1; printf("%d\n",x); } 

我可以像使用gdb一样轻松地确定这个值。

(编译w / -g以包含调试符号)

 (gdb) break function ... (gdb) run ... (gdb) p $ebp $1 = (void *) 0x22ff28 (gdb) p &buffer1 $2 = (char (*)[5]) 0x22ff10 (gdb) quit 

0x22ff28 + 4) – 0x22ff10 = 28

(ebp值+字的大小) – buffer1的地址=字节数


除了Smashing The Stack For Fun和Profit之外 ,我建议阅读我在回答你之前关于你的问题和/或关于这个主题的其他材料时提到的一些文章。 充分了解这种类型的漏洞利用程序应该如何帮助您编写更安全的代码 。

很难预测buffer1 + 12真正指向的是什么。 你的编译器可以将buffer1buffer2放在它想要的堆栈上的任何位置,甚至可以根本不为buffer2节省空间。 真正知道buffer1所在的唯一方法是查看编译器的汇编器输出,并且它很可能会使用不同的优化设置或同一编译器的不同版本跳转。

我还没有在自己的机器上测试代码,但是你考虑了内存对齐吗? 尝试用gcc反汇编代码。 我认为汇编代码可以让您进一步了解代码。 🙂

此代码在OpenBSD和FreeBSD上打印出1,并在Linux上出现分段错误。

这种漏洞很大程度上依赖于特定机器的指令集,以及编译器和操作系统的调用约定。 关于堆栈布局的一切都是由实现定义的,而不是C语言。 本文假设Linux在x86上,但看起来你正在使用Windows,而你的系统可能是64位,尽管你可以用-m32将gcc切换到32位。

你必须调整的参数是12,它是从栈顶到返回地址的偏移量,8是你要跳过的main字节数。 正如文章所说,您可以使用gdb检查函数的反汇编,以查看(a)调用function时堆栈被推送的距离,以及(b) main指令的字节偏移量。

+8字节部分取决于他希望保存的EIP增加多少。 EIP被保存,因此程序可以在function完成后返回到最后一个赋值 – 现在他想通过向保存的EIP添加8个字节来跳过它。

所以他试图去“跳过”

 x = 1; 

在您的情况下,保存的EIP将指向0x0040133b ,这是function返回后的第一条指令。 要跳过分配,您需要将保存的EIP点设置为0x00401342 。 这是7个字节。

它实际上是“混乱使用RET EIP”而不是缓冲区溢出示例。

就局部变量的56个字节而言,这可能是编译器提出的任何内容,如填充,堆栈金丝雀等。

编辑:

这表明在C中制作缓冲区溢出的例子是多么困难。来自buffer1的12的偏移量假定了某种填充样式和编译选项。 GCC现在很乐意插入堆栈金丝雀(它成为“保护”已保存的EIP的局部变量),除非你告诉它不要。 此外,他想要跳转到的新地址( printf调用的开始指令)实际上必须从汇编中手动解决。 在他的情况下,他的机器人,他的操作系统,他的编译器,在那一天….它是8。

您正在使用C ++编译器编译C程序。 将hw.cpp重命名为hw.c,你会发现它会编译。