# 基于变异的模糊化

> 原文： [https://www.fuzzingbook.org/html/MutationFuzzer.html](https://www.fuzzingbook.org/html/MutationFuzzer.html)

大多数[随机生成的输入](Fuzzer.html)在语法上都是*无效*，因此很快就被处理程序拒绝。 要行使输入处理之外的功能，我们必须增加获得有效输入的机会。 一种这样的方法就是所谓的*突变模糊测试*-也就是说，对现有输入进行小的更改，这些更改可能仍会保持输入有效，但会行使新的行为。 我们将展示如何创建此类突变，以及如何使用流行的AFL模糊器的中心概念来引导它们走向尚未发现的代码。

**前提条件**

*   您应该知道基本的模糊测试是如何工作的。 例如，在[“模糊处理”](Fuzzer.html) 一章中。

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.MutationFuzzer](MutationFuzzer.html) import <identifier>

```

然后利用以下功能。

本章介绍`MutationFuzzer`类，该类采用*种子输入*的列表，然后将其突变：

```py
>>> seed_input = "http://www.google.com/search?q=fuzzing"
>>> mutation_fuzzer = MutationFuzzer(seed=[seed_input])
>>> [mutation_fuzzer.fuzz() for i in range(10)]
['http://www.google.com/search?q=fuzzing',
 'http://www.g=oNogl.om/search?q=fuzzing/',
 'RttpX://w)ww.goo(gle.comq/sarc(q=fuzzng',
 'hdt8p://"wWw.goole.com/seDarb`*?q=fuzzing',
 'httop://www.CooglGe.om/s$arch?q=fuzzingY',
 'http://wwlw.google.c"om/secrch?yq=fuzzin',
 'hup://www.google.comC/search?q=fuzzing',
 'http://w7w.google.com/search?q=ufuzgzing',
 'http://www,google.com/sear4ch?.q=fuzzing',
 'http://w&ww.google.cKom/search7q=fuzzing']

```

`MutationCoverageFuzzer`保持输入的*种群*，然后对其进行演化以最大化覆盖范围。

```py
>>> mutation_fuzzer = MutationCoverageFuzzer(seed=[seed_input])
>>> mutation_fuzzer.runs(http_runner, trials=10000)
>>> mutation_fuzzer.population[:5]
['http://www.google.com/search?q=fuzzing',
 'htTp://www.googld.cqom/searchq=fuzzIng',
 'htTp://www.gloogld.qom/|searchq=fuzzng',
 'htTp://www.googld.cqomo0searchq=fuzzIng',
 'htTp://www*goegld.cqoe/sa7#hq=fuzIng']

```

## 带有突变的

2013年11月，发布了 [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/) （AFL）的第一版。 从那时起，AFL已成为最成功的模糊测试工具之一，并具有许多特色，例如 [AFLFast](https://github.com/mboehme/aflfast) ， [AFLGo](https://github.com/aflgo/aflgo) 和 [AFLSmart](https://github.com/aflsmart/aflsmart) （在 这本书）。 AFL使模糊测试成为自动漏洞检测的流行选择。 它是第一个证明可以在许多对安全性要求很高的实际应用程序中大规模自动检测漏洞的工具。

![American Fuzzy Lop Command Line User Interface](img/9b2f0f8d40bfc500d25dd91bdf3f77d3.jpg)

<center>**Figure 1.** American Fuzzy Lop Command Line User Interface</center>

在本章中，我们将介绍突变模糊测试的基础。 接下来的下一章将进一步说明如何将模糊测试引导到特定的代码目标。

## 模糊化URL解析器

许多程序期望它们的输入以非常特定的格式输入，然后才能实际处理它们。 例如，考虑一个接受URL（Web地址）的程序。 该URL必须采用有效格式（即URL格式），以便程序能够对其进行处理。 当使用随机输入进行模糊测试时，我们实际产生有效URL的机会是多少？

为了更深入地研究这个问题，让我们探索一下URL的组成。 URL由许多元素组成：

```py
scheme://netloc/path?query#fragment 
```

哪里

*   `scheme`是要使用的协议，包括`http`，`https`，`ftp`，`file` ...
*   `netloc`是要连接的主机的名称，例如`www.google.com`
*   `path`是该主机上的路径，例如`search`
*   `query`是键/值对的列表，例如`q=fuzzing`
*   `fragment`是所检索文档中某个位置的标记，例如`#result`

在Python中，我们可以使用`urlparse()`函数来解析URL并将其分解为各个部分。

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

```py
try:
    from [urlparse](https://docs.python.org/3/library/urlparse.html) import urlparse      # Python 2
except ImportError:
    from [urllib.parse](https://docs.python.org/3/library/urllib.parse.html) import urlparse  # Python 3

urlparse("http://www.google.com/search?q=fuzzing")

```

```py
ParseResult(scheme='http', netloc='www.google.com', path='/search', params='', query='q=fuzzing', fragment='')

```

我们将看到结果如何将URL的各个部分编码为不同的属性。

现在让我们假设我们有一个使用URL作为输入的程序。 为简化起见，我们不会让它做太多事情。 我们只是让它检查传递的URL的有效性。 如果URL有效，则返回True；否则返回True。 否则，将引发异常。

```py
def http_program(url):
    supported_schemes = ["http", "https"]
    result = urlparse(url)
    if result.scheme not in supported_schemes:
        raise ValueError("Scheme must be one of " + repr(supported_schemes))
    if result.netloc == '':
        raise ValueError("Host must be non-empty")

    # Do something with the URL
    return True

```

现在让我们开始模糊`http_program()`。 为了模糊起见，我们使用了所有可打印的ASCII字符，包括`:`，`/`和小写字母。

```py
from [Fuzzer](Fuzzer.html) import fuzzer

```

```py
fuzzer(char_start=32, char_range=96)

```

```py
'"N&+slk%h\x7fyp5o\'@[3(rW*M5W]tMFPU4\\P@tz%[X?uo\\1?b4T;1bDeYtHx #UJ5w}pMmPodJM,_'

```

让我们尝试对1000个随机输入进行模糊测试，看看我们是否取得了一些成功。

```py
for i in range(1000):
    try:
        url = fuzzer()
        result = http_program(url)
        print("Success!")
    except ValueError:
        pass

```

实际获得有效URL的机会是什么？ 我们需要以`"http://"`或`"https://"`开头的字符串。 让我们先来看`"http://"`案例。 这是我们需要开始的七个非常具体的字符。 随机产生这七个字符（字符范围为96个不同字符）的机会为$ 1：96 ^ 7 $，或者

```py
96 ** 7

```

```py
75144747810816

```

产生`"https://"`前缀的几率甚至更糟，为$ 1：96 ^ 8 $：

```py
96 ** 8

```

```py
7213895789838336

```

这给了我们完全的机会

```py
likelihood = 1 / (96 ** 7) + 1 / (96 ** 8)
likelihood

```

```py
1.344627131107667e-14

```

这是我们产生有效的URL方案所需的运行次数（平均）：

```py
1 / likelihood

```

```py
74370059689055.02

```

让我们测量一下`http_program()`的运行时间：

```py
from [Timer](Timer.html) import Timer

```

```py
trials = 1000
with Timer() as t:
    for i in range(trials):
        try:
            url = fuzzer()
            result = http_program(url)
            print("Success!")
        except ValueError:
            pass

duration_per_run_in_seconds = t.elapsed_time() / trials
duration_per_run_in_seconds

```

```py
5.87425309995524e-05

```

那非常快，不是吗？ 不幸的是，我们有很多需要解决的问题。

```py
seconds_until_success = duration_per_run_in_seconds * (1 / likelihood)
seconds_until_success

```

```py
4368685536.722877

```

转化为

```py
hours_until_success = seconds_until_success / 3600
days_until_success = hours_until_success / 24
years_until_success = days_until_success / 365.25
years_until_success

```

```py
138.4352909195527

```

即使我们并行处理很多事情，我们仍需要等待数月至数年。 这是为了使*成功运行一次*，这将使`http_program()`更加深入。

基本的模糊测试会很好地进行测试`urlparse()`，如果此解析函数中有错误，则很有可能将其发现。 但是，只要我们无法产生有效的输入，我们就无法达到任何更深层次的功能。

## 突变输入

从头开始生成随机字符串的替代方法是从给定的*有效*输入开始，然后随后*对其进行*变异。 在此上下文中，*突变*是一种简单的字符串操作-例如，插入（随机）字符，删除字符或在字符表示中翻转一位。 这被称为*突变模糊测试*-与之前讨论的*世代模糊测试*技术相反。

以下是一些可以帮助您入门的变体：

```py
import [random](https://docs.python.org/3/library/random.html)

```

```py
def delete_random_character(s):
    """Returns s with a random character deleted"""
    if s == "":
        return s

    pos = random.randint(0, len(s) - 1)
    # print("Deleting", repr(s[pos]), "at", pos)
    return s[:pos] + s[pos + 1:]

```

```py
seed_input = "A quick brown fox"
for i in range(10):
    x = delete_random_character(seed_input)
    print(repr(x))

```

```py
'A uick brown fox'
'A quic brown fox'
'A quick brown fo'
'A quic brown fox'
'A quick bown fox'
'A quick bown fox'
'A quick brown fx'
'A quick brown ox'
'A quick brow fox'
'A quic brown fox'

```

```py
def insert_random_character(s):
    """Returns s with a random character inserted"""
    pos = random.randint(0, len(s))
    random_character = chr(random.randrange(32, 127))
    # print("Inserting", repr(random_character), "at", pos)
    return s[:pos] + random_character + s[pos:]

```

```py
for i in range(10):
    print(repr(insert_random_character(seed_input)))

```

```py
'A quick brvown fox'
'A quwick brown fox'
'A qBuick brown fox'
'A quick broSwn fox'
'A quick brown fvox'
'A quick brown 3fox'
'A quick brNown fox'
'A quick brow4n fox'
'A quick brown fox8'
'A equick brown fox'

```

```py
def flip_random_character(s):
    """Returns s with a random bit flipped in a random position"""
    if s == "":
        return s

    pos = random.randint(0, len(s) - 1)
    c = s[pos]
    bit = 1 << random.randint(0, 6)
    new_c = chr(ord(c) ^ bit)
    # print("Flipping", bit, "in", repr(c) + ", giving", repr(new_c))
    return s[:pos] + new_c + s[pos + 1:]

```

```py
for i in range(10):
    print(repr(flip_random_character(seed_input)))

```

```py
'A quick bRown fox'
'A quici brown fox'
'A"quick brown fox'
'A quick brown$fox'
'A quick bpown fox'
'A quick brown!fox'
'A 1uick brown fox'
'@ quick brown fox'
'A quic+ brown fox'
'A quick bsown fox'

```

现在让我们创建一个随机变异器，它随机选择要应用的变异：

```py
def mutate(s):
    """Return s with a random mutation applied"""
    mutators = [
        delete_random_character,
        insert_random_character,
        flip_random_character
    ]
    mutator = random.choice(mutators)
    # print(mutator)
    return mutator(s)

```

```py
for i in range(10):
    print(repr(mutate("A quick brown fox")))

```

```py
'A qzuick brown fox'
' quick brown fox'
'A quick Brown fox'
'A qMuick brown fox'
'A qu_ick brown fox'
'A quick bXrown fox'
'A quick brown fx'
'A quick!brown fox'
'A! quick brown fox'
'A quick brownfox'

```

现在的想法是*如果*我们有一些有效的输入作为开始，那么我们可以通过应用上述突变之一来创建更多输入候选。 要了解其工作原理，让我们回到URL。

## 突变网址

现在让我们回到我们的URL解析问题。 让我们创建一个函数`is_valid_url()`，该函数检查`http_program()`是否接受输入。

```py
def is_valid_url(url):
    try:
        result = http_program(url)
        return True
    except ValueError:
        return False

```

```py
assert is_valid_url("http://www.google.com/search?q=fuzzing")
assert not is_valid_url("xyzzy")

```

现在让我们在给定的URL上应用`mutate()`函数，并查看我们获得了多少有效输入。

```py
seed_input = "http://www.google.com/search?q=fuzzing"
valid_inputs = set()
trials = 20

for i in range(trials):
    inp = mutate(seed_input)
    if is_valid_url(inp):
        valid_inputs.add(inp)

```

现在我们可以观察到，通过*突变*原始输入，我们得到了很大一部分有效输入：

```py
len(valid_inputs) / trials

```

```py
0.8

```

通过突变`http:`样本种子输入来产生`https:`前缀的几率是多少？ 我们必须在正确的位置（$ 1：l $）中插入（$ 1：3 $）右字符`'s'`（$ 1：96 $），其中$ l $是种子输入的长度。 这意味着平均而言，我们需要进行多次运行：

```py
trials = 3 * 96 * len(seed_input)
trials

```

```py
10944

```

我们实际上可以负担得起。 我们试试吧：

```py
from [Timer](Timer.html) import Timer

```

```py
trials = 0
with Timer() as t:
    while True:
        trials += 1
        inp = mutate(seed_input)
        if inp.startswith("https://"):
            print(
                "Success after",
                trials,
                "trials in",
                t.elapsed_time(),
                "seconds")
            break

```

```py
Success after 3656 trials in 0.010670263999600138 seconds

```

当然，如果我们想获得一个`"ftp://"`前缀，我们将需要更多的突变和更多的运行–但是，最重要的是，我们需要应用*多个*突变。

## 多个突变

到目前为止，我们仅对示例字符串应用了一个突变。 但是，我们也可以应用*多个*突变，以进一步对其进行更改。 例如，如果我们对样本字符串应用20个突变，会发生什么情况？

```py
seed_input = "http://www.google.com/search?q=fuzzing"
mutations = 50

```

```py
inp = seed_input
for i in range(mutations):
    if i % 5 == 0:
        print(i, "mutations:", repr(inp))
    inp = mutate(inp)

```

```py
0 mutations: 'http://www.google.com/search?q=fuzzing'
5 mutations: 'http:/L/www.googlej.com/seaRchq=fuz:ing'
10 mutations: 'http:/L/www.ggoWglej.com/seaRchqfu:in'
15 mutations: 'http:/L/wwggoWglej.com/seaR3hqf,u:in'
20 mutations: 'htt://wwggoVgle"j.som/seaR3hqf,u:in'
25 mutations: 'htt://fwggoVgle"j.som/eaRd3hqf,u^:in'
30 mutations: 'htv://>fwggoVgle"j.qom/ea0Rd3hqf,u^:i'
35 mutations: 'htv://>fwggozVle"Bj.qom/eapRd[3hqf,u^:i'
40 mutations: 'htv://>fwgeo6zTle"Bj.\'qom/eapRd[3hqf,tu^:i'
45 mutations: 'htv://>fwgeo]6zTle"BjM.\'qom/eaR[3hqf,tu^:i'

```

如您所见，原始种子输入几乎无法识别。 通过一次又一次地改变输入，我们得到了更高的输入多样性。

为了在单个程序包中实现这样的多个突变，让我们介绍一个`MutationFuzzer`类。 它需要种子（字符串列表）以及最小和最大数量的突变。

```py
from [Fuzzer](Fuzzer.html) import Fuzzer

```

```py
class MutationFuzzer(Fuzzer):
    def __init__(self, seed, min_mutations=2, max_mutations=10):
        self.seed = seed
        self.min_mutations = min_mutations
        self.max_mutations = max_mutations
        self.reset()

    def reset(self):
        self.population = self.seed
        self.seed_index = 0

```

接下来，让我们通过添加更多方法来进一步开发`MutationFuzzer`。 Python语言要求我们使用所有方法将整个方法定义为单个连续单元； 但是，我们想介绍一种方法。 为了避免这个问题，我们使用了一种特殊的技巧：每当我们想向某个类`C`引入新方法时，我们都会使用

```py
class C(C):
    def new_method(self, args):
        pass

```

这似乎将`C`定义为其自身的子类，这没有任何意义-但实际上，它引入了一个新的`C`类作为*旧* `C`类的子类，然后进行了阴影处理 旧的`C`定义。 这使我们得到的是`C`类，并以`new_method()`作为方法，这正是我们想要的。 （不过，较早定义的`C`对象将保留较早的`C`定义，因此必须重新构建。）

使用此技巧，我们现在可以添加一个`mutate()`方法，该方法实际调用上述`mutate()`函数。 当我们以后要扩展`MutationFuzzer`时，将`mutate()`作为方法很有用。

```py
class MutationFuzzer(MutationFuzzer):
    def mutate(self, inp):
        return mutate(inp)

```

让我们回到我们的策略，在我们的人群中最大化覆盖率。 首先，让我们创建一个方法`create_candidate()`，该方法从当前种群（`self.population`）中随机选择一些输入，然后在`min_mutations`和`max_mutations`突变步骤之间应用，返回最终结果：

```py
class MutationFuzzer(MutationFuzzer):
    def create_candidate(self):
        candidate = random.choice(self.population)
        trials = random.randint(self.min_mutations, self.max_mutations)
        for i in range(trials):
            candidate = self.mutate(candidate)
        return candidate

```

`fuzz()`方法设置为首先采摘种子； 当这些消失后，我们进行变异：

```py
class MutationFuzzer(MutationFuzzer):
    def fuzz(self):
        if self.seed_index < len(self.seed):
            # Still seeding
            self.inp = self.seed[self.seed_index]
            self.seed_index += 1
        else:
            # Mutating
            self.inp = self.create_candidate()
        return self.inp

```

```py
seed_input = "http://www.google.com/search?q=fuzzing"
mutation_fuzzer = MutationFuzzer(seed=[seed_input])
mutation_fuzzer.fuzz()

```

```py
'http://www.google.com/search?q=fuzzing'

```

```py
mutation_fuzzer.fuzz()

```

```py
'http://www.gogl9ecom/earch?qfuzzing'

```

```py
mutation_fuzzer.fuzz()

```

```py
'htotq:/www.googleom/yseach?q=fzzijg'

```

每次`fuzz()`的新调用，我们都会得到另一个应用了多个突变的变体。 但是，输入的多样性较高，会增加输入无效的风险。 成功的关键在于*指导*这些突变的想法，也就是说*保留那些特别有价值的突变。*

## 覆盖率指导

为了涵盖尽可能多的功能，可以依赖*指定的*或*实现的*功能，如[“覆盖”](Coverage.html) 一章中所述。 现在，我们不会假设程序行为是规范的（尽管*肯定是*会很好！）。 但是，我们*将假定*存在，并且我们可以利用其结构来指导测试的产生。

由于测试总是执行手头的程序，因此人们总是可以收集有关其执行的信息-最少是决定测试是否通过所需的信息。 由于也经常测量覆盖率以确定测试质量，因此我们还假设我们可以检索测试运行的覆盖率。 问题是：*我们如何利用覆盖率来指导测试的产生？*

一种特别成功的想法是在名为 [American Fuzzy lop](http://lcamtuf.coredump.cx/afl/) 或简称为 *AFL* 的流行模糊器中实现的。 就像上面的示例一样，AFL会开发成功的测试用例-但是对于AFL，``成功''表示*通过程序执行*找到了新的路径。 这样，AFL可以继续变异到目前为止已经找到新路径的输入。 如果输入找到另一条路径，则也会保留该路径。

让我们制定这样的策略。 我们首先介绍一个`Runner`类，该类捕获给定功能的覆盖范围。 首先，一个`FunctionRunner`类：

```py
from [Fuzzer](Fuzzer.html) import Runner

```

```py
class FunctionRunner(Runner):
    def __init__(self, function):
        """Initialize.  `function` is a function to be executed"""
        self.function = function

    def run_function(self, inp):
        return self.function(inp)

    def run(self, inp):
        try:
            result = self.run_function(inp)
            outcome = self.PASS
        except Exception:
            result = None
            outcome = self.FAIL

        return result, outcome

```

```py
http_runner = FunctionRunner(http_program)
http_runner.run("https://foo.bar/")

```

```py
(True, 'PASS')

```

现在，我们可以扩展`FunctionRunner`类，以便它也可以测量覆盖范围。 调用`run()`之后，`coverage()`方法返回上次运行时获得的覆盖率。

```py
from [Coverage](Coverage.html) import Coverage, population_coverage

```

```py
class FunctionCoverageRunner(FunctionRunner):
    def run_function(self, inp):
        with Coverage() as cov:
            try:
                result = super().run_function(inp)
            except Exception as exc:
                self._coverage = cov.coverage()
                raise exc

        self._coverage = cov.coverage()
        return result

    def coverage(self):
        return self._coverage

```

```py
http_runner = FunctionCoverageRunner(http_program)
http_runner.run("https://foo.bar/")

```

```py
(True, 'PASS')

```

以下是前五个位置：

```py
print(list(http_runner.coverage())[:5])

```

```py
[('urlparse', 375), ('__exit__', 25), ('_coerce_args', 115), ('http_program', 10), ('http_program', 3)]

```

现在是主要班级。 我们维持人口和已经实现的一系列覆盖率（`coverages_seen`）。 `fuzz()`辅助函数接受输入并在其上运行给定的`function()`。 如果其覆盖范围是新的（即`coverages_seen`中没有），则将输入添加到`population`并将覆盖范围添加到`coverages_seen`。

```py
class MutationCoverageFuzzer(MutationFuzzer):
    def reset(self):
        super().reset()
        self.coverages_seen = set()
        # Now empty; we fill this with seed in the first fuzz runs
        self.population = []

    def run(self, runner):
        """Run function(inp) while tracking coverage.
 If we reach new coverage,
 add inp to population and its coverage to population_coverage
 """
        result, outcome = super().run(runner)
        new_coverage = frozenset(runner.coverage())
        if outcome == Runner.PASS and new_coverage not in self.coverages_seen:
            # We have new coverage
            self.population.append(self.inp)
            self.coverages_seen.add(new_coverage)

        return result

```

现在让我们使用它：

```py
seed_input = "http://www.google.com/search?q=fuzzing"
mutation_fuzzer = MutationCoverageFuzzer(seed=[seed_input])
mutation_fuzzer.runs(http_runner, trials=10000)
mutation_fuzzer.population

```

```py
['http://www.google.com/search?q=fuzzing',
 'http://www.goog.com/search;q=fuzzilng',
 'http://ww.6goog\x0eoomosearch;/q=f}zzilng',
 'http://uv.Lboo.comoseakrch;q=fuzilng',
 'http://ww.6goog\x0eo/mosarch;/q=f}z{il~g',
 'Http://www.g/ogle.com/earchq=fuzzing',
 'http://www.goog.com/lsearkh;q=fuzzilng',
 'http://www.Ifnole.com/searchq=fzzing',
 'Http://ww.g/ogle.com/earchq=furzing',
 'Http://wwQw.g/oGle.ug/m/ear#hq=uzzGing',
 'Http://ww.g/ogle.cnom?earchq=fZrzing',
 'http://www.goog.co#m/lsearkh;q=fuzilng',
 'Http://wwQw.g/oGle.ug/m/ear#hqg=uzzGvi~g',
 'http://RuV.Lboo.comose`krch;q=ftzil~g',
 "http://www.googco#_mx/lsa'rkhq=fuzilng",
 'htTp://w.6goog\x0eo/mosarch;/q=f}z{il~g',
 'http://www.google.com/sea[arch?q=fuzzGing',
 'Http://wwQ?w.g/oGle6.ug/m/ekar#hq=uzzGing',
 'httP://uv.Lboo.comoseakrch;q=fzilng',
 'http://ww.6goog\x0eo/mosarch;/?q=fz{il~g',
 'htTp://w.6eoog\x0eo/mosarch;?p=f}z{il~w',
 'http://www.goog\x0eom/sea7rch;#q=fuezzi-mog',
 'httP://uv.L"Noo.omosekrch;q=fziln',
 'htTp://w.6go7opg\x0eo/mosayrch;/ #q=b}z{il~g',
 'Http://wwQ?w.g/%oGle6.ug//ekar!hq=uzzGinc',
 'htTp://wS.6eoog\x0e/msarcah;?p=f}z{i~7,',
 'http://www.goog\x0eo}/qa5ch;Y#q=fuezzi-mog',
 'http://ww.W=6goxog\x0eo/mosarXch;+?q=fz{il~g',
 'htTp://w.6Foo\x0en/mosarch;q=fK}z{il~g',
 'htTp://7.6Gogo\x0e/iowarcj;q=fK}z{Il~gX',
 'http://ww.62gogo/m%/saRch;/q=fl}ziil~g',
 'http://www.go?og\x0eo}/qac`P;Y#eq=fuezzik-mog',
 'http://www.go?g\x0ek}/qa`P;Y#q=fuezzik-moga',
 'htTp://Hw.6g7`g\x0eo/mosayrc)h;/K #q=j}zyil~g',
 'htTp://Hw.6g`g\x0eo/moseyrc)h;OK(#q=j}zyil~g',
 'http://*ww.W=goxog\x0e/2RmosarXch;+?Cq=f{i2l~g',
 'htTp://.6,gokg\x0eo~/mosrch;/?f\\}z{il~g',
 'htTp://Hw.6g`g\x0eo/}oseyrc)h;OK(#p=j}zyil~g',
 'htTp://w.A6go7evopg\nFo/mosayvch;?R#q=b}z{il~g',
 'htTp://w.Ago7e%vopgV\nzFo/oayv=ch;?R6#q=b}z{il~g',
 'Http://wwQ?w.g/oGle6.ug/m/ka2#hq9=uzzGing',
 'http://wHw.67`g\x0eo/mosaygrc)h;/K #q=j}zyil~g',
 'htTp://w.g&o\x0eo/mocarh;/q=b}Mx{iBl~g',
 'http://w.Ago7e%vopoV\nzIFo/oayv=ch;?R6#q=b}z{il~+g=',
 'htTp://.6O,gokg\x0eo~/mTmsrc;h;/?f\\}Ez{il~g',
 'http://ww%.6goog\x0eo/mosach;/?q=fz{il~g',
 'http://wHw.67`g\x0eo/mosaygc)h;/K #q#`=j}zyilg']

```

成功！ 在我们的总体中，*的每个输入*现在都是有效的，并且覆盖范围不同，来自方案，路径，查询和片段的各种组合。

```py
all_coverage, cumulative_coverage = population_coverage(
    mutation_fuzzer.population, http_program)

```

```py
import [matplotlib.pyplot](https://docs.python.org/3/library/matplotlib.pyplot.html) as [plt](https://docs.python.org/3/library/plt.html)

```

```py
plt.plot(cumulative_coverage)
plt.title('Coverage of urlparse() with random inputs')
plt.xlabel('# of inputs')
plt.ylabel('lines covered');

```

![](data:image/png;base64, 
)

这种策略的好处是，应用于较大的程序时，它将很乐于探索另一条路径–涵盖功能与功能之间的关系。 所需要的只是捕获覆盖范围的一种方法。

## 经验教训

*   随机生成的输入通常是无效的-因此主要行使输入处理功能。
*   来自现有有效输入的变异具有较高的有效机会，因此可以行使输入处理以外的功能。

## 后续步骤

在有关[灰箱模糊](GreyboxFuzzer.html)的下一章中，我们进一步扩展了基于突变的测试的概念，其中包含*功率调度表*，可以为执行“不太可能”路径和种子运行的种子提供更多能量 与目标位置“更近”。

## 练习

### 练习1：对带有突变的CGI解码进行模糊处理

将上述*指导的基于*突变的模糊技术应用于[“封面”](Coverage.html) 一章的`cgi_decode()`。 在涵盖`+`，`%`（有效和无效）和常规字符的所有变体之前，您需要进行几次试验？

```py
from [Coverage](Coverage.html) import cgi_decode

```

```py
seed = ["Hello World"]
cgi_runner = FunctionCoverageRunner(cgi_decode)
m = MutationCoverageFuzzer(seed)
results = m.runs(cgi_runner, 10000)

```

```py
m.population

```

```py
['Hello World', 'he_<+llo(or<D', 'L}eml &Wol%dD', 'L)q<}aml &cWol%d3D+']

```

```py
cgi_runner.coverage()

```

```py
{('__exit__', 25),
 ('cgi_decode', 9),
 ('cgi_decode', 10),
 ('cgi_decode', 11),
 ('cgi_decode', 12),
 ('cgi_decode', 15),
 ('cgi_decode', 16),
 ('cgi_decode', 17),
 ('cgi_decode', 18),
 ('cgi_decode', 19),
 ('cgi_decode', 20),
 ('cgi_decode', 21),
 ('cgi_decode', 22),
 ('cgi_decode', 23),
 ('cgi_decode', 24),
 ('cgi_decode', 25),
 ('cgi_decode', 26),
 ('cgi_decode', 30),
 ('cgi_decode', 31),
 ('cgi_decode', 32),
 ('run_function', 7)}

```

```py
all_coverage, cumulative_coverage = population_coverage(
    m.population, cgi_decode)

import [matplotlib.pyplot](https://docs.python.org/3/library/matplotlib.pyplot.html) as [plt](https://docs.python.org/3/library/plt.html)
plt.plot(cumulative_coverage)
plt.title('Coverage of cgi_decode() with random inputs')
plt.xlabel('# of inputs')
plt.ylabel('lines covered');

```

![](data:image/png;base64, 
)

经过10,000次运行，我们设法合成了`+`字符和有效的`%xx`表格。 我们仍然可以做得更好。

### 练习2：使用突变对bc进行模糊处理

在[“模糊化简介”](Fuzzer.html) 一章中，将上述基于突变的模糊化技术应用于`bc`。

#### 第1部分：非引导式突变

从非引导性突变开始。 有多少输入有效？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/MutationFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

#### 第2部分：引导突变

继续*指导的*突变。 为此，您将必须找到一种从诸如`bc`之类的C程序中提取覆盖率的方法。 继续执行以下步骤：

首先，获得 [GNU bc](https://www.gnu.org/software/bc/) ； 下载并说`bc-1.07.1.tar.gz`并解压缩：

```py
!curl -O mirrors.kernel.org/gnu/bc/bc-1.07.1.tar.gz

```

```py
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  410k  100  410k    0     0   146k      0  0:00:02  0:00:02 --:--:--  146k

```

```py
!tar xfz bc-1.07.1.tar.gz

```

其次，配置软件包：

```py
!cd bc-1.07.1; ./configure

```

```py
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables... 
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking whether gcc understands -c and -o together... yes
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking minix/config.h usability... no
checking minix/config.h presence... no
checking for minix/config.h... no
checking whether it is safe to define __EXTENSIONS__... yes
checking for flex... flex
checking lex output file root... lex.yy
checking lex library... -ll
checking whether yytext is a pointer... yes
checking for ar... ar
checking the archiver (ar) interface... ar
checking for bison... bison -y
checking for ranlib... ranlib
checking whether make sets $(MAKE)... (cached) yes
checking for stdarg.h... yes
checking for stddef.h... yes
checking for stdlib.h... (cached) yes
checking for string.h... (cached) yes
checking for errno.h... yes
checking for limits.h... yes
checking for unistd.h... (cached) yes
checking for lib.h... no
checking for an ANSI C-conforming const... yes
checking for size_t... yes
checking for ptrdiff_t... yes
checking for vprintf... yes
checking for _doprnt... no
checking for isgraph... yes
checking for setvbuf... yes
checking for fstat... yes
checking for strtol... yes
Adding GCC specific compile flags.
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating bc/Makefile
config.status: creating dc/Makefile
config.status: creating lib/Makefile
config.status: creating doc/Makefile
config.status: creating doc/texi-ver.incl
config.status: creating config.h
config.status: executing depfiles commands

```

第三，用特殊标志编译软件包：

```py
!cd bc-1.07.1; make CFLAGS="--coverage"

```

```py
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in lib
gcc -DHAVE_CONFIG_H  -I. -I..  -I. -I.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT getopt.o -MD -MP -MF .deps/getopt.Tpo -c -o getopt.o getopt.c
mv -f .deps/getopt.Tpo .deps/getopt.Po
gcc -DHAVE_CONFIG_H  -I. -I..  -I. -I.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT getopt1.o -MD -MP -MF .deps/getopt1.Tpo -c -o getopt1.o getopt1.c
mv -f .deps/getopt1.Tpo .deps/getopt1.Po
gcc -DHAVE_CONFIG_H  -I. -I..  -I. -I.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT vfprintf.o -MD -MP -MF .deps/vfprintf.Tpo -c -o vfprintf.o vfprintf.c
mv -f .deps/vfprintf.Tpo .deps/vfprintf.Po
gcc -DHAVE_CONFIG_H  -I. -I..  -I. -I.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT number.o -MD -MP -MF .deps/number.Tpo -c -o number.o number.c
mv -f .deps/number.Tpo .deps/number.Po
rm -f libbc.a
ar cru libbc.a getopt.o getopt1.o vfprintf.o number.o 
ranlib libbc.a
Making all in bc
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT bc.o -MD -MP -MF .deps/bc.Tpo -c -o bc.o bc.c
mv -f .deps/bc.Tpo .deps/bc.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT scan.o -MD -MP -MF .deps/scan.Tpo -c -o scan.o scan.c
mv -f .deps/scan.Tpo .deps/scan.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT execute.o -MD -MP -MF .deps/execute.Tpo -c -o execute.o execute.c
mv -f .deps/execute.Tpo .deps/execute.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT load.o -MD -MP -MF .deps/load.Tpo -c -o load.o load.c
mv -f .deps/load.Tpo .deps/load.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT storage.o -MD -MP -MF .deps/storage.Tpo -c -o storage.o storage.c
mv -f .deps/storage.Tpo .deps/storage.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT util.o -MD -MP -MF .deps/util.Tpo -c -o util.o util.c
mv -f .deps/util.Tpo .deps/util.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT warranty.o -MD -MP -MF .deps/warranty.Tpo -c -o warranty.o warranty.c
mv -f .deps/warranty.Tpo .deps/warranty.Po
echo '{0}' > libmath.h
/Applications/Xcode.app/Contents/Developer/usr/bin/make global.o
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT global.o -MD -MP -MF .deps/global.Tpo -c -o global.o global.c
mv -f .deps/global.Tpo .deps/global.Po
gcc -g -O2 -Wall -funsigned-char --coverage   -o libmath.h -o fbc main.o bc.o scan.o execute.o load.o storage.o util.o warranty.o global.o ../lib/libbc.a -ll  
./fbc -c ./libmath.b </dev/null >libmath.h
./fix-libmath_h
2655
2793
rm -f ./fbc ./global.o
gcc -DHAVE_CONFIG_H -I. -I..  -I. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT global.o -MD -MP -MF .deps/global.Tpo -c -o global.o global.c
mv -f .deps/global.Tpo .deps/global.Po
gcc -g -O2 -Wall -funsigned-char --coverage   -o bc main.o bc.o scan.o execute.o load.o storage.o util.o global.o warranty.o ../lib/libbc.a -ll  
Making all in dc
gcc -DHAVE_CONFIG_H -I. -I..  -I./.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT dc.o -MD -MP -MF .deps/dc.Tpo -c -o dc.o dc.c
mv -f .deps/dc.Tpo .deps/dc.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I./.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT misc.o -MD -MP -MF .deps/misc.Tpo -c -o misc.o misc.c
mv -f .deps/misc.Tpo .deps/misc.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I./.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT eval.o -MD -MP -MF .deps/eval.Tpo -c -o eval.o eval.c
mv -f .deps/eval.Tpo .deps/eval.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I./.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT stack.o -MD -MP -MF .deps/stack.Tpo -c -o stack.o stack.c
mv -f .deps/stack.Tpo .deps/stack.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I./.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT array.o -MD -MP -MF .deps/array.Tpo -c -o array.o array.c
mv -f .deps/array.Tpo .deps/array.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I./.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT numeric.o -MD -MP -MF .deps/numeric.Tpo -c -o numeric.o numeric.c
mv -f .deps/numeric.Tpo .deps/numeric.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I./.. -I./../h  -g -O2 -Wall -funsigned-char --coverage -MT string.o -MD -MP -MF .deps/string.Tpo -c -o string.o string.c
mv -f .deps/string.Tpo .deps/string.Po
gcc -g -O2 -Wall -funsigned-char --coverage   -o dc dc.o misc.o eval.o stack.o array.o numeric.o string.o ../lib/libbc.a 
Making all in doc
restore=: && backupdir=".am$$" && \
	am__cwd=`pwd` && CDPATH="${ZSH_VERSION+.}:" && cd . && \
	rm -rf $backupdir && mkdir $backupdir && \
	if (makeinfo --no-split --version) >/dev/null 2>&1; then \
	  for f in bc.info bc.info-[0-9] bc.info-[0-9][0-9] bc.i[0-9] bc.i[0-9][0-9]; do \
	    if test -f $f; then mv $f $backupdir; restore=mv; else :; fi; \
	  done; \
	else :; fi && \
	cd "$am__cwd"; \
	if makeinfo --no-split   -I . \
	 -o bc.info bc.texi; \
	then \
	  rc=0; \
	  CDPATH="${ZSH_VERSION+.}:" && cd .; \
	else \
	  rc=$?; \
	  CDPATH="${ZSH_VERSION+.}:" && cd . && \
	  $restore $backupdir/* `echo "./bc.info" | sed 's|[^/]*$||'`; \
	fi; \
	rm -rf $backupdir; exit $rc
restore=: && backupdir=".am$$" && \
	am__cwd=`pwd` && CDPATH="${ZSH_VERSION+.}:" && cd . && \
	rm -rf $backupdir && mkdir $backupdir && \
	if (makeinfo --no-split --version) >/dev/null 2>&1; then \
	  for f in dc.info dc.info-[0-9] dc.info-[0-9][0-9] dc.i[0-9] dc.i[0-9][0-9]; do \
	    if test -f $f; then mv $f $backupdir; restore=mv; else :; fi; \
	  done; \
	else :; fi && \
	cd "$am__cwd"; \
	if makeinfo --no-split   -I . \
	 -o dc.info dc.texi; \
	then \
	  rc=0; \
	  CDPATH="${ZSH_VERSION+.}:" && cd .; \
	else \
	  rc=$?; \
	  CDPATH="${ZSH_VERSION+.}:" && cd . && \
	  $restore $backupdir/* `echo "./dc.info" | sed 's|[^/]*$||'`; \
	fi; \
	rm -rf $backupdir; exit $rc
make[4]: Nothing to be done for `all-am'.

```

文件`bc/bc`现在应该是可执行的...

```py
!cd bc-1.07.1/bc; echo 2 + 2 | ./bc

```

```py
4

```

...并且您应该能够运行`gcov`程序来检索覆盖率信息。

```py
!cd bc-1.07.1/bc; gcov main.c

```

```py
File 'main.c'
Lines executed:51.69% of 118
main.c:creating 'main.c.gcov'

```

如[“覆盖率”一章](Coverage.html)中所述，文件 [bc-1.07.1 / bc / main.c.gcov](bc-1.07.1/bc/main.c.gcov) 现在保存`bc.c`的覆盖范围信息。 每行都以执行次数为前缀。 `#####`表示零倍； `-`表示不可执行的行。

像`FunctionCoverageRunner`一样，为`bc`解析GCOV文件并创建`coverage`集。 将其设置为`ProgramCoverageRunner`类，将使用一系列源文件（`bc.c`，`main.c`和`load.c`）构建该类以运行`gcov`。

完成后，请不要忘记清理：

```py
!rm -fr bc-1.07.1 bc-1.07.1.tar.gz

```

### 练习3

在[博客文章](https://lcamtuf.blogspot.com/2014/08/binary-fuzzing-strategies-what-works.html)中， *American Fuzzy Lop* （AFL）的作者，一个非常流行的基于变异的模糊器，讨论了各种变异算子的效率。 如上例所示，实施其中四个并评估其效率。

### 练习4

将新元素添加到候选列表时，AFL实际上并不比较*覆盖率*，但如果它行使新的*分支*则添加元素。 使用[“覆盖率”](Coverage.html) 一章的练习中的分支覆盖率，实施此“分支”策略，并将其与上述“覆盖率”策略进行比较。

### 练习5

设计并实现一个系统，该系统将从Web收集大量URL。 您可以使用这些样本获得更高的覆盖率吗？ 如果将它们用作进一步突变的初始种群怎么办？