后三个字节作为标志
mmap
系统调用 分配(而非从主堆中分配),通常用于较大的内存块(如超过 mmap_threshold
的块)。mmap
分配的块在释放时直接通过 munmap
系统调用释放,不返回给堆分配器。仅当块处于空闲状态时有效,用于将块链入空闲链表(如 fastbins
、tcache
、unsorted bin
等)
在bin中指向下一个(非物理相邻)
只存在于空闲的Chunk
在bin中指向上一个(非物理相邻)
只存在于空闲的Chunk
在堆内存管理(如 glibc 的 ptmalloc)中,空闲内存块通过双向链表组织。当一个内存块被释放或合并时,需要从链表中移除,unlink 用来将一个双向链表(只存储空闲的 chunk)中的一个元素取出来
unlink常用于free()中进行 chunk 的整理,可以对空闲 chunk 进行前向合并和后向合并。
当被free 的 chunk 的 P 位为 0 时,说明被 free 的 chunk 的前一个 chunk 为空,于是对前一个 chunk 进行 unlink 操作,将前一个 chunk 与被 free 的 chunk 进行后向合并。
后向合并的操作首先将两个 chunk 的大小相加,然后对前一个 chunk 进行 unlink。
unlink正常情况下的执行流程:
假设当前块 P 位于链表中间,执行 unlink(P) 后:
P
的前一个块(FD)的 bk 指针会指向 P 的后一个块(BK)P
的后一个块(BK)的 fd 指针会指向 P 的前一个块(FD)unlink 宏的核心代码句:
FD = P->fd;
BK = P->bk; //获取P这个内存块的前驱节点(fd)和后继节点(bk)
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
//检查链表的完整性 确保下一个块的 bk 必须指向当前块,上一个块的 fd 也必须指向当前块
FD->bk = BK;
BK->fd = FD; //链表删除
![[Pasted image 20250523214657.png]]
总的就是由A <-> B <-> C这样变成 A <-> C这样
64位
目的:将堆指针伪造到目标地址,实现任意地址写或代码执行
1.先创建两个连续的chunk0和chunk1(设一个大小为0x30,一个大小为0x80)
2.选择目标地址(也就是这里&p)
(一般是题目中add函数中的创建堆块时的一个参数,一般在bss段)
3.在0块中构造一个fake_chunk(伪造堆块)
在chunk0只差一个chunk头处开始,fake_chunk大小为0x30
![[Pasted image 20250523215757.png]]
要将fake_chunk伪装成一个空闲的chunk,这样我们在下面释放1块就可以触发unlink,所以控制chunk1的prev_size和size位
注意点:区分p和&p
p |
指针变量的值,指向 fake_chunk 头部 |
&p |
指针变量 |
# fake_chunk(伪造堆块)
&p #这里写的是p本身存储的地址,目标地址
payload=p64(0) #prev_size
payload+=p64(0x31) #size
(PREV_INUSE(P)=1,表示前一个堆块未释放,不启用prev_size,存放前一个堆块数据(这里prev_size就写0))
payload+=p64(&p-0x18) #fd
payload+=p64(&p-0x10) #bk
payload = payload.ljust(0x30, b'a') # padding
(填充满剩余fack_chunk空间,使下面的值正确填入)
payload+=p64(0x30) #next_prev_size
payload+=p64(0x90) #next_size
(PREV_INUSE(P)=0,表示前一个堆块以释放,启用prev_size,存放前一个堆块大小)
现在我们把1块释放
系统会先检查与1块相邻的fake_chunk,发现fake_chunk已经释放,是空闲状态
所以会触发unlink(fake_chunk)
看这里unlink的核心代码句
FD=fake_chunk->fd;
BK=fake_chunk->bk;
FD->bk=BK;
BK->fd=FD;
先获取fake_chunk
这个内存块的前驱节点(fd
)和后继节点(bk
)
1.FD=fake_chunk->fd
fake_chunk 的 fd 被我们设计成 &p-0x18
FD=fake_chunk->fd=&p-0x18
2.BK=fake_chunk->bk
fake_chunk 的 bk 被我们设计成 &p-0x10
BK=fake_chunk->bk=&p-0x10
在进行链表删除
3.FD->bk=BK
FD(FD=&p-0x18)的bk,起始距fd是0x18,所以加0x18
*(&p-0x18+0x18)=BK=&p-0x10
4.BK->fd=FD
BK(BK=&p-0x10)的fd,起始距fd是0x10,所以加0x10
*(&p-0x10+0x10)=FD=&p-0x18
最后得到的就是*(&p)=&p-0x18
这里选取-0x18和-0x10这两个值
也是考虑unlink前会检查链表的完整性:
if (FD->bk != fake_chunk || BK->fd != fake_chunk)
要确保下一个块的 bk 必须指向当前块,上一个块的 fd 也必须指向当前块
用了这两个值到最后可以看到FD->bk=&p-0x18+0x18=&p ,BK->fd=&p-0x10+0x10=&p,成功绕过检查
我们在下面想要往目标地址写入数据,先填充0x18就行,这样就可以任意地址写
补充一点:unlink后,目标地址&p[]和chunk是对应的,从一开始创建堆块后,看&p处的具体数据和堆情况就能看出,&p存储的数据就是每一个堆块的相关地址
https://blog.csdn.net/2401_88087539/article/details/148218987
https://blog.csdn.net/2401_88087539/article/details/148218314?spm=1001.2014.3001.5502