如何在GCC、Windows XP、x86系统中编写缓冲区溢出攻击?

8
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 <main+0>:    push   %ebp
0x004012ef <main+1>:    mov    %esp,%ebp
0x004012f1 <main+3>:    sub    $0x18,%esp
0x004012f4 <main+6>:    and    $0xfffffff0,%esp
0x004012f7 <main+9>:    mov    $0x0,%eax
0x004012fc <main+14>:   add    $0xf,%eax
0x004012ff <main+17>:   add    $0xf,%eax
0x00401302 <main+20>:   shr    $0x4,%eax
0x00401305 <main+23>:   shl    $0x4,%eax
0x00401308 <main+26>:   mov    %eax,0xfffffff8(%ebp)
0x0040130b <main+29>:   mov    0xfffffff8(%ebp),%eax
0x0040130e <main+32>:   call   0x401b00 <_alloca>
0x00401313 <main+37>:   call   0x4017b0 <__main>
0x00401318 <main+42>:   movl   $0x0,0xfffffffc(%ebp)
0x0040131f <main+49>:   movl   $0x3,0x8(%esp)
0x00401327 <main+57>:   movl   $0x2,0x4(%esp)
0x0040132f <main+65>:   movl   $0x1,(%esp)
0x00401336 <main+72>:   call   0x4012d0 <function>
0x0040133b <main+77>:   movl   $0x1,0xfffffffc(%ebp)
0x00401342 <main+84>:   mov    0xfffffffc(%ebp),%eax
0x00401345 <main+87>:   mov    %eax,0x4(%esp)
0x00401349 <main+91>:   movl   $0x403000,(%esp)
0x00401350 <main+98>:   call   0x401b60 <printf>
0x00401355 <main+103>:  leave
0x00401356 <main+104>:  ret
0x00401357 <main+105>:  nop
0x00401358 <main+106>:  add    %al,(%eax)
0x0040135a <main+108>:  add    %al,(%eax)
0x0040135c <main+110>:  add    %al,(%eax)
0x0040135e <main+112>:  add    %al,(%eax)
End of assembler dump.

Dump of assembler code for function function:
0x004012d0 <function+0>:        push   %ebp
0x004012d1 <function+1>:        mov    %esp,%ebp
0x004012d3 <function+3>:        sub    $0x38,%esp
0x004012d6 <function+6>:        lea    0xffffffe8(%ebp),%eax
0x004012d9 <function+9>:        add    $0xc,%eax
0x004012dc <function+12>:       mov    %eax,0xffffffd4(%ebp)
0x004012df <function+15>:       mov    0xffffffd4(%ebp),%edx
0x004012e2 <function+18>:       mov    0xffffffd4(%ebp),%eax
0x004012e5 <function+21>:       movzbl (%eax),%eax
0x004012e8 <function+24>:       add    $0x5,%al
0x004012ea <function+26>:       mov    %al,(%edx)
0x004012ec <function+28>:       leave
0x004012ed <function+29>:       ret

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

为什么函数需要56字节的本地变量?(sub $0x38,%esp


2
为什么如此痴迷于缓冲区溢出?难道是想写一个漏洞利用程序吗? - anon
1
他想了解也无可厚非,但如果不理解机器代码层面,就无法完全掌握。 - Donal Fellows
3
你没有提供文章的上下文。那8个字节来自于在main中通过更改返回地址(从function)跳过指令以避免重新分配x。打印出你的目标文件的反汇编代码,你就能看到在你的情况下有多少个字节是正确的。 - p00ya
5
请记住,这是来自1996年发布的Phrack 49! - Henry B
3
@Neil:我不得不查一下,但似乎普遍共识是黑帽问题是可以的。无法确定意图,所以假设安全漏洞问题背后有防御目的,我猜。参见:http://meta.stackexchange.com/questions/25448/is-it-okay-to-discuss-poor-security-practices 和 http://meta.stackexchange.com/questions/12621/would-you-teach-black-hat-techniques-in-public-forums - Bill the Lizard
显示剩余3条评论
6个回答

2
正如 joveha 指出的call 指令保存在堆栈上的 EIP 值(返回地址)需要增加 7 字节(0x00401342 - 0x0040133b = 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 来确定这个值。(编译时使用了 -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的地址 = 字节数


除了阅读《玩乐破坏栈》之外,我建议阅读我在回答你之前的问题中提到的一些文章和/或其他相关材料。深入理解这种类型的漏洞是有助于编写更安全的代码的。

顺便问一句,如何使用gdb来确定它的值?好文章,点赞 :) - Mask

2

很难预测buffer1 + 12实际指向哪里。你的编译器可以将buffer1buffer2放置在堆栈上的任何位置,甚至可以不为buffer2保存空间。真正了解buffer1所在位置的唯一方法是查看编译器的汇编输出,而且很可能会因不同的优化设置或相同编译器的不同版本而发生变化。


但是根据那篇文章的gdb转储,为什么距离是8? - Mask

1

+8字节部分是他想让保存的EIP自增的量。程序保存了EIP以便在function结束后返回到最后一个赋值位置,现在他想通过在保存的EIP上添加8字节来跳过它。

所以他所尝试的就是“跳过”它。

x = 1;

在您的情况下,保存的 EIP 将指向 0x0040133b,即 function 返回后的第一条指令。要跳过赋值,您需要使保存的 EIP 指向 0x00401342。这是 7 个字节。
这实际上是一个“RET EIP”示例,而不是缓冲区溢出示例。
至于本地变量的 56 个字节,那可能是编译器生成的任何内容,如填充、堆栈保护等。
编辑:
这说明了在 C 中制作缓冲区溢出示例有多么困难。从 buffer1 的偏移量为 12 假定了某种填充样式和编译选项。现在的 GCC 会愉快地插入堆栈保护(成为“保护”保存的 EIP 的本地变量),除非您告诉它不要这样做。此外,他想要跳转到的新地址(printf 调用的起始指令)确实必须从汇编手动解析。在他的情况下,在他的机器上,使用他的操作系统、他的编译器,在那一天……它是 8。

为什么在他的电脑、操作系统和编译器上,结果是8呢?我认为应该是10。 - Mask
呃,刚看了一下文章中的gdb转储。我也得到了10。奇怪。 - joveha
那篇文章没有提到堆栈保护机制,你能详细解释一下吗? - Mask
金丝雀只是自动本地变量和返回地址之间的一小段填充,可以用于检查完整性。 - p00ya
@jschmier,谢谢!另外,你能回答一下我在你的回答下面的评论吗? - Mask
显示剩余2条评论

1

我还没有在自己的机器上测试这段代码,但你有没有考虑过内存对齐? 尝试使用gcc对代码进行反汇编。我认为汇编代码可能会让你更好地理解这段代码。 :-)


1
我认为gdb比gcc提供更多的细节。 - Mask

1

这段代码在OpenBSD和FreeBSD上也会打印出1,但在Linux上会导致分段错误。

这种漏洞利用非常依赖于特定机器的指令集以及编译器和操作系统的调用约定。关于堆栈布局的所有内容都是由实现定义的,而不是C语言。文章假设在x86架构的Linux上运行,但看起来你正在使用Windows,并且你的系统可能是64位的,尽管你可以使用-m32将gcc切换到32位。

你需要调整的参数是12,它是从堆栈顶部到返回地址的偏移量,以及8,它是你想跳过多少字节的main函数。正如文章所说,你可以使用gdb来检查函数的反汇编,以查看(a)当你调用function时堆栈被推了多远,以及(b)main中指令的字节偏移量。


我在帖子中提供了 gdb 转储,请看一下 :) - Mask

0
你正在使用C++编译器编译一个C程序。将hw.cpp重命名为hw.c,你会发现它可以被编译。

更改为hw.c后输出仍然相同。 - Mask

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