文章目录

    • 0. 前言
    • 1. 故事起源
    • 2. 模糊测试事例
    • 3. 模糊测试框架的简单实现
      • 3.1 Runner 类
      • 3.2 Runner 类

0. 前言

来源:Fuzzing: Breaking Things with Random Inputs

建议阅读原文,我这里仅仅整理下思路。我敲的相关代码见:fuzzing仓库

在本章中,我们将从最简单的测试生成技术开始。随机文本生成的关键思想,也称为fuzzing,是将一串随机字符输入程序,以期发现失败。

要求:知道最简单的测试概念:here

1. 故事起源

模糊测试诞生于“ 1988年秋天的黑暗和暴风雨之夜”中。 巴顿·米勒教授坐在麦迪逊威斯康星州的公寓里,通过一条1200波特的电话线连接到他的大学计算机。 雷暴在线路上造成噪音,而该噪音又导致两端的UNIX命令获得错误的输入,并崩溃。 频繁的崩溃使他感到惊讶。当然,程序应该比这更强大吗? 作为一名科学家,他想研究问题的严重程度及其原因。 因此,他为威斯康星大学麦迪逊分校的学生编写了一个编程练习,该练习将使他的学生创建第一个模糊测试器。

作业重点如下:

该项目的目标是在给定不可预测的输入流的情况下评估各种UNIX实用程序的健壮性。 首先,您将构建一个模糊发生器。 这是一个将输出随机字符流的程序。 其次,您将使用模糊发生器,并使用它来攻击尽可能多的UNIX实用程序,以试图破坏它们。

该作业抓住了模糊测试的本质:创建随机输入,并查看它们是否破坏东西。 只要让它运行足够长的时间,您就会看到。

2. 模糊测试事例

参考连接:Python中with用法详解 | with语句 – python官方文档 |subprocess – 子进程管理

我们的目标和上面类似:创建随机输入,测试bc应用。

# 生成一个指定范围内,随机长度,随机字母的字符串
import random
def fuzzer(min_length=5,max_length=15,char_start=ord('a'),char_range=26):
    str_length = random.randrange(min_length,max_length+1)
    out = ""
    for i in range(str_length):
        out += chr(random.randrange(char_start,char_start+char_range))
    return out

# 将生成的字符串写入文件
import os
import tempfile
basename = "input.txt"
tmp_dir = tempfile.mkdtemp()
FILE = os.path.join(tmp_dir,basename)

data = fuzzer()
with open(FILE,"w") as f:
    f.write(data)
print(open(FILE).read())

# 调用外部程序
import subprocess
program = "bc"
with open(FILE, "w") as f:
    f.write("2 + 2\n")
result = subprocess.run([program, FILE],
                        stdin=subprocess.DEVNULL,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        universal_newlines=True)  # Will be "text" in Python 3.7
# print(result)
print(result.stdout)
# print(result.returncode)
# print(result.stderr)

# 测试bc程序
trials = 50
program = "bc"
results = []
for i in range(trials):
    data = fuzzer(min_length=2,char_start=ord('0'))
    data += '\n'
    with open(FILE,"w") as f:
        f.write(data)
    result = subprocess.run([program, FILE],
                        stdin=subprocess.DEVNULL,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        universal_newlines=True)  # Will be "text" in Python 3.7
    results.append((data,result))
    
# 测试bc程序的结果分析,没有返回值为非0存在,即没有崩溃
# 如果有返回代码非0,断言
sum_suc = 0
for i in range(trials):
    (data,result) = results[i]
    assert result.returncode == 0
    if result.stderr == "":
        sum_suc += 1
        print(f"suc:{data}-->{result.stdout}")
    else:
        print(f"fail:{data}-->{result.stderr}")
print(sum_suc)

3. 模糊测试框架的简单实现

上面通过一个事例演示了模糊测试。但上面的代码是面向过程编程,在测试其他程序的时候,不好重复利用。下面我们思考如何写一个简单的模糊测试框架。比较明显的是,框架可以分为两部分:生成随机字符串;将字符串作为输入测试程序。好,下面我们实现这样的想法

3.1 Runner 类

我们首先介绍的是Runner的概念––其工作是使目标对象执行给定的输入。目标对象为指定的待测试程序。
让我们从Runner的基础类开始。 运行程序本质上提供了一种run(input)方法,该方法用于将输入(string)传递给目标对象。 run()返回一对(result,outcome)。 在这里,result是Runner特定的值,提供了Runner返回的详细信息; outcome是将结果分为三类的值:

  • Runner.PASS – the test passed. The run produced correct results.
  • Runner.FAIL – the test failed. The run produced incorrect results.
  • Runner.UNRESOLVED – the test neither passed nor failed. This happens if the run could not take place – for instance, because the input was invalid.
# base runner class : essentially provides a method run(input),run() returns a pair (result, outcome)
# result是返回值的详细信息,outcome是三种分类之一
# 这个类是基类,下面通过继承覆盖,产生不同的子类
class Runner(object):
    # Test outcomes
    PASS = "PASS"
    FAIL = "FAIL"
    UNRESOLVED = "UNRESOLVED"
    def __init__(self):
        pass
    def run(self,inp):
        return(inp, self.UNRESOLVED)
    
    # 继承Runner类。打印输入
class PrintRunner(Runner):
    def run(self,inp):
        print(inp)
        return(inp, self.UNRESOLVED)
    
# 继承Runner类
# 把输入发送给程序;程序在创建对象的时候制定
class ProgramRunner(Runner):
    def __init__(self,program):
        self.program = program
    def run_process(self,inp=""):
        return subprocess.run(self.program,
                                input=inp,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                universal_newlines=True)
                                # text=True) 我的是3.6版本,还没有text
    def run(self,inp=""):
        result = self.run_process(inp)
        if result.returncode == 0:
            outcome = self.PASS
        elif result.outcome < 0:
            outcome = self.FAIL
        else:
            outcome = self.UNRESOLVED
        return (result,outcome)
    
# 继承ProgramRunner类
# 如果输入是二进制形式
class BinaryProgramRunner(ProgramRunner):
    def run_process(self,inp=""):
        return subprocess.run(self.program,
                            input=inp.encode(),
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    
# 测试下ProgramRunner
cat = ProgramRunner(program="cat")
(result,outcome) = cat.run("I am cat")
print(result)

3.2 Runner 类

Fuzzer 类主要是创建随机输入,用以喂给run()方法;

# Fuzzer基类:生成随机输入,并使用run方法运行
class Fuzzer(object):
    def __init__(self):
        pass
    def fuzz(self):
        return ""
    def run(self,runner=Runner()):
        return runner.run(self.fuzz())
    def runs(self,runner=PrintRunner(),trials=10):
        outcomes = []
        for i in range(trials):
            # outcomes.append(runner.run(self.fuzz()))
            outcomes.append(self.run(runner))
        return outcomes
    
# 随机模糊测试
class RandomFuzzer(Fuzzer):
    def __init__(self, min_length=10, max_length=100,char_start=32, char_range=32):
        self.min_length = min_length
        self.max_length = max_length
        self.char_start = char_start
        self.char_range = char_range
    def fuzz(self):
        str_len = random.randrange(self.min_length,self.max_length)
        out = ""
        for i in range(str_len):
            out += chr(random.randrange(self.char_start,self.char_start + self.char_range))
        return out

# 测试RandomFuzzer
random_fuzzer = RandomFuzzer(min_length=5,max_length=10,char_start=ord('a'),char_range=26)
# random_fuzzer.fuzz() # 可以随机生成字符串,很好
cat_runner = ProgramRunner("cat")
outcomes = random_fuzzer.runs(cat_runner,10)
print(outcomes)
# 上面的bc测试使用我们的基类
random_fuzzer = RandomFuzzer(min_length=2,max_length=6,char_start=ord('0'),char_range=10)
bc_runner = ProgramRunner("bc")
outcomes = random_fuzzer.runs(bc_runner,10)
print(outcomes)

本文地址:https://blog.csdn.net/sinat_38816924/article/details/110880475