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

问题描述 投票:0回答:7
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,对吧?但它似乎不起作用..

为什么局部变量

function
需要56字节?(
sub    $0x38,%esp
)

c gcc stack buffer-overflow exploit
7个回答
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 地址 = 字节数


除了为了乐趣和利润而粉碎堆栈之外,我建议阅读我在我对您之前问题的回答中提到的一些文章和/或有关该主题的其他材料。充分了解此类漏洞的工作原理应该可以帮助您编写更安全的代码


2
投票

很难预测

buffer1 + 12
真正指向的是什么。您的编译器可以将
buffer1
buffer2
放在堆栈上的任何位置,甚至根本不为
buffer2
节省空间。真正了解
buffer1
去向的唯一方法是查看编译器的汇编器输出,并且很有可能它会在不同的优化设置或同一编译器的不同版本中跳转。


1
投票

我还没有在自己的机器上测试代码,但是你考虑过内存对齐吗? 尝试用 gcc 反汇编代码。我想一段汇编代码可能会让你对代码有更进一步的理解。 :-)


1
投票

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

这种漏洞严重依赖于特定机器的指令集以及编译器和操作系统的调用约定。有关堆栈布局的所有内容均由实现定义,而不是由 C 语言定义。本文假设 Linux 运行在 x86 上,但看起来您使用的是 Windows,并且您的系统可能是 64 位,尽管您可以使用

-m32
将 gcc 切换到 32 位。

您需要调整的参数是 12,这是从堆栈尖端到返回地址的偏移量,以及 8,这是您要跳过的

main
的字节数。正如文章所述,您可以使用 gdb 检查函数的反汇编,以查看 (a) 调用
function
时堆栈被推入多远,以及 (b)
main
中指令的字节偏移量。


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。


0
投票

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


0
投票

8 是用于更改返回指针的字节数,以跳过这两条指令:

0x80004a8 <main+24>:    addl   $0xc,%esp
0x80004ab <main+27>:    movl   $0x1,0xfffffffc(%ebp)
© www.soinside.com 2019 - 2024. All rights reserved.