以前一直搞不明白unlink,直到学了点数据结构….

unlink分为向前合并向后合并两种脱链方式,是为了减少堆块的碎片化所提出的,当一个处于free状态的堆块的前后堆块被free的时候,就是触发unlink机制将两个小的堆块合并成一个大的堆块,unsafe unlink问题就是出现在合并的时候检查没有到位导致的

wiki上的脱链示意图,当进行脱链的时候FD的bk指向BK,BK的fd指向FD

但是unlink也不是随便进行的,还需要通过下面的检查才能进行unlink,但在很早以前,它并没有这种机制,我们先通过介绍没有检查的情况,然后在此基础上进行修改,实现现在的unlink攻击手法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");
// 检查 fd 和 bk 指针(双向链表完整性检查)
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);

// largebin 中 next_size 双向链表完整性检查
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
malloc_printerr (check_action,
"corrupted double-linked list (not small)",
P, AV);

以32位程序为例

假设申请了三个堆块,并存在堆溢出,我们先将chunk 2free掉,然后利用堆溢出覆盖fdbk

我们回忆一下,unlink会执行啥

1
2
3
4
5
6
7
FD = P -> fd = free@got - 12

BK = P -> bk = system@got

FD -> bk = BK => (free@got - 12) + 12 = system@got

BK -> fd = FD => system@got + 8 = free@got

执行完之后,free@got就变成了system@got,还要一点需要注意的是system@got + 8 = free@got,如果没有改回来有可能会发送一下奇奇怪怪的事情

讲完没有检查的情况,接下来就是有检查的情况了,它只要检查的是FD->bk != P || BK->fd != P

只要绕过这个检查就可以了

1
2
3
FD -> bk = BK => (free@got - 12) + 12

BK -> fd = FD => system@got + 8

我们看看刚刚的转换,明显不相等,是吧!

那按照这个检查,我们就可以进行伪造,这样一来就相等了对吧!

1
2
3
FD -> bk => (p - 12) + 12 = p

BK -> fd => (p - 8) + 8 = p

那么最终实现的效果就是,跟刚刚的直接修改GOT表相比,只是修改了chunk的指针,反差有点大,但是它还是有它的攻击效果的

1
(p - 8) + 8 = p - 12

例题为2016 ZCTF note2,参考《buu刷题记之PWN系列》向前和向后合并的源码解析都在那,这里就不赘述了!

完整源码分析

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
#define unlink(AV, P, BK, FD) {
//检查chunk的size如果和nextchunk的prev_size不相等就报错
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr ("corrupted double-linked list");
else {

FD->bk = BK;
BK->fd = FD;
//下面是largechunk的检查
if (!in_smallbin_range (chunksize_nomask (P))
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
malloc_printerr ("corrupted double-linked list (not small)");
if (FD->fd_nextsize == NULL) {
if (P->fd_nextsize == P)
FD->fd_nextsize = FD->bk_nextsize = FD;
else {
FD->fd_nextsize = P->fd_nextsize;
FD->bk_nextsize = P->bk_nextsize;
P->fd_nextsize->bk_nextsize = FD;
P->bk_nextsize->fd_nextsize = FD;
}
} else {
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
} }
} }