exit_hook

文章发布时间:

最后更新时间:

1
2
3
4
5
6
7
//gcc exit.c -g -no-pie -o exit
#include<stdio.h>
void main()
{
printf("str1k3\n");
exit(0);
}

看看exit

1
2
3
4
5
6
7
8
9
0x7ffff7e08a40 <exit>         endbr64 
0x7ffff7e08a44 <exit+4> push rax
0x7ffff7e08a45 <exit+5> pop rax
0x7ffff7e08a46 <exit+6> mov ecx, 1
0x7ffff7e08a4b <exit+11> mov edx, 1
0x7ffff7e08a50 <exit+16> lea rsi, [rip + 0x1a5cc1] <__exit_funcs>
0x7ffff7e08a57 <exit+23> sub rsp, 8
0x7ffff7e08a5b <exit+27> call __run_exit_handlers <__run_exit_handlers>

跟进 __run_exit_handlers看看

1
2
3
4
5
6
  0x7ffff7e0888a <__run_exit_handlers+218>    mov    rdi, qword ptr [rax + 0x20]
0x7ffff7e0888e <__run_exit_handlers+222> mov qword ptr [rax + 0x10], 0
0x7ffff7e08896 <__run_exit_handlers+230> mov esi, ebp
0x7ffff7e08898 <__run_exit_handlers+232> ror rdx, 0x11
0x7ffff7e0889c <__run_exit_handlers+236> xor rdx, qword ptr fs:[0x30]
0x7ffff7e088a5 <__run_exit_handlers+245> call rdx <_dl_fini>

调用了_dl_fini,接着跟进

1
2
0x7ffff7fe0dca <_dl_fini+106>    lea    rdi, [rip + 0x1cb97]          <_rtld_global+2312>
0x7ffff7fe0dd1 <_dl_fini+113> call qword ptr [rip + 0x1d191] <rtld_lock_default_lock_recursive>
1
2
3
4
5
6
7
8
9
0x7ffff7fe103f <_dl_fini+735>    cmovne rdi, r13
0x7ffff7fe1043 <_dl_fini+739> jmp _dl_fini+366 <_dl_fini+366>

0x7ffff7fe0ece <_dl_fini+366> xor edx, edx
0x7ffff7fe0ed0 <_dl_fini+368> mov ecx, 1
0x7ffff7fe0ed5 <_dl_fini+373> call _dl_sort_maps <_dl_sort_maps>

0x7ffff7fe0eda <_dl_fini+378> lea rdi, [rip + 0x1ca87] <_rtld_global+2312>
0x7ffff7fe0ee1 <_dl_fini+385> call qword ptr [rip + 0x1d089] <rtld_lock_default_unlock_recursive>
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
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));
}
}

放一下这两函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
# 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

gdb输入p _rtld_global可以看到结构体_rtld_global

1
2
_dl_rtld_lock_recursive = 0x7ffff7fd0150 <rtld_lock_default_lock_recursive>,
_dl_rtld_unlock_recursive = 0x7ffff7fd0160 <rtld_lock_default_unlock_recursive>,

这俩函数是结构体_rtld_global的一部分 存在于_dl_load_lock 里,
是 _rtld_global._dl_load_lock.mutex.__size的地址
并且在调用之前会把某个地址里的值赋给 rdi。
由于__rtld_lock_unlock_recursive存放在结构体空间,为可读可写,那么如果可以修改__rtld_lock_unlock_recursive,就可以在调用exit()时劫持程序流。
_rtld_lock_lock_recursive也是一样的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
//在libc-2.23中
exit_hook = libc_base+0x5f0040+3848

exit_hook = libc_base+0x5f0040+3856

//在libc-2.27中

exit_hook = libc_base+0x619060+3840

exit_hook = libc_base+0x619060+3848

//libc-2.31
exit_hook = libc_base + 0x222060 + 3848

这样一来,只要知道libc版本和任意地址的写,我们可以直接写这个指针,执行exit后就可以劫持控制流了。

任意一个改写成one_gadget都可以拿到shell

或将任意一个改为system,将_rtld_global._dl_load_lock.mutex.__size改为/bin/sh\x00也可