Skip to content

unsafe unlink

复制本地路径 | 在线编辑

历史悠久的攻击手段。

原理核心

从 unsorted bin 等双向链表取出的时候,会进行 unlink 操作,攻击点就是如下的代码:

FD = P->fd
BK = P->bk
FD->bk = BK
BK->fd = FD

如果我们可以控制 Pfdbk 指针,就可以做点攻击。

古老的攻击

我们构造:

P->fd = target_addr-24
P->bk = expect_value

那么:

FD->bk = BK <==> *(target_addr-24+24) = expect_value
BK->fd = FD <==> *(expect_value+16) = target_addr-24

实现任意地址读写的目的,但是我们还是需要确保 expect value + 16 地址具有可写的权限,并且被破坏之后不影响程序运行。

现在的攻击

现在 unlink 有了如下的检查,所以之前的方式就不行了:

FD->bk != P || BK->fd != P,
题外话

多说一句,其实这个检查也给很多其他一些攻击带来了困扰。有些攻击重点不是在于 unlink,他有其他的攻击方式,但是避免不了要把 chunk 从双向链表种取出来。需要能过掉这个检查就行,所以这种攻击通常就直接伪造这样的字段,可以带入上面的式子,是能够 PASS 掉的:

chunk->fd == chunk
chunk->bk == chunk

那么现在的 unlink,我们进行这样的构造:

P->fd = &P-24
P->bk = &P-16

那么

FD->bk != P <==> *(&P-24+24) != P
BK->fd != P <==> *(&P-16+16) != P

FD->bk = BK <==> *(&P-24+24) = &P-16
BK->fd = FD <==> *(&P-16+16) = &P-24

相当于修改了 P 的指针指向比自己低 24 字节的地址,即下图中灰色框的内容变成 0x7ffff0840-24

如果我们可以对 P 的指针有写入多个字节的功能,就能够改变 P 的指针了,具体可以看下一章节。

用途

关键是这有啥用... 从上面就能看出,现在的 unlink 已经今非昔比了,攻击力量断崖式下降。

下面是一个例子,必须要先了解 house of einherjar 才可以。

house of einherjar 回顾

回顾 house of einherjar,如下图所示,我们是修改了下图中 chunkC 的 prev_in_use 字段,这样 free(C) 的时候会合并我们伪造的红色框。

由于合并红色框的时候,会默认红色框在双向链表中(想一想为什么,因为如果是 fastbin 大小,释放块的时候,不会修改 nextchunk 的 prev_in_use 字段),所以会执行 unlink 操作。为了避免检查出错,我们将 fdbk 都改成自身,这样可以 PASS 检查。

而 unsafe unlink 的区别就是这里的 fdbk 不同,不再是自身,而是伪造的地址。 如下图所示:

除了大小不一样,唯一的区别就是 fdbk,这里是因为:

gdb> p &a
$1 = (uint64_t **) 0x555555558020 <a>

fd = &a-24 = 0x555555558020-24 = 0x555555558008
bk = &a-16 = 0x555555558020-16 = 0x555555558010

所以在 free(c) 之后,两个块合并了,然后进行了 unlink 操作,最终的结果:

即最终指向了低自己 24 字节部分,如果我们这个指针有写入多个字节的功能(基本上肯定有,因为 a 原来是堆的地址):

// 一般都会有写入多个字节的功能,因为 a 一开始是堆的地址,即一般都是下面的语句
// uint64_t *a = malloc(0x20);

a[3] = target_addr

最终变成下图所示,我们成功可以在 target_addr 上任意写入!

a[0] = 'hi~'

Comments