ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

针对复杂文件格式的 Fuzzing 驱动开发:以 libpng 图像解析为例

针对复杂文件格式的 Fuzzing 驱动开发:以 libpng 图像解析为例 针对复杂文件格式的 Fuzzing 驱动开发以 libpng 图像解析为例在底层二进制安全与漏洞挖掘领域复杂文件格式解析器如图像、音频、PDF 以及容器封装格式始终是高危漏洞的高发区。解析器通常包含复杂的解压缩算法如 Deflate、色彩空间转换、分块Chunk校验与动态内存重分配逻辑。一旦输入被精心畸变的数据流极易触发堆溢出Heap Overflow、空指针解引用、整型溢出以及使用后释放UAF。为了高效挖掘此类缺陷基于反馈驱动的内存模糊测试In-Memory Feedback-Driven Fuzzing是目前最高效的工程手段。本文以开源图像解析库libpng为例深入探讨如何编写高性能的 Fuzzing 驱动Harness并配合 Sanitizer 实现高覆盖率测试。文件解析器 Fuzzing 的核心瓶颈与破局思路如果直接采用黑盒命令行模糊测试如通过afl-fuzz调用包含main()的可执行文件测试吞吐量通常只有几十次/秒。其主要性能损耗来自以下三个方面进程创建开销每次测试都要经历fork()与execve()消耗大量系统调用时间。磁盘 I/O 阻塞将变异数据写入物理磁盘或临时文件再由解析器读取磁盘 I/O 成为绝对瓶颈。状态未完全重置如果驱动中存在全局变量残留可能导致前后测试用例相互污染产生难以复现的假崩溃Flaky Crashes。因此现代 Fuzzing 必须采用基于libFuzzer或AFL LLVM Persistent Mode的内存 Harness将每秒执行速度Exec/s提升至数万次。libpng 格式特征与目标解析路径标准 PNG 文件以固定的 8 字节文件签名\x89PNG\r\n\x1a\n开头随后由多个独立 Chunk 组成包括核心关键块IHDR,IDAT,PLTE,IEND以及辅助块tEXt,zTXt,pHYs等。在设计 Harness 时我们的目标是让输入数据流能够尽可能深入地遍历png_read_info、png_read_image以及png_read_end等深层解码逻辑同时避免因浅层格式错误过早退出。高性能内存 Harness 实现libpng 默认提供了通过自定义回调读取内存数据的接口png_set_read_fn。利用该机制我们可以彻底摆脱文件系统直接将 Fuzzer 提供的变异字节切片送入解析器。以下是一个生产级的 libFuzzer 驱动完整实现代码#include stdint.h #include stdlib.h #include string.h #include png.h // 自定义内存读取上下文结构体 struct MemoryReader { const uint8_t *data; size_t size; size_t offset; }; // 内存读取回调函数替换底层 fread 逻辑 static void user_read_data(png_structp png_ptr, png_bytep dst, png_size_t length) { struct MemoryReader *reader (struct MemoryReader *)png_get_io_ptr(png_ptr); if (!reader) { png_error(png_ptr, Read Error: Invalid reader context); return; } // 检查剩余缓冲区长度 if (reader-offset length reader-size) { png_error(png_ptr, Read Error: Unexpected End of Data); return; } memcpy(dst, reader-data reader-offset, length); reader-offset length; } // libFuzzer 标准入口函数 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // 基础过滤PNG 最小合法长度签名 最小 IHDR if (size 8) { return 0; } // 检查 PNG 魔数签名过滤完全无关的数据以加快变异效率 if (png_sig_cmp(data, 0, 8) ! 0) { return 0; } // 初始化读取上下文 struct MemoryReader reader { data, size, 0 }; // 创建 libpng 核心控制结构体 png_structp png_ptr png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { return 0; } png_infop info_ptr png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(png_ptr, NULL, NULL); return 0; } // 设置错误跳转libpng 内部使用 setjmp 处理异常 if (setjmp(png_jmpbuf(png_ptr))) { // 解码异常时释放资源防止内存泄漏干扰 Fuzzer png_destroy_read_struct(png_ptr, info_ptr, NULL); return 0; } // 绑定自定义内存读取回调 png_set_read_fn(png_ptr, reader, user_read_data); // 解析文件头部元数据 png_read_info(png_ptr, info_ptr); // 获取图像核心尺寸与色彩属性 png_uint_32 width, height; int bit_depth, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, interlace_type, NULL, NULL); // 防御过大尺寸导致的 OOM 耗时限制测试范围为合理尺寸 if (width 2048 || height 2048) { png_destroy_read_struct(png_ptr, info_ptr, NULL); return 0; } // 触发内部格式转换扩展分支 png_set_expand(png_ptr); png_set_strip_16(png_ptr); png_set_gray_to_rgb(png_ptr); png_read_update_info(png_ptr, info_ptr); // 分配行指针并触发完整像素解码 png_size_t rowbytes png_get_rowbytes(png_ptr, info_ptr); png_bytep *row_pointers (png_bytep *)malloc(sizeof(png_bytep) * height); if (row_pointers) { for (png_uint_32 i 0; i height; i) { row_pointers[i] (png_bytep)malloc(rowbytes); } // 核心目标函数执行逐行解码 png_read_image(png_ptr, row_pointers); // 释放行内存 for (png_uint_32 i 0; i height; i) { free(row_pointers[i]); } free(row_pointers); } // 读取结尾 Chunk 并清理环境 png_read_end(png_ptr, info_ptr); png_destroy_read_struct(png_ptr, info_ptr, NULL); return 0; }编译插桩与 Sanitizer 联动为了能够精确捕捉越界访问OOB与未定义行为必须在编译 Harness 时启用 Clang 提供的 AddressSanitizerASan与 UndefinedBehaviorSanitizerUBSan。1. 编译命令# 编译并开启 ASan、UBSan 与 libFuzzer 支持 clang -O1 -g -fsanitizeaddress,undefined,fuzzer \ -I/path/to/libpng/include \ harness_png.c /path/to/libpng/lib/libpng.a -lz \ -o png_fuzzer2. 种子语料库构建与精简高质量的初始语料Seed Corpus能大幅降低变异引擎探索分支的成本从 libpng 自带的测试套件及公开图片集中提取 50~100 张覆盖各种颜色类型灰度、RGB、索引色、Alpha 通道的合法小尺寸 PNG。使用libFuzzer的-merge1选项对语料库进行覆盖率去重与精简mkdir -p min_corpus ./png_fuzzer -merge1 min_corpus/ raw_seeds/3. 启动并行 Fuzzing# 启动 8 个并行 Worker 开展压力模糊测试 ./png_fuzzer min_corpus/ -workers8 -jobs8 -max_len65536驱动开发避坑指南严格捕获setjmplibpng 遇到非法数据时默认通过longjmp抛出错误。如果 Harness 缺少setjmp错误处理一旦触发解析错误进程会直接异常中止导致 Fuzzer 误判为崩溃。规避全局分配陷阱确保每次进入LLVMFuzzerTestOneInput时所使用的上下文都是局部独立的并在所有退出分支彻底调用释放函数杜绝内存泄漏导致的虚假 OOM。尺寸边界硬约束在 Harness 中显式限制图片分辨率上限如宽/高不超过 2048避免 Fuzzer 生成超大分辨率参数导致测试进程在无意义的巨大数组分配中陷入卡死。
返回列表