Unlink

Unlink

我们在利用unlink所造成的漏洞是,其实就是对chunk进行内存布局,然后借助unlink操作
来达成修改指针的效果。

我们先来简单回顾一下unlink的目的与过程,其目的是把一个双向链表中的空闲块拿出来
(例如free时和目前物理相邻的free chunk进行合并)。其基本的过程如下:
1.png
下面我们首先介绍一下unlink最初没有防护时的利用方法,然后介绍目前利用Unlink的方式。

最初unlink实现的时候,其实是没有对chunk的size检查和双向链表检查的,即没有如下检查
代码。

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位为例,假设堆内存最初的布局是下面的样子
2.png
现在有物理空间连续的两个chunk(Q,Nextchunk),其中Q处于使用状态、Nextchunk
处于释放状态。那么如果我们通过某种方式(比如溢出)将Nextchunk的fd和bk指针修改为
指定的值。则当我们free(Q)时
①glibc判断这个块是small chunk
②判断向前合并,发现前一个chunk处于使用状态,不需要向前合并
③判断向后合并,发现后一个chunk处于空闲状态,需要合并
④继而对Nextchunk采取unlink操作
那么unlink具体执行的效果是什么样子呢?
①FD=P->fd = target addr -12
②BK=P->bk = expect value
③FD->bk = BK,即 *(target addr-12+12)=BK=expect value
④BK->fd = FD,即 *(expect value +8) = FD = target addr-12
看起来我们似乎可以通过unlink直接实现任意地址读写目的,但是我们还是需要确保expect
value+8的地址具有可写权限。

比如说我们将target addr 设置成某个got表项,那么当程序调用对应的Libc函数时,就会直接
执行我们设置的值(expect value)处的代码。需要注意的是,expect value+8处的值被破坏
了,需要想办法绕过。

我们刚才考虑的是没有检查的情况,但是一旦加上检查,就没有这么简单了。我们对比一下
fd和bk的检查

1
2
3
// fd bk
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \

此时
①FD->bk = target addr - 12 + 12=target_addr
②BK->fd = expect value + 8
那么我们上面所利用的修改GOT表项的方法就可能不可用了。但是我们可以通过伪造的方式
绕过这个机制。
首先我们通过覆盖,将nextchunk的FD指针指向了fakeFD,将nextchunk的BX指针指向了
fakeBK。那么为了通过验证,我们需要
①fakeFD -> bk == P <=> *(fakeFD + 12) == P
②fakeBK -> fd == P <=> *(fakeBK + 8) == P
当满足上述两个式子时,可以进入unlink环节,进行如下操作:
①fakeFD -> bk = fakeBK <=> (fakeFD + 12) = fakeBK
②fakeBK -> fd = fakeFD <=> (fakeBK + 8) = fakeFD
如果让fakeFD+12和fakeBK+8指向同一个指向P的指针,那么:
P = P - 8
P = P - 12
即通过此方式,P的指针指向了比自己低12的地址处。此方法虽然不可以实现任意地址写,
但是可以修改指向chunk的指针,这样的修改是可以达到一定的效果的。

如果我们想要使得两者都指向P,只需要按照如下方式修改即可
3.png
需要注意的是,这里我们并没有违背下面的约束,因为P在unlink前是指向正确的chunk的
指针。

1
2
3
// 由于P已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \

此外,其实如果我们设置next chunk的fd和bk均为nextchunk的地址也是可以绕过上面的
检测的。但是这样的话,并不能达到修改指针内容的效果。

利用思路

条件

1、UAF,可修改free状态下smallbin或是unsorted bin 的fd和Bk指针
2、一直位置存在一个指针指向可进行UAF的chunk

效果

使得已指向UAF chunk的指针ptr变为ptr-0x18

思路

设指向可UAF chunk的指针的地址为ptr
①修改fd为 ptr-0x18
②修改bk为ptr-0x10
③触发unlink
ptr处的指针会变为Ptr-0x18


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!