fastbin_reverse_into_tcache
复制本地路径 | 在线编辑
建议直接看 tcache_stashing_unlink 记录文章,建议从头看到尾,不要贪图块。尤其是最后面的引申,把这两个攻击完全串起来了。
这里还是简单稍微介绍一下:
- 申请 N 块内存,释放七次,塞满 tcahe;然后释放七次,全都放进了 fastbin
- 把 fastbin 最后一个块,里面的 fd 指针修改成目标地址
target_addr - 申请七次块,目的是清空 tcahe,这样只有 fastbin 中有数据了
- 申请一次,malloc 会从 fastbin 中找到块,此时会触发 stash 机制,把剩下的块放入到 tcache 中
- 由于第 2 步中,最后一个块的
fd被修改成了target_addr,所以target_addr也被放入了 tcahe,最终 malloc 一次就得到了目标地址的块
要求:可以复写某个 freed chunk 的 fd 内容
效果:任意地址块控制
而且因为这样思考和延申,我还发现了 how2heap 中 fastbin_reverse_into_tcahe.c 的错误,之前这个文件的注释认为第 1 步中不一定要释放七次块到 fastbin 中,但其实是必须的。
当时的提交 Issue(英文):https://github.com/shellphish/how2heap/issues/222
当时 Issue 的本地草稿
在 fastbin_reverse_into_tcahe 中,注释宣称如果目标控制地址的内容为 0,那么可以指释放一个 fastbin:
char* victim = ptrs[7];
printf("The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n", victim);
free(victim);
printf("Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n");
// Fill the fastbin.
// for (i = 8; i < 14; i++) free(ptrs[i]);
// 如果按照注释,我们只释放一个
free(ptrs[8]);
此时堆空间为:
[fastbin] -> victim -> target_addr(it's fd is zero)
注释这样写应该是认为 fd 为 0,此时 stash 会终止。
但由于 safe-linking,把 target_addr 放入到 tcache 时,会解析 target_addr→next 为一个非法地址 REVEAL_PTR(0),而不是 0,因此会检测失败:
/* While we're here, if we see other chunks of the same size, stash them in the tcache. */
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. */
while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
// 如果 tc_victim->fd,但 *fd 并不是 0,最终导致上一条语句的检测失败
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);
}
}