Skip to content

tcache poisoning

复制本地路径 | 在线编辑

tache poisoning 暨 safe-linking 说明

介绍

一句话,就是把某个块的 next 指针给改了,然后 malloc 就能得到任意地址的数据。

本质上,double free 最终很多也是修改了 next 指针,所以 tcache poisoning 是一种更泛的术语。

counts 很重要

但是其实这里有细节:tcache 判断的时候是通过对应的 count 来判断的:

if (tc_idx < mp_.tcache_bins && tcache && tcache->counts[tc_idx] > 0)
{
  victim = tcache_get (tc_idx);
  return tag_new_usable (victim);
}

所以如果假设链表上只有一个 chunk,把它改成 next 并没有效果,因为第一次 malloc 之后 counts 已经是 0 了:

// 放入 tcache 中
vuln_chunk = malloc(0x20)
free(vuln_chunk)

// 修改 next 指针
vuln_chunk->next = target_addr

malloc(0x20) // 这次会得到 vuln_chunk,但是此时 counts 变为了 0

malloc(0x20) // 此时不会得到 target_addr,因为 counts 是 0,系统认为 tcache 上是空的

所以如果要攻击,通常是需要在 tcache 中放上两个才行,这样修改第一个的 next,然后再去两次 malloc 就可以了:

// 先放入一个到 tcache
a = malloc(0x20);
free(a);

// 放入 tcache 中
vuln_chunk = malloc(0x20)
free(vuln_chunk)

// 修改 next 指针
vuln_chunk->next = target_addr

malloc(0x20) // 这次会得到 vuln_chunk,此时 counts 变为了 1

malloc(0x20) // 此时会得到 target_addr

防御机制:safe-linking

glibc 2.32 增加了 safe-linking 这个操作,先介绍宏定义:

#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))

#define REVEAL_PTR(ptr) \
  PROTECT_PTR (&ptr, ptr)

核心改动,每次在 free 之后,加入链表的 next 多做如下的操作:

e->next = PROTECT_PTR(&e->next, next);
// 等价于
e->next = next ^ (chunk_addr >> 12)

找链表下一个的操作变成了:

next = REVEAL_PTR(e->next);
// 等价于
next = PROTECT_PTR(&e->next, e->next) = ((&e->next >> 12) ^ e->next);

我知道现在会有很多疑问,别着急慢慢来。

正确时候的分析

假设没有恶意攻击。原来的 next,即下一个 chunk 的地址,我们叫做 real_next。当 free(e) 时:

// chunk_addr 是 e 自身的地址
e->next = (real_next) ^ (chunk_addr >> 12)

malloc 的时候,我们需要从 e 去寻找下一个,那么:

next = ((&e->next >> 12) ^ e->next)
     = ((chunk_addr >> 12) ^ (real_next) ^ (chunk_addr >> 12))
     = real_next

所以,正确的时候,可以找到下一个 chunk 的地址。

恶意时候的攻击

1. 原来的攻击方式,攻击把 e->next 指针改成了 fake_next,然后 malloc()e 从链表取走,再来一次 malloc() 得到了 fake_next 指向的内容。

现在来看,则变成了,此时就不对了。

e->next = fake_addr ^ (chunk_addr >> 12);

2. 那么转换思路,攻击把 e->next 指针改成 fake_next ^ (chunk_addr >> 12),这样不就可以了吗?

但是:攻击者很难知道 chunk_addr 这相当于攻击者需要知道 heap 的地址,如果开了 ASLR,那么攻击者是没办法知道准确地址的。

3. 那如果我知道 heap 地址再去做,是的,这样你就可以攻击了。但那就不是 safe-linking 所考虑的事情了,他这个防御的存在就是为了让攻击者不知道 heap 地址的时候可以防护。

至于为什么是右移 12 位,其实就是低 12 位在 ASLR 中表示的偏移量,这个是可以根据调试得到已知的。

其他说明

当时的疑问:加了 tcache_key 应该已经可以防止 safe-linking 预防的场景了吧?

简单回答:tcache_key 只能防止 double free,有的时候 tcache poisoning,即修改 next 指针不一定是通过 double free 来完成的。

decrypt safe-linking

在正常的时候,假设我们不知道堆地址,但是我们知道 next 指针,我们能否解出正确的指针?答案是肯定的。

回顾 safe-linking,可以看到,高 12 bit 是没有动的。并且基于一个事实:real_nextchunk_addr 在 12bit 往上的部分大概率一样。 之所以用 12 bit 就是这表示了一个页的大小,堆块一般都在一个页之间,所以他们除了低 12 bit 有所不同,剩下的是一样的。

// chunk_addr 是 e 自身的地址
e->next = (real_next) ^ (chunk_addr >> 12)

所以如下图所示,我们逐步的去求解即可。我们按照每 12 bit 划分,最后的 x5 其实是 4bit,这个暂且忽略,就假设地址是 60 bit 吧。x0-x4 是正确的地址,c0-c4 是密文,即 e->next。蓝色是未知、绿色是已知,我们逐步能推出来...

Comments