前言
本文是针对两篇论文的总结,发表于2019 Usenix Security
FuzziFication: Anti-Fuzzing Techniques
AntiFuzz: Impeding Fuzzing Audits of Binary Executables
背景
fuzz技术已经被大规模应用,是至今为止最为有效的自动化漏洞挖掘技术。但是,目前为止并没有针对fuzz的反制技术。在很多场景下,恶意的攻击者会使用fuzz对软件进行漏洞挖掘,进而实施攻击。 代码混淆技术在较早就出现了,代码混淆技术主要针对的是逆向分析,特别是静态分析。当然,也有混淆算法针对动态分析的,但这类算法大多对性能影响巨大而不实用。 借鉴传统代码混淆的思路,可以设计一套针对fuzz的代码混淆算法。
Fuzzer的类型
盲Fuzzer
这一类fuzzer是最原始的fuzz,他们只是生产随机的输入,然后观测程序运行的结果,例如是否崩溃等。这类fuzzer可以主要分为两类,一类是随机生成输入。另一类针对输入有良好定义的格式,根据格式生成输入,一般fuzz的目标拥有类似parser的逻辑模块。
覆盖导向的Fuzzer
这类fuzzer是主流,著名的AFL就是这类fuzzer。这类fuzzer通过记录下路径覆盖信息,指导测试用例的生成。测试信息的搜集主要分三类。
静态插桩
需要程序源代码,使用此类方法的主要有AFL、ANGORA、LIBFUZZER、HONGGFUZZ。
动态插桩
针对无法获取程序的源代码的情况下,可以考虑动态插桩。动态插桩的主要方法有QEMU、PIN、DYNAMORIO、DYNINST。VUZZER和STEELIX即是使用以上的方法。一些AFL的变体也使用以上的插桩方法。DRILLER和T-FUZZ则是依赖由AFL和基于QEMU的插桩。
硬件支持的插桩
这类插桩可以提高效率,主要是依靠Intel PT技术记录程序历史路径。KAFL即是基于Intel PT的。
混合Fuzzer
这类Fuzzer将组合一些程序分析技术,包括污点分析和符号执行,用来覆盖到难以到达的路径。主要有DRILLER、QSYM、T-FUZZ。
Fuzz的特征
覆盖率导向
fuzz依赖于获取覆盖率信息,认为越大的覆盖率意味着越可能产生crash。绝大多数的fuzz都依赖于覆盖率来产生新的测试用例
可检测的crash
fuzz需要能够监测到crash,才能判断测试用例是否有效。
可高效运行
fuzz需要程序每秒能够运行多次。一个运行一次需要很久的程序是难以fuzz的
条件可解
最近的混合fuzz通过结合污点分析和符号执行来到达一些难以到达的路径,这要求这些条件是符号执行和污点分析能够求解的。
Fuzz对抗
针对覆盖率导向
插入大量分支无效代码
这是最简单的方法。通过插入大量fake code,根据输入的hash值决定跳转的位置,这将导致fuzzer花费大量时间测试无效代码。为了降低对效率的影响,想要将这些代码插在针对输入的错误处理部分。这个方法也是最容易被自动化移除的,通过借助控制流和数据流分析技术可以一定程度的移除这类fake code。
基于ROP
思想和上一个类似,即是根据输入的hash跳转执行,从而欺骗fuzzer找到新路径。但这里可以重用已有代码。将所有函数的epilogue分组,相同的分为一组。在函数返回时,根据输入的hash确定所使用epilogue,由于同组所有epilogue是相同的,不影响函数返回。这种方法相较于上一种难以去除。
针对高效运行
加入时延
错误处理部分和正常场景下很少执行的部分在时延代码。这两类代码块在正常情况下不会被执行,而在fuzz时则可能被频繁执行。时延代码可以使用类似CSmath的高运输负载的代码,这样能保证在程序分析下存在数据流和控制流依赖而难以被去除。
针对可检测的crash
反调试
使用各种反调试技巧
针对条件可解
增加间接数据流依赖
主要针对污点分析。间接数据流依赖一直是污点分析中难以解决的问题。
爆炸路径条件
针对符号执行。最简单的一种方法是在输入和魔数进行比较时候,计算他们的hash并进行比较
总结
对抗是需要成本的,无论哪个混淆算法,代价都是性能的损失,在实际当中需要衡量好安全和性能的平衡。一种可使用的方法是先确定正常场景下难以进入的代码块,在这类代码块中插入各种保护,同时结合常规代码保护方法。