是我的错觉么,总觉得unlink比UAF好懂好多…也有可能是我觉得做题模板比较好理解一点,真要深入的话我感觉一个头会比两个大emmmm
原理及其条件
原理
unlink顾名思义,脱链,把一个空闲的chunk从unsorted bin里取出来,与物理相邻的chunk合成一个一个大堆块(分“前合”,“后合”’)。这里用图来解释会更清晰一些
原本的堆块结构是这样的,双向链表
1 | FD = P->fd |

在unlink经过一系列操作之后成了这样
1 | FD->bk = BK |

我们可以很清楚的看到,BK->fd不再指向p->prev_size而是FD->prev_size,而FD-bk同理,指向了BK->prev_size,就把P给脱了出来,等待下一个被free的与它物理相邻的堆块,与其合并成新的堆块。比如P和FD都被free了,那么P和FD就会合成为新的堆块,chunk头就是P的首地址,而如果是BK和P合并的话,chunk头就是BK的首地址,所以前合与后合是不一样的
那么我们要是可以伪造fd和bk弄出了fake_chunk,是不是就可以进行任意地址读写了?
条件
想要利用这个漏洞,那么你就必须拥有修改被free掉的堆块的权限,即UAF漏洞
关键源码
unlink的源码其实很长,但我们需要动用的部分其实很少,我就把那部分代码截出来进行解读
1 |
|
所以我们要想伪造fake_chunk,那么我们就要满足这样的表达式
1 | P->fd->bk == P <=> *(P->fd + 0x18) == P |
那么我们伪造的fd和bk就是
1 | P->fd = P - 0x18 |
其最终效果就是往P里写入(P-0x18)的值
例题(stkof)
审视源码
main函数,可以看到是去符号表的,通过1,2,3,4来执行相应函数,类似于menu

首先看寻找下malloc在哪里,经查询,当v3 == 1时,该函数就是malloc

有了malloc,后面的函数就更好对应了,那我们来看看v3 == 2是什么

可以看到,有两个输入,最开始会查询数组s中的v2存不存在,不存在就Fail,所以可以看做是index,而第二个输入比较复杂,先是把s字符串转为整形赋给n,ptr取s[v2]的内容,在for循环里逐字节对应,那么我们可以得到n为size,而ptr为content,那么这一整个函数就可以说是edit
再看看v3 == 3,很明显是个free

其实只要有这三个函数就足够了,剩下的那个函数就是个checkin,看看你写入没有,非必要就不展示了
Payload
可能是我ubuntu22.04版本有点高,本地调不通,所以我本来想很详细的写gdb调试过程的,但是却因为本地调不成功而远端可打,迫于无奈只能先把payload整个放出来,然后逐步解释
1 | from pwn import * |
逐步讲解
伪造fake_chunk
首先是很常规的步骤,创建四个堆块,然后根据unlink原理伪造fd和bk,创造出fake_chunk
1 | malloc(0x30) |
我们一次来看edit之前,edit和free后的状况
edit前

首先我们先排除三个堆块,分别为系统创造的size为0x290,0x1000,0x410(其实都比真实开辟的地址大0x10),其余的才是我们创造的堆块。
我们可以看到最初的堆块构造
edit

现在我们已经把payload的内容写进去了,fd和bk也都伪造好了,不过在伪造fd,bk之前,得先确定堆块的位置,先来看看如何根据堆块的位置进行伪造fd,bk指针

0x602140是数组s的首地址,也是我们堆所在的地方,我们可以很清楚的看到,因为我选取的是堆块2,对应的地址是0x602150-->0x1ed4700,所以我的P == 0x612150,那么fd和bk也就应运而生
free
从这里开始我的gdb就开始失败了,为了连贯我还是把它放出来

按照正常情况,我最开始的0x31应该会被修改成0xc1,而bk和fd也会被解析为真实地址,但是不知为何没有,应该是ubuntu版本问题。
如果正常运行的话,堆会变成这样

那么我们现在就可以进行任意地址写了
PS:由于堆块2和3已经合并了,所以这里的chunk3实际是我开辟的第四个堆块
修改chunk
思路
从这里开始可能就比较意识流了,我会尽量进行详细解释,让大家看懂
1 | payload_change = p64(0) #0x602138 |
我们要进行任意地址写,就必须从0x602138入手,也就是我们的堆块2入手,我们把got表填写进chunk之中,通过改写got表来实现任意操作。因为我们的程序中不存在system,所以我们得先泄露出libc_base才行,把free改成puts即可通过free任意地址打印出对应函数的真实地址
first_edit

second_edit

泄露
我们已经把free_got指向了puts,那么我们free任意堆块都相当于puts出了它的真实地址,只需要接收地址即可,因为2我泄露的是puts函数,puts函数是在堆块1内,所以我是free(1)
收尾
1 | libc_base = puts_real_addr - 0x6f690 |
把free再次改成system,然后往空堆块里塞/bin/sh再free即可
注意
当时写这个blog的时候是Ubuntu22.04且还不熟悉bins的结构和tcache的异或,所以前边写的有些怪,时间太长懒得改了。这个适用的是类似small_bins和large_bins,不适用于tcache