House Of Atum
在 libc-2.31 之前,tcache 还没有 key 值保护,因此实现 Double free 是很简单的事情
甚至可以把同一个 chunk 连续释放8次,使其进入 fastbin(同时它自身还会存留在 tcache 中)
利用场景:
- glibc < 2.31(没有 key 检查)
- double free
glibc2.31下的Tcache检查
对于每一个 tcache 中的 chunk,增加了一个 key 指针,用于指向所属的 tcache 结构体:
1 2 3 4 5
| typedef struct tcache_entry { struct tcache_entry *next; struct tcache_perthread_struct *key; } tcache_entry;
|
当 chunk 被放入时会设置 key 指针:
1 2 3 4 5 6 7 8 9 10 11 12
| static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *)chunk2mem(chunk); e->key = tcache; e->next = tcache->entries[tc_idx]; tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); }
|
ptmalloc 使用了一种更机智的方法,在不影响效率的前提下,完成了对double 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
| size_t tc_idx = csize2tidx(size);
if (tcache != NULL && tc_idx < mp_.tcache_bins) { tcache_entry *e = (tcache_entry *)chunk2mem(p);
if (__glibc_unlikely(e->key == tcache)) { tcache_entry *tmp; LIBC_PROBE(memory_tcache_double_free, 2, e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next) if (tmp == e) malloc_printerr("free(): double free detected in tcache 2"); } if (tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put(p, tc_idx); return; } }
|
简单来说:
- 在 free chunk 被放入 tcache 时,程序会设置一个 key 值
- 每次程序把 new free chunk 放入 tcache 前,都会检查一下它是否携带有 key 值
- 注意:key 值原本的位置是用户数据区(可以认为是随机值),有极小的概率会触发检查报错
House Of Atum 利用姿势
House Of Atum 通常在那些限制节点数量的题目中使用(在可利用节点小于7个的情况下将某个 free chunk 放入 fastbin)
- 以下案例展示把同一个 chunk 连续释放8次的情况:
1 2 3 4
| for i in range(7): dele(1,'n')
dele(1,'n')
|
1 2 3 4 5 6 7
| tcachebins 0x50 [ 7]: 0x564499ecc2b0 ◂— 0x564499ecc2b0 fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0
|
1 2 3 4 5 6 7 8
| pwndbg> bin tcachebins 0x50 [ 7]: 0x564499ecc2b0 ◂— 0x0 fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x564499ecc2a0 ◂— 0x0
|
- 现在该 chunk 同时存在于 tcache 和 fastbin 中
- 构造好堆风水,覆盖伪造的
chunk->fd
- 实现堆重叠并修改
chunk presize/size
版本对 House Of Atum 的影响
libc 版本必须小于 2.31