# 带有语法的Greybox模糊测试

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

在本章中，我们介绍了语法模糊技术的两个重要扩展：

1.  我们展示了如何将[解析](Parser.html)和[模糊化](GrammarFuzzer.html)与语法结合在一起。 这允许*在保留语法正确性的同时对*现有输入进行变异，并允许*在生成新输入时重用来自现有输入的*片段。 如本章所述，解析和模糊测试的结合在实践中非常成功：用于JavaScript的 *LangFuzz* 模糊器已经通过这种方式在JavaScript解释器中发现了2,600多个错误。

2.  在前面的章节中，我们以*黑盒*的方式使用语法-也就是说，无论测试程序如何，我们都使用它们来生成输入。 在本章中，我们介绍使用语法的突变*灰盒模糊测试：这些技术利用被测程序*的*反馈来指导测试世代实现特定目标。 就像[词法灰箱模糊](GreyboxFuzzer.html)中一样，此反馈大部分是*覆盖率*，这使我们可以将基于语法的测试指向未发现的代码部分。*

**前提条件**

*   我们基于[上关于灰盒模糊化（无语法）](GreyboxFuzzer.html)的几个概念。
*   如标题所示，您应该从语法的一章中知道如何对语法[进行模糊处理。](Grammars.html)

## 背景

首先，我们[回顾了](GreyboxFuzzer.html#Ingredients-for-Greybox-Fuzzing)突变模糊测试仪的一些基本要素。

*   **种子**。 *种子*是模糊器用来通过应用一系列突变产生新输入的输入。
*   **变异子**。 *变异器*实现了一组变异运算符，这些变异运算符应用于输入会产生稍微修改的输入。
*   **PowerSchedule** 。 *功率调度*将*能量*分配给种子。 在整个模糊测试中，能量较高的种子会更加模糊。
*   **MutationFuzzer** 。 我们的*突变黑盒模糊测试器*通过对初始输入种群中的种子进行突变来生成输入。
*   **GreyboxFuzzer** 。 我们的*灰盒模糊测试仪*动态地将输入添加到增加覆盖率的种子种群中。
*   **FunctionCoverageRunner** 。 我们的*函数覆盖率运行程序*收集用于执行给定Python函数的覆盖率信息。

让我们尝试一下这些概念。

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

```

```py
from [GreyboxFuzzer](GreyboxFuzzer.html) import Mutator, Seed, PowerSchedule, MutationFuzzer, GreyboxFuzzer
from [MutationFuzzer](MutationFuzzer.html) import FunctionCoverageRunner

```

以下命令将突变应用于输入“ Hello World”。

```py
Mutator().mutate("Hello World")

```

```py
'Lello World'

```

默认的功率计划在所有种子之间均匀分配能量。 让我们检查一下是否可行。

我们从三个种子的种群中选择1万次。 正如我们在`hits`计数器中看到的那样，每个种子的选择时间约为三分之一。

```py
population = [Seed("A"), Seed("B"), Seed("C")]
schedule = PowerSchedule()
hits = {
    "A" : 0,
    "B" : 0,
    "C" : 0
}

for i in range(10000):
    seed = schedule.choose(population)
    hits[seed.data] += 1

hits

```

```py
{'A': 3267, 'B': 3328, 'C': 3405}

```

在解释功能介绍之前，让我们以导入Python的HTML解析器为例...

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

```

...并创建一个*包装函数*，该函数将每个输入传递到新的解析器对象中。

```py
def my_parser(inp):
    parser = HTMLParser()
    parser.feed(inp)

```

`FunctionCoverageRunner`构造函数使用Python `function`执行。 函数`run()`接受输入，将其传递给Python `function`，并收集此执行的覆盖率信息。 函数`coverage()`为Python `function`中涵盖的每个语句返回元组`(function name, line number)`的列表。

```py
runner = FunctionCoverageRunner(my_parser)
runner.run("Hello World")
cov = runner.coverage()

list(cov)[:5] # Print 5 statements covered in HTMLParser

```

```py
[('__init__', 93),
 ('goahead', 148),
 ('updatepos', 53),
 ('goahead', 251),
 ('goahead', 141)]

```

我们的灰盒模糊器需要种子种群，变异者和能量计划。 让我们从“空”种子语料库开始生成5000个模糊输入。

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

```

```py
n = 5000
seed_input = " " # empty seed
runner = FunctionCoverageRunner(my_parser)
fuzzer = GreyboxFuzzer([seed_input], Mutator(), PowerSchedule())

start = time.time()
fuzzer.runs(runner, trials=n)
end = time.time()

"It took the fuzzer %0.2f seconds to generate and execute %d inputs." % (end - start, n)

```

```py
'It took the fuzzer 1.41 seconds to generate and execute 5000 inputs.'

```

```py
"During this fuzzing campaign, we covered %d statements." % len(runner.coverage())

```

```py
'During this fuzzing campaign, we covered 85 statements.'

```

## 建立关键字字典

为了模糊我们的HTML解析器，将有关输入中的重要关键字（即重要的HTML关键字）通知突变模糊器可能很有用。 为此，我们扩展了变量器，以考虑*字典*中的关键字。

```py
class DictMutator(Mutator):
    def __init__(self, dictionary):
        super().__init__()
        self.dictionary = dictionary
        self.mutators.append(self.insert_from_dictionary)

    def insert_from_dictionary(self,s):
        """Returns s with a keyword from the dictionary inserted"""
        pos = random.randint(0, len(s))
        random_keyword = random.choice(self.dictionary)
        return s[:pos] + random_keyword + s[pos:]

```

让我们尝试添加一些HTML标记和属性，并查看`DictMutator`的覆盖率是否增加。

```py
runner = FunctionCoverageRunner(my_parser)
dict_mutator = DictMutator(["<a>","</a>","<a/>", "='a'"])
dict_fuzzer = GreyboxFuzzer([seed_input], dict_mutator, PowerSchedule())

start = time.time()
dict_fuzzer.runs(runner, trials = n)
end = time.time()

"It took the fuzzer %0.2f seconds to generate and execute %d inputs." % (end - start, n)

```

```py
'It took the fuzzer 11.81 seconds to generate and execute 5000 inputs.'

```

显然，这需要更长的时间。 根据我们的经验，这意味着涉及更多代码：

```py
"During this fuzzing campaign, we covered %d statements." % len(runner.coverage())

```

```py
'During this fuzzing campaign, we covered 136 statements.'

```

覆盖范围随时间变化的模糊测试如何比较？

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

```

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

```

```py
_, dict_cov = population_coverage(dict_fuzzer.inputs, my_parser)
_, fuzz_cov = population_coverage(fuzzer.inputs, my_parser)
line_dict, = plt.plot(dict_cov, label="With Dictionary")
line_fuzz, = plt.plot(fuzz_cov, label="Without Dictionary")
plt.legend(handles=[line_dict, line_fuzz])
plt.xlim(0,n)
plt.title('Coverage over time')
plt.xlabel('# of inputs')
plt.ylabel('lines covered');

```

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

***摘要。*** 向Fuzzer告知重要的关键字已经对快速实现大量覆盖大有帮助。

***试试。*** 以Jupyter笔记本的形式打开本章，并将其他与HTML相关的关键字添加到字典中，以查看覆盖范围的差异是否实际上有所增加（假设5k生成的测试输入的预算相同）。

***读取。*** AFL的作者MichałZalewski在[上撰写了几篇很棒的博客文章，其中包括手头的词典](https://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html)和[凭空抽出JPEG](https://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-air.html) 构成语法！

## 输入片段模糊化

虽然字典有助于将重要的关键字注入种子输入，但是字典却不能保持生成的输入的结构完整性。 相反，我们需要使模糊器知道*输入结构*。 我们可以使用[语法](Grammars.html)来做到这一点。 我们的第一种方法

1.  [解析](Parser.html)种子输入，
2.  将它们分解为输入片段，然后
3.  通过根据语法规则重新组合这些片段来生成新文件。

*解析*和*模糊化*的组合可能非常强大，正如我们将在瞬间看到的那样

### 解析和重组JavaScript，或如何在四周内赚取50,000美元

在“使用代码片段进行模糊处理” [ [Holler *等人*，2012。](https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final73.pdf)]中，Holler，Herzig和Zeller应用这些步骤来模糊JavaScript解释器。 他们使用JavaScript语法

1.  *解析*（有效）JavaScript输入到解析树中，
2.  *将*分解为片段（子树），
3.  *将这些片段重新组合为有效的JavaScript程序，然后*
4.  *将这些程序*馈入JavaScript解释器以执行。

与大多数模糊测试场景一样，其目的是使JavaScript解释器崩溃。 这是LangFuzz生成的JavaScript代码的示例（来自[ [Holler *等人*，2012。](https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final73.pdf)]），该代码导致Mozilla JavaScript解释器崩溃：

```py
var haystack = "foo";
var re_text = "^foo";
haystack += "x";
re_text += "(x)";
var re = new RegExp(re_text);
re.test(haystack);
RegExp.input = Number();
print(RegExp.$1);

```

由于JavaScript解释器崩溃，通常可以构造*漏洞*，该漏洞不仅会使解释器崩溃，而且要使它在攻击者的控制下执行代码。 因此，此类崩溃是严重的缺陷，这就是为什么如果报告这些错误就会得到赏金的原因。

在运行他的 *LangFuzz* 工具的前四周，该论文的第一作者Christian Holler从*的错误赏金*中获得了超过50,000美元的收入。 迄今为止，LangFuzz已在Mozilla Firefox，Google Chrome和Microsoft Edge的JavaScript浏览器中发现了2,600多个错误。 如果您使用这些浏览器中的任何一个（例如，在您的Android手机上），解析和模糊测试的结合就可以极大地确保您的浏览会话的安全。

（请注意，与本书的合著者相同的是Holler和Zeller。如果您想知道为什么我们专门针对基于语法的模糊测试撰写了两章，那是因为我们在此方面有丰富的经验。）

### 解析和重新组合HTML

在本书中，让我们停留一段时间的HTML输入。 要为我们的Python `HTMLParser`生成有效的HTML输入，我们应该首先定义一个简单的语法。 它允许定义带有属性的HTML标签。 我们的无上下文语法不要求开始和结束标记必须匹配。 但是，我们将看到可以在派生的输入片段中并因此在生成的输入中维护此类上下文相关功能。

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

```

```py
from [Grammars](Grammars.html) import is_valid_grammar, srange

```

```py
XML_TOKENS = {"<id>","<text>"}

XML_GRAMMAR = {
    "<start>": ["<xml-tree>"],
    "<xml-tree>": ["<text>",
                   "<xml-open-tag><xml-tree><xml-close-tag>", 
                   "<xml-openclose-tag>", 
                   "<xml-tree><xml-tree>"],
    "<xml-open-tag>":      ["<<id>>", "<<id> <xml-attribute>>"],
    "<xml-openclose-tag>": ["<<id>/>", "<<id> <xml-attribute>/>"],
    "<xml-close-tag>":     ["</<id>>"],
    "<xml-attribute>" :    ["<id>=<id>", "<xml-attribute> <xml-attribute>"],
    "<id>":                ["<letter>", "<id><letter>"],
    "<text>" :             ["<text><letter_space>","<letter_space>"],
    "<letter>":            srange(string.ascii_letters + string.digits +"\""+"'"+"."),
    "<letter_space>":      srange(string.ascii_letters + string.digits +"\""+"'"+" "+"\t"),
}

```

```py
assert is_valid_grammar(XML_GRAMMAR)

```

为了将输入解析为派生树，我们使用 [Earley解析器](Parser.html#Parsing-Context-Free-Grammars)。

```py
from [Parser](Parser.html) import EarleyParser
from [GrammarFuzzer](GrammarFuzzer.html) import display_tree

```

让我们在简单的HTML输入上运行解析器，并显示所有可能的解析树。 *解析树*表示根据给定语法的输入结构。

```py
parser = EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS)

for tree in parser.parse("<html>Text</html>"):
    display_tree(tree)

```

如我们所见，输入以开始标签开始，包含一些文本，然后以结束标签结束。 优秀的。 这是我们可以使用的结构。

### 建立片段池

现在，我们准备实现我们的第一个可识别输入结构的mutator。 让我们用表示空片段池的字典`fragments`初始化mutator。 它包含语法中每个符号的键（并将空集作为值）。

```py
class FragmentMutator(Mutator):
    def __init__(self, parser):
        """Initialize empty fragment pool and add parser"""
        self.parser = parser
        self.fragments = {k: [] for k in self.parser.cgrammar}
        super().__init__()

```

`FragmentMutator`递归添加片段。 *片段*是解析树中的子树，由当前节点和子节点的符号（即后代片段）组成。 我们可以排除以记号，结尾或不属于语法的符号开头的片段。

```py
from [Parser](Parser.html) import terminals

```

```py
class FragmentMutator(FragmentMutator):
    def add_fragment(self, fragment):
        """Recursively adds fragments to the fragment pool"""
        (symbol, children) = fragment
        if not self.is_excluded(symbol):
            self.fragments[symbol].append(fragment)
            for subfragment in children:
                self.add_fragment(subfragment)

    def is_excluded(self, symbol):
        """Returns true if a fragment starting with a specific
 symbol and all its decendents can be excluded"""
        return ((not symbol in self.parser.grammar()) or
                symbol in self.parser.tokens or
                symbol in terminals(self.parser.grammar()))

```

解析可能需要很长时间，尤其是在解析过程中含糊不清的情况下。 为了保持突变模糊测试的效率，我们将解析时间限制为200ms。

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

```

```py
class Timeout(Exception): pass
def timeout(signum, frame): 
    raise Timeout()

# Register timeout() as handler for signal 'SIGALRM'"
signal.signal(signal.SIGALRM, timeout);

```

函数`add_to_fragment_pool()`解析一个种子（不超过200毫秒），并将其所有片段添加到片段池中。 如果解析`seed`成功，则将属性`seed.has_structure`设置为`True`。 否则，将其设置为`False`。

```py
class FragmentMutator(FragmentMutator):
    def add_to_fragment_pool(self, seed):
        """Adds all fragments of a seed to the fragment pool"""
        try: # only allow quick parsing of 200ms max
            signal.setitimer(signal.ITIMER_REAL, 0.2)
            seed.structure = next(self.parser.parse(seed.data))
            signal.setitimer(signal.ITIMER_REAL, 0)

            self.add_fragment(seed.structure)
            seed.has_structure = True
        except (SyntaxError, Timeout):
            seed.has_structure = False
            signal.setitimer(signal.ITIMER_REAL, 0)

```

让我们看看`FragmentMutator`如何填充简单HTML种子输入的片段池。 我们用`EarleyParser`初始化mutator，而`XML_GRAMMAR`本身也被初始化。

```py
from [GrammarFuzzer](GrammarFuzzer.html) import tree_to_string

```

```py
valid_seed = Seed("<html><header><title>Hello</title></header><body>World<br/></body></html>")
fragment_mutator = FragmentMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))
fragment_mutator.add_to_fragment_pool(valid_seed)

for key in fragment_mutator.fragments:
    print(key)
    for f in fragment_mutator.fragments[key]:
        print("|-%s" % tree_to_string(f))

```

```py
<start>
|-<html><header><title>Hello</title></header><body>World<br/></body></html>
<xml-tree>
|-<html><header><title>Hello</title></header><body>World<br/></body></html>
|-<header><title>Hello</title></header><body>World<br/></body>
|-<header><title>Hello</title></header>
|-<title>Hello</title>
|-Hello
|-<body>World<br/></body>
|-World<br/>
|-World
|-<br/>
<xml-open-tag>
|-<html>
|-<header>
|-<title>
|-<body>
<xml-openclose-tag>
|-<br/>
<xml-close-tag>
|-</title>
|-</header>
|-</body>
|-</html>
<xml-attribute>
<id>
<text>
<letter>
<letter_space>

```

对于语法中的许多符号，我们收集了许多片段。 从`xml-tree`符号开始，有几个打开和关闭标签以及几个有趣的片段。

***摘要*** 。 对于语法中的每个有趣符号，`FragmentMutator`具有一组片段。 通过首先解析要突变的输入来提取这些片段。

### 基于片段的突变

我们可以使用片段池中的片段来生成新的输入。 每一个被突变的种子都被分解成碎片，并被记录下来-即仅在第一次时被分解。

```py
class FragmentMutator(FragmentMutator):
    def __init__(self, parser):
        """Initialize mutators"""
        super().__init__(parser)
        self.seen_seeds = []

    def mutate(self, seed):
        """Implement structure-aware mutation. Memoize seeds."""
        if not seed in self.seen_seeds:
            self.seen_seeds.append(seed)
            self.add_to_fragment_pool(seed)
        return super().mutate(seed)

```

我们的第一个结构突变算子是`swap_fragments()`，它在给定的种子中选择一个随机片段，并用池中的随机片段替换它。 我们确保两个片段都以相同的符号开头。 例如，我们可以用片段池中的另一个结束标记交换种子HTML中的结束标记。

为了选择随机片段，变异器对与起始符号关联的根片段之下的所有片段（`n_count`）进行计数。

```py
class FragmentMutator(FragmentMutator):
    def count_nodes(self, fragment):
        """Returns the number of nodes in the fragment"""
        symbol, children = fragment
        if self.is_excluded(symbol):
            return 0
        return 1 + sum(map(self.count_nodes, children))

```

为了交换选定的片段-使用``全局''变量`self.to_swap`标识-递归遍历种子的解析树。

```py
class FragmentMutator(FragmentMutator):
    def recursive_swap(self, fragment):
        """Recursively finds the fragment to swap."""
        symbol, children = fragment
        if self.is_excluded(symbol):
            return symbol, children

        self.to_swap -= 1
        if self.to_swap == 0: 
            return random.choice(list(self.fragments[symbol]))
        return symbol, list(map(self.recursive_swap, children))

```

我们的结构变异器在2（即`start`符号除外）和片段总数（`n_count`）之间选择一个随机数，并使用递归交换生成新片段。 新片段被序列化为字符串，并作为新种子返回。

```py
class FragmentMutator(FragmentMutator):
    def __init__(self, parser):
        super().__init__(parser)
        self.mutators = [self.swap_fragment]

    def swap_fragment(self, seed):
        """Substitutes a random fragment with another with the same symbol"""
        if seed.has_structure:
            n_nodes = self.count_nodes(seed.structure)
            self.to_swap = random.randint(2, n_nodes)
            new_structure = self.recursive_swap(seed.structure)

            new_seed = Seed(tree_to_string(new_structure))
            new_seed.has_structure = True
            new_seed.structure = new_structure
            return new_seed
        return seed

```

```py
valid_seed = Seed("<html><header><title>Hello</title></header><body>World<br/></body></html>")
lf_mutator = FragmentMutator(parser)
print(valid_seed)
lf_mutator.mutate(valid_seed)

```

```py
<html><header><title>Hello</title></header><body>World<br/></body></html>

```

```py
<html><header><header>Hello</title></header><body>World<br/></body></html>

```

如我们所见，一个片段已被另一个片段取代。

我们可以使用类似的递归遍历来*删除*一个随机片段。

```py
class FragmentMutator(FragmentMutator):
    def recursive_delete(self, fragment):
        """Recursively finds the fragment to delete"""
        symbol, children = fragment
        if self.is_excluded(symbol):
            return symbol, children

        self.to_delete -= 1
        if self.to_delete == 0: 
            return symbol, []
        return symbol, list(map(self.recursive_delete, children))

```

我们还应该定义相应的结构删除操作符。

```py
class FragmentMutator(FragmentMutator):
    def __init__(self, parser):
        super().__init__(parser)
        self.mutators.append(self.delete_fragment)

    def delete_fragment(self, seed):
        """Deletes a random fragment"""
        if seed.has_structure:
            n_nodes = self.count_nodes(seed.structure)
            self.to_delete = random.randint(2, n_nodes)
            new_structure = self.recursive_delete(seed.structure)

            new_seed = Seed(tree_to_string(new_structure))
            new_seed.has_structure = True
            new_seed.structure = new_structure
            # do not return an empty new_seed
            if not new_seed.data: return seed
            else: return new_seed
        return seed

```

***摘要*** 。 现在，我们掌握了结构感知的模糊测试的所有要素。 我们的变异器将所有种子分解为片段，然后将其添加到片段库中。 我们的变异器将给定种子中的随机片段与相同类型的片段交换。 而且我们的变异子会删除给定种子中的随机片段。 这允许为所生成的输入保持较高的有效性。 给定的语法。

***尝试*** 。 尝试添加其他结构突变算子。 *加号运算符*如何知道给定种子文件中的位置，可以在其中添加以某个符号开头的片段？

### 基于片段的模糊测试

现在，我们可以定义一种在LangFuzzer中首创的可识别输入结构的模糊器。 为了实现LangFuzz，我们修改了[黑盒突变模糊器](GreyboxFuzzer.html#Blackbox-Mutation-based-Fuzzer)，以堆叠最多四个结构突变。

```py
class LangFuzzer(MutationFuzzer):
    def create_candidate(self):
        """Returns an input generated by fuzzing a seed in the population"""
        candidate = self.schedule.choose(self.population)
        trials = random.randint(1,4)
        for i in range(trials):
            candidate = self.mutator.mutate(candidate)
        return candidate

```

好的，让我们来研究第一个具有输入结构意识的模糊器。 请注意，我们暂时将n = 300设置为。

```py
n = 300
runner = FunctionCoverageRunner(my_parser)
mutator = FragmentMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))
schedule = PowerSchedule()

langFuzzer = LangFuzzer([valid_seed.data], mutator, schedule)

start = time.time()
langFuzzer.runs(runner, trials = n)
end = time.time()

"It took LangFuzzer %0.2f seconds to generate and execute %d inputs." % (end - start, n)

```

```py
'It took LangFuzzer 15.40 seconds to generate and execute 300 inputs.'

```

我们观察到结构突变非常缓慢。 尽管我们有200ms的时间预算用于解析。 相比之下，仅我们的黑盒测试器就可以每秒产生约1万次输入！

```py
runner = FunctionCoverageRunner(my_parser)
mutator = Mutator()
schedule = PowerSchedule()

blackFuzzer = MutationFuzzer([valid_seed.data], mutator, schedule)

start = time.time()
blackFuzzer.runs(runner, trials = n)
end = time.time()

"It took a blackbox fuzzer %0.2f seconds to generate and execute %d inputs." % (end - start, n)

```

```py
'It took a blackbox fuzzer 0.17 seconds to generate and execute 300 inputs.'

```

确实，我们的黑匣子模糊测试是在眨眼间完成的。

***尝试*** 。 我们可以使用[延迟解析](https://arxiv.org/abs/1811.09447)处理此开销。 延迟解析不会浪费时间在模糊测试活动的开始时，因为字节级的mutator会取得有效的进展，而是建议在模糊测试活动可行时才在结构突变中投入时间。

```py
blackbox_coverage = len(runner.coverage())
"During this fuzzing campaign, the blackbox fuzzer covered %d statements." % blackbox_coverage

```

```py
'During this fuzzing campaign, the blackbox fuzzer covered 93 statements.'

```

让我们为我们的模糊测试活动打印一些统计数据。 由于以后我们需要更频繁地打印统计信息，因此我们应该将其包装到一个函数中。 为了衡量覆盖率，我们导入了 [Population_coverage](Coverage.html#Coverage-of-Basic-Fuzzing) 函数。 它接受一组输入和一个Python函数，在该函数上执行输入并收集coverage信息。 具体来说，它返回一个元组`(all_coverage, cumulative_coverage)`，其中`all_coverage`是所有输入覆盖的语句集，`cumulative_coverage`是随着执行的输入数量增加而覆盖的语句数。 我们只是对后者感兴趣，以便随着时间的推移绘制覆盖范围。

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

```

```py
def print_stats(fuzzer, parser):
    coverage, _ = population_coverage(fuzzer.inputs, my_parser)

    has_structure = 0
    for seed in fuzzer.inputs:
        # reuse memoized information
        if hasattr(seed, "has_structure"):
            if seed.has_structure: 
                has_structure += 1
        else:
            if isinstance(seed, str):
                seed = Seed(seed)
            try:
                signal.setitimer(signal.ITIMER_REAL, 0.2)
                next(parser.parse(seed.data))
                signal.setitimer(signal.ITIMER_REAL, 0)
                has_structure += 1
            except (SyntaxError, Timeout):
                signal.setitimer(signal.ITIMER_REAL, 0)

    print("From the %d generated inputs, %d (%0.2f%%) can be parsed.\n"
          "In total, %d statements are covered." % (
        len(fuzzer.inputs),
        has_structure,
        100 * has_structure / len(fuzzer.inputs),
        len(coverage)))

```

对于LangFuzzer，让我们看看LangFuzz生成的输入中有多少是有效的（即，可分析的）以及覆盖了多少条语句。

```py
print_stats(langFuzzer, EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))

```

```py
From the 300 generated inputs, 166 (55.33%) can be parsed.
In total, 92 statements are covered.

```

仅使用字节级突变（不使用语法）的突变模糊器的统计信息是什么？

```py
print_stats(blackFuzzer, EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))

```

```py
From the 300 generated inputs, 20 (6.67%) can be parsed.
In total, 151 statements are covered.

```

***摘要*** 。 我们的片段级黑盒模糊器（LangFuzzer）比字节级模糊器生成的*有效输入*多，但实现的代码覆盖率少[​​HTG6]。 因此，生成不遵循所提供语法的输入会有一些价值。

### 与Greybox Fuzzing 集成

在下面的代码中，我们将片段级黑盒模糊测试（LangFuzz风格）与[字节级灰盒模糊测试](GreyboxFuzzer.html#Greybox-Mutation-based-Fuzzer)（AFL风格）集成在一起。 额外的覆盖率反馈可能使我们能够更快地增加代码覆盖率。

[灰盒模糊器](GreyboxFuzzer.html#Greybox-Mutation-based-Fuzzer)将所有生成的输入添加到种子种群中，从而增加了代码覆盖率。 输入分两个阶段生成，最多堆叠四个结构突变和多达32个字节级突变。

```py
class GreyboxGrammarFuzzer(GreyboxFuzzer):
    def __init__(self, seeds, byte_mutator, tree_mutator, schedule):
        super().__init__(seeds, byte_mutator, schedule)
        self.tree_mutator = tree_mutator

    def create_candidate(self):
        """Returns an input generated by structural mutation of a seed in the population"""
        seed = self.schedule.choose(self.population)

        # Structural mutation
        trials = random.randint(0,4)
        for i in range(trials):
            seed = self.tree_mutator.mutate(seed)

        # Byte-level mutation
        candidate = seed.data
        if trials == 0 or not seed.has_structure or 1 == random.randint(0, 1):
            dumb_trials = min(len(seed.data), 1 << random.randint(1,5))
            for i in range(dumb_trials):
                candidate = self.mutator.mutate(candidate)
        return candidate

```

让我们使用上面介绍的[标准字节级转换器](GreyboxFuzzer.html#Mutator-and-Seed)和[基于片段的结构转换器](#Fragment-based-Mutation)来运行集成模糊器。

```py
runner = FunctionCoverageRunner(my_parser)
byte_mutator = Mutator()
tree_mutator = FragmentMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))
schedule = PowerSchedule()

gg_fuzzer = GreyboxGrammarFuzzer([valid_seed.data], byte_mutator, tree_mutator, schedule)

start = time.time()
gg_fuzzer.runs(runner, trials = n)
end = time.time()

"It took the greybox grammar fuzzer %0.2f seconds to generate and execute %d inputs." % (end - start, n)

```

```py
'It took the greybox grammar fuzzer 1.79 seconds to generate and execute 300 inputs.'

```

```py
print_stats(gg_fuzzer, EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))

```

```py
From the 300 generated inputs, 4 (1.33%) can be parsed.
In total, 169 statements are covered.

```

***摘要*** 。 我们的结构灰箱模糊器

*   比基于片段的LangFuzzer运行得更快，
*   与基于片段的LangFuzzer和香草黑盒突变型Fuzzer相比，覆盖范围更大，并且
*   产生的有效输入甚至比香草黑匣子突变模糊器少。

## 突变无效的种子

在上一节中，我们看到大多数作为种子添加的输入都是*无效* w.r.t.。 我们给定的语法。 但是，为了应用基于片段的变异器，我们需要它来成功解析种子。 否则，整个基于片段的方法将变得毫无用处。 问题出现了：*我们如何从无法成功解析的（无效）种子中导出结构？*

为此，我们介绍了*基于区域的突变*的概念，该概念首先是通过 [AFLSmart](https://github.com/aflsmart/aflsmart) 结构灰盒模糊器[ [Van-Thuan Pham *等人*探索的 ，2018年。](https://arxiv.org/abs/1811.09447)。 AFLSmart实施字节级，基于片段和基于区域的突变以及基于有效性的功率计划。 我们定义了基于*区域的修饰符*，其中*区域*是输入中可以与语法中的符号关联的连续字节序列。

### 确定符号区域

[Earley解析器](Parser.html#The-Parsing-Algorithm)的函数`chart_parse`生成字符串的解析表。 对于字符串中的每个字母，此表给出了可能的符号以及可能属于同一符号的相邻字母的*区域*。

```py
invalid_seed = Seed("<html><body><i>World</i><br/>>/body></html>")
parser = EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS)
table = parser.chart_parse(invalid_seed.data, parser.start_symbol())
for column in table:
    print(column)
    print("---")

```

```py
None chart[0]

---
< chart[1]

---
h chart[2]
<letter>:= h |(1,2)
<id>:= <letter> |(1,2)
---
t chart[3]
<letter>:= t |(2,3)
<id>:= <id> <letter> |(1,3)
---
m chart[4]
<letter>:= m |(3,4)
<id>:= <id> <letter> |(1,4)
---
l chart[5]
<letter>:= l |(4,5)
<id>:= <id> <letter> |(1,5)
---
> chart[6]
<xml-open-tag>:= < <id> > |(0,6)
---
< chart[7]

---
b chart[8]
<letter>:= b |(7,8)
<id>:= <letter> |(7,8)
---
o chart[9]
<letter>:= o |(8,9)
<id>:= <id> <letter> |(7,9)
---
d chart[10]
<letter>:= d |(9,10)
<id>:= <id> <letter> |(7,10)
---
y chart[11]
<letter>:= y |(10,11)
<id>:= <id> <letter> |(7,11)
---
> chart[12]
<xml-open-tag>:= < <id> > |(6,12)
---
< chart[13]

---
i chart[14]
<letter>:= i |(13,14)
<id>:= <letter> |(13,14)
---
> chart[15]
<xml-open-tag>:= < <id> > |(12,15)
---
W chart[16]
<letter_space>:= W |(15,16)
<text>:= <letter_space> |(15,16)
<xml-tree>:= <text> |(15,16)
---
o chart[17]
<letter_space>:= o |(16,17)
<text>:= <text> <letter_space> |(15,17)
<text>:= <letter_space> |(16,17)
<xml-tree>:= <text> |(15,17)
<xml-tree>:= <text> |(16,17)
<xml-tree>:= <xml-tree> <xml-tree> |(15,17)
---
r chart[18]
<letter_space>:= r |(17,18)
<text>:= <text> <letter_space> |(15,18)
<text>:= <text> <letter_space> |(16,18)
<text>:= <letter_space> |(17,18)
<xml-tree>:= <text> |(15,18)
<xml-tree>:= <text> |(16,18)
<xml-tree>:= <text> |(17,18)
<xml-tree>:= <xml-tree> <xml-tree> |(15,18)
<xml-tree>:= <xml-tree> <xml-tree> |(16,18)
---
l chart[19]
<letter_space>:= l |(18,19)
<text>:= <text> <letter_space> |(15,19)
<text>:= <text> <letter_space> |(16,19)
<text>:= <text> <letter_space> |(17,19)
<text>:= <letter_space> |(18,19)
<xml-tree>:= <text> |(15,19)
<xml-tree>:= <text> |(16,19)
<xml-tree>:= <text> |(17,19)
<xml-tree>:= <text> |(18,19)
<xml-tree>:= <xml-tree> <xml-tree> |(15,19)
<xml-tree>:= <xml-tree> <xml-tree> |(16,19)
<xml-tree>:= <xml-tree> <xml-tree> |(17,19)
---
d chart[20]
<letter_space>:= d |(19,20)
<text>:= <text> <letter_space> |(15,20)
<text>:= <text> <letter_space> |(16,20)
<text>:= <text> <letter_space> |(17,20)
<text>:= <text> <letter_space> |(18,20)
<text>:= <letter_space> |(19,20)
<xml-tree>:= <text> |(15,20)
<xml-tree>:= <text> |(16,20)
<xml-tree>:= <text> |(17,20)
<xml-tree>:= <text> |(18,20)
<xml-tree>:= <text> |(19,20)
<xml-tree>:= <xml-tree> <xml-tree> |(15,20)
<xml-tree>:= <xml-tree> <xml-tree> |(16,20)
<xml-tree>:= <xml-tree> <xml-tree> |(17,20)
<xml-tree>:= <xml-tree> <xml-tree> |(18,20)
---
< chart[21]

---
/ chart[22]

---
i chart[23]
<letter>:= i |(22,23)
<id>:= <letter> |(22,23)
---
> chart[24]
<xml-close-tag>:= < / <id> > |(20,24)
<xml-tree>:= <xml-open-tag> <xml-tree> <xml-close-tag> |(12,24)
---
< chart[25]

---
b chart[26]
<letter>:= b |(25,26)
<id>:= <letter> |(25,26)
---
r chart[27]
<letter>:= r |(26,27)
<id>:= <id> <letter> |(25,27)
---
/ chart[28]

---
> chart[29]
<xml-openclose-tag>:= < <id> / > |(24,29)
<xml-tree>:= <xml-openclose-tag> |(24,29)
<xml-tree>:= <xml-tree> <xml-tree> |(12,29)
---
> chart[30]

---
/ chart[31]

---
b chart[32]

---
o chart[33]

---
d chart[34]

---
y chart[35]

---
> chart[36]

---
< chart[37]

---
/ chart[38]

---
h chart[39]

---
t chart[40]

---
m chart[41]

---
l chart[42]

---
> chart[43]

---

```

该表中与潜在符号关联的列数对应于可以成功解析的字母数。 换句话说，我们可以使用该表来计算最长的可分析子字符串。

```py
cols = [col for col in table if col.states]
parsable = invalid_seed.data[:len(cols)-1]

print("'%s'" % invalid_seed)
parsable

```

```py
'<html><body><i>World</i><br/>>/body></html>'

```

```py
'<html><body><i>World</i><br/>'

```

据此，我们可以计算输入的*有效性*。

```py
validity = 100 * len(parsable) / len(invalid_seed.data)

"%0.1f%% of the string can be parsed successfully." % validity

```

```py
'67.4% of the string can be parsed successfully.'

```

***摘要*** 。 与输入片段不同，即使解析器无法生成整个解析树，也可以派生输入区域。

### 基于区域的突变

为了模糊无效种子，基于区域的变异器将语法中的符号与种子中的区域（即索引子串）相关联。 [替代](#Building-the-Fragment-Pool)方法`add_to_fragment_pool()`首先尝试从种子中挖掘碎片。 如果失败，则区域更改器将使用 [Earley解析器](Parser.html#The-Parsing-Algorithm)来导出解析表。 对于每一列（即字母），它提取符号和相应的区域。 这使更改器可以将区域集与每个符号一起存储。

```py
class RegionMutator(FragmentMutator):
    def add_to_fragment_pool(self, seed):
        """Mark fragments and regions in a seed file"""
        super().add_to_fragment_pool(seed)
        if not seed.has_structure:
            try:
                signal.setitimer(signal.ITIMER_REAL, 0.2) # set 200ms timeout
                seed.regions = {k: set() for k in self.parser.cgrammar}
                for column in self.parser.chart_parse(seed.data, self.parser.start_symbol()):
                    for state in column.states:
                        if (not self.is_excluded(state.name) and
                                state.e_col.index - state.s_col.index > 1 and
                                state.finished()):
                            seed.regions[state.name].add((state.s_col.index, state.e_col.index))
                signal.setitimer(signal.ITIMER_REAL, 0) # cancel timeout
                seed.has_regions = True
            except Timeout:
                seed.has_regions = False
        else:
            seed.has_regions = False

```

这就是我们无效种子的这些区域的样子。 区域由种子字符串中的开始和结束索引组成。

```py
mutator = RegionMutator(parser)
mutator.add_to_fragment_pool(invalid_seed)
for symbol in invalid_seed.regions:
    print(symbol)
    for (s, e) in invalid_seed.regions[symbol]:
        print("|-(%d,%d) : %s" % (s, e, invalid_seed.data[s:e]))

```

```py
<start>
<xml-tree>
|-(17,19) : rl
|-(18,20) : ld
|-(24,29) : <br/>
|-(15,20) : World
|-(17,20) : rld
|-(12,24) : <i>World</i>
|-(15,18) : Wor
|-(16,20) : orld
|-(12,29) : <i>World</i><br/>
|-(15,19) : Worl
|-(16,19) : orl
|-(16,18) : or
|-(15,17) : Wo
<xml-open-tag>
|-(0,6) : <html>
|-(12,15) : <i>
|-(6,12) : <body>
<xml-openclose-tag>
|-(24,29) : <br/>
<xml-close-tag>
|-(20,24) : </i>
<xml-attribute>
<id>
<text>
<letter>
<letter_space>

```

现在我们知道种子中的哪些区域属于哪个符号，我们可以定义基于区域的交换和删除运算符。

```py
class RegionMutator(RegionMutator):
    def swap_fragment(self, seed):
        """Chooses a random region and swaps it with a fragment
 that starts with the same symbol"""
        if not seed.has_structure and seed.has_regions:
            regions = [r for r in seed.regions
                         if (len(seed.regions[r]) > 0 and
                            len(self.fragments[r]) > 0)]
            if len(regions) == 0: return seed

            key = random.choice(list(regions))
            s, e = random.choice(list(seed.regions[key]))
            swap_structure = random.choice(self.fragments[key])
            swap_string = tree_to_string(swap_structure)
            new_seed = Seed(seed.data[:s] + swap_string + seed.data[e:])
            new_seed.has_structure = False
            new_seed.has_regions = False
            return new_seed
        else:
            return super().swap_fragment(seed)

```

```py
class RegionMutator(RegionMutator):
    def delete_fragment(self, seed):
        """Deletes a random region"""
        if not seed.has_structure and seed.has_regions:
            regions = [r for r in seed.regions
                         if len(seed.regions[r]) > 0]
            if len(regions) == 0: return seed

            key = random.choice(list(regions))
            s, e = (0, 0)
            while (e - s < 2):
                s, e = random.choice(list(seed.regions[key]))
            new_seed = Seed(seed.data[:s] + seed.data[e:])
            new_seed.has_structure = False
            new_seed.has_regions = False
            return new_seed
        else:
            return super().delete_fragment(seed)

```

让我们尝试新的基于区域的增变器。 我们将一个简单的有效种子添加到片段池，并尝试对无效种子进行突变。

```py
simple_seed = Seed("<b>Text</b>")
mutator = RegionMutator(parser)
mutator.add_to_fragment_pool(simple_seed)

print(invalid_seed)
mutator.mutate(invalid_seed)

```

```py
<html><body><i>World</i><br/>>/body></html>

```

```py
<html><body><b>World</i><br/>>/body></html>

```

***摘要*** 。 我们可以使用Earley解析器生成一个解析表，并将输入中的区域分配给语法中的符号。 我们的区域更改器可以用片段池中以相同符号开头的片段替换这些区域，或者完全删除这些区域。

***试试*** 。 实现一个区域池（类似于片段池）和一个`swap_region()` mutator。 您可以通过以Jupyter笔记本打开本章来执行自己的代码。

### 基于区域的模糊测试

让我们将其与[可识别结构的灰盒模糊器](#Integration-with-Greybox-Fuzzing)集成起来，尝试使用闪亮的新区域转换器。

```py
runner = FunctionCoverageRunner(my_parser)
byte_mutator = Mutator()
tree_mutator = RegionMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))
schedule = PowerSchedule()

regionFuzzer = GreyboxGrammarFuzzer([valid_seed.data], byte_mutator, tree_mutator, schedule)

start = time.time()
regionFuzzer.runs(runner, trials = n)
end = time.time()

"It took the structural greybox fuzzer with region mutator\
  %0.2f seconds to generate and execute %d inputs." % (end - start, n)

```

```py
'It took the structural greybox fuzzer with region mutator 5.99 seconds to generate and execute 300 inputs.'

```

我们可以看到，具有基于区域的变体的结构灰箱模糊器要比仅基于[片段的变体](#Fragment-based-Fuzzing)慢。 这是因为基于区域的结构突变适用于*所有种子*。 相反，基于片段的变异子仅适用于极少数可解析的种子。 否则，仅应用（非常高效）字节级更改器。

让我们也打印种群中种子的平均有效度。

```py
def print_more_stats(fuzzer, parser):
    print_stats(fuzzer, parser)
    validity = 0
    total = 0
    for seed in fuzzer.population:
        if not seed.data: continue
        table = parser.chart_parse(seed.data, parser.start_symbol())
        cols = [col for col in table if col.states]
        parsable = invalid_seed.data[:len(cols)-1]
        validity += len(parsable) / len(seed.data)
        total += 1
    print("On average, %0.1f%% of a seed in the population can be successfully parsed." % (100 * validity / total))

```

```py
print_more_stats(regionFuzzer, parser)

```

```py
From the 300 generated inputs, 3 (1.00%) can be parsed.
In total, 174 statements are covered.
On average, 8.5% of a seed in the population can be successfully parsed.

```

***摘要*** 。 与基于片段的突变相比，具有基于区域的突变的灰箱模糊测试器可以实现更高的*覆盖率*，但生成的*合法输入数则更少*。 通过为无法成功解析的种子至少利用*一些*结构来解释更高的覆盖率。

## 关注有效种子

在上一节中，我们有一个问题：有效性低。 为了解决此问题，基于*有效性的功率调度*将更多[能量](GreyboxFuzzer.html#Power-Schedules)分配给具有较高有效性的种子。 换句话说，模糊器*花了更多的时间来模糊更有效*的种子。

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

```

```py
class AFLSmartSchedule(PowerSchedule):

    def __init__(self, parser, exponent):
        self.parser = parser
        self.exponent = exponent

    def parsable(self, seed):
        """Returns the substring that is parsable"""
        table = self.parser.chart_parse(seed.data, parser.start_symbol())
        cols = [col for col in table if col.states]
        return seed.data[:len(cols)-1]

    def degree_of_validity(self, seed):
        """Returns the proportion of a seed that is parsable"""
        if hasattr(seed, "validity"): return seed.validity
        seed.validity = (len(self.parsable(seed)) / len(seed.data)
                         if len(seed.data) > 0 else 0)
        return seed.validity

    def assignEnergy(self, population):
        """Assign exponential energy proportional to degree of validity"""
        for seed in population:
            seed.energy = ((self.degree_of_validity(seed) / math.log(len(seed.data))) ** self.exponent
                           if len(seed.data) > 1 else 0)

```

让我们通过传递有效种子来发挥有效性的作用...

```py
smart_schedule = AFLSmartSchedule(parser, 1)
print("%11s: %s" % ("Entire seed", simple_seed))
print("%11s: %s" % ("Parsable", smart_schedule.parsable(simple_seed)))

"Degree of validity: %0.2f%%" % (100 * smart_schedule.degree_of_validity(simple_seed))

```

```py
Entire seed: <b>Text</b>
   Parsable: <b>Text</b>

```

```py
'Degree of validity: 100.00%'

```

...和无效的种子。

```py
print("%11s: %s" % ("Entire seed", invalid_seed))
print("%11s: %s" % ("Parsable", smart_schedule.parsable(invalid_seed)))

"Degree of validity: %0.2f%%" % (100 * smart_schedule.degree_of_validity(invalid_seed))

```

```py
Entire seed: <html><body><i>World</i><br/>>/body></html>
   Parsable: <html><body><i>World</i><br/>

```

```py
'Degree of validity: 67.44%'

```

优秀的。 我们可以将有效程度计算为可以解析的字符串的比例。

让我们将基于有效性的电源计划插入结构感知的灰箱模糊器中。

```py
runner = FunctionCoverageRunner(my_parser)
byte_mutator = Mutator()
tree_mutator = RegionMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))
schedule = AFLSmartSchedule(parser, 1)

aflsmart = GreyboxGrammarFuzzer([valid_seed.data], byte_mutator, tree_mutator, schedule)

start = time.time()
aflsmart.runs(runner, trials = n)
end = time.time()

"It took AFLSmart %0.2f seconds to generate and execute %d inputs." % (end - start, n)

```

```py
'It took AFLSmart 29.17 seconds to generate and execute 300 inputs.'

```

```py
print_more_stats(aflsmart, parser)

```

```py
From the 300 generated inputs, 19 (6.33%) can be parsed.
In total, 160 statements are covered.
On average, 29.2% of a seed in the population can be successfully parsed.

```

***摘要*** 。 确实，通过花费更多时间对具有较高有效性的种子进行模糊处理，我们还可以生成具有较高有效性的输入。 更多输入完全有效 给定的语法。

***读取*** 。 在原始的AFLSmart论文中了解有关基于区域的模糊处理，延迟解析和基于有效性的计划的更多信息：Pham等人的“ [Smart Greybox Fuzzing](https://arxiv.org/abs/1811.09447) ”。下载并改进AFLSmart： [https：/ /github.com/aflsmart/aflsmart](https://github.com/aflsmart/aflsmart) 。

## 挖掘种子

到目前为止，应该已经很清楚，*种子*的选择会极大地影响模糊测试的成功。 一方面是*变异性* –我们的种子应涵盖尽可能多的不同特征，以增加覆盖率。 但是，另一方面是种子*引起错误*的可能性–也就是说，如果种子之前曾参与导致失败，则该种子的突变很可能会再次导致失败。 这是因为针对过去故障的修复通常可以成功地使不再发生具体故障，但是有时可能无法捕获可能发生故障的所有条件。 因此，即使原始故障是固定的，原始故障引发输入的*环境*中出现错误的可能性仍然更高。 因此，将在已知的之前引起故障的*输入用作种子是有回报的。*

为了说明问题，Holler的 *LangFuzz* 模糊器用作CVE报告中的JavaScript输入种子。 在错误已解决的时候，这些被发布为引发故障的输入。 因此，他们再也不会造成伤害。 但是，通过使用诸如种子之类的输入，LangFuzz会创建大量变异和所有特征的重新组合，其中许多会（并且确实）一次又一次地发现错误。

## 经验教训

*   **词典**可用于将重要的关键字插入到生成的输入中。

*   **基于片段的突变**首先将种子分解为片段，然后重新组装这些片段以生成新的输入。 *片段*是种子的解析树中的子树。 但是，基于片段的突变要求可以成功解析种子，这对于基于覆盖率的灰盒模糊器发现的种子而言可能并非如此。

*   **基于区域的突变**将输入中的区域标记为属于语法中的某个符号。 例如，它可以将子字符串'< / a >'标识为结束标签。 然后可以将这些区域删除或替换为属于同一符号的片段或区域。 与基于片段的突变不同，基于区域的突变适用于所有*所有*种子-即使是只能部分解析的种子。 但是，对于生成的输入，有效性的程度仍然很低。

*   基于**有效性的功率调度表**将更多能量投入到具有更高有效性的种子中。 生成的输入也具有较高的有效性。

*   **从先前导致故障的输入存储库中挖掘种子**会导致输入碎片与过去的故障相关，从而增加了在附近发现更多故障的可能性。

## 后续步骤

本章结束了对语法模糊技术的讨论。

*   在[下一章](Reducer.html)中，我们讨论如何*减少故障后导致故障的输入*，仅保留重现故障所需的输入部分。
*   考虑到目标测试生成的代码语义，[下一部分](04_Semantical_Fuzzing.html)将从语法模糊化为*语义*模糊化。

## Background

本章以以下两项工作为基础：

*   *LangFuzz* 模糊器[ [Holler *等*，2012。](https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final73.pdf)]是一种高效（且有效！）的基于语法的模糊器，适用于（大部分）JavaScript。 它使用语法来解析种子，并将其输入与生成的部分重新组合，迄今为止，在JavaScript解释器中发现了2,600个错误。

*   智能灰盒模糊测试（ [AFLSmart](https://github.com/aflsmart/aflsmart) ）将基于覆盖的模糊测试和基于语法的（结构）模糊测试结合在一起，如[ [Van-Thuan Pham *等人*，2018年所述。](https://arxiv.org/abs/1811.09447) ]。 由此产生的AFLSMART工具已经在广泛使用，经过测试的工具和库中发现了42个零日漏洞。 到目前为止，已分配了17个CVE。

最近的模糊测试工作也将基于语法的模糊测试和覆盖范围结合在一起。

*   *Superion* [[王俊杰*等*，2019\.](https://2019.icse-conferences.org/event/icse-2019-technical-papers-superion-grammar-aware-greybox-fuzzing) ]等同于我们上面的“与Greybox Fuzzing集成”部分–即LangFuzz的组合 和Greybox Fuzzing，但没有AFL样式的字节级突变。 与AFL和jsfunfuzz相比，Superion可以提高代码覆盖率（即行和函数覆盖率分别为16.7％和8.8％）和错误发现功能。 根据作者的说法，他们发现了30个新漏洞，其中发现了21个新漏洞，并分配了16个CVE，并获得了3.2万美元的漏洞赏金。

*   *Nautilus* [ [Cornelius Aschermann *等人*，2019年。](https://www.ndss-symposium.org/ndss-paper/nautilus-fishing-for-deep-bugs-with-grammars/)]还结合了基于语法的模糊化和覆盖率反馈。 它为所有种子和生成的输入维护解析树。 为了允许AFL样式的字节级突变，它将子树“折叠”回字节级表示形式。 这样做的好处是不必重新解析生成的种子。 但是，随着时间的流逝，Nautilus会退化为结构未知的灰箱模糊处理，因为它不会重新解析折叠的子树，而是为大多数解析树被折叠的后续种子重构输入结构。 鹦鹉螺发现了mruby，PHP，ChakraCore和Lua中的错误； 报告这些错误的奖金为2600美元，并分配了6个CVE。

## 练习

### 练习1：“大灰盒子”模糊器枪战

使用我们的灰盒技术实现，并在基准上对其进行评估。 哪种技术（和哪个子技术）会产生什么影响，为什么？ 还请考虑Superion [ [Wang Junjie *等*，2019\.](https://2019.icse-conferences.org/event/icse-2019-technical-papers-superion-grammar-aware-greybox-fuzzing) ]和Nautilus [ [Cornelius Aschermann *等*，2019.的具体方法。](https://www.ndss-symposium.org/ndss-paper/nautilus-fishing-for-deep-bugs-with-grammars/) ]，甚至可能是这些方法使用的基准。

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