fuzz测试(模糊测试)一直是非常流行的未知漏洞挖掘方法
模糊测试大概上个世纪80年代就被人提出,到目前为止已经发现了无数个安全漏洞和代码质量问题 1.随机fuzz 最早阶段的模糊测试大概就是找到被测目标的数据输入点,对数据进行随机变异,然后等待问题的产生 在发现了大量的漏洞之后,模糊测试发现的问题开始收敛 2.黑盒fuzz 大概2000年左右,基于模板格式化变异的黑盒fuzz工具开始流行起来 比较有代表性的工具就是目前仍然在流行的peach peach使用一套比较通用的模板语法,来表述要测试的文件.协议等数据格式和交互状态,并且有良好的扩展性 比如测试一个server,黑盒fuzz工具可以完整的模拟clinet端行为,并在交互过程中串改字段来触发问题 黑盒fuzz工具有个致命的短板,就是并不关心被测程序代码的运行状态 3.灰盒fuzz 大概在2010年前后,基于遗传算法的afl面世 它通过对被测代码进行编译插装,来获取被测代码运行时的动态覆盖覆盖,来决定是否将刚输入的变异样本保存到样本池里供以后使用 与afl原理类似的fuzz工具涌现很多,比如测试用户态的libfuzzer和测试内核态的syzcaller 4.白盒fuzz 伴随着灰盒fuzz的流行,一些基于符号执行等的白盒技术被融入到fuzz工具(比如微软的SAGE) 他们通常以灰盒fuzz手段作为基础,符号执行等技术作为辅助 白盒技术往往因为各种各样的技术限制,业界工程化使用并不是很广 5.目前的灰白盒fuzz也在做一些引入语法的尝试 在一些格式化极强的场景,仅靠灰白盒变异效果差强人意,人们开始场景将语法引入灰白盒fuzz测试 比如libfuzzer与protobuf结合,以及基于afl开发的AFLSmart 但都不太成熟作者开发了codefuzzer+bufferfuzzer,彻底将语法描述方法引入到灰白盒fuzz测试 ,下面请听我慢慢道来
codefuzzer (anner910/codefuzzer)
是一款开源灰盒fuzz工具,使用的默认覆盖反馈手段类似libfuzzer
利用gcc(clang)的插装编译选项-fsanitize-coverage=trace-pc(-guard)来获取被测代码覆盖信息
codefuzzer的创新之一是将二维样本引入到灰白盒fuzz测试
这样一个样本里可以保存多个信息,比如样本权重,比如多个参数,每个参数的值,大小,类型等
比如下边是一个同时变异三个参数的用例测试时自动生成的样本的一部分
cat test_target #start corpus --------------------------------- para number =3 weight =110 random =1709357130 #para0------ type is =u32 len is =4 mutator is =MNumberMagic number is =6785158,0x678886 hexvalue is ="\x86\x88\x67\x00" stringvalue is ="\x86\x88g\x00" #over0------ #para1------ type is =string len is =10 mutator is =MNoMutator hexvalue is ="\x63\x68\x61\x6e\x67\x63\x68\x75\x21\x00" stringvalue is ="changchu!\x00" #over1------ #para2------ type is =blob len is =6 mutator is =MdataSelfToOther hexvalue is ="\x31\x32\xff\xfe\xfd\xfc" stringvalue is ="12\xff\xfe\xfd\xfc" #over2------ #end corpus1 ------------1---20221208_144514 #start corpus ---------------------------------二维样本的引入,可以使得codefuzzer用例同时支持多参数的变异值获取(默认支持256个,
最大值可使用函数CF_SetMaxParaNumber动态调整)
比如上边样本对应的多参数函数被测fuzz代码用例如下:
extern void targer(int num, char* blob,int len, char* string); void test_target(void) { printf("test_target start\r\n"); CF_SetReportPath("./"); CodeFuzz_start(0,100,1000000,"test_target",0) { static int i = 0; printf("\r%d",i++); fflush(stdout); int a = *(u32*)CF_GetU32(0, 0x345678); char* string = CF_GetString(1, "changchu!", 10, 1, 1000); char* blob = CF_GetBlob(2, "123457", 6, 0, 1000); int len = CF_GetMutatorLen(2); targer(a, blob, len, string); } CodeFuzz_end() printf("test_target end\r\n"); }二维样本的引入,可以使得codefuzzer支持多种数据类型,并且更有针对性的编写出更多的变异算法
而传统的灰白盒fuzz工具一般仅支持的buf,size类型,并且变异算法单一
下边是codefuzzer目前支持的变异数据类型,其中CF_GetBlob对应的是传统buf,size类型
extern char* CF_GetS8( int no, s8 initValue); extern char* CF_GetS16(int no, s16 initValue); extern char* CF_GetS32(int no, s32 initValue); extern char* CF_GetS64(int no, s64 initValue); extern char* CF_GetU8( int no, u8 initValue); extern char* CF_GetU16(int no, u16 initValue); extern char* CF_GetU32(int no, u32 initValue); extern char* CF_GetU64(int no, u64 initValue); extern char* CF_GetFloat(int no, float initValue); extern char* CF_GetDouble(int no, double initValue); extern char* CF_GetNumberRange(int no, s32 initValue, int minValue, int maxValue); extern char* CF_GetNumberOnlyTable(int no, s32 initValue, int* table, int count); extern char* CF_GetNumberOutOfTable(int no, s32 initValue, int* table, int count); extern char* CF_GetNumberBasedOnTable(int no, s32 initValue, int* table, int count); extern char* CF_GetString(int no, char* initValue, int initLen, int minLen, int maxLen); extern char* CF_GetStringOnlyTable(int no, char* initValue, int initLen,int minLen, int maxLen, char** table, int count); extern char* CF_GetStringOutOfTable(int no, char* initValue, int initLen,int minLen, int maxLen, char** table, int count); extern char* CF_GetStringBasedOnTable(int no, char* initValue, int initLen,int minLen, int maxLen, char** table, int count); extern char* CF_GetBlob(int no, char* initValue, int initLen, int minLen, int maxLen); extern char* CF_GetBlobOnlyTable(int no, char* initValue, int initLen, int minLen, int maxLen, char** table, int* tablelen, int count); extern char* CF_GetBlobOutOfTable(int no, char* initValue, int initLen, int minLen, int maxLen, char** table, int* tablelen, int count); extern char* CF_GetBlobBasedOnTable(int no, char* initValue, int initLen, int minLen, int maxLen, char** table, int* tablelen, int count); extern char* CF_GetMySelf(int no, char* initValue, int initLen, int minLen, int maxLen);codefuzzer一个简单的完整使用举例请参考
doc/简单使用举例1.md · anner910/codefuzzer - Gitee.com
codefuzzer的另一个创新是他被直接实现成一个库,并且本身并不包括main函数,每个用例就是一个代码调用片段
因此他可以很好的与其他测试框架融合,并且也可以很容易开发成单独工具,应用具有极强的灵活性
单独命令工具请参考
doc/简单使用举例2.md · anner910/codefuzzer - Gitee.com
gtest下使用请参考
doc/场景适配/gtest下使用.md · anner910/codefuzzer - Gitee.com
运行libfuzzer用例请参考
doc/场景适配/运行libfuzzer用例.md · anner910/codefuzzer - Gitee.com
bufferfuzzer (anner910/bufferfuzzer)
因为引入二维样本,使得codefuzzer在支持遗传进化的基础上,支持多参数同时变异,解决了多参数编写与变异效率的问题
但对于那些拥有复杂结构的buf参数,fuzz工具还仍然不能知道其中的语法结构
基于此,作者开发了bufferfuzzer
bufferfuzzer基于codefuzzer工具扩展开发,支持一套完整的模板语法,用来解析和描述各种复杂buf的结构
并具有良好的可扩展性
比如下边是构建好的arp报文协议模板
<BufferFuzzer name="arp_buffer" author="wanghao 6482543@qq.com"> <!-- ARP RFC826 --> <!-- 字段名称来自wireshark --> <Buffer name="arp_packet" > <Struct name="eth" > <Buf name="eth_dst" length="6"/> <Buf name="eth_src" length="6"/> <U16 name="eth_type" /> </Struct> <Struct name="arp" > <U16 name="arp_hw_type" /> <!-- 1 request 2 reply --> <U16 name="arp_proto_type" /> <U8 name="arp_hw_size" > <Relation type="size" of="arp_src_hw_mac"/> </U8> <U8 name="arp_proto_size" > <Relation type="size" of="arp_src_proto_ipv4"/> </U8> <U16 name="arp_opcode" /> <Buf name="arp_src_hw_mac" length="6"/> <Buf name="arp_src_proto_ipv4" length="4"/> <Buf name="arp_dst_hw_mac" length="6"/> <Buf name="arp_dst_proto_ipv4" length="4"/> </Struct> <Buf /> </Buffer> </BufferFuzzer>通过模板的定义,可以知道被测buf由哪些字段构成,每个字段的大小,类型,值
(一些场景字段的值通过使用模板解析bin文件或者buf内存获得),子段之间的关系如(length value等)
使用上述arp.xml模板的用例大概如下
/* arp Frame (42 bytes) */ static const unsigned char pkt1[42] = { 0xd0, 0xd7, 0x83, 0x99, 0xf3, 0x06, 0x4c, 0xd5, /* ......L. */ 0x77, 0xb7, 0xa9, 0x73, 0x08, 0x06, 0x00, 0x01, /* w..s.... */ 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x4c, 0xd5, /* ......L. */ 0x77, 0xb7, 0xa9, 0x73, 0xc0, 0xa8, 0x03, 0x82, /* w..s.... */ 0xd0, 0xd7, 0x83, 0x99, 0xf3, 0x06, 0xc0, 0xa8, /* ........ */ 0x03, 0x01 /* .. */ }; extern void arp_receive(char* buf,int len); void test_arp(void) { printf("test_arp start\r\n"); //arp初始buf设置 bufferSetBuffer(0, (char*)pkt1, sizeof(pkt1) ); //第一个参数是模型文件路径名字,第二个参数是样本bin文件路径,第三个参数是模型要解析的数据模型名称 bufferDoModel(0, (char*)"./arp.xml", NULL, (char*)"arp_packet"); CodeFuzz_start(0,100,1000000,"test_arp",0) { static int i = 0; printf("\r%d",i++); fflush(stdout); int len; char* buf; //变异后的buf工具自己释放 bufferDoAction(0, &buf,&len); //使用变异buf,调用被测程序 arp_receive(buf, len); //工具释放每次变异过程中的临时内存 bufferDoFree(); } CodeFuzz_end() //工具释放模型解析过程中的内存 bufferDoClear(); printf("test_arp end\r\n"); }bufferDoAction函数里间接调用了诸多codefuzzer get系列函数
测试过程中生成的codefuzzer样本里可以看到多个参数出现
bufferfuzzer 大概的原理如下
1.建模 通过建模描述被测buffer的结构,每个元素的大小,类型,元素之间的关系 2.解析bin 通过使用模型对bin文件的解析,知道被测buffer有多少个元素,每个元素的初始值 3.元素变异 通过调用codefuzzer get系列函数,对每个需要变异的元素进行变异 4.关系字段赋值 通过从模型得到的字段之间的关系,对关系元素进行赋值 5.模型变异 基于模型的变异 6.组合 将变异后的各元素值基于模型结构重新组合在一起 7.测试 调用被测代码开始测试模板语法借鉴peach,十分通用,目前作者已经适配多款协议和文件类型,如下
协议 arp ipv4 dhcp http httpRest 文件 jpg mp4 通用建模 json通用建模 xml通用建模codefuzzer可以单独使用
但bufferfuzzer需要与odefuzzer一同使用
bufferfuzzer完整的简单使用举例请参考
https://gitee.com/anner910/bufferfuzzer/blob/master/doc/%E4%BE%8B%E5%AD%901.md
小结
通过引入二维样本,codefuzzer支持对多参数多类型的测试数据进行同时变异 通过引入通用模板,bufferfuzzer支持对复杂buf的结构描述,运行测试时将之拆分为codefuzzer的多参数多类型 通过bufferfuzzer和codefuzzer结合,完成了将语法引入到灰白盒fuzz测试的全过程讨伦:
在很多场景,比如json应用,xml应用等场景,模型变异可以轻松取得良好的测试效果 依据模型变异,可以使得fuzz工具能迅速变异出大量格式相关的样本,使得覆盖率短期内迅速提高 但是,模型同样也是一种束缚,bufferfuzzer很难变异出与模型出入巨大的样本出来 一些结构复杂多样的场景,仅靠模型变异效果并不理想 最优的步骤应该是 先用bufferfuzzer依据模型变异出大量有效样本(使用CF_SetOutBinCorpusPath调用输出), 在将样本输入(使用CF_SetInBinCorpusPath调用输入)给纯codefuzzer针对buf参数(CF_GetBlob)进行二次测试其他
codefuzzer的样本进化原理请参考
doc/工具原理/样本生成、调度、淘汰.md · anner910/codefuzzer - Gitee.com
codefuzzer可以使用一些范围类型的元素变异结合被测函数返回值来支持发现逻辑问题,请参考
doc/用例编写/发现逻辑问题.md · anner910/codefuzzer - Gitee.com
bufferfuzzer支持基于模型的变异,请参考
doc/工具原理/变异算法.md · anner910/bufferfuzzer - Gitee.com
codefuzzer与bufferfuzzer支持多方面扩展,很方便进行fuzz新技术引入研究,具体请参考各自工具readme
动态扩展 调整变异算法 调整变异频率 调整变异位置 调整样本权重 静态扩展 自定义字典 自定义变异算法 自定义数据类型 自定义样本进化方式 模型扩展 自定义Transform 自定义Fixup 自定义Relation 自定义Analyzer 自定义check(constraint)一款灰白盒fuzzer工具简单的测试,比较工具
anner910/testfuzzer
青草莓科技