{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Greybox Fuzzing\n",
"\n",
"In the [previous chapter](MutationFuzzer.ipynb), we have introduced _mutation-based fuzzing_, a technique that generates fuzz inputs by applying small mutations to given inputs. In this chapter, we show how to _guide_ these mutations towards specific goals such as coverage. The algorithms in this chapter stem from the popular [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/) (AFL) fuzzer, in particular from its [AFLFast](https://github.com/mboehme/aflfast) and [AFLGo](https://github.com/aflgo/aflgo) flavors. We will explore the greybox fuzzing algorithm behind AFL and how we can exploit it to solve various problems for automated vulnerability detection."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:25.627267Z",
"iopub.status.busy": "2025-01-16T09:38:25.627133Z",
"iopub.status.idle": "2025-01-16T09:38:25.713762Z",
"shell.execute_reply": "2025-01-16T09:38:25.713388Z"
},
"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('vBrNT9q2t1Y')"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Prerequisites**\n",
"\n",
"* Reading the introduction on [mutation-based fuzzing](MutationFuzzer.ipynb) is recommended."
]
},
{
"attachments": {},
"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.GreyboxFuzzer import \n",
"```\n",
"\n",
"and then make use of the following features.\n",
"\n",
"\n",
"This chapter introduces advanced methods for grey-box fuzzing inspired by the popular AFL fuzzer. The `GreyboxFuzzer` class has three arguments. First, a list of seed inputs:\n",
"\n",
"```python\n",
">>> seed_input = \"http://www.google.com/search?q=fuzzing\"\n",
">>> seeds = [seed_input]\n",
"```\n",
"Second, a _mutator_ that changes individual parts of the input.\n",
"\n",
"```python\n",
">>> mutator = Mutator()\n",
"```\n",
"Third, a _power schedule_ that assigns fuzzing effort across the population:\n",
"\n",
"```python\n",
">>> schedule = PowerSchedule()\n",
"```\n",
"These three go into the `GreyboxFuzzer` constructor:\n",
"\n",
"```python\n",
">>> greybox_fuzzer = GreyboxFuzzer(seeds=seeds, mutator=mutator, schedule=schedule)\n",
"```\n",
"The `GreyboxFuzzer` class is used in conjunction with a `FunctionCoverageRunner`:\n",
"\n",
"```python\n",
">>> http_runner = FunctionCoverageRunner(http_program)\n",
">>> outcomes = greybox_fuzzer.runs(http_runner, trials=10000)\n",
"```\n",
"After fuzzing, we can inspect the population:\n",
"\n",
"```python\n",
">>> greybox_fuzzer.population[:20]\n",
"[http://www.google.com/search?q=fuzzing,\n",
" http://ww.gomgle.com/searciq=fuzzing,\n",
" |h>Att&p8?wwnOgle.cooarhl~Cp`uzza',\n",
" http2Ot/*gv-VRgogec:om/rearc h\u001f=fu~i\n",
" g,\n",
" http\"Ot/*gv-VRgogecom/rearc h\u001f=u~i\n",
" f,\n",
" http\"Ot/g$vVRgogEecom/#reabc h\u001f=u~a\n",
" f,\n",
" httT`2Ot//:7ev^VRgoec:uom/re!6dctKc= hS;\u000f=fu-yH\n",
" /,\n",
" http'www&go/le.comm/sea:rh*?q=Ftzzifg,\n",
" h4tpw://w.gomMle.m/weazciq=fuezzi.',\n",
" h~Att&p8?ws_nOge.#ooarhBlw~Cp`uzza',\n",
" ht8P:/wwvgkowcom/eacrh?q=f uzzing,\n",
" httT`Ot//:7evVR\"goec:uom/re!dctKc=hS;=fu-yH\n",
" I,\n",
" htp://ww.go/gl%.otm/wearch?q=f5zzing,\n",
" httU`Ot//:7evVR\"goec:uo2Jm/re#dctKc=hS;=fu-yH\n",
" I,\n",
" ht4p://ww.gomgle.co.serciq=fuzzing,\n",
" httpp\"OtSo*gv'VRgkg;eom/earcR >\n",
" =u|in\n",
" f,\n",
" htP:/wwvgMolwcomeachq=f uzzine,\n",
" htt://[/wwgwC6.]gogleg/lE.bo/_sEarcq9Afqwz\"king,\n",
" httT`Ot//:7evVR\"goec:uom/re!dctKc=hS;=fu-yH\n",
" I,\n",
" tPK:,wIfvglwvc#omeaBc`9qOin]\n",
"```\n",
"Besides the simple `PowerSchedule`, we can have advanced power schedules.\n",
"\n",
"* `AFLFastSchedule` assigns high energy to \"unusual\" paths not taken very often.\n",
"* `AFLGoSchedule` assigns high energy to paths close to uncovered program locations. \n",
"\n",
"The `AFLGoSchedule` class constructor requires a `distance` metric from each node towards target locations, as determined via analysis of the program code. See the chapter for details.\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## AFL: An Effective Greybox Fuzzer\n",
"\n",
"The algorithms in this chapter stem from the popular [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/) (AFL) fuzzer."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"AFL is a *mutation-based fuzzer*. Meaning, AFL generates new inputs by slightly modifying a seed input (i.e., mutation), or by joining the first half of one input with the second half of another (i.e., splicing)."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"AFL is also a *greybox fuzzer* (not blackbox nor whitebox). Meaning, AFL leverages coverage-feedback to learn how to reach deeper into the program. It is not entirely blackbox because AFL leverages at least *some* program analysis. It is not entirely whitebox either because AFL does not build on heavyweight program analysis or constraint solving. Instead, AFL uses lightweight program instrumentation to glean some information about the (branch) coverage of a generated input.\n",
"If a generated input increases coverage, it is added to the seed corpus for further fuzzing."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"To instrument a program, AFL injects a piece of code right after every conditional jump instruction. When executed, this so-called trampoline assigns the exercised branch a unique identifier and increments a counter that is associated with this branch. For efficiency, only a coarse branch hit count is maintained. In other words, for each input the fuzzer knows which branches and roughly how often they are exercised. \n",
"The instrumentation is usually done at compile-time, i.e., when the program source code is compiled to an executable binary. However, it is possible to run AFL on non-instrumented binaries using tools such as a virtual machine (e.g., [QEMU](https://github.com/mirrorer/afl/blob/master/qemu_mode)) or a dynamic instrumentation tool (e.g., [Intel PinTool](https://github.com/vanhauser-thc/afl-pin)). For Python programs, we can collect coverage information without any instrumentation (see chapter on [collecting coverage](Coverage.ipynb#Coverage-of-Basic-Fuzzing))."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Ingredients for Greybox Fuzzing\n",
"\n",
"We start with discussing the most important parts we need for mutational testing and goal guidance."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Mutators\n",
"\n",
"We introduce specific classes for mutating a seed."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:25.736681Z",
"iopub.status.busy": "2025-01-16T09:38:25.736461Z",
"iopub.status.idle": "2025-01-16T09:38:25.739571Z",
"shell.execute_reply": "2025-01-16T09:38:25.739260Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import bookutils.setup"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:25.743048Z",
"iopub.status.busy": "2025-01-16T09:38:25.742115Z",
"iopub.status.idle": "2025-01-16T09:38:25.776402Z",
"shell.execute_reply": "2025-01-16T09:38:25.775863Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from typing import List, Set, Any, Tuple, Dict, Union\n",
"from collections.abc import Sequence"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:25.778692Z",
"iopub.status.busy": "2025-01-16T09:38:25.778594Z",
"iopub.status.idle": "2025-01-16T09:38:25.780976Z",
"shell.execute_reply": "2025-01-16T09:38:25.780526Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import random"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:25.783455Z",
"iopub.status.busy": "2025-01-16T09:38:25.783291Z",
"iopub.status.idle": "2025-01-16T09:38:26.222494Z",
"shell.execute_reply": "2025-01-16T09:38:26.222216Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from Coverage import population_coverage"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"First, we'll introduce the `Mutator` class. Given a seed input `inp`, the mutator returns a slightly modified version of `inp`. In the [chapter on greybox grammar fuzzing](GreyboxGrammarFuzzer.ipynb), we extend this class to consider the input grammar for smart greybox fuzzing."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.224430Z",
"iopub.status.busy": "2025-01-16T09:38:26.224251Z",
"iopub.status.idle": "2025-01-16T09:38:26.226621Z",
"shell.execute_reply": "2025-01-16T09:38:26.226343Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Mutator:\n",
" \"\"\"Mutate strings\"\"\"\n",
"\n",
" def __init__(self) -> None:\n",
" \"\"\"Constructor\"\"\"\n",
" self.mutators = [\n",
" self.delete_random_character,\n",
" self.insert_random_character,\n",
" self.flip_random_character\n",
" ]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"For insertion, we add a random character in a random position."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.228161Z",
"iopub.status.busy": "2025-01-16T09:38:26.228059Z",
"iopub.status.idle": "2025-01-16T09:38:26.230013Z",
"shell.execute_reply": "2025-01-16T09:38:26.229770Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Mutator(Mutator):\n",
" def insert_random_character(self, s: str) -> str:\n",
" \"\"\"Returns s with a random character inserted\"\"\"\n",
" pos = random.randint(0, len(s))\n",
" random_character = chr(random.randrange(32, 127))\n",
" return s[:pos] + random_character + s[pos:]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"For deletion, if the string is non-empty choose a random position and delete the character. Otherwise, use the insertion-operation."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.231573Z",
"iopub.status.busy": "2025-01-16T09:38:26.231471Z",
"iopub.status.idle": "2025-01-16T09:38:26.233530Z",
"shell.execute_reply": "2025-01-16T09:38:26.233297Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Mutator(Mutator):\n",
" def delete_random_character(self, s: str) -> str:\n",
" \"\"\"Returns s with a random character deleted\"\"\"\n",
" if s == \"\":\n",
" return self.insert_random_character(s)\n",
"\n",
" pos = random.randint(0, len(s) - 1)\n",
" return s[:pos] + s[pos + 1:]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"For substitution, if the string is non-empty choose a random position and flip a random bit in the character. Otherwise, use the insertion-operation."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.235134Z",
"iopub.status.busy": "2025-01-16T09:38:26.235030Z",
"iopub.status.idle": "2025-01-16T09:38:26.237187Z",
"shell.execute_reply": "2025-01-16T09:38:26.236937Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Mutator(Mutator):\n",
" def flip_random_character(self, s: str) -> str:\n",
" \"\"\"Returns s with a random bit flipped in a random position\"\"\"\n",
" if s == \"\":\n",
" return self.insert_random_character(s)\n",
"\n",
" pos = random.randint(0, len(s) - 1)\n",
" c = s[pos]\n",
" bit = 1 << random.randint(0, 6)\n",
" new_c = chr(ord(c) ^ bit)\n",
" return s[:pos] + new_c + s[pos + 1:]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The main method is `mutate` which chooses a random mutation operator from the list of operators."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.238588Z",
"iopub.status.busy": "2025-01-16T09:38:26.238494Z",
"iopub.status.idle": "2025-01-16T09:38:26.240364Z",
"shell.execute_reply": "2025-01-16T09:38:26.240145Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Mutator(Mutator):\n",
" def mutate(self, inp: Any) -> Any: # can be str or Seed (see below)\n",
" \"\"\"Return s with a random mutation applied. Can be overloaded in subclasses.\"\"\"\n",
" mutator = random.choice(self.mutators)\n",
" return mutator(inp)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let's try the mutator. You can actually interact with such a \"cell\" and try other inputs by loading this chapter as Jupyter notebook. After opening, run all cells in the notebook using \"Kernel -> Restart & Run All\"."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.241828Z",
"iopub.status.busy": "2025-01-16T09:38:26.241717Z",
"iopub.status.idle": "2025-01-16T09:38:26.243732Z",
"shell.execute_reply": "2025-01-16T09:38:26.243520Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'cood'"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Mutator().mutate(\"good\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
},
"tags": []
},
"source": [
"### Seeds and Power Schedules\n",
"\n",
"Now we introduce a new concept; the *power schedule*. A power schedule distributes the precious fuzzing time among the seeds in the population. Our objective is to maximize the time spent fuzzing those (most progressive) seeds which lead to higher coverage increase in shorter time.\n",
"\n",
"We call the likelihood with which a seed is chosen from the population as the seed's *energy*. Throughout a fuzzing campaign, we would like to prioritize seeds that are more promising. Simply said, we do not want to waste energy fuzzing non-progressive seeds. We call the procedure that decides a seed's energy as the fuzzer's *power schedule*. For instance, AFL's schedule assigns more energy to seeds that are shorter, that execute faster, and yield coverage increases more often.\n",
"\n",
"First, there is some information that we need to attach to each seed in addition to the seed's data. Hence, we define the following `Seed` class."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.245333Z",
"iopub.status.busy": "2025-01-16T09:38:26.245229Z",
"iopub.status.idle": "2025-01-16T09:38:26.246874Z",
"shell.execute_reply": "2025-01-16T09:38:26.246620Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from Coverage import Location"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.248437Z",
"iopub.status.busy": "2025-01-16T09:38:26.248327Z",
"iopub.status.idle": "2025-01-16T09:38:26.250577Z",
"shell.execute_reply": "2025-01-16T09:38:26.250326Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Seed:\n",
" \"\"\"Represent an input with additional attributes\"\"\"\n",
"\n",
" def __init__(self, data: str) -> None:\n",
" \"\"\"Initialize from seed data\"\"\"\n",
" self.data = data\n",
"\n",
" # These will be needed for advanced power schedules\n",
" self.coverage: Set[Location] = set()\n",
" self.distance: Union[int, float] = -1\n",
" self.energy = 0.0\n",
"\n",
" def __str__(self) -> str:\n",
" \"\"\"Returns data as string representation of the seed\"\"\"\n",
" return self.data\n",
"\n",
" __repr__ = __str__"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The power schedule that is implemented below assigns each seed the same energy. Once a seed is in the population, it will be fuzzed as often as any other seed in the population.\n",
"\n",
"In Python, we can squeeze long for-loops into much smaller statements.\n",
"* `lambda x: ...` returns a function that takes `x` as input. Lambda allows for quick definitions unnamed functions.\n",
"* `map(f, l)` returns a list where the function `f` is applied to each element in list `l`.\n",
"* `random.choices(l, weights)[0]` returns element `l[i]` with probability in `weights[i]`."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.252132Z",
"iopub.status.busy": "2025-01-16T09:38:26.252027Z",
"iopub.status.idle": "2025-01-16T09:38:26.255117Z",
"shell.execute_reply": "2025-01-16T09:38:26.254785Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class PowerSchedule:\n",
" \"\"\"Define how fuzzing time should be distributed across the population.\"\"\"\n",
"\n",
" def __init__(self) -> None:\n",
" \"\"\"Constructor\"\"\"\n",
" self.path_frequency: Dict = {}\n",
"\n",
" def assignEnergy(self, population: Sequence[Seed]) -> None:\n",
" \"\"\"Assigns each seed the same energy\"\"\"\n",
" for seed in population:\n",
" seed.energy = 1\n",
"\n",
" def normalizedEnergy(self, population: Sequence[Seed]) -> List[float]:\n",
" \"\"\"Normalize energy\"\"\"\n",
" energy = list(map(lambda seed: seed.energy, population))\n",
" sum_energy = sum(energy) # Add up all values in energy\n",
" assert sum_energy != 0\n",
" norm_energy = list(map(lambda nrg: nrg / sum_energy, energy))\n",
" return norm_energy\n",
"\n",
" def choose(self, population: Sequence[Seed]) -> Seed:\n",
" \"\"\"Choose weighted by normalized energy.\"\"\"\n",
" self.assignEnergy(population)\n",
" norm_energy = self.normalizedEnergy(population)\n",
" seed: Seed = random.choices(population, weights=norm_energy)[0]\n",
" return seed"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let's see whether this power schedule chooses seeds uniformly at random. We ask the schedule 10k times to choose a seed from the population of three seeds (A, B, C) and keep track of the number of times we have seen each seed. We should see each seed about 3.3k times."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.256913Z",
"iopub.status.busy": "2025-01-16T09:38:26.256822Z",
"iopub.status.idle": "2025-01-16T09:38:26.258691Z",
"shell.execute_reply": "2025-01-16T09:38:26.258451Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"population = [Seed(\"A\"), Seed(\"B\"), Seed(\"C\")]\n",
"schedule = PowerSchedule()\n",
"hits = {\n",
" \"A\": 0,\n",
" \"B\": 0,\n",
" \"C\": 0\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.260184Z",
"iopub.status.busy": "2025-01-16T09:38:26.260101Z",
"iopub.status.idle": "2025-01-16T09:38:26.279155Z",
"shell.execute_reply": "2025-01-16T09:38:26.278874Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"for i in range(10000):\n",
" seed = schedule.choose(population)\n",
" hits[seed.data] += 1"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.280642Z",
"iopub.status.busy": "2025-01-16T09:38:26.280544Z",
"iopub.status.idle": "2025-01-16T09:38:26.282943Z",
"shell.execute_reply": "2025-01-16T09:38:26.282609Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'A': 3387, 'B': 3255, 'C': 3358}"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hits"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Looks good. Every seed has been chosen about a third of the time."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Runners and a Sample Program\n",
"\n",
"We'll start with a small sample program of six lines. In order to collect coverage information during execution, we import the `FunctionCoverageRunner` class from the chapter on [mutation-based fuzzing](MutationFuzzer.ipynb#Guiding-by-Coverage). \n",
"\n",
"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": 18,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.284540Z",
"iopub.status.busy": "2025-01-16T09:38:26.284430Z",
"iopub.status.idle": "2025-01-16T09:38:26.295132Z",
"shell.execute_reply": "2025-01-16T09:38:26.294872Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from MutationFuzzer import FunctionCoverageRunner, http_program"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The `crashme()` function raises an exception for the input \"bad!\". Let's see which statements are covered for the input \"good\"."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.296730Z",
"iopub.status.busy": "2025-01-16T09:38:26.296644Z",
"iopub.status.idle": "2025-01-16T09:38:26.298779Z",
"shell.execute_reply": "2025-01-16T09:38:26.298540Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def crashme(s: str) -> None:\n",
" if len(s) > 0 and s[0] == 'b':\n",
" if len(s) > 1 and s[1] == 'a':\n",
" if len(s) > 2 and s[2] == 'd':\n",
" if len(s) > 3 and s[3] == '!':\n",
" raise Exception()"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.300230Z",
"iopub.status.busy": "2025-01-16T09:38:26.300148Z",
"iopub.status.idle": "2025-01-16T09:38:26.302911Z",
"shell.execute_reply": "2025-01-16T09:38:26.302663Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[('run_function', 132), ('crashme', 2)]"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"crashme_runner = FunctionCoverageRunner(crashme)\n",
"crashme_runner.run(\"good\")\n",
"list(crashme_runner.coverage())"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"In `crashme`, the input \"good\" only covers the if-statement in line 2. The branch condition `len(s) > 0 and s[0] == 'b'` evaluates to False."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Advanced Blackbox Mutation-based Fuzzing\n",
"\n",
"Let's integrate both the mutator and power schedule into a fuzzer. We'll start with a blackbox fuzzer -- which does *not* leverage any coverage information. \n",
"\n",
"Our `AdvancedMutationFuzzer` class is an advanced and _parameterized_ version of the `MutationFuzzer` class from the [chapter on mutation-based fuzzing](MutationFuzzer.ipynb). It also inherits from the [Fuzzer](Fuzzer.ipynb#Fuzzer-Classes) class. For now, we only need to know the functions `fuzz()` which returns a generated input and `runs()` which executes `fuzz()` a specified number of times. For our `AdvancedMutationFuzzer` class, we override the function `fuzz()`."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.304459Z",
"iopub.status.busy": "2025-01-16T09:38:26.304345Z",
"iopub.status.idle": "2025-01-16T09:38:26.305958Z",
"shell.execute_reply": "2025-01-16T09:38:26.305709Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from Fuzzer import Fuzzer"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The `AdvancedMutationFuzzer` is constructed with a set of initial seeds, a mutator, and a power schedule. Throughout the fuzzing campaign, it maintains a seed corpus called `population`. The function `fuzz` returns either an unfuzzed seed from the initial seeds, or the result of fuzzing a seed in the population. The function `create_candidate` handles the latter. It randomly chooses an input from the population and applies a number of mutations."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.307435Z",
"iopub.status.busy": "2025-01-16T09:38:26.307343Z",
"iopub.status.idle": "2025-01-16T09:38:26.310744Z",
"shell.execute_reply": "2025-01-16T09:38:26.310421Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class AdvancedMutationFuzzer(Fuzzer):\n",
" \"\"\"Base class for mutation-based fuzzing.\"\"\"\n",
"\n",
" def __init__(self, seeds: List[str],\n",
" mutator: Mutator,\n",
" schedule: PowerSchedule) -> None:\n",
" \"\"\"Constructor.\n",
" `seeds` - a list of (input) strings to mutate.\n",
" `mutator` - the mutator to apply.\n",
" `schedule` - the power schedule to apply.\n",
" \"\"\"\n",
" self.seeds = seeds\n",
" self.mutator = mutator\n",
" self.schedule = schedule\n",
" self.inputs: List[str] = []\n",
" self.reset()\n",
"\n",
" def reset(self) -> None:\n",
" \"\"\"Reset the initial population and seed index\"\"\"\n",
" self.population = list(map(lambda x: Seed(x), self.seeds))\n",
" self.seed_index = 0\n",
"\n",
" def create_candidate(self) -> str:\n",
" \"\"\"Returns an input generated by fuzzing a seed in the population\"\"\"\n",
" seed = self.schedule.choose(self.population)\n",
"\n",
" # Stacking: Apply multiple mutations to generate the candidate\n",
" candidate = seed.data\n",
" trials = min(len(candidate), 1 << random.randint(1, 5))\n",
" for i in range(trials):\n",
" candidate = self.mutator.mutate(candidate)\n",
" return candidate\n",
"\n",
" def fuzz(self) -> str:\n",
" \"\"\"Returns first each seed once and then generates new inputs\"\"\"\n",
" if self.seed_index < len(self.seeds):\n",
" # Still seeding\n",
" self.inp = self.seeds[self.seed_index]\n",
" self.seed_index += 1\n",
" else:\n",
" # Mutating\n",
" self.inp = self.create_candidate()\n",
"\n",
" self.inputs.append(self.inp)\n",
" return self.inp"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Okay, let's take the mutation fuzzer for a spin. Given a single seed, we ask it to generate three inputs."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.312493Z",
"iopub.status.busy": "2025-01-16T09:38:26.312393Z",
"iopub.status.idle": "2025-01-16T09:38:26.314661Z",
"shell.execute_reply": "2025-01-16T09:38:26.314383Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"good\n",
"gDoodC\n",
"/\n"
]
}
],
"source": [
"seed_input = \"good\"\n",
"mutation_fuzzer = AdvancedMutationFuzzer([seed_input], Mutator(), PowerSchedule())\n",
"print(mutation_fuzzer.fuzz())\n",
"print(mutation_fuzzer.fuzz())\n",
"print(mutation_fuzzer.fuzz())"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let's see how many statements the mutation-based blackbox fuzzer covers in a campaign with n=30k inputs.\n",
"\n",
"The fuzzer function `runs(crashme_runner, trials=n)` generates `n` inputs and executes them on the `crashme` function via the `crashme_runner`. As stated earlier, the `crashme_runner` also collects coverage information."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.316224Z",
"iopub.status.busy": "2025-01-16T09:38:26.316124Z",
"iopub.status.idle": "2025-01-16T09:38:26.317854Z",
"shell.execute_reply": "2025-01-16T09:38:26.317613Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import time"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.319428Z",
"iopub.status.busy": "2025-01-16T09:38:26.319339Z",
"iopub.status.idle": "2025-01-16T09:38:26.320984Z",
"shell.execute_reply": "2025-01-16T09:38:26.320749Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"n = 30000"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:26.322432Z",
"iopub.status.busy": "2025-01-16T09:38:26.322323Z",
"iopub.status.idle": "2025-01-16T09:38:30.532772Z",
"shell.execute_reply": "2025-01-16T09:38:30.532390Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took the blackbox mutation-based fuzzer 4.21 seconds to generate and execute 30000 inputs.'"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blackbox_fuzzer = AdvancedMutationFuzzer([seed_input], Mutator(), PowerSchedule())\n",
"\n",
"start = time.time()\n",
"blackbox_fuzzer.runs(FunctionCoverageRunner(crashme), trials=n)\n",
"end = time.time()\n",
"\n",
"\"It took the blackbox mutation-based fuzzer %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"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": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We extract the generated inputs from the blackbox fuzzer and measure coverage as the number of inputs increases."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:30.534557Z",
"iopub.status.busy": "2025-01-16T09:38:30.534433Z",
"iopub.status.idle": "2025-01-16T09:38:34.499511Z",
"shell.execute_reply": "2025-01-16T09:38:34.499136Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'The blackbox mutation-based fuzzer achieved a maximum coverage of 2 statements.'"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"_, blackbox_coverage = population_coverage(blackbox_fuzzer.inputs, crashme)\n",
"bb_max_coverage = max(blackbox_coverage)\n",
"\n",
"\"The blackbox mutation-based fuzzer achieved a maximum coverage of %d statements.\" % bb_max_coverage"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The following generated inputs increased the coverage for our `crashme` [example](#Runner-and-Sample-Program)."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:34.501335Z",
"iopub.status.busy": "2025-01-16T09:38:34.501214Z",
"iopub.status.idle": "2025-01-16T09:38:34.504907Z",
"shell.execute_reply": "2025-01-16T09:38:34.504624Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['good', 'bo']"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[seed_input] + \\\n",
" [\n",
" blackbox_fuzzer.inputs[idx] for idx in range(len(blackbox_coverage))\n",
" if blackbox_coverage[idx] > blackbox_coverage[idx - 1]\n",
" ]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"***Summary***. This is how a blackbox mutation-based fuzzer works. We have integrated the *mutator* to generate inputs by fuzzing a provided set of initial seeds and the *power schedule* to decide which seed to choose next."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Greybox Mutation-based Fuzzing\n",
"\n",
"In contrast to a blackbox fuzzer, a greybox fuzzer like [AFL](http://lcamtuf.coredump.cx/afl/) _does_ leverage coverage information. Specifically, a greybox fuzzer adds to the seed population generated inputs which increase code coverage.\n",
"\n",
"The method `run()` is inherited from the [Fuzzer](Fuzzer.ipynb#Fuzzer-Classes) class. It is called to generate and execute exactly one input. We override this function to add an input to the `population` that increases coverage. The greybox fuzzer attribute `coverages_seen` maintains the set of statements, that have previously been covered."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:34.506711Z",
"iopub.status.busy": "2025-01-16T09:38:34.506619Z",
"iopub.status.idle": "2025-01-16T09:38:34.509306Z",
"shell.execute_reply": "2025-01-16T09:38:34.509037Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class GreyboxFuzzer(AdvancedMutationFuzzer):\n",
" \"\"\"Coverage-guided mutational fuzzing.\"\"\"\n",
"\n",
" def reset(self):\n",
" \"\"\"Reset the initial population, seed index, coverage information\"\"\"\n",
" super().reset()\n",
" self.coverages_seen = set()\n",
" self.population = [] # population is filled during greybox fuzzing\n",
"\n",
" def run(self, runner: FunctionCoverageRunner) -> Tuple[Any, str]: # type: ignore\n",
" \"\"\"Run function(inp) while tracking coverage.\n",
" If we reach new coverage,\n",
" add inp to population and its coverage to population_coverage\n",
" \"\"\"\n",
" result, outcome = super().run(runner)\n",
" new_coverage = frozenset(runner.coverage())\n",
" if new_coverage not in self.coverages_seen:\n",
" # We have new coverage\n",
" seed = Seed(self.inp)\n",
" seed.coverage = runner.coverage()\n",
" self.coverages_seen.add(new_coverage)\n",
" self.population.append(seed)\n",
"\n",
" return (result, outcome)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let's take our greybox fuzzer for a spin."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:34.510946Z",
"iopub.status.busy": "2025-01-16T09:38:34.510854Z",
"iopub.status.idle": "2025-01-16T09:38:38.738917Z",
"shell.execute_reply": "2025-01-16T09:38:38.738614Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took the greybox mutation-based fuzzer 4.23 seconds to generate and execute 30000 inputs.'"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"seed_input = \"good\"\n",
"greybox_fuzzer = GreyboxFuzzer([seed_input], Mutator(), PowerSchedule())\n",
"\n",
"start = time.time()\n",
"greybox_fuzzer.runs(FunctionCoverageRunner(crashme), trials=n)\n",
"end = time.time()\n",
"\n",
"\"It took the greybox mutation-based fuzzer %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Does the greybox fuzzer cover more statements after generating the same number of test inputs?"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:38.740535Z",
"iopub.status.busy": "2025-01-16T09:38:38.740431Z",
"iopub.status.idle": "2025-01-16T09:38:42.595373Z",
"shell.execute_reply": "2025-01-16T09:38:42.595075Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'Our greybox mutation-based fuzzer covers 2 more statements'"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"_, greybox_coverage = population_coverage(greybox_fuzzer.inputs, crashme)\n",
"gb_max_coverage = max(greybox_coverage)\n",
"\n",
"\"Our greybox mutation-based fuzzer covers %d more statements\" % (gb_max_coverage - bb_max_coverage)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Our seed population for our [example](#Runner-and-Sample-Program) now contains the following seeds."
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.596999Z",
"iopub.status.busy": "2025-01-16T09:38:42.596883Z",
"iopub.status.idle": "2025-01-16T09:38:42.598914Z",
"shell.execute_reply": "2025-01-16T09:38:42.598698Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[good, bo, baof, bad4u]"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"greybox_fuzzer.population"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Coverage-feedback is indeed helpful. The new seeds are like bread crumbs or milestones that guide the fuzzer to progress more quickly into deeper code regions. Following is a simple plot showing the coverage achieved over time for both fuzzers on our simple [example](#Runner-and-Sample-Program)."
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.600368Z",
"iopub.status.busy": "2025-01-16T09:38:42.600269Z",
"iopub.status.idle": "2025-01-16T09:38:42.607470Z",
"shell.execute_reply": "2025-01-16T09:38:42.607247Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.608994Z",
"iopub.status.busy": "2025-01-16T09:38:42.608885Z",
"iopub.status.idle": "2025-01-16T09:38:42.610500Z",
"shell.execute_reply": "2025-01-16T09:38:42.610301Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt # type: ignore"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.611874Z",
"iopub.status.busy": "2025-01-16T09:38:42.611779Z",
"iopub.status.idle": "2025-01-16T09:38:42.760292Z",
"shell.execute_reply": "2025-01-16T09:38:42.760036Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/png": "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2RP9+vWDubk53nnnHbX1R44cKfE6MDAQABAYGIhr167h2rVrqrM358+fx71791Rjau7cuYOBAwfi448/RlZWFvr3749Tp05pHL7Kq3J+x4mIiExMQUEBsrOzATy5LPX1118jLy8PPXv2LNHW398fa9euxa5du+Dr64t169bh+PHj8PX1VbWZMWMGhg8fDmdnZ4SFheH+/ftISkoqMeblzTffxLp16/Dee+/BwsICb7/9tmpdUlIS5s6di4iICOzZswdxcXHYvn07AKBz584ICgpC//79sXDhQhQVFWHEiBFo3749mjVrBgAYPnw4PD098cknn6CgoABNmjTBxIkTS8y6qmgMNzr0v8cvGLQMIiKqBHbu3Km6ZGNvb4/69esjLi4OHTp0wJUrV9TafvDBB0hJSUFkZCQkEgn69u2LESNGYMeOHao2AwYMwKNHj7BgwQJMnDgRTk5OasHlWW+//TaUSiXee+89mJmZoVevXgCACRMm4MSJE4iJiYFMJsOXX36Jrl2fXOaTSCTYunUrPvroI7Rr1w5mZmbo1q0bvvrqKwBPBhcnJCQgJSUFFhYWsLCwwPr169GmTRv06NEDYWFhFd2FKhIhqtY1FIVCAQcHB+Tm5kImk+nmIHf+BBY3wQNYo8GjVfj5QzmaetfQzbGIiAgA8OjRI2RkZMDX1xfW1taGLofK4XnfQ20+v3kTPyIiIjIpDDdERERkUhhudEio7nDDQTdERET6wnCjC1VrGBMREZFRYbghIiIik8Jwo0N8KjgREZH+MdwQERGRSWG4ISIiIpPCcENEREQmheFGh1SPXzBoFURERM/XoUMHjB071tBlVBiGG13gVHAiItJSdnY2xowZAz8/P1hbW8PFxQWtW7fGsmXL8ODBA0OXV6nwwZlEREQG9ueff6J169ZwdHTE559/jqCgIEilUpw5cwYrV66Eh4cH3njjjRLbFRYWwtLS0gAVGzeeudGpp1PBeWGKiIjKNmLECFhYWODEiRPo06cPAgMDUadOHYSHh2P79u3o2bMngCefJ8uWLcMbb7yBatWq4d///jcAYOvWrXjllVdgbW2NOnXqICYmBkVFRQCAQYMGoUePHmrHKywshLOzM7777jvVsqKiIowaNQoODg5wcnLCv/71Lzz7bO27d+8iKioK1atXh62tLcLCwnDp0iUAwK1bt+Dq6orPP/9c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",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"line_bb, = plt.plot(blackbox_coverage, label=\"Blackbox\")\n",
"line_gb, = plt.plot(greybox_coverage, label=\"Greybox\")\n",
"plt.legend(handles=[line_bb, line_gb])\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": [
"***Summary***. We have seen how a greybox fuzzer \"discovers\" interesting seeds that can lead to more progress. From the input `good`, our greybox fuzzer has slowly learned how to generate the input `bad!` which raises the exception. Now, how can we do that even faster?\n",
"\n",
"***Try it***. How much coverage would be achieved over time using a blackbox *generation-based* fuzzer? Try plotting the coverage for all three fuzzers. You can define the blackbox generation-based fuzzer as follows.\n",
"```Python\n",
"from Fuzzer import RandomFuzzer\n",
"blackbox_gen_fuzzer = RandomFuzzer(min_length=4, max_length=4, char_start=32, char_range=96)\n",
"```\n",
"You can execute your own code by opening this chapter as Jupyter notebook.\n",
"\n",
"***Read***. This is the high-level view how AFL works, one of the most successful vulnerability detection tools. If you are interested in the technical details, have a look at: https://github.com/mirrorer/afl/blob/master/docs/technical_details.txt"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Boosted Greybox Fuzzing\n",
"\n",
"Our boosted greybox fuzzer assigns more energy to seeds that promise to achieve more coverage. We change the power schedule such that seeds that exercise \"unusual\" paths have more energy. With *unusual paths*, we mean paths that are not exercised very often by generated inputs.\n",
"\n",
"In order to identify which path is exercised by an input, we leverage the function `getPathID` from the section on [trace coverage](WhenIsEnough.ipynb#Trace-Coverage)."
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.761988Z",
"iopub.status.busy": "2025-01-16T09:38:42.761877Z",
"iopub.status.idle": "2025-01-16T09:38:42.763535Z",
"shell.execute_reply": "2025-01-16T09:38:42.763298Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import pickle # serializes an object by producing a byte array from all the information in the object\n",
"import hashlib # produces a 128-bit hash value from a byte array"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The function `getPathID` returns a unique hash for a coverage set."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.765075Z",
"iopub.status.busy": "2025-01-16T09:38:42.764978Z",
"iopub.status.idle": "2025-01-16T09:38:42.766647Z",
"shell.execute_reply": "2025-01-16T09:38:42.766438Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"def getPathID(coverage: Any) -> str:\n",
" \"\"\"Returns a unique hash for the covered statements\"\"\"\n",
" pickled = pickle.dumps(sorted(coverage))\n",
" return hashlib.md5(pickled).hexdigest()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"There are several ways to assign energy based on how unusual the exercised path is. In this case, we implement an _exponential power schedule_ which computes the energy $e(s)$ for a seed $s$ as follows\n",
"$$e(s) = \\frac{1}{f(p(s))^a}$$\n",
"where \n",
"* $p(s)$ returns the ID of the path exercised by $s$, \n",
"* $f(p)$ returns the number of times the path $p$ is exercised by generated inputs, and \n",
"* $a$ is a given exponent."
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.768227Z",
"iopub.status.busy": "2025-01-16T09:38:42.768133Z",
"iopub.status.idle": "2025-01-16T09:38:42.770133Z",
"shell.execute_reply": "2025-01-16T09:38:42.769902Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class AFLFastSchedule(PowerSchedule):\n",
" \"\"\"Exponential power schedule as implemented in AFL\"\"\"\n",
"\n",
" def __init__(self, exponent: float) -> None:\n",
" self.exponent = exponent\n",
"\n",
" def assignEnergy(self, population: Sequence[Seed]) -> None:\n",
" \"\"\"Assign exponential energy inversely proportional to path frequency\"\"\"\n",
" for seed in population:\n",
" seed.energy = 1 / (self.path_frequency[getPathID(seed.coverage)] ** self.exponent)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"In the greybox fuzzer, let's keep track of the number of times $f(p)$ each path $p$ is exercised, and update the power schedule."
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.771590Z",
"iopub.status.busy": "2025-01-16T09:38:42.771486Z",
"iopub.status.idle": "2025-01-16T09:38:42.773742Z",
"shell.execute_reply": "2025-01-16T09:38:42.773541Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class CountingGreyboxFuzzer(GreyboxFuzzer):\n",
" \"\"\"Count how often individual paths are exercised.\"\"\"\n",
"\n",
" def reset(self):\n",
" \"\"\"Reset path frequency\"\"\"\n",
" super().reset()\n",
" self.schedule.path_frequency = {}\n",
"\n",
" def run(self, runner: FunctionCoverageRunner) -> Tuple[Any, str]: # type: ignore\n",
" \"\"\"Inform scheduler about path frequency\"\"\"\n",
" result, outcome = super().run(runner)\n",
"\n",
" path_id = getPathID(runner.coverage())\n",
" if path_id not in self.schedule.path_frequency:\n",
" self.schedule.path_frequency[path_id] = 1\n",
" else:\n",
" self.schedule.path_frequency[path_id] += 1\n",
"\n",
" return(result, outcome)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Okay, let's run our boosted greybox fuzzer $n=10k$ times on our simple [example](#Runner-and-Sample-Program). We set the exponentent of our exponential power schedule to $a=5$."
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:42.775152Z",
"iopub.status.busy": "2025-01-16T09:38:42.775058Z",
"iopub.status.idle": "2025-01-16T09:38:44.313180Z",
"shell.execute_reply": "2025-01-16T09:38:44.312845Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took the fuzzer w/ exponential schedule 1.54 seconds to generate and execute 10000 inputs.'"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n = 10000\n",
"seed_input = \"good\"\n",
"fast_schedule = AFLFastSchedule(5)\n",
"fast_fuzzer = CountingGreyboxFuzzer([seed_input], Mutator(), fast_schedule)\n",
"start = time.time()\n",
"fast_fuzzer.runs(FunctionCoverageRunner(crashme), trials=n)\n",
"end = time.time()\n",
"\n",
"\"It took the fuzzer w/ exponential schedule %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:44.314994Z",
"iopub.status.busy": "2025-01-16T09:38:44.314874Z",
"iopub.status.idle": "2025-01-16T09:38:44.316576Z",
"shell.execute_reply": "2025-01-16T09:38:44.316298Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:44.318155Z",
"iopub.status.busy": "2025-01-16T09:38:44.318043Z",
"iopub.status.idle": "2025-01-16T09:38:44.363225Z",
"shell.execute_reply": "2025-01-16T09:38:44.362973Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAGiCAYAAAAFotdwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAiyElEQVR4nO3de3BU9d3H8U8IZhOBTQKaXSIBY61clIuAxi2XUUmJNtKi2BaMQjFCdRJrTFVgLBevwVBUKDdRK8xYFOwURVKgmaDJqCFAaDAEidpiiZdNVMguobJc9jx/dDiPWygCbtjsj/dr5sw05/z25HtOO0/ez96IsSzLEgAAgGHaRXoAAACA1kDkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACOdduRUVFRo1KhRSk1NVUxMjF5//fWQ45ZlacaMGeratasSEhKUmZmpjz76KGTN3r17lZOTI6fTqaSkJOXm5qqlpSVkzfvvv69hw4YpPj5eaWlpKi4uPm6W1157Tb169VJ8fLz69u2rv/71r6d7OQAAwFCnHTkHDhxQ//79tXDhwhMeLy4u1vz587VkyRJVVVWpQ4cOysrK0sGDB+01OTk5qqurU2lpqdauXauKigpNnjzZPu73+zVy5Ej16NFD1dXVmjNnjmbNmqWlS5faa9577z2NGzdOubm5+vvf/67Ro0dr9OjR2rFjx+leEgAAMJH1PUiyVq9ebf8cDAYtt9ttzZkzx97X3NxsORwO65VXXrEsy7J27txpSbK2bNlir1m3bp0VExNjffbZZ5ZlWdaiRYus5ORkKxAI2GumTJli9ezZ0/75F7/4hZWdnR0yT0ZGhvXrX//6+1wSAAAwRPtwBtPu3bvl9XqVmZlp70tMTFRGRoYqKys1duxYVVZWKikpSYMHD7bXZGZmql27dqqqqtLNN9+syspKDR8+XHFxcfaarKwsPfXUU9q3b5+Sk5NVWVmpwsLCkN+flZV13Mtn3xYIBBQIBOyfg8Gg9u7dqy5duigmJiYMdwAAALQ2y7K0f/9+paamql27//2iVFgjx+v1SpJcLlfIfpfLZR/zer1KSUkJHaJ9e3Xu3DlkTXp6+nHnOHYsOTlZXq/3pL/nRIqKivTII4+cwZUBAIC2pqGhQd26dfufx8MaOW3dtGnTQp798fl86t69uxoaGuR0OiM4GQAAOFV+v19paWnq1KnTSdeFNXLcbrckqbGxUV27drX3NzY2asCAAfaapqamkMcdOXJEe/futR/vdrvV2NgYsubYz9+15tjxE3E4HHI4HMftdzqdRA4AAFHmu95qEtbvyUlPT5fb7VZZWZm9z+/3q6qqSh6PR5Lk8XjU3Nys6upqe83GjRsVDAaVkZFhr6moqNDhw4ftNaWlperZs6eSk5PtNd/+PcfWHPs9AADg3HbakdPS0qKamhrV1NRI+s+bjWtqarRnzx7FxMSooKBAjz/+uNasWaPa2lqNHz9eqampGj16tCSpd+/euuGGGzRp0iRt3rxZ7777rvLz8zV27FilpqZKkm677TbFxcUpNzdXdXV1WrlypebNmxfyUtN9992n9evXa+7cudq1a5dmzZqlrVu3Kj8///vfFQAAEP1O9+NYb731liXpuG3ChAmWZf3nY+TTp0+3XC6X5XA4rBEjRlj19fUh5/j666+tcePGWR07drScTqc1ceJEa//+/SFrtm/fbg0dOtRyOBzWRRddZM2ePfu4WVatWmVddtllVlxcnHX55ZdbJSUlp3UtPp/PkmT5fL7TuwkAACBiTvXvd4xlWVYEGyui/H6/EhMT5fP5eE8OAABR4lT/fvNvVwEAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMFLYI+fo0aOaPn260tPTlZCQoB/84Ad67LHHZFmWvcayLM2YMUNdu3ZVQkKCMjMz9dFHH4WcZ+/evcrJyZHT6VRSUpJyc3PV0tISsub999/XsGHDFB8fr7S0NBUXF4f7cgAAQJQKe+Q89dRTWrx4sRYsWKAPPvhATz31lIqLi/WHP/zBXlNcXKz58+dryZIlqqqqUocOHZSVlaWDBw/aa3JyclRXV6fS0lKtXbtWFRUVmjx5sn3c7/dr5MiR6tGjh6qrqzVnzhzNmjVLS5cuDfclAQCAKBRjffspljC46aab5HK59OKLL9r7xowZo4SEBL388suyLEupqan67W9/qwceeECS5PP55HK5tGzZMo0dO1YffPCB+vTpoy1btmjw4MGSpPXr1+snP/mJPv30U6Wmpmrx4sV6+OGH5fV6FRcXJ0maOnWqXn/9de3ateuUZvX7/UpMTJTP55PT6QznbQAAAK3kVP9+h/2ZnB/96EcqKyvThx9+KEnavn273nnnHd14442SpN27d8vr9SozM9N+TGJiojIyMlRZWSlJqqysVFJSkh04kpSZmal27dqpqqrKXjN8+HA7cCQpKytL9fX12rdv3wlnCwQC8vv9IRsAADBT+3CfcOrUqfL7/erVq5diY2N19OhRPfHEE8rJyZEkeb1eSZLL5Qp5nMvlso95vV6lpKSEDtq+vTp37hyyJj09/bhzHDuWnJx83GxFRUV65JFHwnCVAACgrQv7MzmrVq3Sn/70J61YsULbtm3T8uXL9fvf/17Lly8P9686bdOmTZPP57O3hoaGSI8EAABaSdifyXnwwQc1depUjR07VpLUt29f/etf/1JRUZEmTJggt9stSWpsbFTXrl3txzU2NmrAgAGSJLfbraamppDzHjlyRHv37rUf73a71djYGLLm2M/H1vw3h8Mhh8Px/S8SAAC0eWF/Juff//632rULPW1sbKyCwaAkKT09XW63W2VlZfZxv9+vqqoqeTweSZLH41Fzc7Oqq6vtNRs3blQwGFRGRoa9pqKiQocPH7bXlJaWqmfPnid8qQoAAJxbwh45o0aN0hNPPKGSkhJ98sknWr16tZ5++mndfPPNkqSYmBgVFBTo8ccf15o1a1RbW6vx48crNTVVo0ePliT17t1bN9xwgyZNmqTNmzfr3XffVX5+vsaOHavU1FRJ0m233aa4uDjl5uaqrq5OK1eu1Lx581RYWBjuSwIAANHICjO/32/dd999Vvfu3a34+HjrkksusR5+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AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIzU6pEze/ZsxcTEqKCgwN538OBB5eXlqUuXLurYsaPGjBmjxsbGkMft2bNH2dnZOv/885WSkqIHH3xQR44cCVnz9ttva+DAgXI4HLr00ku1bNmy1r4cAAAQJVo1crZs2aLnnntO/fr1C9l///33680339Rrr72m8vJyff7557rlllvs40ePHlV2drYOHTqk9957T8uXL9eyZcs0Y8YMe83u3buVnZ2t6667TjU1NSooKNBdd92lDRs2tOYlAQCAKNFqkdPS0qKcnBw9//zzSk5Otvf7fD69+OKLevrpp3X99ddr0KBBeumll/Tee+9p06ZNkqS//e1v2rlzp15++WUNGDBAN954ox577DEtXLhQhw4dkiQtWbJE6enpmjt3rnr37q38/HzdeuuteuaZZ/7nTIFAQH6/P2QDAABmarXIycvLU3Z2tjIzM0P2V1dX6/DhwyH7e/Xqpe7du6uyslKSVFlZqb59+8rlctlrsrKy5Pf7VVdXZ6/573NnZWXZ5ziRoqIiJSYm2ltaWtr3vk4AANA2tUrkvPrqq9q2bZuKioqOO+b1ehUXF6ekpKSQ/S6XS16v117z7cA5dvzYsZOt8fv9+uabb04417Rp0+Tz+eytoaHhjK4PAAC0fe3DfcKGhgbdd999Ki0tVXx8fLhP/704HA45HI5IjwEAAM6CsD+TU11draamJg0cOFDt27dX+/btVV5ervnz56t9+/ZyuVw6dOiQmpubQx7X2Ngot9stSXK73cd92urYz9+1xul0KiEhIdyXBQAAokzYI2fEiBGqra1VTU2NvQ0ePFg5OTn2fz7vvPNUVlZmP6a+vl579uyRx+ORJHk8HtXW1qqpqcleU1paKqfTqT59+thrvn2OY2uOnQMAAJzbwv5yVadOnXTFFVeE7OvQoYO6dOli78/NzVVhYaE6d+4sp9Ope++9Vx6PR9dcc40kaeTIkerTp4/uuOMOFRcXy+v16ne/+53y8vLsl5vuvvtuLViwQA899JDuvPNObdy4UatWrVJJSUm4LwkAAEShsEfOqXjmmWfUrl07jRkzRoFAQFlZWVq0aJF9PDY2VmvXrtU999wjj8ejDh06aMKECXr00UftNenp6SopKdH999+vefPmqVu3bnrhhReUlZUViUsCAABtTIxlWVakh4gUv9+vxMRE+Xw+OZ3OsJ774qnn5jNKn8zOjvQIAADDnerfb/7tKgAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICR2kd6AODbLp5aEukRIuKT2dmRHgEAjMMzOQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwUtgjp6ioSFdddZU6deqklJQUjR49WvX19SFrDh48qLy8PHXp0kUdO3bUmDFj1NjYGLJmz549ys7O1vnnn6+UlBQ9+OCDOnLkSMiat99+WwMHDpTD4dCll16qZcuWhftyAABAlAp75JSXlysvL0+bNm1SaWmpDh8+rJEjR+rAgQP2mvvvv19vvvmmXnvtNZWXl+vzzz/XLbfcYh8/evSosrOzdejQIb333ntavny5li1bphkzZthrdu/erezsbF133XWqqalRQUGB7rrrLm3YsCHclwQAAKJQjGVZVmv+gi+//FIpKSkqLy/X8OHD5fP5dOGFF2rFihW69dZbJUm7du1S7969VVlZqWuuuUbr1q3TTTfdpM8//1wul0uStGTJEk2ZMkVffvml4uLiNGXKFJWUlGjHjh327xo7dqyam5u1fv36E84SCAQUCATsn/1+v9LS0uTz+eR0OsN63RdPLQnr+aLFJ7Ozv9fjuW8AgO/i9/uVmJj4nX+/W/09OT6fT5LUuXNnSVJ1dbUOHz6szMxMe02vXr3UvXt3VVZWSpIqKyvVt29fO3AkKSsrS36/X3V1dfaab5/j2Jpj5ziRoqIiJSYm2ltaWlp4LhIAALQ5rRo5wWBQBQUFGjJkiK644gpJktfrVVxcnJKSkkLWulwueb1ee823A+fY8WPHTrbG7/frm2++OeE806ZNk8/ns7eGhobvfY0AAKBtat+aJ8/Ly9OOHTv0zjvvtOavOWUOh0MOhyPSYwAAgLOg1Z7Jyc/P19q1a/XWW2+pW7du9n63261Dhw6pubk5ZH1jY6Pcbre95r8/bXXs5+9a43Q6lZCQEO7LAQAAUSbskWNZlvLz87V69Wpt3LhR6enpIccHDRqk8847T2VlZfa++vp67dmzRx6PR5Lk8XhUW1urpqYme01paamcTqf69Oljr/n2OY6tOXYOAABwbgv7y1V5eXlasWKF3njjDXXq1Ml+D01iYqISEhKUmJio3NxcFRYWqnPnznI6nbr33nvl8Xh0zTXXSJJGjhypPn366I477lBxcbG8Xq9+97vfKS8vz3656e6779aCBQv00EMP6c4779TGjRu1atUqlZScm5/OAQAAocL+TM7ixYvl8/l07bXXqmvXrva2cuVKe80zzzyjm266SWPGjNHw4cPldrv1l7/8xT4eGxurtWvXKjY2Vh6PR7fffrvGjx+vRx991F6Tnp6ukpISlZaWqn///po7d65eeOEFZWVlhfuSAABAFGr178lpy071c/Zngu97OTPcNwDAd2kz35MDAAAQCUQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwUvtIDwDg+7t4akmkR4iIT2ZnR3oEAG0Yz+QAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASO0jPQAARMLFU0siPULEfDI7O9IjAGcFz+QAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjMSXAQIATtm5+iWKfIFidOKZHAAAYCQiBwAAGInIAQAARiJyAACA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",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"x_axis = np.arange(len(fast_schedule.path_frequency))\n",
"y_axis = list(fast_schedule.path_frequency.values())\n",
"\n",
"plt.bar(x_axis, y_axis)\n",
"plt.xticks(x_axis)\n",
"plt.ylim(0, n)\n",
"# plt.yscale(\"log\")\n",
"# plt.yticks([10,100,1000,10000])\n",
"plt;"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:44.364853Z",
"iopub.status.busy": "2025-01-16T09:38:44.364738Z",
"iopub.status.idle": "2025-01-16T09:38:44.367039Z",
"shell.execute_reply": "2025-01-16T09:38:44.366811Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" path id 'p' : path frequency 'f(p)'\n"
]
},
{
"data": {
"text/plain": [
"{'e014b68ad4f3bc2daf207e2498d14cbf': 5615,\n",
" '0a1008773804033d8a4c0e3aba4b96a0': 2597,\n",
" 'eae4df5b039511eac56625f47c337d24': 1098,\n",
" 'b14f545c3b39716a455034d9a0c61b8c': 454,\n",
" '11529f85aaa30be08110f3076748e420': 235,\n",
" '669903139e4064f7f07bc8b200fd2d26': 1}"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print(\" path id 'p' : path frequency 'f(p)'\")\n",
"fast_schedule.path_frequency"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"How does it compare to our greybox fuzzer with the classical power schedule?"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:44.368644Z",
"iopub.status.busy": "2025-01-16T09:38:44.368546Z",
"iopub.status.idle": "2025-01-16T09:38:45.802594Z",
"shell.execute_reply": "2025-01-16T09:38:45.802355Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took the fuzzer w/ original schedule 1.43 seconds to generate and execute 10000 inputs.'"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"seed_input = \"good\"\n",
"orig_schedule = PowerSchedule()\n",
"orig_fuzzer = CountingGreyboxFuzzer([seed_input], Mutator(), orig_schedule)\n",
"start = time.time()\n",
"orig_fuzzer.runs(FunctionCoverageRunner(crashme), trials=n)\n",
"end = time.time()\n",
"\n",
"\"It took the fuzzer w/ original schedule %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:45.804254Z",
"iopub.status.busy": "2025-01-16T09:38:45.804148Z",
"iopub.status.idle": "2025-01-16T09:38:45.845285Z",
"shell.execute_reply": "2025-01-16T09:38:45.845033Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/png": "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3Xef1dzcbK9pbW21HnjgAcvtdlsOh8MaM2aM1dDQEHKezz//3Lrhhhusbt26WU6n05o0aZJ18ODBkDVvv/22dfHFF1sOh8M699xzrZkzZ57RrIFAwJJkBQKBb3/BAADge3W6f7/D/js5HQm/kwMAQMcTsd/JAQAAaA+IHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRYiI9gKn6FZVHeoQO46OZWZEeAQBgIF7JAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgJCIHAAAYicgBAABGapPI+eSTT3TTTTepZ8+eio+P16BBg7Rt2zb7uGVZmj59unr16qX4+HhlZGTogw8+CDnHvn37lJOTI6fTqYSEBOXm5urQoUMha9555x1dcskliouLU0pKikpLS9vicgAAQAcU9sjZv3+/Ro0apbPOOktr1qzRu+++q9mzZ6tHjx72mtLSUs2bN09lZWWqqalR165dlZmZqSNHjthrcnJyVF9fr4qKCq1evVpVVVWaMmWKfTwYDGrs2LHq27evamtrNWvWLD300ENatGhRuC8JAAB0QFGWZVnhPGFRUZE2btyov//976c8blmWkpOTdeedd+quu+6SJAUCAbndbi1evFjZ2dl67733lJaWpq1bt2rEiBGSpLVr1+qqq67Sxx9/rOTkZC1cuFD33XeffD6fYmNj7cdeuXKldu3adVqzBoNBuVwuBQIBOZ3OMFz9/9evqDys5zPZRzOzIj0CAKADOd2/32F/JWfVqlUaMWKEfvWrXykpKUkXXHCBnn32Wfv47t275fP5lJGRYe9zuVxKT09XdXW1JKm6uloJCQl24EhSRkaGunTpopqaGnvN6NGj7cCRpMzMTDU0NGj//v2nnK25uVnBYDBkAwAAZgp75Pzzn//UwoUL9eMf/1jr1q3T7bffrt///vdasmSJJMnn80mS3G53yP3cbrd9zOfzKSkpKeR4TEyMEhMTQ9ac6hxffYz/VlJSIpfLZW8pKSnf8WoBAEB7FfbIaW1t1bBhw/T444/rggsu0JQpUzR58mSVlZWF+6HOWHFxsQKBgL01NjZGeiQAANBGwh45vXr1UlpaWsi+gQMHas+ePZIkj8cjSfL7/SFr/H6/fczj8aipqSnk+PHjx7Vv376QNac6x1cf4785HA45nc6QDQAAmCnskTNq1Cg1NDSE7Hv//ffVt29fSVJqaqo8Ho8qKyvt48FgUDU1NfJ6vZIkr9erAwcOqLa21l6zfv16tba2Kj093V5TVVWlY8eO2WsqKirUv3//kG9yAQCAzinskTNt2jRt3rxZjz/+uD788EMtXbpUixYtUl5eniQpKipKBQUFmjFjhlatWqUdO3ZowoQJSk5O1rhx4yR9+crPFVdcocmTJ2vLli3auHGj8vPzlZ2dreTkZEnSjTfeqNjYWOXm5qq+vl7Lli3T3LlzVVhYGO5LAgAAHVBMuE84cuRIrVixQsXFxXrkkUeUmpqqOXPmKCcnx15zzz336PDhw5oyZYoOHDigiy++WGvXrlVcXJy95qWXXlJ+fr7GjBmjLl26aPz48Zo3b5593OVy6fXXX1deXp6GDx+uc845R9OnTw/5LR0AANB5hf13cjoSfienfeB3cgAAZyJiv5MDAADQHhA5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwUptHzsyZMxUVFaWCggJ735EjR5SXl6eePXuqW7duGj9+vPx+f8j99uzZo6ysLJ199tlKSkrS3XffrePHj4esefPNNzVs2DA5HA6dd955Wrx4cVtfDgAA6CDaNHK2bt2qZ555RoMHDw7ZP23aNL322mt65ZVXtGHDBn366ae67rrr7OMtLS3KysrS0aNHtWnTJi1ZskSLFy/W9OnT7TW7d+9WVlaWLrvsMtXV1amgoEC33nqr1q1b15aXBAAAOog2i5xDhw4pJydHzz77rHr06GHvDwQCev755/Xkk0/q8ssv1/Dhw/XCCy9o06ZN2rx5syTp9ddf17vvvqsXX3xRQ4cO1ZVXXqlHH31UCxYs0NGjRyVJZWVlSk1N1ezZszVw4EDl5+fr+uuv11NPPfU/Z2publYwGAzZAACAmdoscvLy8pSVlaWMjIyQ/bW1tTp27FjI/gEDBqhPnz6qrq6WJFVXV2vQoEFyu932mszMTAWDQdXX19tr/vvcmZmZ9jlOpaSkRC6Xy95SUlK+83UCAID2qU0i5+WXX9b27dtVUlJy0jGfz6fY2FglJCSE7He73fL5fPaarwbOieMnjn3dmmAwqC+++OKUcxUXFysQCNhbY2Pjt7o+AADQ/sWE+4SNjY2aOnWqKioqFBcXF+7TfycOh0MOhyPSYwAAgO9B2F/Jqa2tVVNTk4YNG6aYmBjFxMRow4YNmjdvnmJiYuR2u3X06FEdOHAg5H5+v18ej0eS5PF4Tvq21Ynb37TG6XQqPj4+3JcFAAA6mLBHzpgxY7Rjxw7V1dXZ24gRI5STk2P/77POOkuVlZX2fRoaGrRnzx55vV5Jktfr1Y4dO9TU1GSvqaiokNPpVFpamr3mq+c4sebEOQAAQOcW9rerunfvrvPPPz9kX9euXdWzZ097f25urgoLC5WYmCin06k77rhDXq9XF110kSRp7NixSktL080336zS0lL5fD7df//9ysvLs99uuu222zR//nzdc889uuWWW7R+/XotX75c5eXl4b4kAADQAYU9ck7HU089pS5dumj8+PFqbm5WZmamnn76aft4dHS0Vq9erdtvv11er1ddu3bVxIkT9cgjj9hrUlNTVV5ermnTpmnu3Lnq3bu3nnvuOWVmZkbikgAAQDsTZVmWFekhIiUYDMrlcikQCMjpdIb13P2KeEXpdH00MyvSIwAAOpDT/fvNv10FAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADBSTKQHAMKpX1F5pEfoMD6amRXpEQCgTfFKDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIwU9sgpKSnRyJEj1b17dyUlJWncuHFqaGgIWXPkyBHl5eWpZ8+e6tatm8aPHy+/3x+yZs+ePcrKytLZZ5+tpKQk3X333Tp+/HjImjfffFPDhg2Tw+HQeeedp8WLF4f7cgAAQAcV9sjZsGGD8vLytHnzZlVUVOjYsWMaO3asDh8+bK+ZNm2aXnvtNb3yyivasGGDPv30U1133XX28ZaWFmVlZeno0aPatGmTlixZosWLF2v69On2mt27dysrK0uXXXaZ6urqVFBQoFtvvVXr1q0L9yUBAIAOKMqyLKstH2Dv3r1KSkrShg0bNHr0aAUCAf3gBz/Q0qVLdf3110uSdu3apYEDB6q6uloXXXSR1qxZo6uvvlqffvqp3G63JKmsrEz33nuv9u7dq9jYWN17770qLy/Xzp077cfKzs7WgQMHtHbt2lPO0tzcrObmZvt2MBhUSkqKAoGAnE5nWK+bfyjy9IXzH4rkeT99/AOdADqqYDAol8v1jX+/2/wzOYFAQJKUmJgoSaqtrdWxY8eUkZFhrxkwYID69Omj6upqSVJ1dbUGDRpkB44kZWZmKhgMqr6+3l7z1XOcWHPiHKdSUlIil8tlbykpKeG5SAAA0O60aeS0traqoKBAo0aN0vnnny9J8vl8io2NVUJCQshat9stn89nr/lq4Jw4fuLY160JBoP64osvTjlPcXGxAoGAvTU2Nn7nawQAAO1TTFuePC8vTzt37tRbb73Vlg9z2hwOhxwOR6THAAAA34M2eyUnPz9fq1ev1htvvKHevXvb+z0ej44ePaoDBw6ErPf7/fJ4PPaa//621Ynb37TG6XQqPj4+3JcDAAA6mLBHjmVZys/P14oVK7R+/XqlpqaGHB8+fLjOOussVVZW2vsaGhq0Z88eeb1eSZLX69WOHTvU1NRkr6moqJDT6VRaWpq95qvnOLHmxDkAAEDnFva3q/Ly8rR06VK9+uqr6t69u/0ZGpfLpfj4eLlcLuXm5qqwsFCJiYlyOp2644475PV6ddFFF0mSxo4dq7S0NN18880qLS2Vz+fT/fffr7y8PPvtpttuu03z58/XPffco1tuuUXr16/X8uXLVV7Ot2sAAEAbvJKzcOFCBQIBXXrpperVq5e9LVu2zF7z1FNP6eqrr9b48eM1evRoeTwe/fWvf7WPR0dHa/Xq1YqOjpbX69VNN92kCRMm6JFHHrHXpKamqry8XBUVFRoyZIhmz56t5557TpmZmeG+JAAA0AG1+e/ktGen+z37b4Pfazl9/E5OZPA7OQA6qnbzOzkAAACRQOQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIMZEeAEDH16+oPNIjdBgfzcyK9AhAp8ErOQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASEQOAAAwEpEDAACMROQAAAAjETkAAMBIRA4AADASkQMAAIxE5AAAACMROQAAwEhEDgAAMBKRAwAAjETkAAAAIxE5AADASDGRHgAA8O30KyqP9AgdxkczsyI9AiKAV3IAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJE6fOQsWLBA/fr1U1xcnNLT07Vly5ZIjwQAANqBDv0V8mXLlqmwsFBlZWVKT0/XnDlzlJmZqYaGBiUlJUV6PACAgfjq/umL9Ff3O3TkPPnkk5o8ebImTZokSSorK1N5ebn+9Kc/qaio6KT1zc3Nam5utm8HAgFJUjAYDPtsrc3/Cfs5TRXO55/n/fTxvEcGz3tk8LxHRlv8ff3qeS3L+vqFVgfV3NxsRUdHWytWrAjZP2HCBOsXv/jFKe/z4IMPWpLY2NjY2NjYDNgaGxu/thU67Cs5n332mVpaWuR2u0P2u91u7dq165T3KS4uVmFhoX27tbVV+/btU8+ePRUVFdWm87YHwWBQKSkpamxslNPpjPQ4nQbPe2TwvEcGz/v3rzM+55Zl6eDBg0pOTv7adR02cr4Nh8Mhh8MRsi8hISEyw0SQ0+nsNP8htCc875HB8x4ZPO/fv872nLtcrm9c02G/XXXOOecoOjpafr8/ZL/f75fH44nQVAAAoL3osJETGxur4cOHq7Ky0t7X2tqqyspKeb3eCE4GAADagw79dlVhYaEmTpyoESNG6MILL9ScOXN0+PBh+9tWCOVwOPTggw+e9JYd2hbPe2TwvEcGz/v3j+f8f4uyrG/6/lX7Nn/+fM2aNUs+n09Dhw7VvHnzlJ6eHumxAABAhHX4yAEAADiVDvuZHAAAgK9D5AAAACMROQAAwEhEDgAAMBKR00ksWLBA/fr1U1xcnNLT07Vly5ZIj2S8qqoqXXPNNUpOTlZUVJRWrlwZ6ZGMV1JSopEjR6p79+5KSkrSuHHj1NDQEOmxjLdw4UINHjzY/sVdr9erNWvWRHqsTmfmzJmKiopSQUFBpEdpN4icTmDZsmUqLCzUgw8+qO3bt2vIkCHKzMxUU1NTpEcz2uHDhzVkyBAtWLAg0qN0Ghs2bFBeXp42b96siooKHTt2TGPHjtXhw4cjPZrRevfurZkzZ6q2tlbbtm3T5Zdfrl/+8peqr6+P9GidxtatW/XMM89o8ODBkR6lXeEr5J1Aenq6Ro4cqfnz50v68pehU1JSdMcdd6ioqCjC03UOUVFRWrFihcaNGxfpUTqVvXv3KikpSRs2bNDo0aMjPU6nkpiYqFmzZik3NzfSoxjv0KFDGjZsmJ5++mnNmDFDQ4cO1Zw5cyI9VrvAKzmGO3r0qGpra5WRkWHv69KlizIyMlRdXR3ByYC2FwgEJH35Bxffj5aWFr388ss6fPgw/8TO9yQvL09ZWVkh/z+PL3Xof9YB3+yzzz5TS0uL3G53yH63261du3ZFaCqg7bW2tqqgoECjRo3S+eefH+lxjLdjxw55vV4dOXJE3bp104oVK5SWlhbpsYz38ssva/v27dq6dWukR2mXiBwARsrLy9POnTv11ltvRXqUTqF///6qq6tTIBDQX/7yF02cOFEbNmwgdNpQY2Ojpk6dqoqKCsXFxUV6nHaJyDHcOeeco+joaPn9/pD9fr9fHo8nQlMBbSs/P1+rV69WVVWVevfuHelxOoXY2Fidd955kqThw4dr69atmjt3rp555pkIT2au2tpaNTU1adiwYfa+lpYWVVVVaf78+WpublZ0dHQEJ4w8PpNjuNjYWA0fPlyVlZX2vtbWVlVWVvJ+OYxjWZby8/O1YsUKrV+/XqmpqZEeqdNqbW1Vc3NzpMcw2pgxY7Rjxw7V1dXZ24gRI5STk6O6urpOHzgSr+R0CoWFhZo4caJGjBihCy+8UHPmzNHhw4c1adKkSI9mtEOHDunDDz+0b+/evVt1dXVKTExUnz59IjiZufLy8rR06VK9+uqr6t69u3w+nyTJ5XIpPj4+wtOZq7i4WFdeeaX69OmjgwcPaunSpXrzzTe1bt26SI9mtO7du5/0ebOuXbuqZ8+efA7t/yFyOoHf/OY32rt3r6ZPny6fz6ehQ4dq7dq1J30YGeG1bds2XXbZZfbtwsJCSdLEiRO1ePHiCE1ltoULF0qSLr300pD9L7zwgn77299+/wN1Ek1NTZowYYL+/e9/y+VyafDgwVq3bp1+/vOfR3o0dHL8Tg4AADASn8kBAABGInIAAICRiBwAAGAkIgcAABiJyAEAAEYicgAAgJGIHAAAYCQiBwAAGInIAQAARiJyAACAkYgcAABgpP8DYncHd6iseMkAAAAASUVORK5CYII=",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"x_axis = np.arange(len(orig_schedule.path_frequency))\n",
"y_axis = list(orig_schedule.path_frequency.values())\n",
"\n",
"plt.bar(x_axis, y_axis)\n",
"plt.xticks(x_axis)\n",
"plt.ylim(0, n)\n",
"# plt.yscale(\"log\")\n",
"# plt.yticks([10,100,1000,10000])\n",
"plt;"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:45.846881Z",
"iopub.status.busy": "2025-01-16T09:38:45.846793Z",
"iopub.status.idle": "2025-01-16T09:38:45.848839Z",
"shell.execute_reply": "2025-01-16T09:38:45.848637Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" path id 'p' : path frequency 'f(p)'\n"
]
},
{
"data": {
"text/plain": [
"{'e014b68ad4f3bc2daf207e2498d14cbf': 6579,\n",
" '0a1008773804033d8a4c0e3aba4b96a0': 2381,\n",
" 'eae4df5b039511eac56625f47c337d24': 737,\n",
" 'b14f545c3b39716a455034d9a0c61b8c': 241,\n",
" '11529f85aaa30be08110f3076748e420': 62}"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print(\" path id 'p' : path frequency 'f(p)'\")\n",
"orig_schedule.path_frequency"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The exponential power schedule shaves some of the executions of the \"high-frequency path\" off and adds them to the lower-frequency paths. The path executed least often is either not at all exercised using the traditional power schedule or it is exercised much less often.\n",
"\n",
"Let's have a look at the energy that is assigned to the discovered seeds."
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:45.850622Z",
"iopub.status.busy": "2025-01-16T09:38:45.850512Z",
"iopub.status.idle": "2025-01-16T09:38:45.852569Z",
"shell.execute_reply": "2025-01-16T09:38:45.852343Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"'e014b68ad4f3bc2daf207e2498d14cbf', 0.20000, 'good'\n",
"'0a1008773804033d8a4c0e3aba4b96a0', 0.20000, 'bgI/d'\n",
"'eae4df5b039511eac56625f47c337d24', 0.20000, 'baI/dt'\n",
"'b14f545c3b39716a455034d9a0c61b8c', 0.20000, 'badtuS'\n",
"'11529f85aaa30be08110f3076748e420', 0.20000, 'bad!`tuS'\n"
]
}
],
"source": [
"orig_energy = orig_schedule.normalizedEnergy(orig_fuzzer.population)\n",
"\n",
"for (seed, norm_energy) in zip(orig_fuzzer.population, orig_energy):\n",
" print(\"'%s', %0.5f, %s\" % (getPathID(seed.coverage), # type: ignore\n",
" norm_energy, repr(seed.data)))"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:45.853968Z",
"iopub.status.busy": "2025-01-16T09:38:45.853870Z",
"iopub.status.idle": "2025-01-16T09:38:45.855763Z",
"shell.execute_reply": "2025-01-16T09:38:45.855539Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"'e014b68ad4f3bc2daf207e2498d14cbf', 0.00000, 'good'\n",
"'0a1008773804033d8a4c0e3aba4b96a0', 0.00000, 'bnd'\n",
"'eae4df5b039511eac56625f47c337d24', 0.00000, 'ba.'\n",
"'b14f545c3b39716a455034d9a0c61b8c', 0.00000, 'bad.'\n",
"'11529f85aaa30be08110f3076748e420', 0.00000, 'bad!\\\\.'\n",
"'669903139e4064f7f07bc8b200fd2d26', 1.00000, 'bad!\\\\.'\n"
]
}
],
"source": [
"fast_energy = fast_schedule.normalizedEnergy(fast_fuzzer.population)\n",
"\n",
"for (seed, norm_energy) in zip(fast_fuzzer.population, fast_energy):\n",
" print(\"'%s', %0.5f, %s\" % (getPathID(seed.coverage), # type: ignore\n",
" norm_energy, repr(seed.data)))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Exactly. Our new exponential power schedule assigns most energy to the seed exercising the lowest-frequency path.\n",
"\n",
"Let's compare them in terms of coverage achieved over time for our simple [example](#Runner-and-Sample-Program)."
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:45.857248Z",
"iopub.status.busy": "2025-01-16T09:38:45.857155Z",
"iopub.status.idle": "2025-01-16T09:38:48.567257Z",
"shell.execute_reply": "2025-01-16T09:38:48.566992Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"image/png": "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4WkpIiqzevXq4erVq3j27BmUSiWqVauGvLw85OXloVq1avD09MTp06f1EpupqanKe5lMBqVSWer1jYyM8O+LSZ89e/bS9dzd3ZGSkoIdO3YgPj4eQ4cOxfTp07Fnz55CMRGRdnDmhojKbPPmzUhKSoKzszN+/vlnJCUlITAwELNnz0ZSUhI2b95c7LoKhQKurq5ITExUaU9MTETdunW1HToA4ODBg9LPeXl5OHbsGAICAgAANWrUwMOHD5GTkyP1+fcl32ZmZsjPzy+0XQsLC3Tv3h1z585FQkICDhw4gOTkZO0cBBEVwpkbIiozT09PZGRk4ObNm+jRowdkMhlOnz6NN954Ay4uLi9df9y4cZg0aRJq166N4OBgLF++HElJSVixYoUOogfmz58PX19fBAQEIDY2Fvfv38fAgQMBAM2bN4elpSUmTpyIESNG4NChQ4iLi1NZ38vLC6mpqUhKSkLNmjVhbW2NX375Bfn5+dL6P//8MywsLEq8lJ2INIszN0RULgkJCWjatCnMzc1x+PBh1KxZs1SJDQCMGDECY8aMwccff4z69etj69at+P333+Hr66vlqJ+bNm0apk2bhgYNGmDfvn34/fffpRohe3t7/Pzzz9i8eTPq16+PX375pdCl6G+88QY6d+6Mtm3bokaNGvjll19ga2uLpUuXIiwsDEFBQdixYwf++9//luoKLCLSDN6hmIiqnCtXrsDb2xsnTpwo9pEORFR5ceaGiIiIDAqTGyIiIjIoPC1FREREBoUzN0RERGRQmNwQERGRQWFyQ0RERAaFyQ0REREZFCY3REREZFCY3BAREZFBYXJDREREBoXJDRERERkUJjdERERkUP4f23xxor/FnhQAAAAASUVORK5CYII=",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"_, orig_coverage = population_coverage(orig_fuzzer.inputs, crashme)\n",
"_, fast_coverage = population_coverage(fast_fuzzer.inputs, crashme)\n",
"line_orig, = plt.plot(orig_coverage, label=\"Original Greybox Fuzzer\")\n",
"line_fast, = plt.plot(fast_coverage, label=\"Boosted Greybox Fuzzer\")\n",
"plt.legend(handles=[line_orig, line_fast])\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": [
"As expected, the boosted greybox fuzzer (with the exponential power schedule) achieves coverage much faster.\n",
"\n",
"***Summary***. By fuzzing seeds more often that exercise low-frequency paths, we can explore program paths in a much more efficient manner.\n",
"\n",
"***Try it***. You can try other exponents for the fast power schedule, or change the power schedule entirely. Note that a large exponent can lead to overflows and imprecisions in the floating point arithmetic producing unexpected results. You can execute your own code by opening this chapter as Jupyter notebook.\n",
"\n",
"***Read***. You can find out more about fuzzer boosting in the paper \"[Coverage-based Greybox Fuzzing as Markov Chain](https://mboehme.github.io/paper/CCS16.pdf)\" \\cite{boehme2018greybox} and check out the implementation into AFL at [http://github.com/mboehme/aflfast]."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## A Complex Example: HTMLParser\n",
"\n",
"Let's compare the three fuzzers on a more realistic example, the Python [HTML parser](https://docs.python.org/3/library/html.parser.html). We run all three fuzzers $n=5k$ times on the HTMLParser, starting with the \"empty\" seed."
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:48.569059Z",
"iopub.status.busy": "2025-01-16T09:38:48.568942Z",
"iopub.status.idle": "2025-01-16T09:38:48.571814Z",
"shell.execute_reply": "2025-01-16T09:38:48.571575Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from html.parser import HTMLParser"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:48.573223Z",
"iopub.status.busy": "2025-01-16T09:38:48.573124Z",
"iopub.status.idle": "2025-01-16T09:38:48.574848Z",
"shell.execute_reply": "2025-01-16T09:38:48.574617Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"# create wrapper function\n",
"def my_parser(inp: str) -> None:\n",
" parser = HTMLParser() # resets the HTMLParser object for every fuzz input\n",
" parser.feed(inp)"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:48.576312Z",
"iopub.status.busy": "2025-01-16T09:38:48.576211Z",
"iopub.status.idle": "2025-01-16T09:38:48.578574Z",
"shell.execute_reply": "2025-01-16T09:38:48.578305Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"n = 5000\n",
"seed_input = \" \" # empty seed\n",
"blackbox_fuzzer = AdvancedMutationFuzzer([seed_input], Mutator(), PowerSchedule())\n",
"greybox_fuzzer = GreyboxFuzzer([seed_input], Mutator(), PowerSchedule())\n",
"boosted_fuzzer = CountingGreyboxFuzzer([seed_input], Mutator(), AFLFastSchedule(5))"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:48.580056Z",
"iopub.status.busy": "2025-01-16T09:38:48.579959Z",
"iopub.status.idle": "2025-01-16T09:38:59.812704Z",
"shell.execute_reply": "2025-01-16T09:38:59.812390Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took all three fuzzers 11.23 seconds to generate and execute 5000 inputs.'"
]
},
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"start = time.time()\n",
"blackbox_fuzzer.runs(FunctionCoverageRunner(my_parser), trials=n)\n",
"greybox_fuzzer.runs(FunctionCoverageRunner(my_parser), trials=n)\n",
"boosted_fuzzer.runs(FunctionCoverageRunner(my_parser), trials=n)\n",
"end = time.time()\n",
"\n",
"\"It took all three fuzzers %0.2f seconds to generate and execute %d inputs.\" % (end - start, n)"
]
},
{
"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": 54,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:38:59.814450Z",
"iopub.status.busy": "2025-01-16T09:38:59.814338Z",
"iopub.status.idle": "2025-01-16T09:39:02.366952Z",
"shell.execute_reply": "2025-01-16T09:39:02.366532Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"_, black_coverage = population_coverage(blackbox_fuzzer.inputs, my_parser)\n",
"_, grey_coverage = population_coverage(greybox_fuzzer.inputs, my_parser)\n",
"_, boost_coverage = population_coverage(boosted_fuzzer.inputs, my_parser)\n",
"line_black, = plt.plot(black_coverage, label=\"Blackbox Fuzzer\")\n",
"line_grey, = plt.plot(grey_coverage, label=\"Greybox Fuzzer\")\n",
"line_boost, = plt.plot(boost_coverage, label=\"Boosted Greybox Fuzzer\")\n",
"plt.legend(handles=[line_boost, line_grey, line_black])\n",
"plt.title('Coverage over time')\n",
"plt.xlabel('# of inputs')\n",
"plt.ylabel('lines covered');"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Both greybox fuzzers clearly outperform the blackbox fuzzer. The reason is that the greybox fuzzer \"discovers\" interesting inputs along the way. Let's have a look at the last 10 inputs generated by the greybox versus blackbox fuzzer."
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.369018Z",
"iopub.status.busy": "2025-01-16T09:39:02.368892Z",
"iopub.status.idle": "2025-01-16T09:39:02.371158Z",
"shell.execute_reply": "2025-01-16T09:39:02.370920Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['2 ', '', 'A ', '', '`', '!', '', '', '', '(']"
]
},
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blackbox_fuzzer.inputs[-10:]"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.372570Z",
"iopub.status.busy": "2025-01-16T09:39:02.372465Z",
"iopub.status.idle": "2025-01-16T09:39:02.374483Z",
"shell.execute_reply": "2025-01-16T09:39:02.374244Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"['\\x00',\n",
" '(0<2q$V\\x00\"jy}Kz',\n",
" '_NrGlffJ%\\x14=',\n",
" '2&8,gBa',\n",
" '',\n",
" 'W_&k\\\\, [, ]`). Yet, many important keywords, such as `` are still missing. \n",
"\n",
"To inform the fuzzer about these important keywords, we will need [grammars](Grammars.ipynb); in the section on [smart greybox fuzzing](LangFuzzer.ipynb), we combine them with the techniques above.\n",
"\n",
"***Try it***. You can re-run these experiments to understand the variance of fuzzing experiments. Sometimes, the fuzzer that we claim to be superior does not seem to outperform the inferior fuzzer. In order to do this, you just need to open this chapter as Jupyter notebook."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Directed Greybox Fuzzing\n",
"\n",
"Sometimes, you just want the fuzzer to reach some dangerous location in the source code. This could be a location where you expect a buffer overflow. Or you want to test a recent change in your code base. How do we direct the fuzzer towards these locations?\n",
"\n",
"In this chapter, we introduce directed greybox fuzzing as an optimization problem."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Solving the Maze\n",
"\n",
"To provide a meaningful example where you can easily change the code complexity and target location, we generate the maze source code from the maze provided as string. This example is loosely based on an old [blog post](https://feliam.wordpress.com/2010/10/07/the-symbolic-maze/) on symbolic execution by Felipe Andres Manzano (Quick shout-out!).\n",
"\n",
"You simply specify the maze as a string. Like so."
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.376210Z",
"iopub.status.busy": "2025-01-16T09:39:02.376104Z",
"iopub.status.idle": "2025-01-16T09:39:02.377623Z",
"shell.execute_reply": "2025-01-16T09:39:02.377419Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"maze_string = \"\"\"\n",
"+-+-----+\n",
"|X| |\n",
"| | --+ |\n",
"| | | |\n",
"| +-- | |\n",
"| |#|\n",
"+-----+-+\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The code is generated using the function `generate_maze_code()`. We'll hide the implementation and instead explain what it does. If you are interested in the coding, go [here](ControlFlow.ipynb#Example:-Maze)."
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.379149Z",
"iopub.status.busy": "2025-01-16T09:39:02.379060Z",
"iopub.status.idle": "2025-01-16T09:39:02.536444Z",
"shell.execute_reply": "2025-01-16T09:39:02.536137Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from ControlFlow import generate_maze_code"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.538441Z",
"iopub.status.busy": "2025-01-16T09:39:02.538262Z",
"iopub.status.idle": "2025-01-16T09:39:02.540021Z",
"shell.execute_reply": "2025-01-16T09:39:02.539809Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"# ignore\n",
"def maze(s: str) -> str:\n",
" return \"\" # Will be overwritten by exec()"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.541517Z",
"iopub.status.busy": "2025-01-16T09:39:02.541403Z",
"iopub.status.idle": "2025-01-16T09:39:02.543265Z",
"shell.execute_reply": "2025-01-16T09:39:02.542958Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"# ignore\n",
"def target_tile() -> str:\n",
" return ' ' # Will be overwritten by exec()"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.544911Z",
"iopub.status.busy": "2025-01-16T09:39:02.544793Z",
"iopub.status.idle": "2025-01-16T09:39:02.546604Z",
"shell.execute_reply": "2025-01-16T09:39:02.546294Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"maze_code = generate_maze_code(maze_string)"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.548353Z",
"iopub.status.busy": "2025-01-16T09:39:02.548225Z",
"iopub.status.idle": "2025-01-16T09:39:02.552838Z",
"shell.execute_reply": "2025-01-16T09:39:02.552577Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"exec(maze_code)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The objective is to get the \"X\" to the \"#\" by providing inputs `D` for down, `U` for up, `L` for left, and `R` for right."
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.554751Z",
"iopub.status.busy": "2025-01-16T09:39:02.554585Z",
"iopub.status.idle": "2025-01-16T09:39:02.556803Z",
"shell.execute_reply": "2025-01-16T09:39:02.556478Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"SOLVED\n",
"\n",
"+-+-----+\n",
"| | |\n",
"| | --+ |\n",
"| | | |\n",
"| +-- | |\n",
"| |X|\n",
"+-----+-+\n",
"\n"
]
}
],
"source": [
"print(maze(\"DDDDRRRRUULLUURRRRDDDD\")) # Appending one more 'D', you have reached the target."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Each character in `maze_string` represents a tile. For each tile, a tile-function is generated. \n",
"* If the current tile is \"benign\" (` `), the tile-function corresponding to the next input character (D, U, L, R) is called. Unexpected input characters are ignored. If no more input characters are left, it returns \"VALID\" and the current maze state.\n",
"* If the current tile is a \"trap\" (`+`,`|`,`-`), it returns \"INVALID\" and the current maze state.\n",
"* If the current tile is the \"target\" (`#`), it returns \"SOLVED\" and the current maze state.\n",
"\n",
"***Try it***. You can test other sequences of input characters, or even change the maze entirely. In order to execute your own code, you just need to open this chapter as Jupyter notebook.\n",
"\n",
"To get an idea of the generated code, lets look at the static [call graph](https://en.wikipedia.org/wiki/Call_graph). A call graph shows the order in which functions can be executed."
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.558744Z",
"iopub.status.busy": "2025-01-16T09:39:02.558629Z",
"iopub.status.idle": "2025-01-16T09:39:02.560202Z",
"shell.execute_reply": "2025-01-16T09:39:02.559946Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from ControlFlow import callgraph"
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:02.561830Z",
"iopub.status.busy": "2025-01-16T09:39:02.561712Z",
"iopub.status.idle": "2025-01-16T09:39:03.301133Z",
"shell.execute_reply": "2025-01-16T09:39:03.299976Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"callgraph(maze_code)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### A First Attempt\n",
"\n",
"We introduce a `DictMutator` class which mutates strings by inserting a keyword from a given dictionary:"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:03.304930Z",
"iopub.status.busy": "2025-01-16T09:39:03.304776Z",
"iopub.status.idle": "2025-01-16T09:39:03.308074Z",
"shell.execute_reply": "2025-01-16T09:39:03.307721Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class DictMutator(Mutator):\n",
" \"\"\"Variant of `Mutator` inserting keywords from a dictionary\"\"\"\n",
"\n",
" def __init__(self, dictionary: Sequence[str]) -> None:\n",
" \"\"\"Constructor.\n",
" `dictionary` - a list of strings that can be used as keywords\n",
" \"\"\"\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": [
"To fuzz the maze, we extend the `DictMutator` class to append dictionary keywords to the end of the seed and to remove a character from the end of the seed."
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:03.310030Z",
"iopub.status.busy": "2025-01-16T09:39:03.309919Z",
"iopub.status.idle": "2025-01-16T09:39:03.312787Z",
"shell.execute_reply": "2025-01-16T09:39:03.312506Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class MazeMutator(DictMutator):\n",
" def __init__(self, dictionary: Sequence[str]) -> None:\n",
" super().__init__(dictionary)\n",
" self.mutators.append(self.delete_last_character)\n",
" self.mutators.append(self.append_from_dictionary)\n",
"\n",
" def append_from_dictionary(self, s: str) -> str:\n",
" \"\"\"Returns s with a keyword from the dictionary appended\"\"\"\n",
" random_keyword = random.choice(self.dictionary)\n",
" return s + random_keyword\n",
"\n",
" def delete_last_character(self, s: str) -> str:\n",
" \"\"\"Returns s without the last character\"\"\"\n",
" if len(s) > 0:\n",
" return s[:-1]\n",
" return s"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let's try a standard greybox fuzzer with the classic power schedule and our extended maze mutator (n=20k)."
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:03.314510Z",
"iopub.status.busy": "2025-01-16T09:39:03.314390Z",
"iopub.status.idle": "2025-01-16T09:39:11.589597Z",
"shell.execute_reply": "2025-01-16T09:39:11.589239Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took the fuzzer 8.27 seconds to generate and execute 20000 inputs.'"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n = 20000\n",
"seed_input = \" \" # empty seed\n",
"\n",
"maze_mutator = MazeMutator([\"L\", \"R\", \"U\", \"D\"])\n",
"maze_schedule = PowerSchedule()\n",
"maze_fuzzer = GreyboxFuzzer([seed_input], maze_mutator, maze_schedule)\n",
"\n",
"start = time.time()\n",
"maze_fuzzer.runs(FunctionCoverageRunner(maze), 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": "subslide"
}
},
"source": [
"We will need to print statistics for several fuzzers. Why don't we define a function for that?"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.591532Z",
"iopub.status.busy": "2025-01-16T09:39:11.591408Z",
"iopub.status.idle": "2025-01-16T09:39:11.593916Z",
"shell.execute_reply": "2025-01-16T09:39:11.593632Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"def print_stats(fuzzer: GreyboxFuzzer) -> None:\n",
" total = len(fuzzer.population)\n",
" solved = 0\n",
" invalid = 0\n",
" valid = 0\n",
" for seed in fuzzer.population:\n",
" s = maze(str(seed.data))\n",
" if \"INVALID\" in s:\n",
" invalid += 1\n",
" elif \"VALID\" in s:\n",
" valid += 1\n",
" elif \"SOLVED\" in s:\n",
" solved += 1\n",
" if solved == 1:\n",
" print(\"First solution: %s\" % repr(seed))\n",
" else:\n",
" print(\"??\")\n",
"\n",
" print(\"\"\"Out of %d seeds,\n",
"* %4d solved the maze,\n",
"* %4d were valid but did not solve the maze, and\n",
"* %4d were invalid\"\"\" % (total, solved, valid, invalid))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"How well does our good, old greybox fuzzer do?"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.595606Z",
"iopub.status.busy": "2025-01-16T09:39:11.595488Z",
"iopub.status.idle": "2025-01-16T09:39:11.605360Z",
"shell.execute_reply": "2025-01-16T09:39:11.605095Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Out of 1275 seeds,\n",
"* 0 solved the maze,\n",
"* 319 were valid but did not solve the maze, and\n",
"* 956 were invalid\n"
]
}
],
"source": [
"print_stats(maze_fuzzer)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"It probably didn't solve the maze a single time. How can we make the fuzzer aware how \"far\" a seed is from reaching the target? If we know that, we can just assign more energy to that seed.\n",
"\n",
"***Try it***. Print the statistics for the boosted fuzzer using the `AFLFastSchedule` and the `CountingGreyboxFuzzer`. It will likely perform much better than the unboosted greybox fuzzer: The lowest-probablity path happens to be also the path which reaches the target. You can execute your own code by opening this chapter as Jupyter notebook."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Computing Function-Level Distance\n",
"\n",
"Using the static call graph for the maze code and the target function, we can compute the distance of each function $f$ to the target $t$ as the length of the shortest path between $f$ and $t$.\n",
"\n",
"Fortunately, the generated maze code includes a function called `target_tile` which returns the name of the target-function."
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.606886Z",
"iopub.status.busy": "2025-01-16T09:39:11.606798Z",
"iopub.status.idle": "2025-01-16T09:39:11.609052Z",
"shell.execute_reply": "2025-01-16T09:39:11.608785Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'tile_6_7'"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"target = target_tile()\n",
"target"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Now, we need to find the corresponding function in the call graph. The function `get_callgraph` returns the call graph for the maze code as [networkx](https://networkx.github.io/) graph. Networkx provides some useful functions for graph analysis."
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.610526Z",
"iopub.status.busy": "2025-01-16T09:39:11.610422Z",
"iopub.status.idle": "2025-01-16T09:39:11.611859Z",
"shell.execute_reply": "2025-01-16T09:39:11.611642Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import networkx as nx # type: ignore"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.613285Z",
"iopub.status.busy": "2025-01-16T09:39:11.613200Z",
"iopub.status.idle": "2025-01-16T09:39:11.614863Z",
"shell.execute_reply": "2025-01-16T09:39:11.614640Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from ControlFlow import get_callgraph"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.616249Z",
"iopub.status.busy": "2025-01-16T09:39:11.616170Z",
"iopub.status.idle": "2025-01-16T09:39:11.807539Z",
"shell.execute_reply": "2025-01-16T09:39:11.807261Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"cg = get_callgraph(maze_code)\n",
"for node in cg.nodes():\n",
" if target in node:\n",
" target_node = node\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.809370Z",
"iopub.status.busy": "2025-01-16T09:39:11.809286Z",
"iopub.status.idle": "2025-01-16T09:39:11.811431Z",
"shell.execute_reply": "2025-01-16T09:39:11.811202Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'callgraphX__tile_6_7'"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"target_node"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We can now generate the function-level distance. The dictionary `distance` contains for each function the distance to the target-function. If there is no path to the target, we assign a maximum distance (`0xFFFF`).\n",
"\n",
"The function `nx.shortest_path_length(CG, node, target_node)` returns the length of the shortest path from function `node` to function `target_node` in the call graph `CG`."
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.812950Z",
"iopub.status.busy": "2025-01-16T09:39:11.812839Z",
"iopub.status.idle": "2025-01-16T09:39:11.815354Z",
"shell.execute_reply": "2025-01-16T09:39:11.815122Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"distance = {}\n",
"for node in cg.nodes():\n",
" if \"__\" in node:\n",
" name = node.split(\"__\")[-1]\n",
" else:\n",
" name = node\n",
" try:\n",
" distance[name] = nx.shortest_path_length(cg, node, target_node)\n",
" except:\n",
" distance[name] = 0xFFFF"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"These are the distance values for all tile-functions on the path to the target function."
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.816777Z",
"iopub.status.busy": "2025-01-16T09:39:11.816678Z",
"iopub.status.idle": "2025-01-16T09:39:11.819368Z",
"shell.execute_reply": "2025-01-16T09:39:11.818806Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'callgraphX': 1,\n",
" 'maze': 23,\n",
" 'tile_2_1': 22,\n",
" 'tile_2_3': 8,\n",
" 'tile_2_4': 7,\n",
" 'tile_2_5': 6,\n",
" 'tile_2_6': 5,\n",
" 'tile_2_7': 4,\n",
" 'tile_3_1': 21,\n",
" 'tile_3_3': 9,\n",
" 'tile_3_7': 3,\n",
" 'tile_4_1': 20,\n",
" 'tile_4_3': 10,\n",
" 'tile_4_4': 11,\n",
" 'tile_4_5': 12,\n",
" 'tile_4_7': 2,\n",
" 'tile_5_1': 19,\n",
" 'tile_5_5': 13,\n",
" 'tile_5_7': 1,\n",
" 'tile_6_1': 18,\n",
" 'tile_6_2': 17,\n",
" 'tile_6_3': 16,\n",
" 'tile_6_4': 15,\n",
" 'tile_6_5': 14,\n",
" 'tile_6_7': 0}"
]
},
"execution_count": 77,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{k: distance[k] for k in list(distance) if distance[k] < 0xFFFF}"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"***Summary***. Using the static call graph and the target function $t$, we have shown how to compute the function-level distance of each function $f$ to the target $t$.\n",
"\n",
"***Try it***. You can try and execute your own code by opening this chapter as Jupyter notebook.\n",
"\n",
"* How do we compute distance if there are multiple targets? (Hint: [Geometric Mean](https://en.wikipedia.org/wiki/Geometric_mean)).\n",
"* Given the call graph (CG) and the control-flow graph (CFG$_f$) for each function $f$, how do we compute basic-block (BB)-level distance? (Hint: In CFG$_f$, measure the BB-level distance to *calls* of functions on the path to the target function. Remember that BB-level distance in functions with higher function-level distance is higher, too.)\n",
"\n",
"***Read***. If you are interested in other aspects of search, you can follow up by reading the chapter on [Search-based Fuzzing](SearchBasedFuzzer.ipynb). If you are interested, how to solve the problems above, you can have a look at our paper on \"[Directed Greybox Fuzzing](https://mboehme.github.io/paper/CCS17.pdf)\"."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Directed Power Schedule\n",
"Now that we know how to compute the function-level distance, let's try to implement a power schedule that assigns *more energy to seeds with a lower average distance* to the target function. Notice that the distance values are all *pre-computed*. These values are injected into the program binary, just like the coverage instrumentation. In practice, this makes the computation of the average distance *extremely efficient*.\n",
"\n",
"If you really want to know. Given the function-level distance $d_f(s,t)$ of a function $s$ to a function $t$ in call graph $CG$, our directed power schedule computes the seed distance $d(i,t)$ for a seed $i$ to function $t$ as $d(i,t)=\\dfrac{\\sum_{s\\in CG} d_f(s,t)}{|CG|}$ where $|CG|$ is the number of nodes in the call graph $CG$."
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.821641Z",
"iopub.status.busy": "2025-01-16T09:39:11.821504Z",
"iopub.status.idle": "2025-01-16T09:39:11.824797Z",
"shell.execute_reply": "2025-01-16T09:39:11.824365Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class DirectedSchedule(PowerSchedule):\n",
" \"\"\"Assign high energy to seeds close to some target\"\"\"\n",
"\n",
" def __init__(self, distance: Dict[str, int], exponent: float) -> None:\n",
" self.distance = distance\n",
" self.exponent = exponent\n",
"\n",
" def __getFunctions__(self, coverage: Set[Location]) -> Set[str]:\n",
" functions = set()\n",
" for f, _ in set(coverage):\n",
" functions.add(f)\n",
" return functions\n",
"\n",
" def assignEnergy(self, population: Sequence[Seed]) -> None:\n",
" \"\"\"Assigns each seed energy inversely proportional\n",
" to the average function-level distance to target.\"\"\"\n",
" for seed in population:\n",
" if seed.distance < 0:\n",
" num_dist = 0\n",
" sum_dist = 0\n",
" for f in self.__getFunctions__(seed.coverage):\n",
" if f in list(self.distance):\n",
" sum_dist += self.distance[f]\n",
" num_dist += 1\n",
" seed.distance = sum_dist / num_dist\n",
" seed.energy = (1 / seed.distance) ** self.exponent"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let's see how the directed schedule performs against the good, old greybox fuzzer."
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:11.826601Z",
"iopub.status.busy": "2025-01-16T09:39:11.826501Z",
"iopub.status.idle": "2025-01-16T09:39:22.379521Z",
"shell.execute_reply": "2025-01-16T09:39:22.379252Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took the fuzzer 10.55 seconds to generate and execute 20000 inputs.'"
]
},
"execution_count": 79,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"directed_schedule = DirectedSchedule(distance, 3)\n",
"directed_fuzzer = GreyboxFuzzer([seed_input], maze_mutator, directed_schedule)\n",
"\n",
"start = time.time()\n",
"directed_fuzzer.runs(FunctionCoverageRunner(maze), 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": "code",
"execution_count": 80,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:22.381225Z",
"iopub.status.busy": "2025-01-16T09:39:22.381119Z",
"iopub.status.idle": "2025-01-16T09:39:22.400033Z",
"shell.execute_reply": "2025-01-16T09:39:22.399776Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Out of 2524 seeds,\n",
"* 0 solved the maze,\n",
"* 916 were valid but did not solve the maze, and\n",
"* 1608 were invalid\n"
]
}
],
"source": [
"print_stats(directed_fuzzer)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"It probably didn't solve a single maze either, but we have more valid solutions. So, there is definitely progress.\n",
"\n",
"Let's have a look at the distance values for each seed."
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:22.401759Z",
"iopub.status.busy": "2025-01-16T09:39:22.401632Z",
"iopub.status.idle": "2025-01-16T09:39:22.473891Z",
"shell.execute_reply": "2025-01-16T09:39:22.473504Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAGwCAYAAAC0HlECAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAACHsElEQVR4nO3deXhU1f0/8PedySyZJDNJCMkECElYBMNqQCAutCqrVET9WWvF4oZKsa1L1WKrgLZFbavdqKj9VqzWam3dUIqlIG4EUJAlBqhC2LNAtsk+ycz5/RHuMDOZ5d7ZE96v5+HRZM6cc+655975ZO69nyMJIQSIiIiIKCBNvDtARERE1BswaCIiIiJSgEETERERkQIMmoiIiIgUYNBEREREpACDJiIiIiIFGDQRERERKZAU7w70FU6nEydOnEBaWhokSYp3d4iIiEgBIQSampowYMAAaDSBv0ti0BQhJ06cQF5eXry7QURERCE4evQoBg0aFLAMg6YISUtLA9A96GazOez6th2swy0vfqbqPQ/MPAffu6Aw7Dr/suB8TBqSqaptIiKi3shmsyEvL8/1OR4Ig6YIkS/Jmc3miARNl4xNQ27WV6husisqr5GA26eNgT7J/1eLl4xNw8DsA6hqbIevtXMkAFaLEZeMzYdWw0uMRER09lByaw1vBE9QWo2E5VeOVlx+4cWFAQMmuc6lVxQB6A6Q3Mk/L72iiAETERGRDwyaEtis0blYNb8YJr3WbxlJAu6YWogllxcprvOZ+cWwWowev7dajHhmfjFmjc4Nq89ERER9lSSE8HWlhlSy2WywWCxobGyMyOU5dw6nwOavTuGfO47iaF0r7A6BXIsRkwr7YcEFBUG/YfJX57aKOtQ0tSM7zYhJhZn8homIiM46aj6/GTRFSDSDJiIiIooONZ/fvDxHREREpACDJiIiIiIFGDQRERERKcCgiYiIiEgBBk1ERERECjBoIiIiIlKAQRMRERGRAgyaiIiIiBRg0ERERESkAIMmIiIiIgUYNBEREREpwKCJiIiISAEGTUREREQKMGgiIiIiUoBBExEREZECDJqIiIiIFGDQRERERKQAgyYiIiIiBRg0ERERESnAoImIiIhIAQZNRERERArENWh65plnMHbsWJjNZpjNZpSUlODf//636/X29nYsXrwY/fr1Q2pqKq655hpUV1d71HHk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VtBERERE0RP15JZDhgxBbW1tj983NDRgyJAhoVRJRERElNBCCpoOHToEh8PR4/cdHR04fvx42J0iIiIiSjSqklu+8847rv9///33YbFYXD87HA5s2LABBQUFEescERERUaJQFTTNmzcPQPeNxAsWLPB4TafToaCgAL/5zW8i1jkiIiKiRKEqaHI6nQCAwsJCfPbZZ8jKyopKp4iIiIgSTUj3NFVUVPQImBoaGlTX89FHH+GKK67AgAEDIEkS3nrrLY/Xb7rpJkiS5PFv1qxZHmXq6upwww03wGw2Iz09Hbfeeiuam5s9yuzevRsXX3wxjEYj8vLy8OSTT/boy+uvv46RI0fCaDRizJgxWLt2rertISIior4rpKDpiSeewGuvveb6+dprr0VmZiYGDhyIXbt2Ka6npaUF48aNw8qVK/2WmTVrFiorK13//v73v3u8fsMNN+DLL7/E+vXr8e677+Kjjz7C7bff7nrdZrNhxowZyM/Px/bt2/GrX/0Ky5Ytw3PPPecqs3nzZlx//fW49dZb8cUXX2DevHmYN28eysrKFG8LERER9XEiBAUFBeLTTz8VQgjxn//8R6Snp4v3339f3HrrrWL69OmhVCkAiDfffNPjdwsWLBBXXnml3/eUl5cLAOKzzz5z/e7f//63kCRJHD9+XAghxJ/+9CeRkZEhOjo6XGUefPBBMWLECNfP3/72t8WcOXM86p48ebK44447FPe/sbFRABCNjY2K30NERETxpebzW9U9TbKqqirk5eUBAN599118+9vfxowZM1BQUIDJkydHLKADgE2bNiE7OxsZGRm49NJL8fOf/xz9+vUDAJSWliI9PR0TJ050lZ82bRo0Gg22bt2Kq666CqWlpZg6dSr0er2rzMyZM/HEE0+gvr4eGRkZKC0txb333uvR7syZM3tcLnTX0dGBjo4O1882my1CWxwdSjOvemdlrmu140R9m0cZIQRqWzphMmgxMT8TRblm1LXakZViQJfDiTd3HkdzeycEuh8aSDMk4erT2bcdToEXPj2I9eU1EMKJkVYzzhucgca2Tlf277rWM5nN5azlVnN3Vt+tB2rx+vYj2FfdDLMxCTOKrLjpwu41tHxlGN56oBb/+uIYWjq6kGM2YnxeOmpbOlB+woYjtS2oa+2EJEkYmJ6M2y4qhF6ndWXkhgTU2NpR13ImQ3lmancG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",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"y = [seed.distance for seed in directed_fuzzer.population] # type: ignore\n",
"x = range(len(y))\n",
"plt.scatter(x, y)\n",
"plt.ylim(0, max(y))\n",
"plt.xlabel(\"Seed ID\")\n",
"plt.ylabel(\"Distance\");"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let's normalize the y-axis and improve the importance of the small distance seeds."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Improved Directed Power Schedule\n",
"\n",
"The improved directed schedule normalizes seed distance between the minimal and maximal distance.\n",
"Again, if you really want to know. Given the seed distance $d(i,t)$ of a seed $i$ to a function $t$, our improved power schedule computes the new seed distance $d'(i,t)$ as \n",
"$$\n",
"d'(i,t)=\\begin{cases}\n",
"1 & \\text{if } d(i,t) = \\text{minD} = \\text{maxD}\\\\\n",
"\\text{maxD} - \\text{minD} & \\text{if } d(i,t) = \\text{minD} \\neq \\text{maxD}\\\\\n",
"\\frac{\\text{maxD} - \\text{minD}}{d(i,t)-\\text{minD}} & \\text{otherwise}\n",
"\\end{cases}\n",
"$$\n",
"where \n",
"$$\\text{minD}=\\min_{i\\in T}[d(i,t)]$$\n",
"and\n",
"$$\\text{maxD}=\\max_{i\\in T}[d(i,t)]$$\n",
"where $T$ is the set of seeds (i.e., the population)."
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:22.475767Z",
"iopub.status.busy": "2025-01-16T09:39:22.475646Z",
"iopub.status.idle": "2025-01-16T09:39:22.479812Z",
"shell.execute_reply": "2025-01-16T09:39:22.479579Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class AFLGoSchedule(DirectedSchedule):\n",
" \"\"\"Assign high energy to seeds close to the target\"\"\"\n",
"\n",
" def assignEnergy(self, population: Sequence[Seed]):\n",
" \"\"\"Assigns each seed energy inversely proportional\n",
" to the average function-level distance to target.\"\"\"\n",
" min_dist: Union[int, float] = 0xFFFF\n",
" max_dist: Union[int, float] = 0\n",
"\n",
" for seed in population:\n",
" if seed.distance < 0:\n",
" num_dist = 0\n",
" sum_dist = 0\n",
" for f in self.__getFunctions__(seed.coverage):\n",
" if f in list(self.distance):\n",
" sum_dist += self.distance[f]\n",
" num_dist += 1\n",
" seed.distance = sum_dist / num_dist\n",
" if seed.distance < min_dist:\n",
" min_dist = seed.distance\n",
" if seed.distance > max_dist:\n",
" max_dist = seed.distance\n",
"\n",
" for seed in population:\n",
" if seed.distance == min_dist:\n",
" if min_dist == max_dist:\n",
" seed.energy = 1\n",
" else:\n",
" seed.energy = max_dist - min_dist\n",
" else:\n",
" seed.energy = (max_dist - min_dist) / (seed.distance - min_dist)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let's see how the improved power schedule performs."
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:22.481404Z",
"iopub.status.busy": "2025-01-16T09:39:22.481319Z",
"iopub.status.idle": "2025-01-16T09:39:38.610714Z",
"shell.execute_reply": "2025-01-16T09:39:38.610447Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'It took the fuzzer 16.13 seconds to generate and execute 20000 inputs.'"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"aflgo_schedule = AFLGoSchedule(distance, 3)\n",
"aflgo_fuzzer = GreyboxFuzzer([seed_input], maze_mutator, aflgo_schedule)\n",
"\n",
"start = time.time()\n",
"aflgo_fuzzer.runs(FunctionCoverageRunner(maze), 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": "code",
"execution_count": 84,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:38.612347Z",
"iopub.status.busy": "2025-01-16T09:39:38.612244Z",
"iopub.status.idle": "2025-01-16T09:39:38.646384Z",
"shell.execute_reply": "2025-01-16T09:39:38.646100Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"First solution: lMDD+D4D\u0010M69WGR>PR?RRq\u0003UUVLJLGUURRRRDDDD\n",
"Out of 4071 seeds,\n",
"* 649 solved the maze,\n",
"* 438 were valid but did not solve the maze, and\n",
"* 2984 were invalid\n"
]
}
],
"source": [
"print_stats(aflgo_fuzzer)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"In contrast to all previous power schedules, this one generates hundreds of solutions. It has generated many solutions. \n",
"\n",
"Let's filter out all ignored input characters from the first solution. The function `filter(f, seed.data)` returns a list of elements `e` in `seed.data` where the function `f` applied on `e` returns True."
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:38.648153Z",
"iopub.status.busy": "2025-01-16T09:39:38.648036Z",
"iopub.status.idle": "2025-01-16T09:39:38.656269Z",
"shell.execute_reply": "2025-01-16T09:39:38.655989Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DDDDRRRRUULLUURRRRDDDD\n"
]
}
],
"source": [
"for seed in aflgo_fuzzer.population:\n",
" s = maze(str(seed.data))\n",
" if \"SOLVED\" in s:\n",
" filtered = \"\".join(list(filter(lambda c: c in \"UDLR\", seed.data)))\n",
" print(filtered)\n",
" break"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"This is definitely a solution for the maze specified at the beginning!\n",
"\n",
"***Summary***. After pre-computing the function-level distance to the target, we can develop a power schedule that assigns more energy to a seed with a smaller average function-level distance to the target. By normalizing seed distance values between the minimum and maximum seed distance, we can further boost the directed power schedule.\n",
"\n",
"***Try it***. Implement and evaluate a simpler directed power that uses the minimal (rather than average) function-level distance. What is the downside of using the minimal distance? In order to execute your code, you just need to open this chapter as Jupyter notebook.\n",
"\n",
"***Read***. You can find out more about directed greybox fuzzing in the equally-named paper \"[Directed Greybox Fuzzing](https://mboehme.github.io/paper/CCS17.pdf)\" \\cite{boehme2017greybox} and check out the implementation into AFL at [http://github.com/aflgo/aflgo](http://github.com/aflgo/aflgo)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Synopsis\n",
"\n",
"This chapter introduces advanced methods for grey-box fuzzing inspired by the popular AFL fuzzer. The `GreyboxFuzzer` class has three arguments. First, a list of seed inputs:"
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:38.658216Z",
"iopub.status.busy": "2025-01-16T09:39:38.658080Z",
"iopub.status.idle": "2025-01-16T09:39:38.659856Z",
"shell.execute_reply": "2025-01-16T09:39:38.659590Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"seed_input = \"http://www.google.com/search?q=fuzzing\"\n",
"seeds = [seed_input]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Second, a _mutator_ that changes individual parts of the input."
]
},
{
"cell_type": "code",
"execution_count": 87,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:38.661535Z",
"iopub.status.busy": "2025-01-16T09:39:38.661424Z",
"iopub.status.idle": "2025-01-16T09:39:38.663152Z",
"shell.execute_reply": "2025-01-16T09:39:38.662798Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"mutator = Mutator()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Third, a _power schedule_ that assigns fuzzing effort across the population:"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:38.664740Z",
"iopub.status.busy": "2025-01-16T09:39:38.664627Z",
"iopub.status.idle": "2025-01-16T09:39:38.666216Z",
"shell.execute_reply": "2025-01-16T09:39:38.665874Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"schedule = PowerSchedule()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"These three go into the `GreyboxFuzzer` constructor:"
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:38.668055Z",
"iopub.status.busy": "2025-01-16T09:39:38.667852Z",
"iopub.status.idle": "2025-01-16T09:39:38.670486Z",
"shell.execute_reply": "2025-01-16T09:39:38.670188Z"
},
"slideshow": {
"slide_type": "fragment"
},
"tags": []
},
"outputs": [],
"source": [
"greybox_fuzzer = GreyboxFuzzer(seeds=seeds, mutator=mutator, schedule=schedule)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The `GreyboxFuzzer` class is used in conjunction with a `FunctionCoverageRunner`:"
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:38.672078Z",
"iopub.status.busy": "2025-01-16T09:39:38.671960Z",
"iopub.status.idle": "2025-01-16T09:39:40.713871Z",
"shell.execute_reply": "2025-01-16T09:39:40.713588Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"http_runner = FunctionCoverageRunner(http_program)\n",
"outcomes = greybox_fuzzer.runs(http_runner, trials=10000)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"After fuzzing, we can inspect the population:"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:40.715936Z",
"iopub.status.busy": "2025-01-16T09:39:40.715821Z",
"iopub.status.idle": "2025-01-16T09:39:40.718537Z",
"shell.execute_reply": "2025-01-16T09:39:40.718188Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[http://www.google.com/search?q=fuzzing,\n",
" http://ww.gomgle.com/searciq=fuzzing,\n",
" |h>Att&p8?wwnOgle.cooarhl~Cp`uzza',\n",
" http2Ot/*gv-VRgogec:om/rearc h\u001f=fu~i\n",
" g,\n",
" http\"Ot/*gv-VRgogecom/rearc h\u001f=u~i\n",
" f,\n",
" http\"Ot/g$vVRgogEecom/#reabc h\u001f=u~a\n",
" f,\n",
" httT`2Ot//:7ev^VRgoec:uom/re!6dctKc= hS;\u000f=fu-yH\n",
" /,\n",
" http'www&go/le.comm/sea:rh*?q=Ftzzifg,\n",
" h4tpw://w.gomMle.m/weazciq=fuezzi.',\n",
" h~Att&p8?ws_nOge.#ooarhBlw~Cp`uzza',\n",
" ht8P:/wwvgkowcom/eacrh?q=f uzzing,\n",
" httT`Ot//:7evVR\"goec:uom/re!dctKc=hS;=fu-yH\n",
" I,\n",
" htp://ww.go/gl%.otm/wearch?q=f5zzing,\n",
" httU`Ot//:7evVR\"goec:uo2Jm/re#dctKc=hS;=fu-yH\n",
" I,\n",
" ht4p://ww.gomgle.co.serciq=fuzzing,\n",
" httpp\"OtSo*gv'VRgkg;eom/earcR >\n",
" =u|in\n",
" f,\n",
" htP:/wwvgMolwcomeachq=f uzzine,\n",
" htt://[/wwgwC6.]gogleg/lE.bo/_sEarcq9Afqwz\"king,\n",
" httT`Ot//:7evVR\"goec:uom/re!dctKc=hS;=fu-yH\n",
" I,\n",
" tPK:,wIfvglwvc#omeaBc`9qOin]"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"greybox_fuzzer.population[:20]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Besides the simple `PowerSchedule`, we can have advanced power schedules.\n",
"\n",
"* `AFLFastSchedule` assigns high energy to \"unusual\" paths not taken very often.\n",
"* `AFLGoSchedule` assigns high energy to paths close to uncovered program locations. \n",
"\n",
"The `AFLGoSchedule` class constructor requires a `distance` metric from each node towards target locations, as determined via analysis of the program code. See the chapter for details."
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:40.720502Z",
"iopub.status.busy": "2025-01-16T09:39:40.720371Z",
"iopub.status.idle": "2025-01-16T09:39:40.722001Z",
"shell.execute_reply": "2025-01-16T09:39:40.721734Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"# ignore\n",
"from ClassDiagram import display_class_hierarchy"
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:40.723581Z",
"iopub.status.busy": "2025-01-16T09:39:40.723482Z",
"iopub.status.idle": "2025-01-16T09:39:41.945815Z",
"shell.execute_reply": "2025-01-16T09:39:41.945351Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 93,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# ignore\n",
"display_class_hierarchy([CountingGreyboxFuzzer, AFLFastSchedule, AFLGoSchedule,\n",
" DictMutator, Seed],\n",
" public_methods=[\n",
" Fuzzer.run,\n",
" Fuzzer.__init__,\n",
" Fuzzer.runs,\n",
" Fuzzer.fuzz,\n",
" AdvancedMutationFuzzer.__init__,\n",
" AdvancedMutationFuzzer.fuzz,\n",
" GreyboxFuzzer.run,\n",
" CountingGreyboxFuzzer.run,\n",
" PowerSchedule.__init__,\n",
" DirectedSchedule.__init__,\n",
" AFLGoSchedule.__init__,\n",
" AFLFastSchedule.__init__,\n",
" Seed.__init__,\n",
" Mutator.__init__,\n",
" DictMutator.__init__,\n",
" ],\n",
" types={'Location': Location},\n",
" project='fuzzingbook')"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"jp-MarkdownHeadingCollapsed": true,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
},
"tags": []
},
"source": [
"## Lessons Learned\n",
"\n",
"* A *greybox fuzzer* generates thousands of inputs per second. Pre-processing and lightweight instrumentation \n",
" * allows maintaining the efficiency *during* the fuzzing campaign, and \n",
" * still provides enough information to control progress and slightly steer the fuzzer.\n",
"* The *power schedule* allows _steering and controlling_ the fuzzer. For instance,\n",
" * Our [boosted greybox fuzzer](#Fuzzer-Boosting) spends more energy on seeds that exercise \"unlikely\" paths. The hope is that the generated inputs exercise even more unlikely paths. This in turn increases the number of paths explored per unit time.\n",
" * Our [directed greybox fuzzer](#Directed-Greybox-Fuzzing) spends more energy on seeds that are \"closer\" to a target location. The hope is that the generated inputs get even closer to the target.\n",
"* The *mutator* defines the fuzzer's search space. [Customizing the mutator](GreyboxFuzzer.ipynb#A-First-Attempt) for the given program allows reducing the search space to only relevant inputs. In a couple of chapters, we'll learn about [dictionary-based, and grammar-based mutators](GreyboxGrammarFuzzer.ipynb) to increase the ratio of valid inputs generated."
]
},
{
"cell_type": "markdown",
"metadata": {
"jp-MarkdownHeadingCollapsed": true,
"slideshow": {
"slide_type": "slide"
},
"tags": []
},
"source": [
"## Background\n",
"\n",
"* **Find out more about AFL**: http://lcamtuf.coredump.cx/afl/\n",
"* **Learn about LibFuzzer** (another famous greybox fuzzer): http://llvm.org/docs/LibFuzzer.html\n",
"* **How quickly must a whitebox fuzzer exercise each path to remain more efficient than a greybox fuzzer?** Marcel Böhme and Soumya Paul. 2016. [A Probabilistic Analysis of the Efficiency of Automated Software Testing](https://mboehme.github.io/paper/TSE15.pdf), IEEE TSE, 42:345-360 \\cite{boehme2016efficiency}"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Next Steps\n",
"\n",
"Our aim is still to sufficiently cover functionality, such that we can trigger as many bugs as possible. To this end, we focus on two classes of techniques:\n",
"\n",
"1. Try to cover as much _specified_ functionality as possible. Here, we would need a _specification of the input format,_ distinguishing between individual input elements such as (in our case) numbers, operators, comments, and strings – and attempting to cover as many of these as possible. We will explore this as it comes to [grammar-based testing](GrammarFuzzer.ipynb), and especially in [grammar-based mutations](GreyboxGrammarFuzzer.ipynb).\n",
"\n",
"2. Try to cover as much _implemented_ functionality as possible. The concept of a \"population\" that is systematically \"evolved\" through \"mutations\" will be explored in depth when discussing [search-based testing](SearchBasedFuzzer.ipynb). Furthermore, [symbolic testing](SymbolicFuzzer.ipynb) introduces how to systematically reach program locations by solving the conditions that lie on their paths.\n",
"\n",
"These two techniques make up the gist of the book; and, of course, they can also be combined with each other. As usual, we provide runnable code for all. Enjoy!"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We're done, so we clean up:"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:41.948368Z",
"iopub.status.busy": "2025-01-16T09:39:41.948211Z",
"iopub.status.idle": "2025-01-16T09:39:41.950254Z",
"shell.execute_reply": "2025-01-16T09:39:41.949986Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import os"
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:41.951971Z",
"iopub.status.busy": "2025-01-16T09:39:41.951851Z",
"iopub.status.idle": "2025-01-16T09:39:41.954079Z",
"shell.execute_reply": "2025-01-16T09:39:41.953773Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"if os.path.exists('callgraph.dot'):\n",
" os.remove('callgraph.dot')\n",
"\n",
"if os.path.exists('callgraph.py'):\n",
" os.remove('callgraph.py')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Compatibility"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"In previous version of the chapter, the `AdvancedMutationFuzzer` class was named simply `MutationFuzzer`, causing name confusion with the `MutationFuzzer` class in the [introduction to mutation-based fuzzing](MutationFuzzer.ipynb). The following declaration introduces a backward-compatible alias."
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {
"execution": {
"iopub.execute_input": "2025-01-16T09:39:41.956124Z",
"iopub.status.busy": "2025-01-16T09:39:41.956009Z",
"iopub.status.idle": "2025-01-16T09:39:41.957969Z",
"shell.execute_reply": "2025-01-16T09:39:41.957656Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class MutationFuzzer(AdvancedMutationFuzzer):\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Exercises\n",
"\n",
"To be added. \\todo{}"
]
}
],
"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.12.8"
},
"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
}