0%

exit_hook

何为exit_hook

hook即修改地址,exit_hook即修改exit函数内的地址达到控制执行流的目的

hook rtld_lock_(un)lock_recursive

由fini_array这篇文章知道了exit)()在执行时会先调用__run_exit_handlers,而在这个函数中又会调用_dl_fini

_dl_fini函数源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef SHARED
int do_audit = 0;
again:
#endif
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
__rtld_lock_lock_recursive (GL(dl_load_lock));
unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
if (nloaded == 0
#ifdef SHARED
|| GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
#endif
)
__rtld_lock_unlock_recursive (GL(dl_load_lock));

关键函数

1
2
__rtld_lock_lock_recursive (GL(dl_load_lock));
__rtld_lock_unlock_recursive (GL(dl_load_lock));

__rtld_lock_lock_recursive()的定义:

1
2
# define __rtld_lock_lock_recursive(NAME) \
GL(dl_rtld_lock_recursive) (&(NAME).mutex)

GL的定义:

1
2
3
4
5
# if IS_IN (rtld)
# define GL(name) _rtld_local._##name
# else
# define GL(name) _rtld_global._##name
# endif

_rtld_global是一个结构体,我们可以在pwndbg中用p _rtld_global来查看

结构体太长,这里我们这看结构体中作为上面俩函数的参数的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_dl_load_lock = {
mutex = {
__data = {
__lock = 0,
__count = 0,
__owner = 0,
__nusers = 0,
__kind = 1,
__spins = 0,
__elision = 0,
__list = {
__prev = 0x0,
__next = 0x0
}
},
__size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>,
__align = 0
}
},

函数的参数即为 _rtld_global._dl_load_lock.mutex.__size的地址

如何利用

既然我们已经知道了关键函数及其参数的位置,那么我们直接将关键函数改为one_gadget来get shell,也可以将关键函数改为system,再把参数地址改为/bin/sh\x00地址来get shell

如何寻找偏移

_rtld_global结构位于ld.so中 ( ld.sym['_rtld_global'] ),而libc_baseld_base又有固定的差值,所以我们可以通过libc的基址来推出函数及其参数的地址

1
2
3
libc2.23下偏移为0×5f0040,两个hook的偏移为3848和3850

libc2.27下偏移为0×61b060,两个hook的偏移为3840和3848

由于这个函数对于libc_base的偏移是固定的,故可以使用p &_rtld_global._dl_rtld_lock_recursive来查看函数地址,再减去libc_base得到偏移

1
2
pwndbg> p &_rtld_global._dl_rtld_lock_recursive
$4 = (void (**)(void *)) 0x7f1c260a8f60 <_rtld_global+3840>

hook __libc_atexit

exit.c源码如下:

1
2
3
4
5
6
7
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
...
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
...

而在我们调用__run_exit_handlers这个函数时,参数run_list_atexit传进去的值就为真:

1
2
3
4
void exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}

因此,可以直接改__libc_atexit的值为one_gadget,在执行exit函数(从main函数退出时也调用了exit())时,就能直接getshell了。
这个__libc_atexit有一个极大的优点,就是它在libc而非ld中,随远程环境的改变,不会有变化。缺点就是,它是无参调用的hook,传不了/bin/sh的参数,one_gadget不一定都能打通。

参考文章

pwn总结

exit hook

宸极实验室——『CTF』从两道题目学习 exit_hook