模糊测试简介
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