基于污点信息的函数内存模糊测试技术研究
崔宝江1, 王福维1, 2, 郭涛2, 柳本金2    
1. 北京邮电大学 计算机学院, 北京 100876;
2. 中国信息安全测评中心, 北京 100085
摘要:针对二进制程序文件处理漏洞的挖掘, 目前业界主流自动化方案为基于文件变异的模糊测试, 但该方法盲目性高、代码覆盖率低、效率低下。为研究具有高针对性的测试方法, 该文讨论了一种新型的函数内存模糊测试技术。该技术利用动态污点分析的结果, 获取目标程序中处理输入数据流的函数与指令。测试中基于二进制插桩, 对上述函数构造循环执行结构, 并针对内存中的污点数据进行变异。原型系统实验表明: 该测试方法可有效用于栈溢出等漏洞类型的挖掘; 相比传统模糊测试, 消除了因数据盲目测试造成的执行路径中断瓶颈, 且在执行效率上具有95%以上的提升。
关键词软件测试    模糊测试    污点分析    控制流劫持    
Research of taint-analysis based API in-memory fuzzing tests
CUI Baojiang1, WANG Fuwei1, 2, GUO Tao2, LIU Benjin2    
1. School of Computer Science, Beijing University of Posts and Telecommunications, Beijing 100876, China;
2. China Information Technology Security Evaluation Center, Beijing 100085, China
Abstract: Fuzzing testing is widely utilized as an automatic solution to discover vulnerabilities in file-processing binary programs. Restricted by the high blindness and low code path coverage, fuzzing tests normally work quite inefficiently. An API in-memory fuzzing testing technique was developed to eliminate the blindness. The technique employs dynamic taint analysis to locate the routines and instructions which belong to the target binary executables and involve the input data parsing and processing. Within the testing phase, binary instrumentation was used to construct circulations around such routines, where the contained taint memory values were mutated in each loop. According to the experiments on the prototype tool, this technique can effectively detect defects such as stack overflows. The results also show that the API in-memory fuzzing testing eliminates the bottleneck of interrupting execution paths while gaining an over 95% enhancement of the execution speed in comparison with traditional fuzzing tools.
Key words: software testing    fuzzing testing    taint analysis    control-flow hijacking    

近年来,随着信息产业的发展,数据处理密集型办公自动化快速普及。因软件需求量增大、开发周期紧缩,应用软件中存在漏洞的风险日趋提升。其中,最为普遍的是文件结构触发类漏洞,其成因为文件结构或字段取值异于常规而造成数据处理过程异常。

针对文件结构触发类漏洞的挖掘,主要思想是构建畸形化的文件从外部输入,用以使目标程序的处理过程覆盖到尽可能多的执行路径组合,尝试触发其中存在逻辑缺陷的分支路径。标志性的实现技术为模糊测试(fuzzing test),借助随机生成的输入文件或者在正常文件上进行任意修改,对程序执行路径进行暴力遍历。这种技术的关键在于无人工干预的随机性,因而往往可触发到一些正向逻辑无法分析出的异常; 但这一特征也造成了测试盲目性,生成的输入数据往往无法通过程序的早期格式校验、完整性验证。作为改进,结构模糊测试引入目标格式规约,用来使生成的输入数据在尽量符合基本格式限制的前提下,做可能的负载数据变异。这样有效降低了测试盲目性,但目标格式规约却往往无法获取,从格式规约到变异策略的转化又需要根据经验完成。此外,考虑到模糊测试极低的缺陷检出率,目前业界为提高测试效率,主要方向是进行大规模运算,如构建分布式测试框架,这就抬高了漏洞挖掘的研究门槛。

为了对应用程序行为进行更深入的刻画分析,近年学术领域在两条主线上进行了探索: 数据流与控制流分析。数据流分析提取特定数据在进程中的处理流程,其代表性技术为动态污点分析[1]。控制流分析对二进制程序进行静态逆向,抽象出可能的路径组合; 在此基础上,符号执行技术[2]以控制流中的关键跳转为约束,反向求解可触发特定执行路径的原始输入。但是,当前两种分析技术都存在各自的限制: 污点分析多作为人工分析的辅助定位技术,很少直接应用于漏洞挖掘; 符号执行面临路径爆炸等计算难题,尚难以应用于实际。

本文讨论了一种新型的模糊测试技术。基本思想为: 利用污点分析得出的数据流信息,定位在程序动态执行过程中处理的逐个数据的模块; 在动态测试过程中,在上述目标函数处人为改变控制流,制造循环执行结构,并在每一轮循环中直接修改保存有输入文件数值的内存数据,直接测试目标函数处理各种外部输入的行为,以挖掘局限于函数内部的处理逻辑缺陷。

1 离线式多标签污点分析框架

动态污点分析(DTA)技术通过对目标二进制程序的执行进行监控,检测特定数据的引入,标定为原始污点源,并在进程中,分析污点数据的访问、传播和消除,最终得出完整的污点数据流。本文研究实现了具有实用性的污点分析框架,用来为模糊测试提供测试目标信息。

1.1 离线式多标签污点分析框架FlowWalker

因为动态污点分析需要做细粒度的数据跟踪,所以仅能在二进制程序汇编指令粒度实施。而汇编指令中的算术运算类指令往往造成数据的叠加和部分消减。本文研究采用了多标签污点分析方法,用污点源的代数运算形式来表示存储单元的污点属性。

现有成型的动态污点分析工具,对进程执行造成巨大的时间空间开销,只能牺牲分析准确性以换取对中大规模程序的分析效率。在前期工作中,本研究借鉴了TaintScope[3]项目的经验,实现了动态执行与静态重放分析离线整合的框架FlowWalker[4],并借助其他改进技术,在研究中完成了对中大型规模应用程序的实际测试。

1.2 污点分析与模糊测试数据接口

污点分析面向每个污点源,输出结果为对应输入文件中每个字节的完整时序数据流,数据流中每个处理节点是一个包含时间戳、所属二进制映象、代码地址的三元组。为进行以函数为单元的污点数据整合,本研究借助IDA的脚本功能,将污点分析结果导入,以函数、指令为单元,依据各个字节的污点记录节点的时间戳进行归并操作,并导出为XML的污点函数清单。

一份污点函数清单由内到外依次包含了涉及污点数据处理的二进制映像、函数、代码块与指令节点信息,用于在二进制插桩中确定指令位置并进行插桩; 污点源集合是对应污点指令在进程中依次处理的污点数据的代数表示,用以匹配污点内存数据。

2 污点函数内存模糊测试设计 2.1 概念原理

内存模糊测试(in-memory fuzzing)概念初见于Sutton[5]关于模糊测试的专著中,针对难以重复构造的输入数据,如网络数据流,绕过数据接收过程,循环测试数据处理功能。据此,国外团队Corelan Team开发出原型系统[6]

内存模糊测试的原理如图1所示。对于数据处理类程序,可分为数据载入过程与处理(施效)过程。面向函数结构的模糊测试在单进程中,保持输入数据不变并经过载入与校验步骤,对处理过程的每个函数,在入口处修改内存中保存的输入数据拷贝,以在函数内部遍历路径组合,并在每个函数出口处有限次地返回入口,变异不同的内存数据后循环执行。但是,该设计需要通过大量前期手工分析剥离出数据处理模块,且尚无可行的过程循环控制方法,因而仅停留在概念探索阶段。

图1 函数内存模糊测试原理

本研究利用污点分析的结果,可以准确锁定处理污点数据的所有函数,粒度细至具体指令的操作数,可直接选定这些函数与内存操作数为变异的对象与目标。研究采用了Pin二进制插桩平台[7],可以实现对程序控制流的劫持和接管,通过自己实现的定点插桩(断点)、运行时状态还原(快照)等功能,可以完整实现执行路径的循环控制。本研究设计的函数内存模糊测试框架如图2所示。

图2 函数内存模糊测试框架
2.2 污点信息载入模块

本模块工作于测试执行之前,从指定XML清单文件中读入污点信息。根据所属的二进制映象,在Pin载入各包含污点函数的模块时,确定映象载入基址,并对应调整所有污点函数、污点指令的偏移量为虚拟地址。

2.3 污点函数调用栈

内存模糊测试的对象是特定的污点函数,即“测试循环体”。考虑到函数存在调用、嵌套甚至迭代关系,同一组污点数据通常由外层处理函数传递到内层处理函数进行其他操作; 为此,本文引入污点函数栈的概念,作为循环执行主体。

污点函数栈类同于真实的函数调用堆栈,但其中仅考虑污点函数集合中的成员。例如,考虑图3所示的一般化函数调用关系。以二元组<Dep,Cnt>标识每次调用的污点函数,其中Dep为该函数调用发生时在栈中的层级,Cnt为该污点函数栈此次到达该层级的次数。这样,可以不必保存完整调用栈,将污点函数在调用链中的层次关系进行区分。

图3 污点函数调用示例

作为循环测试体,每一次循环执行时,需要完整执行最外层污点函数,并在其中一个子函数领空内对操作的污点内存数据进行变异。变异的原则是从内层子函数到外层,从被调用者到调用者。

2.4 污点指令监测/变异

为确定污点指令特定于处理污点数据的上下文,对每个污点函数内的污点处理指令插桩; 在污点函数第一次执行时,比较其内存操作数数值是否匹配实际污点数据的算术形式。若内存数据匹配其中的候选污点形式,则以一集合存储该指令。

在污点函数的循环变异过程中,对每一处污点指令,需要检索该处是否在第一次执行所保存的集合中,若在,则可以直接对其内存操作数进行变异。

目前,已实现的原型系统采用了3项结合的变异策略: 除按照操作数位数设边界值/随机值以外,还可以将操作数变异为候选的其他污点数据源,这样可以在一定程度上保证变异数据的合法性。

2.5 函数入口控制

污点函数的入口指令进行插桩,用来维护污点函数调用栈,在母函数调用子污点函数时,将子函数以及其调用标识压栈。

入口控制的另一项功能是入口上下文保存。为了保证每一次循环执行的运行时环境相同,在一个完整的污点函数调用栈的最外层函数入口处,保存当前的上下文数据,就可以在循环体结束需要返回入口处重新执行时,还原该上下文,包括了EIP以及栈帧指针,从而实现了循环控制。

2.6 函数出口控制

函数出口控制和入口控制一起,组成了二进制插桩下对函数调用栈的循环测试结构控制。在功能上,出口控制完成以下3方面任务。

1) 污点函数调用栈维护。在调用栈非空时,对每一条RET指令进行插桩分析,若指令地址在调用栈顶的污点函数的代码范围内则弹栈。

2) 循环结构维护。根据污点指令监测结果,判断调用栈中有污点处理的函数调用数次,计算总循环轮数; 在每次污点函数调用栈为空时,标识一轮执行完成,判断下一轮进行变异的函数与指令。

3) 执行环境还原。在每一轮循环测试出口,需要还原保存的入口上下文,完成循环结构。

2.7 内存快照管理

为保证循环测试的每一轮具有相同的运行时环境,还需要具有完全相同的内存数据。本研究设计了“内存修改页快照池”保存每一轮中所有被改写过数据的内存页,而不保存其他内容。在循环测试过程中,对所有写内存的指令插桩,在执行前获取写内存的地址,通过维护并查询一个内存页映射表,判断该页是否已经被快照保存,若否,则备份该页的数据。在一轮循环测试结束后,对所有已做快照的内存页数据进行还原,即可保证在每次回滚到污点函数栈入口处时的全局内存完全一致。

2.8 文件对象管理

文件对象是需要在内存快照管理外单独考虑的细节。文件的I/O操作设计内核对象状态,若文件内核对象与文件指针存在不一致,会导致无法从文件对象中读出正确的数据。为此,需要单独对所有文件对象进行记录和调整。

在循环体的预处理轮中,对所有文件读操作,调用SetFilePointer获取当前文件内核对象中的文件指针偏移量,并在后续测试轮开始时,将文件指针位置进行重置。

2.9 流程与异常监控记录

对测试的完整过程,经过测试的测试体,由该模块进行记录,以判断测试覆盖范围。同样记录每一轮测试中选取的变异位置、变异对应的原始文件字节范围,以及变异后的数值,用来在原始种子文件基础上进行畸形文件的生成。此外,利用Pin的异常处理机制,监控所有异常。对已触发的异常,记录下异常代码、触发点前的上下文数据。在发生异常后,跳过后续异常处理机制,直接按照函数出口进行处理,回滚上下文与内存快照,继续向后测试。

2.10 变异样本生成与测试

变异样本生成与测试工作和动态测试工作异步完成,以免降低动态测试效率。根据动态测试后生成的日志,可以对所有发生了异常的测试轮中进行的数据变异,对应地在种子文件中进行修改,构造出符合异常发生的畸形变异样本。之后,利用Python脚本,可以借助调试器监控目标程序处理变异样本的行为,判断是否重现了异常。

3 测试评估

为验证本研究实现的函数内存模糊测试技术,本文从正确性、有效性和效率3个方面,依次进行了实验论证。

3.1 正确性验证

为进行本模糊测试技术概念正确性的验证,实验对象选取了包含如下栈溢出漏洞的程序(表1)。

表1 栈溢出程序代码
const char header[4]={‘H’,‘E’,‘A’,‘D’};
int vul( char * payload) {
char buffer[8];
int index=0;
do {
buffer[index]=payload[index];
} while (payload[index++]!=0);
return index;
}
int wrapper( char * payload) {
int idx=0;
for (;idx!=4;++idx)
if (payload[idx]!=header[idx])
return 0;
return vul(payload+4);
}
int main( ) {
FILE* fptr=fopen(“Y:\\test.txt”,“rb”);
char src[1024]=“\0”;
fread(src,1024,1,fptr);
if (wrapper(src)!=0)
wrapper(src+4+temp);
return 0;
}

负载文件test.txt内容如表2所示。

表2 正常样本文件数据
00h: 48 45 41 44 01 02 03 04 05 06 07 00 48 45 41 44;
HEAD... HEAD
10h: 0A 0B 0C 00 ;…

在处理该文件时,对HEAD标识的修改将使校验失败,在变异时不能更改。当负载部分超过8字节,将导致程序的vul函数发生栈溢出,保存的EBP寄存器和返回地址被改写。

对上述正常样本文件做污点分析后,以函数内存模糊测试工具进行如图4流程的自动测试。

图4 目标程序执行与测试流程

得到的日志中,对第2轮变异存在如表3所示异常触发记录。

表3 测试中异常信息记录
391 299: TaintByte c/14 at 24f793 on 2-1
391 299: Mutate 24f793 from 00 to ff
Exception c201601021d on 000c0b0a @0
 EAX:12    EBX:7ffd3 000
  ECX:0    EDX:12
 ESI:0    EDI:0
  ESP:24f6d8    EBP:44414548
roll back

根据日志,在污点函数调用栈的第2层中,检测到污点字节0xc/0x14。之后根据变异策略,跳过对HEAD标识,直接变异负载部分内存数据。在将0x24f793位置由0x00变异为0xff后,触发了异常0xc201601021d(无效指令),显示触发指令地址为0x000c0b0a,且EBP寄存器值为0x44414548。由分析可知,栈溢出漏洞被触发,导致在vul函数返回时,下一段的标识覆盖了从栈上弹出的EBP值,后面的4字节覆盖了返回地址。由此可见,在测试过程中,工具实现了对执行路径上层验证过程的绕过,直接变异污点数据对应内存值,并捕获了异常。

3.2 有效性测试

为对比污点函数内存模糊测试与基于文件变异和调试器监控的传统模糊测试方法在代码覆盖率上的差别,本文构造如下对照试验: 对正常的样本文件,通过污点分析,得出主程序在文件处理过程涉及的函数,通过插桩得出正常执行中在这些函数内执行的代码基本块基准集合。统计利用函数内存模糊测试方法执行的基本块与基准集合交集占基准集合的比例; 而后利用传统文件变异器生成100份变异样本,传递给目标程序处理,同样通过插桩得出其执行过程中基准基本块执行平均比例。

实验中选取Oulu大学研发的用于模糊测试的文件变异器Radamsa[8],从各样本文件为种子生成100组变异样本。通过对所选样本及目标程序的测试,得到实验数据如表4所示。

表4 污点函数代码基本块覆盖率
测试程序/格式 正常执行污点函数基本块数污点函数内存测试污点基本块覆盖率/% 传统测试平均污点基本块覆盖率/%
7-Zip/.zip69799.4268.05
Word 2010/.doc3 92397.1772.31
XnView/.png1 73010075.12
Foxit Reader/.pdf2 41198.7162.06

表4数据可知,应用程序正常处理随机变异的样本文件时,代码覆盖率约在60%~75%,且随文档的有效负载数据占全文档数据量比例而改变: 对控制字段较少、承载文档内容的负载占主要部分的文档类型,变异到控制字段的概率相对较低,从而使文件处理过程受到的影响相对较小。相比之下,污点函数内存测试可以确保全部污点函数在测试中都能得到执行,代码覆盖率可达95%以上。

3.3 效率测试

为进行测试效率论证,本文选取了业内广泛用于文件格式漏洞挖掘的Peach Fuzzer[9],版本号 3.0.202。不失一般性,实验选取两组对象: 代表数据处理运算密集的命令行程序,选择7-zip控制台程序7za.exe; 以及代表文件处理操作稀疏的Windows GUI程序,选择Microsoft Office 2010 Picture Manager图像查看程序ois.exe。

1) 7-zip命令行程序测试。

实验中校验zip压缩文件,作为预处理环节,进行离线污点分析与污点信息导出共耗时5 s。设定函数内存测试针对单个目标函数循环测试50轮,经过测试,对整个进程测试共计10 900轮循环。对应地以Peach Fuzzer进行等轮数测试,二者用时如图5所示。

图5 控制台程序测试速度对比

图5可得,执行10 900次测试,Peach Fuzzer用时共2 342 s,平均每轮用时214 ms; 函数内存测试用时共89 s,平均每轮用时8.16 ms,相比前者,速度提升为96.20%。

2) OIS图形界面程序测试。

实验中以主程序ois.exe执行png图像文件的打开操作,预设定函数内存测试针对单个目标函数循环测试轮数为5。先期污点分析处理过程耗时23 s。经过测试,对整个进程执行共计3 835轮循环。原型系统与Peach Fuzzer的测试用时如图6所示。

图6 图形界面程序测试速度对比

根据时间计量,Peach Fuzzer完成3 800轮测试共用时2 027 s,平均每轮用时533.4 ms。函数内存测试用时共124 s,其中从进程启动到开始对第一个污点函数调用栈执行测试的时间间隔为48 s,全局最后一轮测试完成时间为93 s,完成3 835轮测试共用时45 s,平均每轮用时11.73 ms,相比前者提高97.8%。

为保证线程同步,对多线程程序的测试采用的策略是暂停除在测线程以外的其他所有线程,因此,执行效率的提升会随着单个函数预设的测试轮数增加而进一步提高。

4 结 论

本文探讨了针对传统模糊测试盲目性消除的内存模糊测试概念,并利用污点分析的数据流信息,描述了进行以污点函数为目标导向的函数粒度内存模糊测试的架构及实现关键,并对原型系统进行了实验。结果证明了架构的理论正确性,以及在代码覆盖率和测试执行效率上的显著优势。

本文研究的主要贡献如下。

1) 分析得出了传统模糊测试效率的主要瓶颈在于外部测试的盲目性,并以污点分析的数据流信息增强了测试针对性。

2) 对测试的架构实现方面,给出了基于二进制插桩的循环测试结构,讨论及实现了此类控制流劫持需要额外考虑的上下文、内存快照还原、文件对象调整等机制。整套机制可视为完整实现了进程控制流劫持的二进制分析框架,可用于其他研究用途。

3) 提出了进行模糊测试工具的有效性、性能测试方法,并对本文研究原型系统与传统Fuzz工具进行了多方位的客观测试。结果证明了借助污点分析结果定位关键代码范围的准确性、函数粒度测试的高代码覆盖率以及二者结合所实现的绝对优势的执行效率。

参考文献
[1] Newsome J, Song D. Dynamic taint analysis for automatic detection, analysis, and signature generation of exploits on commodity software [C]//Proceedings of the 12th Annual Network and Distributed System Security Symposium (NDSS 2005). New York: ACM, 2005.
[2] Schwartz E, Avgerinos T, Brumley T. All you ever wanted to know about dynamic taint analysis and forward symbolic execution (but might have been afraid to ask) [C]//Proceedings of the IEEE Symposium on Security and Privacy. Washington DC: IEEE Computer Society, 2010: 317-331.
[3] WANG Tielei, WEI Tao, GU Guofei, et al. TaintScope: A checksum-aware directed fuzzing tool for automatic software vulnerability detection [C]//ACM Transactions on Information and System Security (TISSEC). 2011, 14(2): 15:1-15:28.
[4] CUI Baojiang, WANG Fuwei, GUO Tao, et al. FlowWalker: A fast and precise off-line taint analysis framework [C]//Proceedings of the 2013 Fourth International Conference on Emerging Intelligent Data and Web Technologies. Washington DC: IEEE Computer Society, 2013: 583-588.
[5] Sutton M, Greene A, Amini P. Fuzzing: Brute Force Vulnerability Discovery [M]. Addison-Wesley Professional, 2007.
[6] Corelan Team.[EB/OL]. (2010-10-20). https://www.corelan.be/index.php/2010/10/20/in-memory-fuzzing/.
[7] Luk C, Cohn R, Muth R, et al. Pin: Building customized program analysis tools with dynamic instrumentation [C]//Proceedings of the 2005 ACM SIGPLAN Conference on Programming Language Design and Implementation. New York: ACM, 2005: 190-200.
[8] Oulu University Secure Programming Group. Radamsa[EB/OL]. [2014-06-29]. https://www.ee.oulu.fi/research/ouspg/Radamsa.
[9] Eddington M. Peach Fuzzer[EB/OL]. (2014-06-07). http://sourceforge.net/projects/peachfuzz/.