利用GPU构建高性能模糊测试器!
TL;DR: 我们能否在使用云服务对嵌入式软件进行模糊测试时,通过GPU实现10倍性价比?基于初步研究,答案是可以的!
模糊测试是一种通过向程序提供大量随机化输入来触发异常行为的软件测试技术。作为重要的行业标准技术,它已发现众多安全漏洞并预防了更多问题。然而,高效的模糊测试耗时较长,且测试嵌入式软件面临额外挑战。
嵌入式平台并非为模糊测试所需的插桩和高吞吐量计算而设计。在没有源代码的情况下,对此类平台进行实际模糊测试需要模拟器(速度慢)或大量物理设备(通常不切实际)。
核心理念:GPU模糊测试
模糊测试旨在生成程序的意外输入并引发不良行为(如崩溃或内存错误)。最常用的覆盖引导模糊测试器专注于发现能触发新代码覆盖(如执行此前未运行的函数)的输入,以探索可能导致程序崩溃的边界情况。该任务高度可并行化,因为每个输入可独立执行。
GPU具有显著成本优势:谷歌云上可抢占式Tesla T4仅需0.11美元/小时。Tesla T4可在超过40,000个线程间上下文切换,并能同时并行执行2,560个线程。理论上,使用数千个线程应能同时测试数千个不同输入。
为何此前无人实现?
在GPU上运行代码与CPU存在关键差异:
- GPU无法直接执行x86/aarch64等指令集,因其拥有专属指令集架构(PTX)
- GPU没有操作系统,缺乏进程隔离机制,任何内存违规都会导致整个模糊测试器崩溃
- 系统调用需通过主机操作系统模拟或中继执行
- GPU内存管理复杂,16GB显存在40,000个线程间分配后,每个线程仅获419 KiB
技术实现方案
通过二进制翻译执行代码
我们使用Trail of Bits开发的Remill工具,将ARMv7/aarch64等嵌入式二进制文件提升为LLVM中间表示(IR),再编译为PTX代码。例如:
原始ARM汇编:
|
|
经Remill转换后的LLVM IR:
|
|
最终编译为PTX汇编:
|
|
内存管理创新
通过替换内存访问为read_memory
/write_memory
调用,我们实现了软件内存管理单元(MMU),为每个线程提供虚拟地址空间并验证内存访问。采用写时复制策略显著提升内存使用效率——线程共享内存直至执行写入操作时才进行复制。
性能测试与优化
初始性能表现
选择libpcap的BPF包过滤代码作为测试对象(无系统调用、复杂状态机、历史漏洞记录)。与libFuzzer对比:
- libFuzzer: 520万次执行/秒/美元
- GPU模糊测试器: 36.1万次执行/秒/美元
内存交错访问优化
性能分析显示GPU计算利用率仅3%,根源在于内存访问模式低效。GPU线程以32个为一组(warp)执行,内存访问以128字节块进行。通过为warp分配内存块并交错存储线程数据,使相邻线程值位于相同内存块,将内存事务从32次减少至1次。此优化使性能提升一个数量级!
减少数据传输与内核启动
采用异步数据传输策略,将数据保留在GPU全局内存中,仅在需要时向CPU发送崩溃信息。避免CPU-GPU同步等待,实现近另一个数量级的加速。最终达到libFuzzer 5倍的性价比!
未来发展方向
当前硬件利用率仍较低,存在优化空间。下一步需:
- 添加系统调用支持(对I/O密集型应用至关重要)
- 构建变异引擎生成新输入
- 持续优化内存访问模式
这项早期研究展现出GPU模糊测试在特定应用中的巨大潜力,我们期待在嵌入式二进制文件模糊测试领域实现数量级的突破。
致谢:感谢Artem Dinaburg的系统初始设计和全程指导,以及Peter Goodman的设计反馈与调试建议。