模糊测试简介

本文介绍了模糊测试的起源,通过一个Python示例展示了如何使用模糊测试来测试`bc`应用。文章还提出并实现了模糊测试框架的简单概念,包括`Runner`和`Fuzzer`类,用于生成随机输入并运行测试。最后,通过`RandomFuzzer`类生成随机字符串,测试了`cat`和`bc`程序。
摘要由CSDN通过智能技术生成

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)

4. 附录

模糊测试框架的UML图。

@startuml 2_break_thing_with_random_input

class Runner{
    PASS = "PASS"
    FAIL = "FAIL"
    UNRESOLVED = "UNRESOLVED"
    __init__(self)
    (inp, self.UNRESOLVED) run(self,inp)
}
note right of Runner
    Runner为基类(不是抽象类)
    使用inp作为输入
    返回结果和三种结果状态中的一种
    此时init和run方法啥也没干
end note


class ProgramRunner{
    program
    __init__(self,program)
    run_process(self,inp="")
    (result,outcome) run(self,inp="")
}
Runner <|-- ProgramRunner
note right of ProgramRunner
    ProgramRunner类继承Runner类
    init方法读取将要运行的程序名
    run方法读取输入,并调用run_process,将输入传递给run_process
    run_process使用子进程运行程序
end note


class Fuzzer{
    __init__(self)
    string fuzz(self)
    (result,outcome) run(self,runner=Runner())
    (result,outcome)[] runs(self,runner=PrintRunner(),trials)
}
Fuzzer ..> Runner
note right of Fuzzer
    Fuzzer是一个基础类
    fuzz方法生成测试的输入内容
    run方法使用Runner类生成的一个对象来运行被测试程序;
        被测试的程序使用fuzz方法生成的内容作为输入
    runs方法多次调用run方法,调用次数为trials
end note

class RandomFuzzer{
    __init__(self, min_length, max_length,char_start, char_range)
    string fuzz(self)
}
RandomFuzzer --|> Fuzzer
note right of RandomFuzzer
    RandomFuzzer继承Fuzzer类
    init方法读取参数:随机字符串的最小长度、最大长度、字符的开始值、字符的最大值
    fuzz根据参数的限制,随机生成字符串
end note
@enduml

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

da1234cao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值