/* If a small request, check regular bin. Since these "smallbins" hold one size each, no searching within bins is necessary. (For a large request, we need to wait until unsorted chunks are processed to find best fit. But for small ones, fits are exact anyway, so we can check now, which is faster.) */
if (in_smallbin_range(nb)) { // 获取 small bin 的索引 idx = smallbin_index(nb); // 获取对应 small bin 中的 chunk 指针 bin = bin_at(av, idx); // 先执行 victim = last(bin),获取 small bin 的最后一个 chunk // 如果 victim = bin ,那说明该 bin 为空。 // 如果不相等,那么会有两种情况 if ((victim = last(bin)) != bin) { // 第一种情况,small bin 还没有初始化。 if (victim == 0) /* initialization check */ // 执行初始化,将 fast bins 中的 chunk 进行合并 malloc_consolidate(av); // 第二种情况,small bin 中存在空闲的 chunk else { // 获取 small bin 中倒数第二个 chunk 。(victim 此时为 small bin 中最后一个 chunk) bck = victim->bk; // 检查 bck->fd 是不是 victim,防止伪造 if (__glibc_unlikely(bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted"; goto errout; } // 设置 victim 对应的 inuse 位 set_inuse_bit_at_offset(victim, nb); // 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来 bin->bk = bck; bck->fd = bin; // 如果不是 main_arena,设置对应的标志 if (av != &main_arena) set_non_main_arena(victim); // 细致的检查 check_malloced_chunk(av, victim, nb); // 将申请到的 chunk 转化为对应的 mem 状态 void *p = chunk2mem(victim); // 如果设置了 perturb_type, 则将获取到的 chunk 初始化为 perturb_type ^ 0xff alloc_perturb(p, bytes); return p; } } }
主要漏洞点
1 2 3 4 5 6 7 8 9 10 11 12
// 获取 small bin 中倒数第二个 chunk 。 bck = victim->bk; // 检查 bck->fd (倒数第二个chunk)是不是 victim,防止伪造 if (__glibc_unlikely(bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted"; goto errout; } // 设置 victim 对应的 inuse 位 set_inuse_bit_at_offset(victim, nb); // 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来 bin->bk = bck; bck->fd = bin;
如果我们可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的 fake chunk,并且同时__glibc_unlikely(bck->fd != victim检查通过(即将 fake chunk 的 fd改为 victim),那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk。也就是说,当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chunk,实现任意地址写。
#if USE_TCACHE //如果程序启用了Tcache /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ //遍历整个smallbin,获取相同size的free chunk size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; /* While bin not empty and tcache not full, copy chunks over. */ //判定Tcache的size链表是否已满,并且取出smallbin的末尾Chunk。 //验证取出的Chunk是否为Bin本身(Smallbin是否已空) while ( tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin) ) != bin) { //如果成功获取了Chunk if (tc_victim != 0) { // 获取 small bin 中倒数第二个 chunk 。 bck = tc_victim->bk; //设置标志位 set_inuse_bit_at_offset (tc_victim, nb); // 如果不是 main_arena,设置对应的标志 if (av != &main_arena) set_non_main_arena (tc_victim); //取出最后一个Chunk bin->bk = bck; bck->fd = bin; //将其放入到Tcache中 tcache_put (tc_victim, tc_idx); } } } #endif
结合之前 2.23 的源码可以发现,tcache 并没有检查 house of lore 要经历的检查
1 2 3
// 检查 bck->fd 是不是 victim,防止伪造 if ( __glibc_unlikely( bck->fd != victim ) ) malloc_printerr ("malloc(): smallbin double linked list corrupted");
我们知道,tcache 是享有绝对优先权的,我们不能越过 tcache 向 small bin 中增删 chunk(但是 calloc 函数不会在 tcache 中拿 chunk)
攻击条件有两点:
small bin 中至少两个 chunk
tcache 不为空
然后是 Unsorted Bin 的last remainder基址,当申请的 Chunk 大于 Unsorted Bin 中 Chunk 的大小且其为 Unsorted Bin 中的唯一 Chunk 时,该 Chunk 不会进入 Tcache 。
calloc 在申请 chunk 时会直接越过 tcache bin 向 small bin 中拿 chunk,又因为 small bin 遵循先进先出,所以 chunk0 会被拿出来,tcache bin 没有放满,会从 small bin 中拿 chunk (顺着bk)挂进 tcachebin 中,并且这个过程只会检查第一个 chunk (此处对应 chunk2)的完整性