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

发布于 2024-08-27 01:33:41 字数 3378 浏览 3 评论 0原文

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 字节作为局部变量?( 子$0x38,%esp

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);
}

The above demo is from here:

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

But it's not working here:

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

And I don't understand why it's 8 though the author thinks:

A little math tells us the distance is
8 bytes.

My gdb dump as called:

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

In my case the distance should be - = 5,right?But it seems not working..

Why function needs 56 bytes for local variables?( sub $0x38,%esp )

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

陌生 2024-09-03 01:33:41

正如 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 的地址 = 字节数


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

As joveha pointed out, the value of EIP saved on the stack (return address) by the call instruction needs to be incremented by 7 bytes (0x00401342 - 0x0040133b = 7) in order to skip the x = 1; instruction (movl $0x1,0xfffffffc(%ebp)).

You are correct that 56 bytes are being reserved for local variables (sub $0x38,%esp), so the missing piece is how many bytes past buffer1 on the stack is the saved EIP.


A bit of test code and inline assembly tells me that the magic value is 28 for my test. I cannot provide a definitive answer as to why it is 28, but I would assume the compiler is adding padding and/or stack canaries.

The following code was compiled using GCC 3.4.5 (MinGW) and tested on 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);
}

I could have just as easily used gdb to determine this value.

(compiled w/ -g to include debug symbols)

(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 value + size of word) - address of buffer1 = number of bytes


In addition to Smashing The Stack For Fun And Profit, I would suggest reading some of the articles I mentioned in my answer to a previous question of yours and/or other material on the subject. Having a good understanding of exactly how this type of exploit works should help you write more secure code.

扶醉桌前 2024-09-03 01:33:41

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

It's hard to predict what buffer1 + 12 really points to. Your compiler can put buffer1 and buffer2 in any location on the stack it desires, even going as far as to not save space for buffer2 at all. The only way to really know where buffer1 goes is to look at the assembler output of your compiler, and there's a good chance it would jump around with different optimization settings or different versions of the same compiler.

她说她爱他 2024-09-03 01:33:41

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

I do not test the code on my own machine yet, but have you taken memory alignment into consideration?
Try to disassembly the code with gcc. I think a assembly code may give you a further understanding of the code. :-)

凌乱心跳 2024-09-03 01:33:41

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

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

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

This code prints out 1 as well on OpenBSD and FreeBSD, and gives a segmentation fault on Linux.

This kind of exploit is heavily dependent on both the instruction set of the particular machine, and the calling conventions of the compiler and operating system. Everything about the layout of the stack is defined by the implementation, not the C language. The article assumes Linux on x86, but it looks like you're using Windows, and your system could be 64-bit, although you can switch gcc to 32-bit with -m32.

The parameters you'll have to tweak are 12, which is the offset from the tip of the stack to the return address, and 8, which is how many bytes of main you want to jump over. As the article says, you can use gdb to inspect the disassembly of the function to see (a) how far the stack gets pushed when you call function, and (b) the byte offsets of the instructions in main.

喵星人汪星人 2024-09-03 01:33:41

+8字节部分是他想要保存的EIP增加多少。 EIP 已保存,因此程序可以在函数完成后返回到上次分配 - 现在他想通过向保存的 EIP 添加 8 个字节来跳过它。

因此,他所做的就是“跳过”

x = 1;

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

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

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

编辑:

这显示了在 C 中制作缓冲区溢出示例是多么困难。 12 的偏移量buffer1 采用特定的填充样式和编译选项。如今,GCC 会很乐意插入堆栈金丝雀(它成为“保护”保存的 EIP 的局部变量),除非您告诉它不要这样做。此外,他想要跳转到的新地址(printf 调用的起始指令)确实必须从汇编中手动解析。就他而言,那天,在他的机器上,用他的操作系统,用他的编译器……当时是 8。

The +8 bytes part is by how much he wants the saved EIP to the incremented with. The EIP was saved so the program could return to the last assignment after the function is done - now he wants to skip over it by adding 8 bytes to the saved EIP.

So all he tries to is to "skip" the

x = 1;

In your case the saved EIP will point to 0x0040133b, the first instruction after function returns. To skip the assignment you need to make the saved EIP point to 0x00401342. That's 7 bytes.

It's really a "mess with RET EIP" rather than an buffer overflow example.

And as far as the 56 bytes for local variables goes, that could be anything your compiler comes up with like padding, stack canaries, etc.

Edit:

This shows how difficult it is to make buffer overflows examples in C. The offset of 12 from buffer1 assumes a certain padding style and compile options. GCC will happily insert stack canaries nowadays (which becomes a local variable that "protects" the saved EIP) unless you tell it not to. Also, the new address he wants to jump to (the start instruction for the printf call) really has to be resolved manually from assembly. In his case, on his machie, with his OS, with his compiler, on that day.... it was 8.

滴情不沾 2024-09-03 01:33:41

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

You're compiling a C program with the C++ compiler. Rename hw.cpp to hw.c and you'll find it will compile.

稀香 2024-09-03 01:33:41

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

0x80004a8 <main+24>:    addl   $0xc,%esp
0x80004ab <main+27>:    movl   $0x1,0xfffffffc(%ebp)

8 is number of bytes for changing the return pointer in a way that it skips these two instructions:

0x80004a8 <main+24>:    addl   $0xc,%esp
0x80004ab <main+27>:    movl   $0x1,0xfffffffc(%ebp)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文