CLIFuzzer:命令行调用的挖掘语法

 很多程序都靠命令行来传递选项和参数,因此要对这些程序做测试,需要遍历所有的命令行选项。CLIFuzzer使用动态分析来跟踪输入,并自动提取参数,我们可以变换不同的输入参数,生成无穷的随机参数列表,对程序进行模糊测试,提高代码覆盖率。

 注:与之很相似的有基于变异的fuzzer,它通过对输入数据进行随机变异和扰动来寻找软件的异常行为。这种模糊测试方法基于以下假设:在输入数据中的细微变化和异常情况可能会引发软件中的漏洞。

 命令行程序输入参数的格式为$<程序名> <parameter>*。其中,<parameter>要么为参数名,例如-v|-a,要么为参数值,例如1.txt等。目前,已经有很多研究关注于命令行程序,其局限性在于:只测试标准输入,即要么忽略程序接受的不同选项,要么只用特定的选项序列。还有的研究通过分析--help来获得选项集,但是--help选项可能不存在。

 而CLIFuzzer可以从代码中自动确定选项列表,这基于一个假设:大多数的程序使用类似于getopt()之类的选项parser。在本论文中,主要是针对python的argparse进行原型设计,以创建命令行参数的语法。进一步地,我们将上述原型转为针对C语言的,使用getopt()来进行模糊测试的fuzzer。但是,getopt()无法获得选项类型,因此,我们通过跟踪运行时库函数(runtime library function)来获得选项类型。最终,CLIFuzzer获得了能准确描述程序参数的一套语法,如下所示,ls命令的语法为:

image-20230914110731000

 CLIFuzzer利用这套语法创建无穷的命令行序列,来测试程序。

getopt()函数

 标准C语言库函数parse命令行主要用getopt | getopt_long | getopt_long_only函数。这些函数中的两个参数定义可能的选项:

(1)optstring。保存程序的短选项(例如-a)。举个例子,optstring="1ac:d::",这代表一个短选项列表,见链接。选项类型分为3种:(a)不带值的参数,其定义就是参数本身;(b)必须带值的参数,在定义后加:;(c)可选值的参数,在定义后加::。其表示语法如下(其中<prefix>不知道是什么?):

image-20230914113111507

(2)longopts。指向选项结构体的指针,描述了程序接受的长选项。从结构体为:

image-20230914121158381

参数规范分析

 此节的目的是生成参数语法。其步骤为:(1)将选项字符串转为上下文无关的语法。(2)将选项参数转为谓词。(3)参数到谓词。(看不太明白,接着向下看)

从选项规范(optstring、longopts)构建语法

 此步骤将短选项与长选项规范转为上下文无关的语法。我们将getopt函数更改,从而记录参数。更改后的getopt被加入到共享库中,并重写LD_PRELOAD环境变量,以加载此共享库。

 一旦CLIFuzzer提取到了optstring,就会用如下算法将短选项转为语法。

image-20230914123616739

 上述算法中,首先检查optstring是否以'-'开头。如果是,则表明该程序接受任何未指定的选项字母,而不会立即出现错误,这部分在第 2 节中详细描述。因此,我们将-<letter>附加到上下文无关语法中。如果optstring:+开头,它将影响向程序指示缺失参数的方式,具体见链接。然而,它对选项规范没有直接影响,因此跳过。longopts不需要parse,直接转为语法即可。

挖掘选项参数类型

 CLIFuzzer扫描libc,以找寻需要参数的函数。它重写每一个libc的函数,以便调用这些函数时会记录参数。CLIFuzzer使用每个选项的随机参数来调用被测程序(如何理解?),举个例子,如果文件名为参数,那么程序可能会调用open|stat函数;如果以整数为参数,那么程序可能调用atoi|strtol函数;如果以浮点数为参数,那么程序可能调用atof|strtod

参数的谓词

 最后一步就是找到参数要满足的条件(谓词)(理解的不一定对)。因此,使用多个参数来调用实用程序来确定程序需要多少个参数。

评估

 程序最起码有一个文件输入,保证完全随机性。选项个数平均数为27,代码量平均为1w行。以AFL++为基线,当程序具有大量有效选项时,CLIFuzzer的覆盖范围明显优于AFL++,因为选项不是AFL++模糊测试过程的一部分。当程序没有有效选项(什么叫有效选项?)时,CLIFuzzer与AFL++的覆盖率相当。但是,CLIFuzzer并不能找到col程序中的crash部分,因为这需要特定字符作为输入,但是AFL++可以,这是因为它的目标就是更高的代码覆盖率。

 突然感觉fuzzer的目的是让程序崩溃?

 举几个CLIFuzzer找到的崩溃点:

(1)bison(接受一个上下文无关语法规范作为输入,并生成一个解析器,用于分析输入的语法结构):当运行bison --trace s1时,会挂起。其中,--trace选项并未在使用文档中提到。

(2)tac(反向输出):当运行tac --separator=.+5 --regex E.coli时会挂起,程序会卡在regexec.c中,其中csplit|expr|nl中也调用了此文件,因此猜测这些指令中可能也会出现这种毛病。

相关工作

 AFL(Americal Fuzzy Lop)主要关注stdin与文件输入,不太关注参数输入。相关变体有AFLGo|AFL++

AFL++中有一个实验性的参数fuzz,但是并不是专门针对创建选项,因此用处不大。RIDDLE利用程序的一些选项(通过语法)来进行fuzz(不知道和CLIFuzzer的区别)。iFUZZ则需要用户主动提供getopt()中的optstring参数。Wang等人在在CLI程序上选择某些引导模糊测试的选项,其具体目标是最大化覆盖率,选项在Protobuf中指定为语法,并指导模糊测试工具进行fuzz。Lee首先从程序文档中提取了一组选项,之后他们确定了最大化覆盖率的选项子集,这些选项用于构造十个调用字符串,然后用于对程序进行模糊测试,在此期间,仅输入文件发生变化。与CLIFuzzer相比,上述所有方法都需要一定的人力来推断完整的命令行调用(感觉优势不是很明显)。

论文代码

https://github.com/vrthra/fse2022-clifuzzer

To be continued…

留言

© 2024 wd-z711

⬆︎TOP