{ "cells": [ { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "# Greybox Fuzzing with Grammars\n", "\n", "In this chapter, we introduce important extensions to our syntactic fuzzing techniques, all leveraging _syntactic_ parts of _existing inputs_.\n", "\n", "1. We show how to leverage _dictionaries_ of input fragments during fuzzing. The idea is to integrate such dictionaries into a _mutator_, which would then inject these fragments (typically keywords and other items of importance) into population.\n", "\n", "2. We show how to combine [parsing](Parser.ipynb) and [fuzzing](GrammarFuzzer.ipynb) with grammars. This allows _mutating_ existing inputs while preserving syntactical correctness, and to _reuse_ fragments from existing inputs while generating new ones. The combination of language-based parsing and generating, as demonstrated in this chapter, has been highly successful in practice: The _LangFuzz_ fuzzer for JavaScript has found more than 2,600 bugs in JavaScript interpreters this way.\n", "\n", "3. In the previous chapters, we have used grammars in a _black-box_ manner – that is, we have used them to generate inputs regardless of the program being tested. In this chapter, we introduce mutational _greybox fuzzing with grammars_: Techniques that make use of _feedback from the program under test_ to guide test generations towards specific goals. As in [lexical greybox fuzzing](GreyboxFuzzer.ipynb), this feedback is mostly _coverage_, allowing us to direct grammar-based testing towards uncovered code parts. This part is inspired by the _AFLSmart_ fuzzer, which combines parsing and mutational fuzzing." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:28.676385Z", "iopub.status.busy": "2024-05-16T11:21:28.676175Z", "iopub.status.idle": "2024-05-16T11:21:28.727139Z", "shell.execute_reply": "2024-05-16T11:21:28.726682Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from bookutils import YouTubeVideo\n", "YouTubeVideo('hSGzcjUj7Vs')" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "**Prerequisites**\n", "\n", "* We build on several concepts from [the chapter on greybox fuzzing (without grammars)](GreyboxFuzzer.ipynb).\n", "* As the title suggests, you should know how to fuzz with grammars [from the chapter on grammars](Grammars.ipynb)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## Synopsis\n", "\n", "\n", "To [use the code provided in this chapter](Importing.ipynb), write\n", "\n", "```python\n", ">>> from fuzzingbook.GreyboxGrammarFuzzer import \n", "```\n", "\n", "and then make use of the following features.\n", "\n", "\n", "This chapter introduces advanced methods for language-based grey-box fuzzing inspired by the _LangFuzz_ and _AFLSmart_ fuzzers.\n", "\n", "### Fuzzing with Dictionaries\n", "\n", "Rather than mutating strings randomly, the `DictMutator` class allows inserting tokens from a dictionary, thus increasing fuzzer performance. The dictionary comes as a list of strings, out of which random elements are picked and inserted - in addition to the given mutations such as deleting or inserting individual bytes.\n", "\n", "```python\n", ">>> dict_mutator = DictMutator([\"\", \"\", \"\", \"='a'\"])\n", ">>> seeds = [\"HelloWorld
\"]\n", ">>> for i in range(10):\n", ">>> print(dict_mutator.mutate(seeds[0]))\n", "Hello</tile></head><body>World<br/></body></html>\n", "<html><head><title>Hello</tile></head><body>World<br/></body></html>\n", "<html><head><title>HelloWorld
\n", "HelloWorld
\n", "HelloWorld
\n", "HelloWorld
\n", "HelloWorld
\n", "HelloWorld
<(/html>\n", "HelloWorld
\n", "HelloWorld
\n", "\n", "```\n", "This `DictMutator` can be used as an argument to `GreyboxFuzzer`:\n", "\n", "```python\n", ">>> runner = FunctionCoverageRunner(my_parser)\n", ">>> dict_fuzzer = GreyboxFuzzer(seeds, dict_mutator, PowerSchedule())\n", ">>> dict_fuzzer_outcome = dict_fuzzer.runs(runner, trials=5)\n", "```\n", "![](PICS/GreyboxGrammarFuzzer-synopsis-1.svg)\n", "\n", "### Fuzzing with Input Fragments\n", "\n", "The `LangFuzzer` class introduces a _language-aware_ fuzzer that can recombine fragments from existing inputs – inspired by the highly effective `LangFuzz` fuzzer. At its core is a `FragmentMutator` class that that takes a [_parser_](Parser.ipynb) as argument:\n", "\n", "```python\n", ">>> parser = EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS)\n", ">>> mutator = FragmentMutator(parser)\n", "```\n", "The fuzzer itself is initialized with a list of seeds, the above `FragmentMutator`, and a power schedule:\n", "\n", "```python\n", ">>> seeds = [\"HelloWorld
\"]\n", ">>> schedule = PowerSchedule()\n", ">>> lang_fuzzer = LangFuzzer(seeds, mutator, schedule)\n", ">>> for i in range(10):\n", ">>> print(lang_fuzzer.fuzz())\n", "HelloWorld
\n", "World\n", "HelloWorld
\n", "HelloWorld
\n", "
\n", "World
\n", "HelloWorld
\n", "World\n", "<head><body>HelloWorld
\n", "HelloWorld
\n", "\n", "```\n", "![](PICS/GreyboxGrammarFuzzer-synopsis-2.svg)\n", "\n", "### Fuzzing with Input Regions\n", "\n", "The `GreyboxGrammarFuzzer` class uses two mutators:\n", "* a _tree mutator_ (a `RegionMutator` object) that can parse existing strings to identify _regions_ in that string to be swapped or deleted.\n", "* a _byte mutator_ to apply bit- and character-level mutations.\n", "\n", "```python\n", ">>> tree_mutator = RegionMutator(parser)\n", ">>> byte_mutator = Mutator()\n", "```\n", "The _schedule_ for the `GreyboxGrammarFuzzer` class can be a regular `PowerSchedule` object. However, a more sophisticated schedule is provided by `AFLSmartSchedule`, which assigns more [energy](GreyboxFuzzer.ipynb#Power-Schedules) to seeds that have a higher degree of validity.\n", "\n", "```python\n", ">>> schedule = AFLSmartSchedule(parser)\n", "```\n", "The `GreyboxGrammarFuzzer` constructor takes a set of seeds as well as the two mutators and the schedule:\n", "\n", "```python\n", ">>> aflsmart_fuzzer = GreyboxGrammarFuzzer(seeds, byte_mutator, tree_mutator, schedule)\n", "```\n", "As it relies on code coverage, it is typically combined with a `FunctionCoverageRunner`:\n", "\n", "```python\n", ">>> runner = FunctionCoverageRunner(my_parser)\n", ">>> aflsmart_outcome = aflsmart_fuzzer.runs(runner, trials=5)\n", "```\n", "![](PICS/GreyboxGrammarFuzzer-synopsis-3.svg)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Background\n", "First, we [recall](GreyboxFuzzer.ipynb#Ingredients-for-Greybox-Fuzzing) a few basic ingredients for mutational fuzzers.\n", "* **Seed**. A _seed_ is an input that is used by the fuzzer to generate new inputs by applying a sequence of mutations.\n", "* **Mutator**. A _mutator_ implements a set of mutation operators that applied to an input produce a slightly modified input.\n", "* **PowerSchedule**. A _power schedule_ assigns _energy_ to a seed. A seed with higher energy is fuzzed more often throughout the fuzzing campaign.\n", "* **AdvancedMutationFuzzer**. Our _mutational blackbox fuzzer_ generates inputs by mutating seeds in an initial population of inputs.\n", "* **GreyboxFuzzer**. Our _greybox fuzzer_ dynamically adds inputs to the population of seeds that increased coverage.\n", "* **FunctionCoverageRunner**. Our _function coverage runner_ collects coverage information for the execution of a given Python function.\n", "\n", "Let's try to get a feeling for these concepts." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:28.751937Z", "iopub.status.busy": "2024-05-16T11:21:28.751746Z", "iopub.status.idle": "2024-05-16T11:21:28.754176Z", "shell.execute_reply": "2024-05-16T11:21:28.753900Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils.setup" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:28.755676Z", "iopub.status.busy": "2024-05-16T11:21:28.755575Z", "iopub.status.idle": "2024-05-16T11:21:28.757209Z", "shell.execute_reply": "2024-05-16T11:21:28.756936Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from typing import List, Set, Dict, Sequence, cast" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:28.758790Z", "iopub.status.busy": "2024-05-16T11:21:28.758674Z", "iopub.status.idle": "2024-05-16T11:21:29.477022Z", "shell.execute_reply": "2024-05-16T11:21:29.476688Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Fuzzer import Fuzzer\n", "from GreyboxFuzzer import Mutator, Seed, PowerSchedule\n", "from GreyboxFuzzer import AdvancedMutationFuzzer, GreyboxFuzzer\n", "from MutationFuzzer import FunctionCoverageRunner" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The following command applies a mutation to the input \"Hello World\"." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.479274Z", "iopub.status.busy": "2024-05-16T11:21:29.478994Z", "iopub.status.idle": "2024-05-16T11:21:29.481895Z", "shell.execute_reply": "2024-05-16T11:21:29.481413Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'Lello World'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Mutator().mutate(\"Hello World\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The default power schedule assigns energy uniformly across all seeds. Let's check whether this works.\n", "\n", "We choose 10k times from a population of three seeds. As we see in the `hits` counter, each seed is chosen about a third of the time." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.484095Z", "iopub.status.busy": "2024-05-16T11:21:29.483823Z", "iopub.status.idle": "2024-05-16T11:21:29.508179Z", "shell.execute_reply": "2024-05-16T11:21:29.507784Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "{'A': 3387, 'B': 3255, 'C': 3358}" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "population = [Seed(\"A\"), Seed(\"B\"), Seed(\"C\")]\n", "schedule = PowerSchedule()\n", "hits = {\n", " \"A\": 0,\n", " \"B\": 0,\n", " \"C\": 0\n", "}\n", "\n", "for i in range(10000):\n", " seed = schedule.choose(population)\n", " hits[seed.data] += 1\n", "\n", "hits" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Before explaining the function coverage runner, lets import Python's HTML parser as example..." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.510741Z", "iopub.status.busy": "2024-05-16T11:21:29.510578Z", "iopub.status.idle": "2024-05-16T11:21:29.512827Z", "shell.execute_reply": "2024-05-16T11:21:29.512510Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from html.parser import HTMLParser" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "... and create a _wrapper function_ that passes each input into a new parser object." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.514984Z", "iopub.status.busy": "2024-05-16T11:21:29.514761Z", "iopub.status.idle": "2024-05-16T11:21:29.517018Z", "shell.execute_reply": "2024-05-16T11:21:29.516706Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def my_parser(inp: str) -> None:\n", " parser = HTMLParser()\n", " parser.feed(inp)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The `FunctionCoverageRunner` constructor takes a Python `function` to execute. The function `run()` takes an input, passes it on to the Python `function`, and collects the coverage information for this execution. The function `coverage()` returns a list of tuples `(function name, line number)` for each statement that has been covered in the Python `function`." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.518832Z", "iopub.status.busy": "2024-05-16T11:21:29.518693Z", "iopub.status.idle": "2024-05-16T11:21:29.521690Z", "shell.execute_reply": "2024-05-16T11:21:29.521244Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[('goahead', 244),\n", " ('goahead', 250),\n", " ('goahead', 134),\n", " ('reset', 33),\n", " ('goahead', 137)]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "runner.run(\"Hello World\")\n", "cov = runner.coverage()\n", "\n", "list(cov)[:5] # Print 5 statements covered in HTMLParser" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Our greybox fuzzer takes a seed population, mutator, and power schedule. Let's generate 5000 fuzz inputs starting with an \"empty\" seed corpus." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.523919Z", "iopub.status.busy": "2024-05-16T11:21:29.523786Z", "iopub.status.idle": "2024-05-16T11:21:29.525715Z", "shell.execute_reply": "2024-05-16T11:21:29.525428Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import time\n", "import random" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.527341Z", "iopub.status.busy": "2024-05-16T11:21:29.527232Z", "iopub.status.idle": "2024-05-16T11:21:29.529065Z", "shell.execute_reply": "2024-05-16T11:21:29.528783Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "n = 5000\n", "seed_input = \" \" # empty seed\n", "runner = FunctionCoverageRunner(my_parser)\n", "fuzzer = GreyboxFuzzer([seed_input], Mutator(), PowerSchedule())" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.531229Z", "iopub.status.busy": "2024-05-16T11:21:29.531110Z", "iopub.status.idle": "2024-05-16T11:21:29.952133Z", "shell.execute_reply": "2024-05-16T11:21:29.951746Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "start = time.time()\n", "fuzzer.runs(runner, trials=n)\n", "end = time.time()" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.954001Z", "iopub.status.busy": "2024-05-16T11:21:29.953879Z", "iopub.status.idle": "2024-05-16T11:21:29.956365Z", "shell.execute_reply": "2024-05-16T11:21:29.956053Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took the fuzzer 0.42 seconds to generate and execute 5000 inputs.'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"It took the fuzzer %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.958168Z", "iopub.status.busy": "2024-05-16T11:21:29.958054Z", "iopub.status.idle": "2024-05-16T11:21:29.960500Z", "shell.execute_reply": "2024-05-16T11:21:29.960136Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'During this fuzzing campaign, we covered 73 statements.'" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"During this fuzzing campaign, we covered %d statements.\" % len(runner.coverage())" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Fuzzing with Dictionaries\n", "\n", "To fuzz our HTML parser, it may be useful to inform a mutational fuzzer about important keywords in the input – that is, important HTML keywords. The general idea is to have a _dictionary_ of pre-defined useful inputs that could then be inserted as such when mutating an input.\n", "\n", "This concept is illustrated in the following diagram. When mutating an input, we may insert given keywords from the dictionary (in red).\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "To implement this concept, we extend our mutator to consider keywords from a dictionary." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.962303Z", "iopub.status.busy": "2024-05-16T11:21:29.962188Z", "iopub.status.idle": "2024-05-16T11:21:29.964943Z", "shell.execute_reply": "2024-05-16T11:21:29.964656Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class DictMutator(Mutator):\n", " \"\"\"Mutate strings using keywords from a dictionary\"\"\"\n", "\n", " def __init__(self, dictionary: List[str]) -> None:\n", " \"\"\"Constructor. `dictionary` is the list of keywords to use.\"\"\"\n", " super().__init__()\n", " self.dictionary = dictionary\n", " self.mutators.append(self.insert_from_dictionary)\n", "\n", " def insert_from_dictionary(self, s: str) -> str:\n", " \"\"\"Returns s with a keyword from the dictionary inserted\"\"\"\n", " pos = random.randint(0, len(s))\n", " random_keyword = random.choice(self.dictionary)\n", " return s[:pos] + random_keyword + s[pos:]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let's try to add a few HTML tags and attributes and see whether the coverage with `DictMutator` increases." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:29.966614Z", "iopub.status.busy": "2024-05-16T11:21:29.966495Z", "iopub.status.idle": "2024-05-16T11:21:33.129035Z", "shell.execute_reply": "2024-05-16T11:21:33.128538Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took the fuzzer 3.16 seconds to generate and execute 5000 inputs.'" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "dict_mutator = DictMutator([\"
\", \"\", \"\", \"='a'\"])\n", "dict_fuzzer = GreyboxFuzzer([seed_input], dict_mutator, PowerSchedule())\n", "\n", "start = time.time()\n", "dict_fuzzer.runs(runner, trials=n)\n", "end = time.time()\n", "\n", "\"It took the fuzzer %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Clearly, it takes longer. In our experience, this means more code is covered:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:33.131177Z", "iopub.status.busy": "2024-05-16T11:21:33.131033Z", "iopub.status.idle": "2024-05-16T11:21:33.133638Z", "shell.execute_reply": "2024-05-16T11:21:33.133377Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'During this fuzzing campaign, we covered 110 statements.'" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"During this fuzzing campaign, we covered %d statements.\" % len(runner.coverage())" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "How do the fuzzers compare in terms of coverage over time?" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:33.135923Z", "iopub.status.busy": "2024-05-16T11:21:33.135784Z", "iopub.status.idle": "2024-05-16T11:21:33.137697Z", "shell.execute_reply": "2024-05-16T11:21:33.137387Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Coverage import population_coverage" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:33.139510Z", "iopub.status.busy": "2024-05-16T11:21:33.139372Z", "iopub.status.idle": "2024-05-16T11:21:33.141420Z", "shell.execute_reply": "2024-05-16T11:21:33.141021Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import matplotlib.pyplot as plt # type: ignore" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:33.143336Z", "iopub.status.busy": "2024-05-16T11:21:33.143218Z", "iopub.status.idle": "2024-05-16T11:21:35.474003Z", "shell.execute_reply": "2024-05-16T11:21:35.473547Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "_, dict_cov = population_coverage(dict_fuzzer.inputs, my_parser)\n", "_, fuzz_cov = population_coverage(fuzzer.inputs, my_parser)\n", "line_dict, = plt.plot(dict_cov, label=\"With Dictionary\")\n", "line_fuzz, = plt.plot(fuzz_cov, label=\"Without Dictionary\")\n", "plt.legend(handles=[line_dict, line_fuzz])\n", "plt.xlim(0, n)\n", "plt.title('Coverage over time')\n", "plt.xlabel('# of inputs')\n", "plt.ylabel('lines covered');" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "\n", "\n", "***Summary.*** Informing the fuzzer about important keywords already goes a long way towards achieving lots of coverage quickly.\n", "\n", "***Try it.*** Open this chapter as Jupyter notebook and add other HTML-related keywords to the dictionary in order to see whether the difference in coverage actually increases (given the same budget of 5k generated test inputs).\n", "\n", "***Read up.*** Michał Zalewski, author of AFL, wrote several great blog posts on [making up grammars with a dictionary in hand](https://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html) and [pulling JPEGs out of thin air](https://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-air.html)!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "tags": [], "toc-hr-collapsed": false }, "source": [ "## Fuzzing with Input Fragments\n", "\n", "While dictionaries are helpful to inject important keywords into seed inputs, they do not allow maintaining the structural integrity of the generated inputs. Instead, we need to make the fuzzer aware of the _input structure_. We can do this using [grammars](Grammars.ipynb). Our first approach \n", "\n", "1. [parses](Parser.ipynb) the seed inputs, \n", "2. disassembles them into input fragments, and \n", "3. generates new files by reassembling these fragments according to the rules of the grammar." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "This combination of _parsing_ and _fuzzing_ can be very powerful. For instance, we can _swap_ existing substructures in an input:\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We can also _replace_ existing substructures by newly generated ones:\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "All these operations take place on [derivation trees](GrammarFuzzer.ipynb), which can be parsed from and produced into strings at any time." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Parsing and Recombining JavaScript, or How to Make 50,000 USD in Four Weeks\n", "\n", "In \"Fuzzing with Code Fragments\" \\cite{Holler2012}, Holler, Herzig, and Zeller apply these steps to fuzz a JavaScript interpreter. They use a JavaScript grammar to\n", "\n", "1. _parse_ (valid) JavaScript inputs into parse trees,\n", "2. _disassemble_ them into fragments (subtrees),\n", "3. _recombine_ these fragments into valid JavaScript programs again, and\n", "4. _feed_ these programs into a JavaScript interpreter for execution." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "As in most fuzzing scenarios, the aim is to cause the JavaScript interpreter to crash. Here is an example of LangFuzz-generated JavaScript code (from \\cite{Holler2012}) that caused a crash in the Mozilla JavaScript interpreter:\n", "\n", "```javascript\n", "var haystack = \"foo\";\n", "var re_text = \"^foo\";\n", "haystack += \"x\";\n", "re_text += \"(x)\";\n", "var re = new RegExp(re_text);\n", "re.test(haystack);\n", "RegExp.input = Number();\n", "print(RegExp.$1);\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "From a crash of the JavaScript interpreter, it is frequently possible to construct an *exploit* that will not only crash the interpreter, but instead have it execute code under the attacker's control. Therefore, such crashes are serious flaws, which is why you get a bug bounty if you report them." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "In the first four weeks of running his _LangFuzz_ tool, Christian Holler, first author of that paper, netted _more than USD 50,000 in bug bounties_. To date, LangFuzz has found more than 2,600 bugs in the JavaScript browsers of Mozilla Firefox, Google Chrome, and Microsoft Edge. If you use any of these browsers (say, on your Android phone), the combination of parsing and fuzzing has contributed significantly in making your browsing session secure.\n", "\n", "(Note that these are the same Holler and Zeller who are co-authors of this book. If you ever wondered why we devote a couple of chapters on grammar-based fuzzing, that's because we have had some great experience with it.)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Parsing and Recombining HTML\n", "\n", "In this book, let us stay with HTML input for a while. To generate valid HTML inputs for our Python `HTMLParser`, we should first define a simple grammar. It allows defining HTML tags with attributes. Our context-free grammar does not require that opening and closing tags must match. However, we will see that such context-sensitive features can be maintained in the derived input fragments, and thus in the generated inputs." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.476185Z", "iopub.status.busy": "2024-05-16T11:21:35.476060Z", "iopub.status.idle": "2024-05-16T11:21:35.477832Z", "shell.execute_reply": "2024-05-16T11:21:35.477470Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import string" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.479742Z", "iopub.status.busy": "2024-05-16T11:21:35.479625Z", "iopub.status.idle": "2024-05-16T11:21:35.568704Z", "shell.execute_reply": "2024-05-16T11:21:35.568323Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Grammars import is_valid_grammar, srange, Grammar" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.571014Z", "iopub.status.busy": "2024-05-16T11:21:35.570825Z", "iopub.status.idle": "2024-05-16T11:21:35.573044Z", "shell.execute_reply": "2024-05-16T11:21:35.572681Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "XML_TOKENS: Set[str] = {\"\", \"\"}" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.574714Z", "iopub.status.busy": "2024-05-16T11:21:35.574583Z", "iopub.status.idle": "2024-05-16T11:21:35.577312Z", "shell.execute_reply": "2024-05-16T11:21:35.576989Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "XML_GRAMMAR: Grammar = {\n", " \"\": [\"\"],\n", " \"\": [\"\",\n", " \"\",\n", " \"\",\n", " \"\"],\n", " \"\": [\"<>\", \"< >\"],\n", " \"\": [\"</>\", \"< />\"],\n", " \"\": [\">\"],\n", " \"\": [\"=\", \" \"],\n", " \"\": [\"\", \"\"],\n", " \"\": [\"\", \"\"],\n", " \"\": srange(string.ascii_letters + string.digits +\n", " \"\\\"\" + \"'\" + \".\"),\n", " \"\": srange(string.ascii_letters + string.digits +\n", " \"\\\"\" + \"'\" + \" \" + \"\\t\"),\n", "}" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.579533Z", "iopub.status.busy": "2024-05-16T11:21:35.579394Z", "iopub.status.idle": "2024-05-16T11:21:35.581466Z", "shell.execute_reply": "2024-05-16T11:21:35.581125Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "assert is_valid_grammar(XML_GRAMMAR)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "In order to parse an input into a derivation tree, we use the [Earley parser](Parser.ipynb#Parsing-Context-Free-Grammars)." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.583279Z", "iopub.status.busy": "2024-05-16T11:21:35.583134Z", "iopub.status.idle": "2024-05-16T11:21:35.703199Z", "shell.execute_reply": "2024-05-16T11:21:35.702844Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Parser import EarleyParser, Parser\n", "from GrammarFuzzer import display_tree, DerivationTree" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's run the parser on a simple HTML input and display all possible parse trees. A *parse tree* represents the input structure according to the given grammar." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.705275Z", "iopub.status.busy": "2024-05-16T11:21:35.705132Z", "iopub.status.idle": "2024-05-16T11:21:35.707197Z", "shell.execute_reply": "2024-05-16T11:21:35.706890Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from IPython.display import display" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:35.708767Z", "iopub.status.busy": "2024-05-16T11:21:35.708659Z", "iopub.status.idle": "2024-05-16T11:21:42.765009Z", "shell.execute_reply": "2024-05-16T11:21:42.764532Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->10\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<text>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "9\n", "Text\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "11\n", "</\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<id>\n", "\n", "\n", "\n", "10->12\n", "\n", "\n", "\n", "\n", "\n", "14\n", "> (62)\n", "\n", "\n", "\n", "10->14\n", "\n", "\n", "\n", "\n", "\n", "13\n", "html\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "14\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->14\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "T (84)\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<text>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "13\n", "ext\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "15\n", "</\n", "\n", "\n", "\n", "14->15\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<id>\n", "\n", "\n", "\n", "14->16\n", "\n", "\n", "\n", "\n", "\n", "18\n", "> (62)\n", "\n", "\n", "\n", "14->18\n", "\n", "\n", "\n", "\n", "\n", "17\n", "html\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "14\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->14\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "Te\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<text>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "13\n", "xt\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "15\n", "</\n", "\n", "\n", "\n", "14->15\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<id>\n", "\n", "\n", "\n", "14->16\n", "\n", "\n", "\n", "\n", "\n", "18\n", "> (62)\n", "\n", "\n", "\n", "14->18\n", "\n", "\n", "\n", "\n", "\n", "17\n", "html\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "18\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->18\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "7->15\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<xml-tree>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "8->12\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<text>\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "11\n", "T (84)\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "e (101)\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<text>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "17\n", "xt\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "19\n", "</\n", "\n", "\n", "\n", "18->19\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<id>\n", "\n", "\n", "\n", "18->20\n", "\n", "\n", "\n", "\n", "\n", "22\n", "> (62)\n", "\n", "\n", "\n", "18->22\n", "\n", "\n", "\n", "\n", "\n", "21\n", "html\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "14\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->14\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "Tex\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<text>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "13\n", "t (116)\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "15\n", "</\n", "\n", "\n", "\n", "14->15\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<id>\n", "\n", "\n", "\n", "14->16\n", "\n", "\n", "\n", "\n", "\n", "18\n", "> (62)\n", "\n", "\n", "\n", "14->18\n", "\n", "\n", "\n", "\n", "\n", "17\n", "html\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "18\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->18\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "7->15\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<xml-tree>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "8->12\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<text>\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "11\n", "T (84)\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "ex\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<text>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "17\n", "t (116)\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "19\n", "</\n", "\n", "\n", "\n", "18->19\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<id>\n", "\n", "\n", "\n", "18->20\n", "\n", "\n", "\n", "\n", "\n", "22\n", "> (62)\n", "\n", "\n", "\n", "18->22\n", "\n", "\n", "\n", "\n", "\n", "21\n", "html\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "18\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->18\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "7->15\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<xml-tree>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "8->12\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<text>\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "11\n", "Te\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "x (120)\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<text>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "17\n", "t (116)\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "19\n", "</\n", "\n", "\n", "\n", "18->19\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<id>\n", "\n", "\n", "\n", "18->20\n", "\n", "\n", "\n", "\n", "\n", "22\n", "> (62)\n", "\n", "\n", "\n", "18->22\n", "\n", "\n", "\n", "\n", "\n", "21\n", "html\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "22\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->22\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "19\n", "<xml-tree>\n", "\n", "\n", "\n", "7->19\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<xml-tree>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<xml-tree>\n", "\n", "\n", "\n", "8->16\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<xml-tree>\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<xml-tree>\n", "\n", "\n", "\n", "9->13\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<text>\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "12\n", "T (84)\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "14\n", "<text>\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "15\n", "e (101)\n", "\n", "\n", "\n", "14->15\n", "\n", "\n", "\n", "\n", "\n", "17\n", "<text>\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "18\n", "x (120)\n", "\n", "\n", "\n", "17->18\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<text>\n", "\n", "\n", "\n", "19->20\n", "\n", "\n", "\n", "\n", "\n", "21\n", "t (116)\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n", "23\n", "</\n", "\n", "\n", "\n", "22->23\n", "\n", "\n", "\n", "\n", "\n", "24\n", "<id>\n", "\n", "\n", "\n", "22->24\n", "\n", "\n", "\n", "\n", "\n", "26\n", "> (62)\n", "\n", "\n", "\n", "22->26\n", "\n", "\n", "\n", "\n", "\n", "25\n", "html\n", "\n", "\n", "\n", "24->25\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "22\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->22\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "19\n", "<xml-tree>\n", "\n", "\n", "\n", "7->19\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<xml-tree>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "8->12\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<text>\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "11\n", "T (84)\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<xml-tree>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<xml-tree>\n", "\n", "\n", "\n", "12->16\n", "\n", "\n", "\n", "\n", "\n", "14\n", "<text>\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "15\n", "e (101)\n", "\n", "\n", "\n", "14->15\n", "\n", "\n", "\n", "\n", "\n", "17\n", "<text>\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "18\n", "x (120)\n", "\n", "\n", "\n", "17->18\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<text>\n", "\n", "\n", "\n", "19->20\n", "\n", "\n", "\n", "\n", "\n", "21\n", "t (116)\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n", "23\n", "</\n", "\n", "\n", "\n", "22->23\n", "\n", "\n", "\n", "\n", "\n", "24\n", "<id>\n", "\n", "\n", "\n", "22->24\n", "\n", "\n", "\n", "\n", "\n", "26\n", "> (62)\n", "\n", "\n", "\n", "22->26\n", "\n", "\n", "\n", "\n", "\n", "25\n", "html\n", "\n", "\n", "\n", "24->25\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "18\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->18\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "T (84)\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "11->15\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "e (101)\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<text>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "17\n", "xt\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "19\n", "</\n", "\n", "\n", "\n", "18->19\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<id>\n", "\n", "\n", "\n", "18->20\n", "\n", "\n", "\n", "\n", "\n", "22\n", "> (62)\n", "\n", "\n", "\n", "18->22\n", "\n", "\n", "\n", "\n", "\n", "21\n", "html\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "18\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->18\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "T (84)\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "11->15\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "ex\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<text>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "17\n", "t (116)\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "19\n", "</\n", "\n", "\n", "\n", "18->19\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<id>\n", "\n", "\n", "\n", "18->20\n", "\n", "\n", "\n", "\n", "\n", "22\n", "> (62)\n", "\n", "\n", "\n", "18->22\n", "\n", "\n", "\n", "\n", "\n", "21\n", "html\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "22\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->22\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "T (84)\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "19\n", "<xml-tree>\n", "\n", "\n", "\n", "11->19\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<xml-tree>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<xml-tree>\n", "\n", "\n", "\n", "12->16\n", "\n", "\n", "\n", "\n", "\n", "14\n", "<text>\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "15\n", "e (101)\n", "\n", "\n", "\n", "14->15\n", "\n", "\n", "\n", "\n", "\n", "17\n", "<text>\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "18\n", "x (120)\n", "\n", "\n", "\n", "17->18\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<text>\n", "\n", "\n", "\n", "19->20\n", "\n", "\n", "\n", "\n", "\n", "21\n", "t (116)\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n", "23\n", "</\n", "\n", "\n", "\n", "22->23\n", "\n", "\n", "\n", "\n", "\n", "24\n", "<id>\n", "\n", "\n", "\n", "22->24\n", "\n", "\n", "\n", "\n", "\n", "26\n", "> (62)\n", "\n", "\n", "\n", "22->26\n", "\n", "\n", "\n", "\n", "\n", "25\n", "html\n", "\n", "\n", "\n", "24->25\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "22\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->22\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "T (84)\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "11->15\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "e (101)\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<xml-tree>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "19\n", "<xml-tree>\n", "\n", "\n", "\n", "15->19\n", "\n", "\n", "\n", "\n", "\n", "17\n", "<text>\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "18\n", "x (120)\n", "\n", "\n", "\n", "17->18\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<text>\n", "\n", "\n", "\n", "19->20\n", "\n", "\n", "\n", "\n", "\n", "21\n", "t (116)\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n", "23\n", "</\n", "\n", "\n", "\n", "22->23\n", "\n", "\n", "\n", "\n", "\n", "24\n", "<id>\n", "\n", "\n", "\n", "22->24\n", "\n", "\n", "\n", "\n", "\n", "26\n", "> (62)\n", "\n", "\n", "\n", "22->26\n", "\n", "\n", "\n", "\n", "\n", "25\n", "html\n", "\n", "\n", "\n", "24->25\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "18\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->18\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "11\n", "<xml-tree>\n", "\n", "\n", "\n", "7->11\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<text>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "Te\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "11->12\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "11->15\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "x (120)\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<text>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "17\n", "t (116)\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "19\n", "</\n", "\n", "\n", "\n", "18->19\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<id>\n", "\n", "\n", "\n", "18->20\n", "\n", "\n", "\n", "\n", "\n", "22\n", "> (62)\n", "\n", "\n", "\n", "18->22\n", "\n", "\n", "\n", "\n", "\n", "21\n", "html\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<xml-tree>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<xml-open-tag>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<xml-tree>\n", "\n", "\n", "\n", "1->7\n", "\n", "\n", "\n", "\n", "\n", "22\n", "<xml-close-tag>\n", "\n", "\n", "\n", "1->22\n", "\n", "\n", "\n", "\n", "\n", "3\n", "< (60)\n", "\n", "\n", "\n", "2->3\n", "\n", "\n", "\n", "\n", "\n", "4\n", "<id>\n", "\n", "\n", "\n", "2->4\n", "\n", "\n", "\n", "\n", "\n", "6\n", "> (62)\n", "\n", "\n", "\n", "2->6\n", "\n", "\n", "\n", "\n", "\n", "5\n", "html\n", "\n", "\n", "\n", "4->5\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<xml-tree>\n", "\n", "\n", "\n", "7->8\n", "\n", "\n", "\n", "\n", "\n", "15\n", "<xml-tree>\n", "\n", "\n", "\n", "7->15\n", "\n", "\n", "\n", "\n", "\n", "9\n", "<xml-tree>\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<xml-tree>\n", "\n", "\n", "\n", "8->12\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<text>\n", "\n", "\n", "\n", "9->10\n", "\n", "\n", "\n", "\n", "\n", "11\n", "T (84)\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "13\n", "<text>\n", "\n", "\n", "\n", "12->13\n", "\n", "\n", "\n", "\n", "\n", "14\n", "e (101)\n", "\n", "\n", "\n", "13->14\n", "\n", "\n", "\n", "\n", "\n", "16\n", "<xml-tree>\n", "\n", "\n", "\n", "15->16\n", "\n", "\n", "\n", "\n", "\n", "19\n", "<xml-tree>\n", "\n", "\n", "\n", "15->19\n", "\n", "\n", "\n", "\n", "\n", "17\n", "<text>\n", "\n", "\n", "\n", "16->17\n", "\n", "\n", "\n", "\n", "\n", "18\n", "x (120)\n", "\n", "\n", "\n", "17->18\n", "\n", "\n", "\n", "\n", "\n", "20\n", "<text>\n", "\n", "\n", "\n", "19->20\n", "\n", "\n", "\n", "\n", "\n", "21\n", "t (116)\n", "\n", "\n", "\n", "20->21\n", "\n", "\n", "\n", "\n", "\n", "23\n", "</\n", "\n", "\n", "\n", "22->23\n", "\n", "\n", "\n", "\n", "\n", "24\n", "<id>\n", "\n", "\n", "\n", "22->24\n", "\n", "\n", "\n", "\n", "\n", "26\n", "> (62)\n", "\n", "\n", "\n", "22->26\n", "\n", "\n", "\n", "\n", "\n", "25\n", "html\n", "\n", "\n", "\n", "24->25\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "parser = EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS)\n", "\n", "for tree in parser.parse(\"Text\"):\n", " display(display_tree(tree))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "As we can see, the input starts with an opening tag, contains some text, and ends with a closing tag. Excellent. This is a structure that we can work with." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Building the Fragment Pool\n", "We are now ready to implement our first input-structure-aware mutator. Let's initialize the mutator with the dictionary `fragments` representing the empty fragment pool. It contains a key for each symbol in the grammar (and the empty set as value)." ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.767340Z", "iopub.status.busy": "2024-05-16T11:21:42.767130Z", "iopub.status.idle": "2024-05-16T11:21:42.770515Z", "shell.execute_reply": "2024-05-16T11:21:42.769908Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class FragmentMutator(Mutator):\n", " \"\"\"Mutate inputs with input fragments from a pool\"\"\"\n", "\n", " def __init__(self, parser: EarleyParser) -> None:\n", " \"\"\"Initialize empty fragment pool and add parser\"\"\"\n", " self.parser = parser\n", " self.fragments: Dict[str, List[DerivationTree]] = \\\n", " {k: [] for k in self.parser.cgrammar}\n", " super().__init__()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The `FragmentMutator` adds fragments recursively. A *fragment* is a subtree in the parse tree and consists of the symbol of the current node and child nodes (i.e., descendant fragments). We can exclude fragments starting with symbols that are tokens, terminals, or not part of the grammar." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.772339Z", "iopub.status.busy": "2024-05-16T11:21:42.772236Z", "iopub.status.idle": "2024-05-16T11:21:42.774731Z", "shell.execute_reply": "2024-05-16T11:21:42.774335Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Parser import terminals" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.776708Z", "iopub.status.busy": "2024-05-16T11:21:42.776481Z", "iopub.status.idle": "2024-05-16T11:21:42.779530Z", "shell.execute_reply": "2024-05-16T11:21:42.779239Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def add_fragment(self, fragment: DerivationTree) -> None:\n", " \"\"\"Recursively adds fragments to the fragment pool\"\"\"\n", " (symbol, children) = fragment\n", " if not self.is_excluded(symbol):\n", " self.fragments[symbol].append(fragment)\n", " assert children is not None\n", " for subfragment in children:\n", " self.add_fragment(subfragment)\n", "\n", " def is_excluded(self, symbol: str) -> bool:\n", " \"\"\"Returns true if a fragment starting with a specific\n", " symbol and all its decendents can be excluded\"\"\"\n", " return (symbol not in self.parser.grammar() or\n", " symbol in self.parser.tokens or\n", " symbol in terminals(self.parser.grammar()))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Parsing can take a long time, particularly if there is too much ambiguity during the parsing. In order to maintain the efficiency of mutational fuzzing, we will limit the parsing time to 200ms." ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.781999Z", "iopub.status.busy": "2024-05-16T11:21:42.781750Z", "iopub.status.idle": "2024-05-16T11:21:42.784721Z", "shell.execute_reply": "2024-05-16T11:21:42.784075Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Timeout import Timeout" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The function `add_to_fragment_pool()` parses a seed (no longer than 200ms) and adds all its fragments to the fragment pool. If the parsing of the `seed` was successful, the attribute `seed.has_structure` is set to `True`. Otherwise, it is set to `False`." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.787334Z", "iopub.status.busy": "2024-05-16T11:21:42.787139Z", "iopub.status.idle": "2024-05-16T11:21:42.789638Z", "shell.execute_reply": "2024-05-16T11:21:42.789264Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class SeedWithStructure(Seed):\n", " \"\"\"Seeds augmented with structure info\"\"\"\n", "\n", " def __init__(self, data: str) -> None:\n", " super().__init__(data)\n", " self.has_structure = False\n", " self.structure: DerivationTree = (\"\", [])" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.791856Z", "iopub.status.busy": "2024-05-16T11:21:42.791623Z", "iopub.status.idle": "2024-05-16T11:21:42.794674Z", "shell.execute_reply": "2024-05-16T11:21:42.794237Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def add_to_fragment_pool(self, seed: SeedWithStructure) -> None:\n", " \"\"\"Adds all fragments of a seed to the fragment pool\"\"\"\n", " try: # only allow quick parsing of 200ms max\n", " with Timeout(0.2):\n", " seed.structure = next(self.parser.parse(seed.data))\n", " self.add_fragment(seed.structure)\n", " seed.has_structure = True\n", " except (SyntaxError, TimeoutError):\n", " seed.has_structure = False" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's see how `FragmentMutator` fills the fragment pool for a simple HTML seed input. We initialize mutator with the `EarleyParser` which itself is initialized with our `XML_GRAMMAR`." ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.797032Z", "iopub.status.busy": "2024-05-16T11:21:42.796815Z", "iopub.status.idle": "2024-05-16T11:21:42.799196Z", "shell.execute_reply": "2024-05-16T11:21:42.798727Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from GrammarFuzzer import tree_to_string" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.801376Z", "iopub.status.busy": "2024-05-16T11:21:42.801227Z", "iopub.status.idle": "2024-05-16T11:21:42.855813Z", "shell.execute_reply": "2024-05-16T11:21:42.855371Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "|-HelloWorld
\n", "\n", "|-HelloWorld
\n", "|-HelloWorld
\n", "|-Hello\n", "|-Hello\n", "|-Hello\n", "|-World
\n", "|-World
\n", "|-World\n", "|-
\n", "\n", "|-\n", "|-\n", "|-\n", "|-<body>\n", "<xml-openclose-tag>\n", "|-<br/>\n", "<xml-close-tag>\n", "|-\n", "|-\n", "|-\n", "|-\n", "\n", "\n", "\n", "\n", "\n" ] } ], "source": [ "valid_seed = SeedWithStructure(\n", " \"HelloWorld
\")\n", "fragment_mutator = FragmentMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))\n", "fragment_mutator.add_to_fragment_pool(valid_seed)\n", "\n", "for key in fragment_mutator.fragments:\n", " print(key)\n", " for f in fragment_mutator.fragments[key]:\n", " print(\"|-%s\" % tree_to_string(f))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "For many symbols in the grammar, we have collected a number of fragments. There are several open and closing tags and several interesting fragments starting with the `xml-tree` symbol.\n", "\n", "***Summary***. For each interesting symbol in the grammar, the `FragmentMutator` has a set of fragments. These fragments are extracted by first parsing the inputs to be mutated." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Fragment-Based Mutation\n", "\n", "We can use the fragments in the fragment pool to generate new inputs. Every seed that is being mutated is disassembled into fragments, and memoized – i.e., disassembled only the first time around." ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.858001Z", "iopub.status.busy": "2024-05-16T11:21:42.857835Z", "iopub.status.idle": "2024-05-16T11:21:42.860386Z", "shell.execute_reply": "2024-05-16T11:21:42.860102Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def __init__(self, parser: EarleyParser) -> None:\n", " \"\"\"Initialize mutators\"\"\"\n", " super().__init__(parser)\n", " self.seen_seeds: List[SeedWithStructure] = []\n", "\n", " def mutate(self, seed: SeedWithStructure) -> SeedWithStructure:\n", " \"\"\"Implement structure-aware mutation. Memoize seeds.\"\"\"\n", " if seed not in self.seen_seeds:\n", " self.seen_seeds.append(seed)\n", " self.add_to_fragment_pool(seed)\n", "\n", " return super().mutate(seed)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Our first structural mutation operator is `swap_fragments()`, which choses a random fragment in the given seed and substitutes it with a random fragment from the pool. We make sure that both fragments start with the same symbol. For instance, we may swap a closing tag in the seed HTML by another closing tag from the fragment pool.\n", "\n", "In order to choose a random fragment, the mutator counts all fragments (`n_count`) below the root fragment associated with the start-symbol." ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.862342Z", "iopub.status.busy": "2024-05-16T11:21:42.862193Z", "iopub.status.idle": "2024-05-16T11:21:42.864716Z", "shell.execute_reply": "2024-05-16T11:21:42.864304Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def count_nodes(self, fragment: DerivationTree) -> int:\n", " \"\"\"Returns the number of nodes in the fragment\"\"\"\n", " symbol, children = fragment\n", " if self.is_excluded(symbol):\n", " return 0\n", "\n", " assert children is not None\n", " return 1 + sum(map(self.count_nodes, children))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "In order to swap the chosen fragment – identified using the \"global\" variable `self.to_swap` – the seed's parse tree is traversed recursively." ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.866262Z", "iopub.status.busy": "2024-05-16T11:21:42.866150Z", "iopub.status.idle": "2024-05-16T11:21:42.868508Z", "shell.execute_reply": "2024-05-16T11:21:42.868199Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def recursive_swap(self, fragment: DerivationTree) -> DerivationTree:\n", " \"\"\"Recursively finds the fragment to swap.\"\"\"\n", " symbol, children = fragment\n", " if self.is_excluded(symbol):\n", " return symbol, children\n", "\n", " self.to_swap -= 1\n", " if self.to_swap == 0: \n", " return random.choice(list(self.fragments[symbol]))\n", "\n", " assert children is not None\n", " return symbol, list(map(self.recursive_swap, children))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Our structural mutator chooses a random number between 2 (i.e., excluding the `start` symbol) and the total number of fragments (`n_count`) and uses the recursive swapping to generate the new fragment. The new fragment is serialized as string and returned as new seed." ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.870294Z", "iopub.status.busy": "2024-05-16T11:21:42.870146Z", "iopub.status.idle": "2024-05-16T11:21:42.872867Z", "shell.execute_reply": "2024-05-16T11:21:42.872552Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def __init__(self, parser: EarleyParser) -> None: # type: ignore\n", " super().__init__(parser)\n", " self.mutators = [self.swap_fragment] # type: ignore\n", "\n", " def swap_fragment(self, seed: SeedWithStructure) -> SeedWithStructure:\n", " \"\"\"Substitutes a random fragment with another with the same symbol\"\"\"\n", " if seed.has_structure: # type: ignore\n", " n_nodes = self.count_nodes(seed.structure)\n", " self.to_swap = random.randint(2, n_nodes)\n", " new_structure = self.recursive_swap(seed.structure)\n", "\n", " new_seed = SeedWithStructure(tree_to_string(new_structure))\n", " new_seed.has_structure = True\n", " new_seed.structure = new_structure\n", " return new_seed\n", "\n", " return seed" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.874331Z", "iopub.status.busy": "2024-05-16T11:21:42.874221Z", "iopub.status.idle": "2024-05-16T11:21:42.972946Z", "shell.execute_reply": "2024-05-16T11:21:42.972548Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HelloWorld
\n" ] }, { "data": { "text/plain": [ "HelloWorld
" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "valid_seed = SeedWithStructure(\n", " \"HelloWorld
\")\n", "lf_mutator = FragmentMutator(parser)\n", "print(valid_seed)\n", "lf_mutator.mutate(valid_seed)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "As we can see, one fragment has been substituted by another. \n", "\n", "We can use a similar recursive traversal to *remove* a random fragment." ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.974714Z", "iopub.status.busy": "2024-05-16T11:21:42.974578Z", "iopub.status.idle": "2024-05-16T11:21:42.976999Z", "shell.execute_reply": "2024-05-16T11:21:42.976719Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def recursive_delete(self, fragment: DerivationTree) -> DerivationTree:\n", " \"\"\"Recursively finds the fragment to delete\"\"\"\n", " symbol, children = fragment\n", " if self.is_excluded(symbol):\n", " return symbol, children\n", "\n", " self.to_delete -= 1\n", " if self.to_delete == 0:\n", " return symbol, []\n", "\n", " assert children is not None\n", " return symbol, list(map(self.recursive_delete, children))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We should also define the corresponding structural deletion operator, as well." ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.979000Z", "iopub.status.busy": "2024-05-16T11:21:42.978691Z", "iopub.status.idle": "2024-05-16T11:21:42.981664Z", "shell.execute_reply": "2024-05-16T11:21:42.981230Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class FragmentMutator(FragmentMutator):\n", " def __init__(self, parser): # type: ignore\n", " super().__init__(parser)\n", " self.mutators.append(self.delete_fragment)\n", "\n", " def delete_fragment(self, seed: SeedWithStructure) -> SeedWithStructure:\n", " \"\"\"Delete a random fragment\"\"\"\n", " if seed.has_structure:\n", " n_nodes = self.count_nodes(seed.structure)\n", " self.to_delete = random.randint(2, n_nodes)\n", " new_structure = self.recursive_delete(seed.structure)\n", "\n", " new_seed = SeedWithStructure(tree_to_string(new_structure))\n", " new_seed.has_structure = True\n", " new_seed.structure = new_structure\n", " # do not return an empty new_seed\n", " if not new_seed.data:\n", " return seed\n", " else:\n", " return new_seed\n", "\n", " return seed" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "***Summary***. We now have all ingredients for structure-aware fuzzing. Our mutator disassembles all seeds into fragments, which are then added to the fragment pool. Our mutator swaps random fragments in a given seed with fragments of the same type. And our mutator deletes random fragments in a given seed. This allows maintaining a high degree of validity for the generated inputs w.r.t. the given grammar.\n", "\n", "***Try it***. Try adding other structural mutation operators. How would an *add-operator* know the position in a given seed file, where it is okay to add a fragment starting with a certain symbol?" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Fragment-Based Fuzzing\n", "\n", "We can now define an input-structure aware fuzzer as pioneered in LangFuzz. To implement LangFuzz, we modify our [blackbox mutational fuzzer](GreyboxFuzzer.ipynb#Blackbox-Mutation-based-Fuzzer) to stack up to four structural mutations." ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.983903Z", "iopub.status.busy": "2024-05-16T11:21:42.983717Z", "iopub.status.idle": "2024-05-16T11:21:42.986400Z", "shell.execute_reply": "2024-05-16T11:21:42.985964Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class LangFuzzer(AdvancedMutationFuzzer):\n", " \"\"\"Blackbox fuzzer mutating input fragments. Roughly based on `LangFuzz`.\"\"\"\n", "\n", " def create_candidate(self) -> Seed: # type: ignore\n", " \"\"\"Returns an input generated by fuzzing a seed in the population\"\"\"\n", " candidate = self.schedule.choose(self.population)\n", " trials = random.randint(1, 4)\n", " for i in range(trials):\n", " candidate = self.mutator.mutate(candidate)\n", "\n", " return candidate" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Okay, let's take our first input-structure aware fuzzer for a spin. Being careful, we set n=300 for now." ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:21:42.988969Z", "iopub.status.busy": "2024-05-16T11:21:42.988790Z", "iopub.status.idle": "2024-05-16T11:22:00.178505Z", "shell.execute_reply": "2024-05-16T11:22:00.178222Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'It took LangFuzzer 17.19 seconds to generate and execute 300 inputs.'" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n = 300\n", "runner = FunctionCoverageRunner(my_parser)\n", "mutator = FragmentMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))\n", "schedule = PowerSchedule()\n", "\n", "lang_fuzzer = LangFuzzer([valid_seed.data], mutator, schedule)\n", "\n", "start = time.time()\n", "lang_fuzzer.runs(runner, trials=n)\n", "end = time.time()\n", "\n", "\"It took LangFuzzer %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "We observe that structural mutation is *sooo very slow*. This is despite our time budget of 200ms for parsing. In contrast, our blackbox fuzzer alone can generate about 10k inputs per second!" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:00.180685Z", "iopub.status.busy": "2024-05-16T11:22:00.180517Z", "iopub.status.idle": "2024-05-16T11:22:00.284263Z", "shell.execute_reply": "2024-05-16T11:22:00.283922Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took a blackbox fuzzer 0.10 seconds to generate and execute 300 inputs.'" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "mutator = Mutator()\n", "schedule = PowerSchedule()\n", "\n", "blackFuzzer = AdvancedMutationFuzzer([valid_seed.data], mutator, schedule)\n", "\n", "start = time.time()\n", "blackFuzzer.runs(runner, trials=n)\n", "end = time.time()\n", "\n", "\"It took a blackbox fuzzer %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Indeed, our blackbox fuzzer is done in the blink of an eye.\n", "\n", "***Try it***. We can deal with this overhead using [deferred parsing](https://arxiv.org/abs/1811.09447). Instead of wasting time in the beginning of the fuzzing campaign when a byte-level mutator would make efficient progress, deferred parsing suggests to invest time in structural mutation only later in the fuzzing campaign when it becomes viable." ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:00.286322Z", "iopub.status.busy": "2024-05-16T11:22:00.286179Z", "iopub.status.idle": "2024-05-16T11:22:00.288667Z", "shell.execute_reply": "2024-05-16T11:22:00.288306Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'During this fuzzing campaign, the blackbox fuzzer covered 105 statements.'" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "blackbox_coverage = len(runner.coverage())\n", "\"During this fuzzing campaign, the blackbox fuzzer covered %d statements.\" % blackbox_coverage" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let's print some stats for our fuzzing campaigns. Since we'll need to print stats more often later, we should wrap this into a function. In order to measure coverage, we import the [population_coverage](Coverage.ipynb#Coverage-of-Basic-Fuzzing) function. It takes a set of inputs and a Python function, executes the inputs on that function and collects coverage information. Specifically, it returns a tuple `(all_coverage, cumulative_coverage)` where `all_coverage` is the set of statements covered by all inputs, and `cumulative_coverage` is the number of statements covered as the number of executed inputs increases. We are just interested in the latter to plot coverage over time." ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:00.290642Z", "iopub.status.busy": "2024-05-16T11:22:00.290516Z", "iopub.status.idle": "2024-05-16T11:22:00.292281Z", "shell.execute_reply": "2024-05-16T11:22:00.291985Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Coverage import population_coverage" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:00.294095Z", "iopub.status.busy": "2024-05-16T11:22:00.293961Z", "iopub.status.idle": "2024-05-16T11:22:00.297209Z", "shell.execute_reply": "2024-05-16T11:22:00.296845Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def print_stats(fuzzer, parser: Parser) -> None:\n", " coverage, _ = population_coverage(fuzzer.inputs, my_parser)\n", "\n", " has_structure = 0\n", " for seed in fuzzer.inputs:\n", " # reuse memoized information\n", " if hasattr(seed, \"has_structure\"):\n", " if seed.has_structure: \n", " has_structure += 1\n", " else:\n", " if isinstance(seed, str):\n", " seed = Seed(seed)\n", " try:\n", " with Timeout(0.2):\n", " next(parser.parse(seed.data)) # type: ignore\n", " has_structure += 1\n", " except (SyntaxError, TimeoutError):\n", " pass\n", "\n", " print(\"From the %d generated inputs, %d (%0.2f%%) can be parsed.\\n\"\n", " \"In total, %d statements are covered.\" % (\n", " len(fuzzer.inputs),\n", " has_structure,\n", " 100 * has_structure / len(fuzzer.inputs),\n", " len(coverage)))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "For LangFuzzer, let's see how many of the inputs generated by LangFuzz are valid (i.e., parsable) and how many statements were covered." ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:00.299113Z", "iopub.status.busy": "2024-05-16T11:22:00.299010Z", "iopub.status.idle": "2024-05-16T11:22:00.411545Z", "shell.execute_reply": "2024-05-16T11:22:00.411223Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "From the 300 generated inputs, 169 (56.33%) can be parsed.\n", "In total, 91 statements are covered.\n" ] } ], "source": [ "print_stats(lang_fuzzer, EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "What are the stats for the mutational fuzzer that uses only byte-level mutation (and no grammars)?" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:00.413498Z", "iopub.status.busy": "2024-05-16T11:22:00.413355Z", "iopub.status.idle": "2024-05-16T11:22:06.052781Z", "shell.execute_reply": "2024-05-16T11:22:06.052178Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "From the 300 generated inputs, 31 (10.33%) can be parsed.\n", "In total, 149 statements are covered.\n" ] } ], "source": [ "print_stats(blackFuzzer, EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "***Summary***. Our fragment-level blackbox fuzzer (LangFuzzer) generates *more valid inputs* but achieves *less code coverage* than a fuzzer with our byte-level fuzzer. So, there is some value in generating inputs that do not stick to the provided grammar. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Integration with Greybox Fuzzing\n", "\n", "In the following we integrate fragment-level blackbox fuzzing (LangFuzz-style) with [byte-level greybox fuzzing](GreyboxFuzzer.ipynb#Greybox-Mutation-based-Fuzzer) (AFL-style). The additional coverage-feedback might allow us to increase code coverage more quickly.\n", "\n", "A [greybox fuzzer](GreyboxFuzzer.ipynb#Greybox-Mutation-based-Fuzzer) adds to the seed population all generated inputs which increase code coverage. Inputs are generated in two stages, stacking up to four structural mutations and up to 32 byte-level mutations." ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:06.056388Z", "iopub.status.busy": "2024-05-16T11:22:06.055650Z", "iopub.status.idle": "2024-05-16T11:22:06.061033Z", "shell.execute_reply": "2024-05-16T11:22:06.060590Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class GreyboxGrammarFuzzer(GreyboxFuzzer):\n", " \"\"\"Greybox fuzzer using grammars.\"\"\"\n", "\n", " def __init__(self, seeds: List[str],\n", " byte_mutator: Mutator, tree_mutator: FragmentMutator,\n", " schedule: PowerSchedule) -> None:\n", " \"\"\"Constructor.\n", " `seeds` - set of inputs to mutate.\n", " `byte_mutator` - a byte-level mutator.\n", " `tree_mutator` = a tree-level mutator.\n", " `schedule` - a power schedule.\n", " \"\"\"\n", " super().__init__(seeds, byte_mutator, schedule)\n", " self.tree_mutator = tree_mutator\n", "\n", " def create_candidate(self) -> str:\n", " \"\"\"Returns an input generated by structural mutation \n", " of a seed in the population\"\"\"\n", " seed = cast(SeedWithStructure, self.schedule.choose(self.population))\n", "\n", " # Structural mutation\n", " trials = random.randint(0, 4)\n", " for i in range(trials):\n", " seed = self.tree_mutator.mutate(seed)\n", "\n", " # Byte-level mutation\n", " candidate = seed.data\n", " if trials == 0 or not seed.has_structure or random.randint(0, 1) == 1:\n", " dumb_trials = min(len(seed.data), 1 << random.randint(1, 5))\n", " for i in range(dumb_trials):\n", " candidate = self.mutator.mutate(candidate)\n", "\n", " return candidate" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let's run our integrated fuzzer with the [standard byte-level mutator](GreyboxFuzzer.ipynb#Mutator-and-Seed) and our [fragment-based structural mutator](#Fragment-based-Mutation) that was introduced above." ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:06.063575Z", "iopub.status.busy": "2024-05-16T11:22:06.063001Z", "iopub.status.idle": "2024-05-16T11:22:07.667191Z", "shell.execute_reply": "2024-05-16T11:22:07.666843Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'It took the greybox grammar fuzzer 1.60 seconds to generate and execute 300 inputs.'" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "byte_mutator = Mutator()\n", "tree_mutator = FragmentMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))\n", "schedule = PowerSchedule()\n", "\n", "gg_fuzzer = GreyboxGrammarFuzzer([valid_seed.data], byte_mutator, tree_mutator, schedule)\n", "\n", "start = time.time()\n", "gg_fuzzer.runs(runner, trials=n)\n", "end = time.time()\n", "\n", "\"It took the greybox grammar fuzzer %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:07.669339Z", "iopub.status.busy": "2024-05-16T11:22:07.669186Z", "iopub.status.idle": "2024-05-16T11:22:08.417075Z", "shell.execute_reply": "2024-05-16T11:22:08.416484Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "From the 300 generated inputs, 2 (0.67%) can be parsed.\n", "In total, 169 statements are covered.\n" ] } ], "source": [ "print_stats(gg_fuzzer, EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "***Summary***. Our structural greybox fuzzer \n", "* runs faster than the fragment-based LangFuzzer,\n", "* achieves more coverage than both the fragment-based LangFuzzer and the vanilla blackbox mutational fuzzer, and\n", "* generates fewer valid inputs than even the vanilla blackbox mutational fuzzer." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "toc-hr-collapsed": false }, "source": [ "## Fuzzing with Input Regions\n", "\n", "In the previous section, we have seen that most inputs that are added as seeds are *invalid* w.r.t. our given grammar. Yet, in order to apply our fragment-based mutators, we need it to parse the seed successfully. Otherwise, the entire fragment-based approach becomes useless. The question arises: *How can we derive structure from (invalid) seeds that cannot be parsed successfully?*\n", "\n", "To this end, we introduce the idea of _region-based mutation_, first explored with the [AFLSmart](https://github.com/aflsmart/aflsmart) structural greybox fuzzer \\cite{Pham2018aflsmart}. AFLSmart implements byte-level, fragment-based, and region-based mutation as well as validity-based power schedules. We define *region-based mutators*, where a *region* is a consecutive sequence of bytes in the input that can be associated with a symbol in the grammar." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The basic idea of fuzzing with input regions is shown in the following diagram. After _parsing_ an input (into a derivation tree), we can identify _regions_ associated with specific subtrees of the derivation tree. These regions can then be deleted or swapped.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Such regions can also be determined even if the input is incomplete or invalid, allowing for robust parsing." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Determining Symbol Regions\n", "\n", "How can we obtain regions for a given input? The function `chart_parse()` of the [Earley parser](Parser.ipynb#The-Parsing-Algorithm) produces a _parse table_ for a string. For each letter in the string, this table gives the potential symbol and a *region* of neighboring letters that might belong to the same symbol." ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "code_folding": [], "execution": { "iopub.execute_input": "2024-05-16T11:22:08.419661Z", "iopub.status.busy": "2024-05-16T11:22:08.419431Z", "iopub.status.idle": "2024-05-16T11:22:08.488348Z", "shell.execute_reply": "2024-05-16T11:22:08.487925Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None chart[0]\n", "\n", "---\n", "< chart[1]\n", "\n", "---\n", "h chart[2]\n", ":= h |(1,2)\n", ":= |(1,2)\n", "---\n", "t chart[3]\n", ":= t |(2,3)\n", ":= |(1,3)\n", "---\n", "m chart[4]\n", ":= m |(3,4)\n", ":= |(1,4)\n", "---\n", "l chart[5]\n", ":= l |(4,5)\n", ":= |(1,5)\n", "---\n", "> chart[6]\n", ":= < > |(0,6)\n", "---\n", "< chart[7]\n", "\n", "---\n", "b chart[8]\n", ":= b |(7,8)\n", ":= |(7,8)\n", "---\n", "o chart[9]\n", ":= o |(8,9)\n", ":= |(7,9)\n", "---\n", "d chart[10]\n", ":= d |(9,10)\n", ":= |(7,10)\n", "---\n", "y chart[11]\n", ":= y |(10,11)\n", ":= |(7,11)\n", "---\n", "> chart[12]\n", ":= < > |(6,12)\n", "---\n", "< chart[13]\n", "\n", "---\n", "i chart[14]\n", ":= i |(13,14)\n", ":= |(13,14)\n", "---\n", "> chart[15]\n", ":= < > |(12,15)\n", "---\n", "W chart[16]\n", ":= W |(15,16)\n", ":= |(15,16)\n", ":= |(15,16)\n", "---\n", "o chart[17]\n", ":= o |(16,17)\n", ":= |(15,17)\n", ":= |(16,17)\n", ":= |(15,17)\n", ":= |(16,17)\n", ":= |(15,17)\n", "---\n", "r chart[18]\n", ":= r |(17,18)\n", ":= |(15,18)\n", ":= |(16,18)\n", ":= |(17,18)\n", ":= |(15,18)\n", ":= |(16,18)\n", ":= |(17,18)\n", ":= |(15,18)\n", ":= |(16,18)\n", "---\n", "l chart[19]\n", ":= l |(18,19)\n", ":= |(15,19)\n", ":= |(16,19)\n", ":= |(17,19)\n", ":= |(18,19)\n", ":= |(15,19)\n", ":= |(16,19)\n", ":= |(17,19)\n", ":= |(18,19)\n", ":= |(15,19)\n", ":= |(16,19)\n", ":= |(17,19)\n", "---\n", "d chart[20]\n", ":= d |(19,20)\n", ":= |(15,20)\n", ":= |(16,20)\n", ":= |(17,20)\n", ":= |(18,20)\n", ":= |(19,20)\n", ":= |(15,20)\n", ":= |(16,20)\n", ":= |(17,20)\n", ":= |(18,20)\n", ":= |(19,20)\n", ":= |(15,20)\n", ":= |(16,20)\n", ":= |(17,20)\n", ":= |(18,20)\n", "---\n", "< chart[21]\n", "\n", "---\n", "/ chart[22]\n", "\n", "---\n", "i chart[23]\n", ":= i |(22,23)\n", ":= |(22,23)\n", "---\n", "> chart[24]\n", ":= < / > |(20,24)\n", ":= |(12,24)\n", "---\n", "< chart[25]\n", "\n", "---\n", "b chart[26]\n", ":= b |(25,26)\n", ":= |(25,26)\n", "---\n", "r chart[27]\n", ":= r |(26,27)\n", ":= |(25,27)\n", "---\n", "/ chart[28]\n", "\n", "---\n", "> chart[29]\n", ":= < / > |(24,29)\n", ":= |(24,29)\n", ":= |(12,29)\n", "---\n", "> chart[30]\n", "\n", "---\n", "/ chart[31]\n", "\n", "---\n", "b chart[32]\n", "\n", "---\n", "o chart[33]\n", "\n", "---\n", "d chart[34]\n", "\n", "---\n", "y chart[35]\n", "\n", "---\n", "> chart[36]\n", "\n", "---\n", "< chart[37]\n", "\n", "---\n", "/ chart[38]\n", "\n", "---\n", "h chart[39]\n", "\n", "---\n", "t chart[40]\n", "\n", "---\n", "m chart[41]\n", "\n", "---\n", "l chart[42]\n", "\n", "---\n", "> chart[43]\n", "\n", "---\n" ] } ], "source": [ "invalid_seed = Seed(\"World
>/body>\")\n", "parser = EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS)\n", "table = parser.chart_parse(invalid_seed.data, parser.start_symbol())\n", "for column in table:\n", " print(column)\n", " print(\"---\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The number of columns in this table that are associated with potential symbols correspond to the number of letters that could be parsed successfully. In other words, we can use this table to compute the longest parsable substring." ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.490691Z", "iopub.status.busy": "2024-05-16T11:22:08.490512Z", "iopub.status.idle": "2024-05-16T11:22:08.493543Z", "shell.execute_reply": "2024-05-16T11:22:08.493210Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'World
>/body>'\n" ] }, { "data": { "text/plain": [ "'World
'" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cols = [col for col in table if col.states]\n", "parsable = invalid_seed.data[:len(cols)-1]\n", "\n", "print(\"'%s'\" % invalid_seed)\n", "parsable" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "From this, we can compute the *degree of validity* for an input." ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.495219Z", "iopub.status.busy": "2024-05-16T11:22:08.495131Z", "iopub.status.idle": "2024-05-16T11:22:08.497628Z", "shell.execute_reply": "2024-05-16T11:22:08.497370Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'67.4% of the string can be parsed successfully.'" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "validity = 100 * len(parsable) / len(invalid_seed.data)\n", "\n", "\"%0.1f%% of the string can be parsed successfully.\" % validity" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "***Summary***. Unlike input fragments, input regions can be derived even if the parser fails to generate the entire parse tree." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "tags": [] }, "source": [ "### Region-Based Mutation\n", "\n", "To fuzz invalid seeds, the region-based mutator associates symbols from the grammar with regions (i.e., indexed substrings) in the seed. The [overridden](#Building-the-Fragment-Pool) method `add_to_fragment_pool()` first tries to mine the fragments from the seed. If this fails, the region mutator uses [Earley parser](Parser.ipynb#The-Parsing-Algorithm) to derive the parse table. For each column (i.e., letter), it extracts the symbols and corresponding regions. This allows the mutator to store the set of regions with each symbol." ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.499459Z", "iopub.status.busy": "2024-05-16T11:22:08.499366Z", "iopub.status.idle": "2024-05-16T11:22:08.501481Z", "shell.execute_reply": "2024-05-16T11:22:08.501177Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class SeedWithRegions(SeedWithStructure):\n", " \"\"\"Seeds augmented with structure info\"\"\"\n", "\n", " def __init__(self, data: str) -> None:\n", " super().__init__(data)\n", " self.has_regions = False\n", " self.regions: Dict[str, Set] = {}" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.503216Z", "iopub.status.busy": "2024-05-16T11:22:08.503071Z", "iopub.status.idle": "2024-05-16T11:22:08.506695Z", "shell.execute_reply": "2024-05-16T11:22:08.506354Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class RegionMutator(FragmentMutator):\n", " def add_to_fragment_pool(self, seed: SeedWithRegions) -> None: # type: ignore\n", " \"\"\"Mark fragments and regions in a seed file\"\"\"\n", " super().add_to_fragment_pool(seed)\n", " if not seed.has_structure:\n", " try:\n", " with Timeout(0.2):\n", " seed.regions = {k: set() for k in self.parser.cgrammar}\n", " for column in self.parser.chart_parse(seed.data,\n", " self.parser.start_symbol()):\n", " for state in column.states:\n", " if (not self.is_excluded(state.name) and\n", " state.e_col.index - state.s_col.index > 1 and\n", " state.finished()):\n", " seed.regions[state.name].add((state.s_col.index,\n", " state.e_col.index))\n", " seed.has_regions = True\n", " except TimeoutError:\n", " seed.has_regions = False\n", " else:\n", " seed.has_regions = False" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "This is how these regions look like for our invalid seed. A region consists of a start and end index in the seed string." ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.508517Z", "iopub.status.busy": "2024-05-16T11:22:08.508420Z", "iopub.status.idle": "2024-05-16T11:22:08.587933Z", "shell.execute_reply": "2024-05-16T11:22:08.587650Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "|-(16,20) : orld\n", "|-(12,24) : World\n", "|-(17,20) : rld\n", "|-(18,20) : ld\n", "|-(16,19) : orl\n", "|-(15,17) : Wo\n", "|-(12,29) : World
\n", "|-(17,19) : rl\n", "|-(15,20) : World\n", "|-(24,29) :
\n", "|-(15,19) : Worl\n", "|-(16,18) : or\n", "|-(15,18) : Wor\n", "\n", "|-(6,12) : \n", "|-(12,15) : \n", "|-(0,6) : \n", "\n", "|-(24,29) :
\n", "\n", "|-(20,24) :
\n", "\n", "\n", "\n", "\n", "\n" ] } ], "source": [ "invalid_seed = SeedWithRegions(\"World
>/body>\")\n", "mutator = RegionMutator(parser)\n", "mutator.add_to_fragment_pool(invalid_seed)\n", "for symbol in invalid_seed.regions: # type: ignore\n", " print(symbol)\n", " for (s, e) in invalid_seed.regions[symbol]: # type: ignore\n", " print(\"|-(%d,%d) : %s\" % (s, e, invalid_seed.data[s:e]))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Now that we know which regions in the seed belong to which symbol, we can define region-based swap and delete operators." ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.590330Z", "iopub.status.busy": "2024-05-16T11:22:08.590179Z", "iopub.status.idle": "2024-05-16T11:22:08.593860Z", "shell.execute_reply": "2024-05-16T11:22:08.593541Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class RegionMutator(RegionMutator):\n", " def swap_fragment(self, seed: SeedWithRegions) -> SeedWithRegions: # type: ignore\n", " \"\"\"Chooses a random region and swaps it with a fragment\n", " that starts with the same symbol\"\"\"\n", " if not seed.has_structure and seed.has_regions:\n", " regions = [r for r in seed.regions\n", " if (len(seed.regions[r]) > 0 and\n", " len(self.fragments[r]) > 0)]\n", " if len(regions) == 0:\n", " return seed\n", "\n", " key = random.choice(list(regions))\n", " s, e = random.choice(list(seed.regions[key]))\n", " swap_structure = random.choice(self.fragments[key])\n", " swap_string = tree_to_string(swap_structure)\n", " new_seed = SeedWithRegions(seed.data[:s] + swap_string + seed.data[e:])\n", " new_seed.has_structure = False\n", " new_seed.has_regions = False\n", " return new_seed\n", " else:\n", " return super().swap_fragment(seed) # type: ignore" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.595621Z", "iopub.status.busy": "2024-05-16T11:22:08.595462Z", "iopub.status.idle": "2024-05-16T11:22:08.598491Z", "shell.execute_reply": "2024-05-16T11:22:08.598173Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class RegionMutator(RegionMutator):\n", " def delete_fragment(self, seed: SeedWithRegions) -> SeedWithRegions: # type: ignore\n", " \"\"\"Deletes a random region\"\"\"\n", " if not seed.has_structure and seed.has_regions:\n", " regions = [r for r in seed.regions\n", " if len(seed.regions[r]) > 0]\n", " if len(regions) == 0:\n", " return seed\n", "\n", " key = random.choice(list(regions))\n", " s, e = (0, 0)\n", " while (e - s < 2):\n", " s, e = random.choice(list(seed.regions[key]))\n", "\n", " new_seed = SeedWithRegions(seed.data[:s] + seed.data[e:])\n", " new_seed.has_structure = False\n", " new_seed.has_regions = False\n", " return new_seed\n", " else:\n", " return super().delete_fragment(seed) # type: ignore" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let's try our new region-based mutator. We add a simple, valid seed to the fragment pool and attempt to mutate the invalid seed." ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.600627Z", "iopub.status.busy": "2024-05-16T11:22:08.600463Z", "iopub.status.idle": "2024-05-16T11:22:08.684847Z", "shell.execute_reply": "2024-05-16T11:22:08.684421Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "World
>/body>\n" ] }, { "data": { "text/plain": [ "WTextd
>/body>" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "simple_seed = SeedWithRegions(\"Text\")\n", "mutator = RegionMutator(parser)\n", "mutator.add_to_fragment_pool(simple_seed)\n", "\n", "print(invalid_seed)\n", "mutator.mutate(invalid_seed)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "***Summary***. We can use the Earley parser to generate a parse table and assign regions in the input to symbols in the grammar. Our region mutators can substitute these regions with fragments from the fragment pool that start with the same symbol, or delete these regions entirely.\n", "\n", "***Try it***. Implement a region pool (similar to the fragment pool) and a `swap_region()` mutator.\n", "You can execute your own code by opening this chapter as Jupyter notebook." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Integration with Greybox Fuzzing\n", "\n", "Let's try our shiny new region mutator by integrating it with our [structure-aware greybox fuzzer](#Integration-with-Greybox-Fuzzing)." ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:08.687271Z", "iopub.status.busy": "2024-05-16T11:22:08.687047Z", "iopub.status.idle": "2024-05-16T11:22:22.134019Z", "shell.execute_reply": "2024-05-16T11:22:22.133686Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'\\nIt took the structural greybox fuzzer with region mutator\\n13.44 seconds to generate and execute 300 inputs.\\n'" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "byte_mutator = Mutator()\n", "tree_mutator = RegionMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))\n", "schedule = PowerSchedule()\n", "\n", "regionFuzzer = GreyboxGrammarFuzzer([valid_seed.data], byte_mutator, tree_mutator, schedule)\n", "\n", "start = time.time()\n", "regionFuzzer.runs(runner, trials = n)\n", "end = time.time()\n", "\n", "\"\"\"\n", "It took the structural greybox fuzzer with region mutator\n", "%0.2f seconds to generate and execute %d inputs.\n", "\"\"\" % (end - start, n)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "We can see that the structural greybox fuzzer with region-based mutator is slower than the [fragment-based mutator alone](#Fragment-based-Fuzzing). This is because region-based structural mutation is applicable for *all seeds*. In contrast, fragment-based mutators were applicable only for tiny number of parsable seeds. Otherwise, only (very efficient) byte-level mutators were applied.\n", "\n", "Let's also print the average degree of validity for the seeds in the population." ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:22.136163Z", "iopub.status.busy": "2024-05-16T11:22:22.135977Z", "iopub.status.idle": "2024-05-16T11:22:22.138801Z", "shell.execute_reply": "2024-05-16T11:22:22.138485Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def print_more_stats(fuzzer: GreyboxGrammarFuzzer, parser: EarleyParser):\n", " print_stats(fuzzer, parser)\n", " validity = 0.0\n", " total = 0\n", " for seed in fuzzer.population:\n", " if not seed.data: continue\n", " table = parser.chart_parse(seed.data, parser.start_symbol())\n", " cols = [col for col in table if col.states]\n", " parsable = invalid_seed.data[:len(cols)-1]\n", " validity += len(parsable) / len(seed.data)\n", " total += 1\n", "\n", " print(\"On average, %0.1f%% of a seed in the population can be successfully parsed.\" % (100 * validity / total))" ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:22.140810Z", "iopub.status.busy": "2024-05-16T11:22:22.140668Z", "iopub.status.idle": "2024-05-16T11:22:23.667327Z", "shell.execute_reply": "2024-05-16T11:22:23.666989Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "From the 300 generated inputs, 3 (1.00%) can be parsed.\n", "In total, 163 statements are covered.\n", "On average, 9.8% of a seed in the population can be successfully parsed.\n" ] } ], "source": [ "print_more_stats(regionFuzzer, parser)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "***Summary***. Compared to fragment-based mutation, a greybox fuzzer with region-based mutation achieves *higher coverage* but generates a *smaller number of valid inputs*. The higher coverage is explained by leveraging at least *some* structure for seeds that cannot be parsed successfully.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Focusing on Valid Seeds\n", "\n", "In the previous section, we have a problem: The low (degree of) validity. To address this problem, a _validity-based power schedule_ assigns more [energy](GreyboxFuzzer.ipynb#Power-Schedules) to seeds that have a higher degree of validity. In other words, the fuzzer _spends more time fuzzing seeds that are more valid_." ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:23.669517Z", "iopub.status.busy": "2024-05-16T11:22:23.669354Z", "iopub.status.idle": "2024-05-16T11:22:23.671220Z", "shell.execute_reply": "2024-05-16T11:22:23.670859Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import math" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:23.672980Z", "iopub.status.busy": "2024-05-16T11:22:23.672843Z", "iopub.status.idle": "2024-05-16T11:22:23.676687Z", "shell.execute_reply": "2024-05-16T11:22:23.676328Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class AFLSmartSchedule(PowerSchedule):\n", " def __init__(self, parser: EarleyParser, exponent: float = 1.0):\n", " self.parser = parser\n", " self.exponent = exponent\n", "\n", " def parsable(self, seed: Seed) -> str:\n", " \"\"\"Returns the substring that is parsable\"\"\"\n", " table = self.parser.chart_parse(seed.data, self.parser.start_symbol())\n", " cols = [col for col in table if col.states]\n", " return seed.data[:len(cols)-1]\n", "\n", " def degree_of_validity(self, seed: Seed) -> float:\n", " \"\"\"Returns the proportion of a seed that is parsable\"\"\"\n", " if not hasattr(seed, 'validity'):\n", " seed.validity = (len(self.parsable(seed)) / len(seed.data) # type: ignore\n", " if len(seed.data) > 0 else 0)\n", " return seed.validity # type: ignore\n", "\n", " def assignEnergy(self, population: Sequence[Seed]):\n", " \"\"\"Assign exponential energy proportional to degree of validity\"\"\"\n", " for seed in population:\n", " seed.energy = ((self.degree_of_validity(seed) / math.log(len(seed.data))) ** self.exponent\n", " if len(seed.data) > 1 else 0)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let's play with the degree of validity by passing in a valid seed ..." ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:23.678670Z", "iopub.status.busy": "2024-05-16T11:22:23.678547Z", "iopub.status.idle": "2024-05-16T11:22:23.754211Z", "shell.execute_reply": "2024-05-16T11:22:23.753721Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Entire seed: Text\n", " Parsable: Text\n" ] }, { "data": { "text/plain": [ "'Degree of validity: 100.00%'" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "smart_schedule = AFLSmartSchedule(parser)\n", "print(\"%11s: %s\" % (\"Entire seed\", simple_seed))\n", "print(\"%11s: %s\" % (\"Parsable\", smart_schedule.parsable(simple_seed)))\n", "\n", "\"Degree of validity: %0.2f%%\" % (100 * smart_schedule.degree_of_validity(simple_seed))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "... and an invalid seed." ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:23.756357Z", "iopub.status.busy": "2024-05-16T11:22:23.756229Z", "iopub.status.idle": "2024-05-16T11:22:23.772756Z", "shell.execute_reply": "2024-05-16T11:22:23.772449Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Entire seed: World
>/body>\n", " Parsable: World
\n" ] }, { "data": { "text/plain": [ "'Degree of validity: 67.44%'" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"%11s: %s\" % (\"Entire seed\", invalid_seed))\n", "print(\"%11s: %s\" % (\"Parsable\", smart_schedule.parsable(invalid_seed)))\n", "\n", "\"Degree of validity: %0.2f%%\" % (100 * smart_schedule.degree_of_validity(invalid_seed))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Excellent. We can compute the degree of validity as the proportion of the string that can be parsed. \n", "\n", "Let's plug the validity-based power schedule into the structure-aware greybox fuzzer." ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:23.774559Z", "iopub.status.busy": "2024-05-16T11:22:23.774443Z", "iopub.status.idle": "2024-05-16T11:22:45.601481Z", "shell.execute_reply": "2024-05-16T11:22:45.601039Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'It took AFLSmart 21.82 seconds to generate and execute 300 inputs.'" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "byte_mutator = Mutator()\n", "tree_mutator = RegionMutator(EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS))\n", "schedule = AFLSmartSchedule(parser)\n", "\n", "aflsmart_fuzzer = GreyboxGrammarFuzzer([valid_seed.data], byte_mutator, \n", " tree_mutator, schedule)\n", "\n", "start = time.time()\n", "aflsmart_fuzzer.runs(runner, trials=n)\n", "end = time.time()\n", "\n", "\"It took AFLSmart %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)" ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:45.603531Z", "iopub.status.busy": "2024-05-16T11:22:45.603382Z", "iopub.status.idle": "2024-05-16T11:22:47.892805Z", "shell.execute_reply": "2024-05-16T11:22:47.892447Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "From the 300 generated inputs, 12 (4.00%) can be parsed.\n", "In total, 161 statements are covered.\n", "On average, 18.8% of a seed in the population can be successfully parsed.\n" ] } ], "source": [ "print_more_stats(aflsmart_fuzzer, parser)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "***Summary***. Indeed, by spending more time fuzzing seeds with a higher degree of validity, we also generate inputs with a higher degree of validity. More inputs are entirely valid w.r.t. the given grammar.\n", "\n", "***Read up***. Learn more about region-based fuzzing, deferred parsing, and validity-based schedules in the original AFLSmart paper: \"[Smart Greybox Fuzzing](https://arxiv.org/abs/1811.09447)\" by Pham and co-authors. Download and improve AFLSmart: [https://github.com/aflsmart/aflsmart](https://github.com/aflsmart/aflsmart)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Mining Seeds\n", "\n", "By now, it should have become clear that the _choice of seeds_ can very much influence the success of fuzzing. One aspect is _variability_ – our seeds should cover as many different features as possible in order to increase coverage. Another aspect, however, is the _likelihood of a seed to induce errors_ – that is, if a seed was involved in causing a failure before, then a mutation of this very seed may be likely to induce failures again. This is because fixes for past failures typically are successful in letting the concrete failure no longer occur, but sometimes may fail to capture all conditions under which a failure may occur. Hence, even if the original failure is fixed, the likelihood of an error in the _surroundings_ of the original failure-inducing input is still higher. It thus pays off to use as seeds _inputs that are known to have caused failures before_.\n", "\n", "To put things in context, Holler's _LangFuzz_ fuzzer used as seeds JavaScript inputs from CVE reports. These were published as failure-inducing inputs at a time when the error already had been fixed; thus they could do no harm anymore. Yet, by using such inputs as seeds, LangFuzz would create plenty of mutations and recombinations of all their features, many of which would (and do) find errors again and again." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Lessons Learned\n", "\n", "* A **dictionary** is useful to inject important keywords into the generated inputs.\n", "\n", "* **Fragment-based mutation** first disassembles seeds into fragments, and reassembles these fragments to generate new inputs. A *fragment* is a subtree in the seed's parse tree. However, fragment-based mutation requires that the seeds can be parsed successfully, which may not be true for seeds discovered by a coverage-based greybox fuzzer.\n", "\n", "* **Region-based mutation** marks regions in the input as belonging to a certain symbol in the grammar. For instance, it may identify a substring '
' as closing tag. These regions can then be deleted or substituted by fragments or regions belonging to the same symbol. Unlike fragment-based mutation, region-based mutation is applicable to *all* seeds - even those that can be parsed only partially. However, the degree of validity is still quite low for the generated inputs.\n", "\n", "* A **validity-based power schedule** invests more energy into seeds with a higher degree of validity. The inputs that are generated also have a higher degree of validity.\n", "\n", "* **Mining seeds** from repositories of previous failure-inducing inputs results in input fragments associated with past failures, raising the likelihood to find more failures in the vicinity." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "tags": [] }, "source": [ "## Background\n", "\n", "This chapter builds on the following two works:\n", "\n", "* The _LangFuzz_ fuzzer \\cite{Holler2012} is an efficient (and effective!) grammar-based fuzzer for (mostly) JavaScript. It uses the grammar for parsing seeds and recombining their inputs with generated parts and found 2,600 bugs in JavaScript interpreters to date.\n", "\n", "* Smart greybox fuzzing ([AFLSmart](https://github.com/aflsmart/aflsmart)) brings together coverage-based fuzzing and grammar-based (structural) fuzzing, as described in \\cite{Pham2018aflsmart}. The resulting AFLSMART tool has discovered 42 zero-day vulnerabilities in widely-used, well-tested tools and libraries; so far 17 CVEs were assigned." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "tags": [] }, "source": [ "Recent fuzzing work also brings together grammar-based fuzzing and coverage.\n", "\n", "* _Superion_ \\cite{Wang2019superion} is equivalent to our section \"Integration with Greybox Fuzzing\", as above – that is, a combination of LangFuzz and Greybox Fuzzing, but no AFL-style byte-level mutation. Superion can improve the code coverage (i.e., 16.7% and 8.8% in line and function coverage) and bug-finding capability over AFL and jsfunfuzz. According to the authors, they found 30 new bugs, among which they discovered 21 new vulnerabilities with 16 CVEs assigned and 3.2K USD bug bounty rewards received.\n", "\n", "* _Nautilus_ \\cite{Aschermann2019nautilus} also combines grammar-based fuzzing with coverage feedback. It maintains the parse tree for all seeds and generated inputs. To allow AFL-style byte-level mutations, it \"collapses\" subtrees back to byte-level representations. This has the advantage of not having to re-parse generated seeds; however, over time, Nautilus degenerates to structure-unaware greybox fuzzing because it does not re-parse collapsed subtrees to reconstitute input structure for later seeds where most of the parse tree is collapsed. Nautilus identified bugs in mruby, PHP, ChakraCore, and in Lua; reporting these bugs was awarded with a sum of 2600 USD and 6 CVEs were assigned.\n", "\n", "* [FormatFuzzer](https://uds-se.github.io/FormatFuzzer/) is a framework for high-efficiency, high-quality generation and parsing of binary inputs. It takes a binary template that describes the format of a binary input and generates an executable that produces and parses the given binary format. From a binary template for GIF, for instance, FormatFuzzer produces a GIF generator - also known as GIF fuzzer. Generators produced by FormatFuzzer are highly efficient, producing thousands of valid test inputs per second. FormatFuzzer operates in black-box settings, but can also integrate with AFL to produce valid inputs that also aim for maximum coverage." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" }, "tags": [] }, "source": [ "## Synopsis\n", "\n", "This chapter introduces advanced methods for language-based grey-box fuzzing inspired by the _LangFuzz_ and _AFLSmart_ fuzzers." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Fuzzing with Dictionaries\n", "\n", "Rather than mutating strings randomly, the `DictMutator` class allows inserting tokens from a dictionary, thus increasing fuzzer performance. The dictionary comes as a list of strings, out of which random elements are picked and inserted - in addition to the given mutations such as deleting or inserting individual bytes." ] }, { "cell_type": "code", "execution_count": 73, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:47.894929Z", "iopub.status.busy": "2024-05-16T11:22:47.894784Z", "iopub.status.idle": "2024-05-16T11:22:47.896863Z", "shell.execute_reply": "2024-05-16T11:22:47.896521Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "dict_mutator = DictMutator([\"\", \"\", \"\", \"='a'\"])" ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:47.898556Z", "iopub.status.busy": "2024-05-16T11:22:47.898440Z", "iopub.status.idle": "2024-05-16T11:22:47.900482Z", "shell.execute_reply": "2024-05-16T11:22:47.900246Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello</tile></head><body>World<br/></body></html>\n", "<html><head><title>Hello</tile></head><body>World<br/></body></html>\n", "<html><head><title>HelloWorld
\n", "HelloWorld
\n", "HelloWorld
\n", "HelloWorld
\n", "HelloWorld
\n", "HelloWorld
<(/html>\n", "HelloWorld
\n", "HelloWorld
\n" ] } ], "source": [ "seeds = [\"HelloWorld
\"]\n", "for i in range(10):\n", " print(dict_mutator.mutate(seeds[0]))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This `DictMutator` can be used as an argument to `GreyboxFuzzer`:" ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:47.902293Z", "iopub.status.busy": "2024-05-16T11:22:47.901987Z", "iopub.status.idle": "2024-05-16T11:22:47.917402Z", "shell.execute_reply": "2024-05-16T11:22:47.917065Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "dict_fuzzer = GreyboxFuzzer(seeds, dict_mutator, PowerSchedule())" ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:47.919562Z", "iopub.status.busy": "2024-05-16T11:22:47.919384Z", "iopub.status.idle": "2024-05-16T11:22:47.924171Z", "shell.execute_reply": "2024-05-16T11:22:47.923842Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "dict_fuzzer_outcome = dict_fuzzer.runs(runner, trials=5)" ] }, { "cell_type": "code", "execution_count": 77, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:47.926243Z", "iopub.status.busy": "2024-05-16T11:22:47.926109Z", "iopub.status.idle": "2024-05-16T11:22:47.927827Z", "shell.execute_reply": "2024-05-16T11:22:47.927572Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "from ClassDiagram import display_class_hierarchy\n", "from Coverage import Location # minor dependency" ] }, { "cell_type": "code", "execution_count": 78, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:47.929792Z", "iopub.status.busy": "2024-05-16T11:22:47.929674Z", "iopub.status.idle": "2024-05-16T11:22:48.540559Z", "shell.execute_reply": "2024-05-16T11:22:48.539868Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "DictMutator\n", "\n", "\n", "DictMutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "insert_from_dictionary()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Mutator\n", "\n", "\n", "Mutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "DictMutator->Mutator\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ignore\n", "display_class_hierarchy([DictMutator],\n", " public_methods=[\n", " Mutator.__init__,\n", " DictMutator.__init__,\n", " ],\n", " types={'DerivationTree': DerivationTree,\n", " 'Location': Location},\n", " project='fuzzingbook')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Fuzzing with Input Fragments\n", "\n", "The `LangFuzzer` class introduces a _language-aware_ fuzzer that can recombine fragments from existing inputs – inspired by the highly effective `LangFuzz` fuzzer. At its core is a `FragmentMutator` class that that takes a [_parser_](Parser.ipynb) as argument:" ] }, { "cell_type": "code", "execution_count": 79, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:48.542950Z", "iopub.status.busy": "2024-05-16T11:22:48.542797Z", "iopub.status.idle": "2024-05-16T11:22:48.546429Z", "shell.execute_reply": "2024-05-16T11:22:48.546028Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "parser = EarleyParser(XML_GRAMMAR, tokens=XML_TOKENS)\n", "mutator = FragmentMutator(parser)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The fuzzer itself is initialized with a list of seeds, the above `FragmentMutator`, and a power schedule:" ] }, { "cell_type": "code", "execution_count": 80, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:48.548562Z", "iopub.status.busy": "2024-05-16T11:22:48.548396Z", "iopub.status.idle": "2024-05-16T11:22:48.551859Z", "shell.execute_reply": "2024-05-16T11:22:48.551459Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "seeds = [\"HelloWorld
\"]\n", "schedule = PowerSchedule()\n", "lang_fuzzer = LangFuzzer(seeds, mutator, schedule)" ] }, { "cell_type": "code", "execution_count": 81, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:48.554004Z", "iopub.status.busy": "2024-05-16T11:22:48.553883Z", "iopub.status.idle": "2024-05-16T11:22:49.041255Z", "shell.execute_reply": "2024-05-16T11:22:49.040918Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HelloWorld
\n", "World\n", "HelloWorld
\n", "HelloWorld
\n", "
\n", "World
\n", "HelloWorld
\n", "World\n", "<head><body>HelloWorld
\n", "HelloWorld
\n" ] } ], "source": [ "for i in range(10):\n", " print(lang_fuzzer.fuzz())" ] }, { "cell_type": "code", "execution_count": 82, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:49.043051Z", "iopub.status.busy": "2024-05-16T11:22:49.042912Z", "iopub.status.idle": "2024-05-16T11:22:49.624473Z", "shell.execute_reply": "2024-05-16T11:22:49.623758Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "LangFuzzer\n", "\n", "\n", "LangFuzzer\n", "\n", "\n", "\n", "create_candidate()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "LangFuzzer->AdvancedMutationFuzzer\n", "\n", "\n", "\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "runs()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer->Fuzzer\n", "\n", "\n", "\n", "\n", "\n", "FragmentMutator\n", "\n", "\n", "FragmentMutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "add_to_fragment_pool()\n", "\n", "\n", "\n", "add_fragment()\n", "\n", "\n", "\n", "count_nodes()\n", "\n", "\n", "\n", "delete_fragment()\n", "\n", "\n", "\n", "is_excluded()\n", "\n", "\n", "\n", "mutate()\n", "\n", "\n", "\n", "recursive_delete()\n", "\n", "\n", "\n", "recursive_swap()\n", "\n", "\n", "\n", "swap_fragment()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Mutator\n", "\n", "\n", "Mutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "FragmentMutator->Mutator\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 82, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ignore\n", "display_class_hierarchy([LangFuzzer, FragmentMutator],\n", " public_methods=[\n", " Fuzzer.__init__,\n", " Fuzzer.fuzz,\n", " Fuzzer.run,\n", " Fuzzer.runs,\n", " AdvancedMutationFuzzer.__init__,\n", " AdvancedMutationFuzzer.fuzz,\n", " GreyboxFuzzer.run,\n", " Mutator.__init__,\n", " FragmentMutator.__init__,\n", " FragmentMutator.add_to_fragment_pool,\n", " ],\n", " types={'DerivationTree': DerivationTree,\n", " 'Location': Location},\n", " project='fuzzingbook')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Fuzzing with Input Regions\n", "\n", "The `GreyboxGrammarFuzzer` class uses two mutators:\n", "* a _tree mutator_ (a `RegionMutator` object) that can parse existing strings to identify _regions_ in that string to be swapped or deleted.\n", "* a _byte mutator_ to apply bit- and character-level mutations." ] }, { "cell_type": "code", "execution_count": 83, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:49.626795Z", "iopub.status.busy": "2024-05-16T11:22:49.626667Z", "iopub.status.idle": "2024-05-16T11:22:49.629312Z", "shell.execute_reply": "2024-05-16T11:22:49.628988Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "tree_mutator = RegionMutator(parser)\n", "byte_mutator = Mutator()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The _schedule_ for the `GreyboxGrammarFuzzer` class can be a regular `PowerSchedule` object. However, a more sophisticated schedule is provided by `AFLSmartSchedule`, which assigns more [energy](GreyboxFuzzer.ipynb#Power-Schedules) to seeds that have a higher degree of validity." ] }, { "cell_type": "code", "execution_count": 84, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:49.631289Z", "iopub.status.busy": "2024-05-16T11:22:49.631123Z", "iopub.status.idle": "2024-05-16T11:22:49.633181Z", "shell.execute_reply": "2024-05-16T11:22:49.632889Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "schedule = AFLSmartSchedule(parser)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The `GreyboxGrammarFuzzer` constructor takes a set of seeds as well as the two mutators and the schedule:" ] }, { "cell_type": "code", "execution_count": 85, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:49.634892Z", "iopub.status.busy": "2024-05-16T11:22:49.634765Z", "iopub.status.idle": "2024-05-16T11:22:49.638323Z", "shell.execute_reply": "2024-05-16T11:22:49.637999Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "aflsmart_fuzzer = GreyboxGrammarFuzzer(seeds, byte_mutator, tree_mutator, schedule)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "As it relies on code coverage, it is typically combined with a `FunctionCoverageRunner`:" ] }, { "cell_type": "code", "execution_count": 86, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:49.640241Z", "iopub.status.busy": "2024-05-16T11:22:49.640148Z", "iopub.status.idle": "2024-05-16T11:22:50.408569Z", "shell.execute_reply": "2024-05-16T11:22:50.408219Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "runner = FunctionCoverageRunner(my_parser)\n", "aflsmart_outcome = aflsmart_fuzzer.runs(runner, trials=5)" ] }, { "cell_type": "code", "execution_count": 87, "metadata": { "execution": { "iopub.execute_input": "2024-05-16T11:22:50.410588Z", "iopub.status.busy": "2024-05-16T11:22:50.410461Z", "iopub.status.idle": "2024-05-16T11:22:50.984805Z", "shell.execute_reply": "2024-05-16T11:22:50.984229Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxGrammarFuzzer\n", "\n", "\n", "GreyboxGrammarFuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "create_candidate()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxFuzzer\n", "\n", "\n", "GreyboxFuzzer\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxGrammarFuzzer->GreyboxFuzzer\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxFuzzer->AdvancedMutationFuzzer\n", "\n", "\n", "\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "runs()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer->Fuzzer\n", "\n", "\n", "\n", "\n", "\n", "AFLSmartSchedule\n", "\n", "\n", "AFLSmartSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "degree_of_validity()\n", "\n", "\n", "\n", "parsable()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PowerSchedule\n", "\n", "\n", "PowerSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AFLSmartSchedule->PowerSchedule\n", "\n", "\n", "\n", "\n", "\n", "RegionMutator\n", "\n", "\n", "RegionMutator\n", "\n", "\n", "\n", "add_to_fragment_pool()\n", "\n", "\n", "\n", "delete_fragment()\n", "\n", "\n", "\n", "swap_fragment()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "FragmentMutator\n", "\n", "\n", "FragmentMutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "add_to_fragment_pool()\n", "\n", "\n", "\n", "add_fragment()\n", "\n", "\n", "\n", "count_nodes()\n", "\n", "\n", "\n", "delete_fragment()\n", "\n", "\n", "\n", "is_excluded()\n", "\n", "\n", "\n", "mutate()\n", "\n", "\n", "\n", "recursive_delete()\n", "\n", "\n", "\n", "recursive_swap()\n", "\n", "\n", "\n", "swap_fragment()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "RegionMutator->FragmentMutator\n", "\n", "\n", "\n", "\n", "\n", "Mutator\n", "\n", "\n", "Mutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "FragmentMutator->Mutator\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 87, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ignore\n", "display_class_hierarchy([GreyboxGrammarFuzzer, AFLSmartSchedule, RegionMutator],\n", " public_methods=[\n", " Fuzzer.__init__,\n", " Fuzzer.fuzz,\n", " Fuzzer.run,\n", " Fuzzer.runs,\n", " AdvancedMutationFuzzer.__init__,\n", " AdvancedMutationFuzzer.fuzz,\n", " GreyboxFuzzer.run,\n", " GreyboxGrammarFuzzer.__init__,\n", " GreyboxGrammarFuzzer.run,\n", " AFLSmartSchedule.__init__,\n", " PowerSchedule.__init__,\n", " Mutator.__init__,\n", " FragmentMutator.__init__,\n", " FragmentMutator.add_to_fragment_pool,\n", " RegionMutator.__init__,\n", " ],\n", " types={'DerivationTree': DerivationTree,\n", " 'Location': Location},\n", " project='fuzzingbook')" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Next Steps\n", "\n", "This chapter closes our discussion of syntactic fuzzing techniques.\n", "\n", "* In the [next chapter](Reducer.ipynb), we discuss how to _reduce failure-inducing inputs_ after a failure, keeping only those portions of the input that are necessary for reproducing the failure.\n", "* The [next part](04_Semantical_Fuzzing.ipynb) will go from syntactical to _semantical_ fuzzing, considering code semantics for targeted test generation." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercises\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" }, "solution2": "hidden", "solution2_first": true }, "source": [ "### Exercise 1: The Big Greybox Fuzzer Shoot-Out\n", "\n", "Use our implementations of greybox techniques and evaluate them on a benchmark. Which technique (and which sub-technique) has which impact and why? Also take into account the specific approaches of Superion \\cite{Wang2019superion} and Nautilus \\cite{Aschermann2019nautilus}, possibly even on the benchmarks used by these approaches." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" }, "solution2": "hidden" }, "source": [ "**Solution.** To be added." ] } ], "metadata": { "ipub": { "bibliography": "fuzzingbook.bib", "toc": true }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.2" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true }, "toc-autonumbering": false, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false }, "vscode": { "interpreter": { "hash": "4185989cf89c47c310c2629adcadd634093b57a2c49dffb5ae8d0d14fa302f2b" } } }, "nbformat": 4, "nbformat_minor": 4 }