Fuzz Testing,又称模糊测试,是一种通过向目标系统提供非预期的“坏数据”输入并监视异常结果来发现软件漏洞的方法。这句话虽然简单,实际上可以引申出很多问题,如:
-
模糊测试的目标系统有哪些?
-
如何生成非预期的“坏数据”?
-
如何有效地输入“坏数据”?
-
如何监视异常?
-
如何定位触发异常的输入?
-
如何判断触发的输入可被利用?
接下来,我将就此疑问做出解答,带大家浅析模糊测试。
-
模糊测试的目标系统有哪些?
模式测试的目标有很多,总的来说可以有文件格式、环境变量、命令行、内存、网络协议、web应用。不仅可以白盒fuzz以验证目标代码路径已满足,还可以黑盒fuzz以验证目标系统的健壮性。只要你愿意,万物皆可Fuzz。
在测试之前,要考虑清楚目标应用程序。拿命令行的模糊测试为例,与其花巨大的精力在普通命令行上,还不如把重点关注在有潜在提权可能的命令上。我们还需要考虑开发商过去被发现的安全漏洞相关历史,可通过浏览器安全漏洞收集站点完成,如securityfocus。如果确要选择目标文件或库,应该选择那些被多个应用程序共享的库,因为这些库的用户群体比较大,出现安全漏洞的风险响应比较高;
在汽车领域,我们更多地关注在网络协议的模糊测试。众所周知,车载通信相对复杂,从近场的WiFi、蓝牙、USB,到本地的CAN、CANfd、Ether,这些通信媒介上的数十种通信协议都可以成为我们模糊测试的重点。下面的介绍会围绕通信fuzz展开。
2. 如何生成非预期的“坏数据”
这是模糊测试的灵魂。模糊测试是否能触发软件的非预期行为,很大程度上依赖于工具生成的 “坏数据”的质量。一般而言,我们有四种生成“坏数据”的方式:
-
随机生成:效率最低,没有任何输入,依靠fuzz engine随机生成;
-
根据已知数据结构生成:常用于文本协议,如DoIP、UDS;
-
基于变异;将现有数据作为种子进行变异,如一个pcap文件,常用于二进制协议;
-
基于生成:基于一个特定模型启发式生成数据;
在vTeststudio中,我们可以基于CAPL实现已知数据结构生成“坏数据”,还可以抓取特定数据包后基于变异生成“坏数据”。编译好测试用例后,导入CANoe即可进行通信模糊测试。
注意:目前模糊测试中“坏数据”都是基于工具以半自动或全自动化生成。
一般而言,“坏数据”不是随意生成的,Fuzz Engine有自己的一套逻辑,但都会有重点考虑:
(1)长串字符:也可以理解为重复字符串,
-
针对的对象主要是内存管理函数、字符串处理函数;
-
造成的后果为缓冲区溢出,包括栈缓冲区溢出和堆缓冲区溢出。
-
主要的原因:在没有额外的检查下,对输入字符串处理不当,导致写入的数据远远大于提前分配的空间,因此覆盖那些位于边界之外的数据;
note1:堆溢出问题对于fuzz来说是个挑战,因为堆溢出后不一定会像栈溢出那样立刻弹出非法访问的错误,因此不好定位是哪些子串导致堆溢出;
note2:Windows有全局标志,即GFlags一旦激活该标志,便可以以细粒度的方式跟踪,记录与调试软件的行为,全局调试中就有一个堆调试器,它将记录下设置的进程下所有的动态内存操作,当发现有堆数据遭受污染时,就会产生一个调试事件;设置工具为gflags.exe,也可以在immunity中执行与Gflags绑定的PyCommand命令就可以编辑Gflags标志
(2)整数边界值:包括大量的高值正整数和低值负整数以及被处理数的整数倍数值。
-
针对于所有涉及整数处理的函数;
-
造成的后果为整数值上溢、下溢、符号溢出等,进一步利用后造成缓冲区溢出;
-
主要的原因:以32位系统为例,在没有额外的检查下,当一个操作数的取值超过了32bit,整数就会发生截断,保留低32位作为数据存储的结果,这样就可能导致预期的数据和实际写入的数据有很大差异,一旦这些数据涉及到内存分配,就很有可能造成缓冲区溢出。
常用的数据根据实际应用大概有:0;数字的边界值,如2^4,2^8,2^16,2^24等,包括其对应的负数和相差为3的整数;将整数替换为浮点数。
(3)格式化字符串:包括格式化串(最好选择“%s”,“%n”)
-
造成的后果为内存泄漏,进而导致的系统提权;
-
主要的问题在于对一类字符串处理不恰当,导致输出一些不该输出的内容。如经常使用的printf函数。我们可以看到spike中集成有13种带有“%n”参数的值;
note: printf的-n格式符,可以完成对内存的写入,这很有可能给攻击者提供覆写函数的返回地址或重写某个函数。选择测试用例时,要格外关注这种能对内存读写的函数。
(4)字段分隔符(非字母数字字符,如空格、制表符、NULL(0x00)等)和扩展字符(ASCII在128-255的字符,如“{”,“(”,“[”,“(”)
主要的问题是当程序处理这种类型的字符时会有一些特定的行为,如printf会在NULL结束的字符串时停止;
(5)错误格式或错误内容或错误数量的字符串
-
在一个数据包中将命令打乱,重新组合,字符串解析这种打乱类型的字符串是有时会发生错误,如正常情况下string为“pass login”,一旦出现“passrloginn”字符串将如何处理;
-
没有任何意义的错误二进制数据可能会触发未知的错误;
-
连续重复的有意义命令字符串,此时可能会有触发一些设计或逻辑缺陷;
-
flood攻击,主要实现DoS;
(6)目录遍历:如“../”“./”“.”等,车内通信由于不存在文件系统,所以没有必要
造成的后果:可以造成某些目录下的数据泄露;
(7)命令注入:向“exec()”、“system()” 之类的 API 调用传递未经过滤的用户数据;
我们需要注意的是,有时候正常模式下可能无法获取到特定的信息,因此可以采取故障注入的方式,让系统处于特定模式后,重新进行模糊测试,也许会有额外的收获或者加速系统错误和失效的发生。通常可注入的错误类型:内存错误、处理器错误、通信错误、进程错误、消息错误、网络错误、程序代码错误等。
3. 如何有效地输入“坏数据”?
模糊测试并不是注入的“坏数据”越多、越快、越畸形就越好。举几个简单的例子,爆发式的发送“坏数据”,由于目标系统的收发buffer有限,我们输入的“坏数据”还没进入应用层,就已经被物理层丢弃;还有,Ether单包长度在64到1518字节,实际构建单包大于1518或小于64字节,这类数据包基本会被丢弃。在实际测试中,我们需要结合目标系统的特性,构建合理的“坏数据”,并以合适的速度发送到目标系统。
4. 如何监视异常?
能够精确地判定目标程序是否发生异常非常的关键,可以采取下面方式获取异常信息:
-
通过程序的正常输出获取信息
-
通过静态代码插桩获取信息
-
通过动态二进制插桩获取信息
-
通过虚拟机获取信息
-
通过调试接口或者调试器获取信息
-
与程序建立连接,如socket或ping
例如我们可以使用Openocd、Lauterbach等调试工具通过诸如Jtag等调试口检测芯片异常,再使用Gdb等查看异常时的堆栈状态。
很多ECU出现异常后会由看门狗自动复位,这会导致异常不容易被检测。如果是在开发环境下,建议先关闭看门狗以更好的保存异常现场。
5. 如何定位触发异常的输入?
这是一个好的模糊测试工具的重要考核指标。如何定位触发异常的输入考验了模糊测试工具的可重现性,即测试者必须能够知道使目标程序状态变化所对应的测试数据是什么,如果不具备重现测试结果的能力,那么整个过程就失去了意义。实现可重现性的一个方法是在发送测试数据的同时记录下测试数据和目标程序的状态。
6. 如何判断触发异常的输入可被利用?
判定发现的漏洞是否可能被利用:这种过程是典型的手工过程,需要操作者具有特定的安全知识。学海无涯苦作舟,平时多锻炼,CTF走一波。
结束语
最后,小编给大家罗列一些模糊测试工具,大多数是开源的,希望未来各位都能成为汽车安全领域的挖洞大拿。
END
原文始发于微信公众号(汽车信息安全):青骥原创 l 模糊测试–六问六答