freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Fuzzbook系列(3):Fuzz的基本框架结构
2021-01-12 17:46:26

由于之后的章节中我们会常常用到第二部分所提到的Fuzzer与Runner思路结构,因此创建一种易于重复使用、后期易于拓展的Fuzz框架结构就显得十分重要。为此我们引入Python中类的概念,来逐步封装之前所提到的功能,为后面的章节做准备。

创建Runner的类:

我么首先需要介绍的是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类,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')]

有了这些类,我们就可以创建更多更复杂的模糊器了。

# 模糊测试 # fuzz # 二进制
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者