Glibc Heap Exploit 坐牢笔记 - 0x06

# libc-cmp : libc-2.27 AND libc-2.31

# Tcache 方面

# Tcache 结构体数据类型

1
2
3
4
5
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

counts 数组由 char 变成了 uint16_t,估计是为了防止元素被 Hacker 利用整数溢出为负数导致每个 bin 的 chunk 个数在之后的 counts 和 TCACHE_FILL_COUNT(数值 7)大小判断导致失误从而在满的 Tcache bin 中插入新的 chunk,从而防止一些 hack 行为,同时保证 Tcache 的执行效率(毕竟 Tcache 的加入是为了加快程序运行,Tcache bin 中放的 chunk 太多了也就失去了它存在的意义)。

# Tcache 的 put get

2.31 的代码:

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
/* 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);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;

e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

/* 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];
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

2.27 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 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]);
}

/* 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]);
return (void *) e;
}

可以看到去掉了 assert () 的内容。应该是为了防止重复判断导致运行效率下降。

# _int_malloc 函数检查条件增加

原本取出 chunk 的时候只有这几行的检查:

1
2
3
4
   if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize_nomask (victim)
> av->system_mem, 0))
malloc_printerr ("malloc(): memory corruption");

现在变成了这一大坨:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

可以看到取出 chunk 时对 chunk 的检查更加仔细,报错更加详细。
1. 检查当前 chunk 的 size
2. 检查 next chunk 的 size
3. 检查下一个 chunk 记录的 prev_size(就是记录的当前 chunk 的 size)是否相同。不相同就说明被篡改了
4. 检查双链表是否正常链接
5. 检查当前 chunk 是否被 next chunk 标记为 in use。

从 unsorted bin 取出 chunk 的时候检查了链表完整性:

1
2
3
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");

取出 unsorted bin 中的 chunk 将要放入 large bin 增加了两个检查,检查了链表的完整性:

1
2
3
4
5
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");

if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");

使用 top chunk 的时候检查了 top chunk 的 size

1
2
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

# _int_free 新条件检查

chunk 向后合并的时候检查了当前 chunk 记录的 prev_size 和后面的 chunk 记录的 size 是否相同,若不同说明被篡改,报错:

1
2
3
4
5
6
7
8
9
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}