简体   繁体   English

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

[英]How to write a buffer-overflow exploit in 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);
}

The above demo is from here: 上面的演示来自这里:

http://insecure.org/stf/smashstack.html 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: 我不明白为什么它是8,尽管作者认为:

A little math tells us the distance is 8 bytes. 一个小数学告诉我们距离是8个字节。

My gdb dump as called: 我调用的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

In my case the distance should be - = 5,right?But it seems not working.. 在我的情况下,距离应该是 - = 5,对吗?但它似乎不起作用..

Why function needs 56 bytes for local variables?( sub $0x38,%esp ) 为什么function需要56个字节用于局部变量?( sub $0x38,%esp

It's hard to predict what buffer1 + 12 really points to. 很难预测buffer1 + 12真正指向的是什么。 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. 你的编译器可以将buffer1buffer2放在它想要的堆栈上的任何位置,甚至可以根本不为buffer2节省空间。 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. 真正知道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; 正如joveha指出的那样call指令保存在堆栈(返回地址)上的EIP值需要增加7个字节( 0x00401342 - 0x0040133b = 7 )才能跳过x = 1; instruction ( movl $0x1,0xfffffffc(%ebp) ). 指令( 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. 你是正确的56个字节是为局部变量保留的( sub $0x38,%esp ),所以缺少的部分是堆栈上buffer1多少字节是保存的EIP。


A bit of test code and inline assembly tells me that the magic value is 28 for my test. 一些测试代码和内联汇编告诉我,我的测试的魔术值是28 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 . 我不能提供关于为什么它是28的明确答案,但我认为编译器正在添加填充和/或堆栈金丝雀

The following code was compiled using GCC 3.4.5 (MinGW) and tested on Windows XP SP3 (x86). 以下代码使用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);
}

I could have just as easily used gdb to determine this value. 我可以像使用gdb一样轻松地确定这个值。

(compiled w/ -g to include debug symbols) (编译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 0x22ff28 + 4) - 0x22ff10 = 28

(ebp value + size of word) - address of buffer1 = number of bytes (ebp值+字的大小) - buffer1的地址=字节数


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. 除了Smashing The Stack For Fun和Profit之外 ,我建议阅读我在回答你之前关于你的问题和/或关于这个主题的其他材料时提到的一些文章。 Having a good understanding of exactly how this type of exploit works should help you write more secure code . 充分了解这种类型的漏洞利用程序应该如何帮助您编写更安全的代码

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. 尝试用gcc反汇编代码。 I think a assembly code may give you a further understanding of the code. 我认为汇编代码可以让您进一步了解代码。 :-) :-)

This code prints out 1 as well on OpenBSD and FreeBSD, and gives a segmentation fault on Linux. 此代码在OpenBSD和FreeBSD上打印出1,并在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. 关于堆栈布局的一切都是由实现定义的,而不是C语言。 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 . 本文假设Linux在x86上,但看起来你正在使用Windows,而你的系统可能是64位,尽管你可以用-m32将gcc切换到32位。

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

The +8 bytes part is by how much he wants the saved EIP to the incremented with. +8字节部分取决于他希望保存的EIP增加多少。 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. EIP被保存,因此程序可以在function完成后返回到最后一个赋值 - 现在他想通过向保存的EIP添加8个字节来跳过它。

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. 在您的情况下,保存的EIP将指向0x0040133b ,这是function返回后的第一条指令。 To skip the assignment you need to make the saved EIP point to 0x00401342 . 要跳过分配,您需要将保存的EIP指向0x00401342 That's 7 bytes. 这是7个字节。

It's really a "mess with RET EIP" rather than an buffer overflow example. 它实际上是“混乱使用RET EIP”而不是缓冲区溢出示例。

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. 就局部变量的56个字节而言,这可能是您的编译器提出的任何内容,如填充,堆栈金丝雀等。

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. 这表明在C中制作缓冲区溢出的例子是多么困难。来自buffer1的12的偏移假定了某种填充样式和编译选项。 GCC will happily insert stack canaries nowadays (which becomes a local variable that "protects" the saved EIP) unless you tell it not to. GCC现在很乐意插入堆栈金丝雀(它成为一个“保护”已保存的EIP的局部变量),除非你告诉它不要。 Also, the new address he wants to jump to (the start instruction for the printf call) really has to be resolved manually from assembly. 此外,他想要跳转到的新地址( printf调用的开始指令)实际上必须从汇编中手动解决。 In his case, on his machie, with his OS, with his compiler, on that day.... it was 8. 在他的情况下,在他的机器上,他的操作系统,他的编译器,那天....它是8。

You're compiling a C program with the C++ compiler. 您正在使用C ++编译器编译C程序。 Rename hw.cpp to hw.c and you'll find it will compile. 将hw.cpp重命名为hw.c,你会发现它会编译。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM