Libc2.29 unlink Attack unsortbin 变化 libc2.23 没有对 unsortedbin 进行完整性检查:
1 2 3 4 5 6 7 8 9 10 11 for (;; ) { int iters = 0 ; while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect (victim->size > av->system_mem, 0 )) malloc_printerr (check_action, "malloc(): memory corruption" , chunk2mem (victim), av); size = chunksize (victim);
libc2.29 增加了完整性检查:
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 for (;; ) { int iters = 0 ; while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; size = chunksize (victim); mchunkptr next = chunk_at_offset (victim, size); 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)" );
原理 当 malloc、calloc 之类的从 smallbin 中取堆块时,取出成功后,如果 tcache 没有满就会将 smallbin 剩下的堆块放入 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 if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx); if ((victim = last (bin)) != bin) { bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): smallbin double linked list corrupted" ); set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) set_non_main_arena (victim); check_malloced_chunk (av, victim, nb); #if USE_TCACHE size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) { if (tc_victim != 0 ) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin; tcache_put (tc_victim, tc_idx); } } } #endif void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } }
smallbin 剩余堆块 unlink 取出没有进行检查:
1 2 3 4 5 6 7 8 9 10 11 if (tc_victim != 0 ) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin;
涉及利用的几条关键语句:
1 2 3 bck = tc_victim->bk; bin->bk = bck; bck->fd = bin;
bin 是一个 libc 地址,也就是 0x7f 开头的 6 位地址。
当劫持 tc_victim->bk
也就是控制 bck
为我们攻击地址-0x10
,bck->fd = bin;
将 bin 写入攻击地址。实现效果为:
攻击完成(放入一个堆块)后,这时原来的链表被打乱无法取出下一个堆块,所以攻击前需要让对应大小 tcache 数量为 6 ,放入一个堆完成攻击就退出循环不再放入堆块,就不会报错退出。
为了不报错需要同一大小堆块在 tcache
有 6 个 、**smallbin
有 2 个**。因为 tcache 机制,tcache 没有满时被释放堆块是放不进 smallbin 。
用切割 unsortedbin 方法在 smallbin 放入两个堆块,即 last remainder 。就是在 unsortedbin 拿了一大块堆块,如果用剩下的大于 remainder_size ,就将剩下的放到对应的 bin 中。
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 if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long ) (size) > (unsigned long ) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL ; remainder->bk_nextsize = NULL ; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; }
假设 unsortedbin 中有一个 0x400 堆,malloc 0x300 后,unsorbin 剩下 0x100 。这时 malloc 一个比 0x100 大的空间,系统遍历 unsortedbin 将各个堆块放入对应的 bin 中,0x100 顺利放入 smallbin。
堆布置图示:
chunk1 fd 指针需要是 chunk0 地址,用来绕过 chunk0 分配时的检查:
1 2 3 if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): smallbin double linked list corrupted" );
总结
使用前提:
UAF:用于修改 chunk1 的 bk
有能够跳过 tcache 申请堆的函数或机制:触发 smallbin unlink 放入 tcache
能泄露堆地址:chunk1 fd 填为 chunk0 地址,让 chunk0 成功分配
实现效果:
任意地址写入一个 libc 地址(0x7fxxxxxxxxxx)
应用:
可以参考 libc2.23 unlink
相关例题 HITCON CTF 2019 Quals — One Punch Man
[Black Watch 入群题]PWN2
参考文章 HITCON CTF 2019 Quals — One Punch Man
HITCON2019-Quals One_punch_Man
glibc2.29下unsortedbin_attack的替代方法