一.堆溢出unlink
条件:当使用 free 函数释放一个堆块时,如果相邻的堆块(前一个或后一个)也处于空闲状态,就可能触发 unlink 操作,以将相邻的空闲堆块合并成一个更大的空闲块,从而提高内存的利用率。具体来说,如果被 free 的堆块的 P 位为 0,说明其前一个堆块为空,就会对前一个堆块进行 unlink 操作,将前一个堆块与当前被 free 的堆块进行后向合并;如果相邻的下一个堆块处于空闲状态,则会进行向前合并。
unlink操作的实质就是:将P所指向的chunk从双向链表中移除,这里BK与FD用作临时变量
也就是这样

那具体拖链是如何实现的
1 | FD = P->fd; \ |
也就是我的上一个的下一个=我的下一个,我的下一个的上一个=我的上一个
FD = p->fd
FD = 堆块3chunk头所在的地址
BK = P->bk;
p->bk
BK = 堆块1chunk头所在的地址
FD->bk = BK;
堆块3的bk指针指向BK(堆块1chunk头所在地址)
BK->fd = FD;
堆块1的fd指针指向FD(堆块3chunk头所在地址)
1 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) |
这个也就是p–>fd–>bk = p–>bk–>fd = p
堆块2的下一个堆块的上一个堆块 = 堆块2的上一个堆块的下一个堆块=堆块2
那么如何绕过与利用呢?

就解释一点吧
chunk = 0x602280
为什么BK->fd == *(0x602270+0x10)
因为chunk头-0x18就是bk指针所在的地址,*()也就是解引用bk指针的值,正常得到的是上一个堆块的chunk头所在的地址,在这里得到的就是堆块2chunk头所在的地址
在这里BK->fd =FD,就是*(0x602270+0x10) = 0x602268
也就是往chunk里面写入了chunk-0x18的值
也就是将堆指针指向的地址改成了chunk-0x18
然后我们往堆块2中写入东西,他就会往堆指针指向的地址里面写,也就是往这个地址里面写0x602268,顺着往下写就会写入0x602280,那么就可以将堆的指针指向的地址改为任意值,就实现任意地址写了
我们结合题目看一下
最最最重要的,要把头部构造在指针的位置
触发unlink一定要伪造一个free掉的堆块在指针处,因为unlink比较是和头部比较,而数组中存储的是指针的值,所以要伪造头部在指针处才能绕过保护
1.有打印堆块的功能
1 | add(0x80,b'chunk0') |
申请两个在unsortbin的堆块,在堆块0中伪造一个free的堆块,并将堆块2的pre_inuse位设为0,这样就伪造堆块0为free掉的堆块,free堆块1就会触发unlink
chunk = 0x06020C8
fd = chunk-0x18
bk = chunk -0x10
看一下gdb

会很明显是往chunk里面写入了chunk - 0x18
然后我们接着修改堆块的内容,也就是往chunk - 0x18中写入数据,先写0x18个垃圾数据,然后再写的就是往chunk中写,也就是往指针所在的地址写,改变指针所存储的值,也就是指针指向的地址
我们将他写为atoi
payload = b’a’*24+p64(elf.got[‘atoi’])

可以看到已经成功了
此时可以泄露libc,我们打印这个堆块,就会将atoi的真实地址打印出来
我们再次修改这个堆块的内容,也就是修改atoi的got表,把他改成system,在调用atoi的时候就会触发system,获得shell
2.没有打印堆块的功能
没有打印堆块,我们就无法泄露libc,但是我们通过都能实现任意地址写了,我们可以将free的got表改为puts
free堆块时候,就可以打印
我们观察gdb,看具体是如何实现的

将第一个堆块指针指向的地址改为free的got表
第二个改为atoi的got表
然后修改第二个堆块的内容为puts函数的plt表这样free堆块时候,就会打印处来堆块的内容,泄露libc

然后在修改堆块2的内容为system的真实地址
在申请一个堆块,内容写为/bin/sh\x00
free这个堆块就可以获得shell
1 | add(0x10) |
最后:需要注意触发unlink free的堆块的上一个需要是free状态,所以我们要伪造堆块2为free状态,让后free堆块3就会触发unlink
那具体是如何伪造的?
我们在堆块2中再伪造一个假堆块
使pre_size = 0
size = 0xn1(n为伪造堆块大小)
fd = chunk-0x18
bk = chunk-0x10
使堆块3
pre_size = 0xn0
size = size(本堆块大小,0xm0) p位为0
这样就伪造堆块2为free状态,然后free堆块3,触发unlink,实现chunk = chunk-0x18
当off by one/null时候因为会多一个字节,free后面就是puts,puts的got表就会被破坏,所以要这样子改,将puts的got表改为printf
1 | edit(6,b'a'*51+p64(elf.got['free'])+p64(elf.got['atoi'])) |


这里也能看到多了一个0xa