由于之后的章节中我们会常常用到第二部分所提到的Fuzzer与Runner思路结构,因此创建一种易于重复使用、后期易于拓展的Fuzz框架结构就显得十分重要。为此我们引入Python中类的概念,来逐步封装之前所提到的功能,为后面的章节做准备。
我么首先需要介绍的是Runner的概念:使用给定的输入来执行某些特定的程序,特定的程序通常是指要接受测试的某些程序或函数。
Runner本质上提供了一种run(input)方法:用于将input(字符串)传递给程序运行。run()会返回一对值(result,outcome),这里的result是run运行后结果的返回值,提供了run运行的细节供我们参考;outcome是将结果分为三类值:
Runner.PASS
—测试通过。运行产生正确的结果。Runner.FAIL
—测试失败。运行产生不正确的结果。Runner.UNRESOLVED
—测试既没有通过也没有失败。如果无法进行运行(例如输入无效),则会发生这种情况。
class Runner(object):
# Test outcomes
PASS = "PASS"
FAIL = "FAIL"
UNRESOLVED = "UNRESOLVED"
def __init__(self):
"""Initialize"""
pass
def run(self, inp):
"""Run the runner with the given input"""
return (inp, Runner.UNRESOLVED)
这里的Runner类是后续的一个基类,仅仅是一个基础的框架。后续要根据自己的需求继承该基类并用额外的函数重写。
这里举一个简单的例子:PrintRunner会打印出所有传递给自身的值,它就是基于Runner继承并修改的:
class PrintRunner(Runner):
def run(self, inp):
"""Print the given input"""
print(inp)
return (inp, Runner.UNRESOLVED)
p = PrintRunner()
(result, outcome) = p.run("Some input")
结果就是我们作为输入传递的字符串:
result:'Some input'
但是到目前为止,我们仍无法对程序行为进行分类:
outcome:'UNRESOLVED'
下面的ProgramRunner
类将输入传送到程序,并根据运行结果分类:
class ProgramRunner(Runner):
def __init__(self, program):
"""Initialize. `program` is a program spec as passed to `subprocess.run()`"""
self.program = program
def run_process(self, inp=""):
"""Run the program with `inp` as input. Return result of `subprocess.run()`."""
return subprocess.run(self.program,
input=inp,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
def run(self, inp=""):
"""Run the program with `inp` as input. Return test outcome based on result of `subprocess.run()`."""
result = self.run_process(inp)
if result.returncode == 0:
outcome = self.PASS
elif result.returncode < 0:
outcome = self.FAIL
else:
outcome = self.UNRESOLVED
return (result, outcome)
如果是针对二进制程序文件,可能还要小改一下:
class BinaryProgramRunner(ProgramRunner):
def run_process(self, inp=""):
"""Run the program with `inp` as input. Return result of `subprocess.run()`."""
return subprocess.run(self.program,
input=inp.encode(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
让我们演示一个ProgramRunner
使用cat
程序的实际例子:cat程序将其输入复制到其输出。
我们看到标准的调用cat
简单地完成了这项工作,其输出cat
与其输入相同:
cat = ProgramRunner(program="cat")cat.run("hello")
输出:(CompletedProcess(args='cat', returncode=0, stdout='hello', stderr=''), 'PASS')
是不是封装后使用起来更方便了。
现在我们来一起定义一个fuzzer类,fuzzer的主要作用其实就是生成数据并送至runner.
Fuzzer的基类提供了一些创建输入的核心方法,随后run()将这些数据传送至Runner并返回结果;runs()可以设定发送的次数(trials)
class Fuzzer(object):
def __init__(self):
pass
def fuzz(self):
"""Return fuzz input"""
return ""
def run(self, runner=Runner()):
"""Run `runner` with fuzz input"""
return runner.run(self.fuzz())
def runs(self, runner=PrintRunner(), trials=10):
"""Run `runner` with fuzz input, `trials` times"""
# Note: the list comprehension below does not invoke self.run() for subclasses
# return [self.run(runner) for i in range(trials)]
outcomes = []
for i in range(trials):
outcomes.append(self.run(runner))
return outcomes
默认情况下,Fuzzer
对象不执行任何操作。但在RandomFuzzer
中,子类实现了上述功能的fuzzer()
功能,并增加了一个附加参数min_length
以指定生成数据的最小长度。
class RandomFuzzer(Fuzzer):
def __init__(self, min_length=10, max_length=100,
char_start=32, char_range=32):
"""Produce strings of `min_length` to `max_length` characters
in the range [`char_start`, `char_start` + `char_range`]"""
self.min_length = min_length
self.max_length = max_length
self.char_start = char_start
self.char_range = char_range
def fuzz(self):
string_length = random.randrange(self.min_length, self.max_length + 1)
out = ""
for i in range(0, string_length):
out += chr(random.randrange(self.char_start,
self.char_start + self.char_range))
return out
使用RandomFuzzer
,我们现在可以创建一个模糊器,在创建模糊器时只需配置一次即可。
random_fuzzer = RandomFuzzer(min_length=20, max_length=20)
for i in range(10):
print(random_fuzzer.fuzz())
'>23>33)(&"09.377.*3
*+:5 ? (?1$4<>!?3>.'
4+3/(3 (0%!>!(+9%,#$
/51$2964>;)2417<9"2&
907.. !7:&--"=$7',7*
(5=5'.!*+&>")6%9)=,/
?:&5) ";.0!=6>3+>)=,
6&,?:!#2))- ?:)=63'-
,)9#839%)?&(0<6("*;)
4?!(49+8=-'&499%?< '
下面我们仍然以cat应用程序为例,将这样生成的输入发送到我们先前定义的cat,验证是否
cat确实确实将其(模糊的)输入复制到其输出中。
for i in range(10):
inp = random_fuzzer.fuzz()
result, outcome = cat.run(inp)
assert result.stdout == inp
assert outcome == Runner.PASS
最后,将Fuzzer与Runner结合十分的普遍
random_fuzzer.run(cat)
(CompletedProcess(args='cat', returncode=0, stdout='?:+= % <1<6$:(>=:9)5', stderr=''),'PASS')
使用runs()
,我们可以重复执行模糊测试多次,以获得结果列表。
random_fuzzer.runs(cat, 10)
[(CompletedProcess(args='cat', returncode=0, stdout='3976%%&+%6=(1)3&3:<9', stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout='33$#42$ 11=*%$20=<.-', stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout='"?<\'#8 </:*%9.--\'97!', stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout="/0-#(03/!#60'+6>&&72", stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout="=,+:,6'5:950+><3(*()", stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout=" 379+0?'%3137=2:4605", stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout="02>!$</'*81.#</22>+:", stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout="=-<'3-#88*%&*9< +1&&", stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout='2;;0=3&6=8&30&<-;?*;', stderr=''),'PASS'),(CompletedProcess(args='cat', returncode=0, stdout='/#05=*3($>::#7!0=12+', stderr=''),'PASS')]
有了这些类,我们就可以创建更多更复杂的模糊器了。