{ "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-10-26T13:24:20.156762Z", "iopub.status.busy": "2025-10-26T13:24:20.156386Z", "iopub.status.idle": "2025-10-26T13:24:20.864267Z", "shell.execute_reply": "2025-10-26T13:24:20.863995Z" }, "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." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Synopsis\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", "**Note**: The examples in this section only work after the rest of the cells have been executed.\n" ] }, { "cell_type": "code", "execution_count": 86, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:25:30.810980Z", "iopub.status.busy": "2025-10-26T13:25:30.810896Z", "iopub.status.idle": "2025-10-26T13:25:30.812471Z", "shell.execute_reply": "2025-10-26T13:25:30.812262Z" }, "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-10-26T13:25:30.813773Z", "iopub.status.busy": "2025-10-26T13:25:30.813696Z", "iopub.status.idle": "2025-10-26T13:25:30.815217Z", "shell.execute_reply": "2025-10-26T13:25:30.814972Z" }, "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-10-26T13:25:30.816521Z", "iopub.status.busy": "2025-10-26T13:25:30.816446Z", "iopub.status.idle": "2025-10-26T13:25:30.817929Z", "shell.execute_reply": "2025-10-26T13:25:30.817621Z" }, "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-10-26T13:25:30.819569Z", "iopub.status.busy": "2025-10-26T13:25:30.819455Z", "iopub.status.idle": "2025-10-26T13:25:30.821715Z", "shell.execute_reply": "2025-10-26T13:25:30.821456Z" }, "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-10-26T13:25:30.823333Z", "iopub.status.busy": "2025-10-26T13:25:30.823213Z", "iopub.status.idle": "2025-10-26T13:25:32.601712Z", "shell.execute_reply": "2025-10-26T13:25:32.601441Z" }, "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-10-26T13:25:32.603639Z", "iopub.status.busy": "2025-10-26T13:25:32.603526Z", "iopub.status.idle": "2025-10-26T13:25:32.605850Z", "shell.execute_reply": "2025-10-26T13:25:32.605570Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[http://www.google.com/search?q=fuzzing,\n", " htpp://w.gfoogne.com/seaRchw?q=fuzzng,\n", " xt:/\\gogle.co\n", " 6eacDhq=duDDjne,\n", " httP4:\u000f)3/~ww?wgo8ogle[GcomoaaF(?qfszzn,\n", " xut\u001ae/w\\Rgw/le.X\n", " f6eacDh1dDDje,\n", " ]Gt4p1:?/wP.hgmgL#e.coe/(mrcqh\u001f=$u&i,\n", " Ht4//Zw,w.*IXGoo.csDM/earchD>q=f\"?3^_zho,\n", " xu\u001ae/~|dw/;m.EX\n", " 6eacD1dDDjN,\n", " http,:*//wws.googe.cOm/a;*hq:=fUzzi;ng,\n", " htgp://{wcfo7H]{%{gn.c\u001fsacc(wqfujzvX,\n", " QhttpRwqicoO.kOVa;yhq:=FU~:i\u001b?~g,\n", " tprsqq#Oo\u000fa{Xy4s=F:\u0017,\n", " htg$p*//Scfo7H]{%{=n.c\u001fac(vqf}jz:~X,\n", " http,*//wws.googe/Om/na;*h?q:}fzzi;ng,\n", " xht;\\fo.qg|nFe>#>eaCDh0yu=dd'udJfHe,\n", " jt,V:)'/>u/w?w'k8gGcomoa7&Sz]*:n,\n", " YHc5.//wZwAj>*IG/.cs/ech@>qx=f.>#^_zo,\n", " http,*//wws.googe/Om/na;*h?q*}fzzi;ng,\n", " http:*//wws.googa.cKYm/a;*hq:=fUzzi;ng,\n", " Xu-a/~|dw>;/E(^\n", " o8O96eacD1d|DWJN]" ] }, "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-10-26T13:25:32.607285Z", "iopub.status.busy": "2025-10-26T13:25:32.607175Z", "iopub.status.idle": "2025-10-26T13:25:32.608884Z", "shell.execute_reply": "2025-10-26T13:25:32.608583Z" }, "slideshow": { "slide_type": "fragment" }, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# ignore\n", "from ClassDiagram import display_class_hierarchy" ] }, { "cell_type": "code", "execution_count": 93, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:25:32.610119Z", "iopub.status.busy": "2025-10-26T13:25:32.610046Z", "iopub.status.idle": "2025-10-26T13:25:33.478183Z", "shell.execute_reply": "2025-10-26T13:25:33.477684Z" }, "slideshow": { "slide_type": "subslide" }, "tags": [ "remove-input" ] }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CountingGreyboxFuzzer\n", "\n", "\n", "CountingGreyboxFuzzer\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "reset()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxFuzzer\n", "\n", "\n", "GreyboxFuzzer\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "reset()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CountingGreyboxFuzzer->GreyboxFuzzer\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "create_candidate()\n", "\n", "\n", "\n", "reset()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxFuzzer->AdvancedMutationFuzzer\n", "\n", "\n", "\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "runs()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer->Fuzzer\n", "\n", "\n", "\n", "\n", "\n", "AFLFastSchedule\n", "\n", "\n", "AFLFastSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PowerSchedule\n", "\n", "\n", "PowerSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "choose()\n", "\n", "\n", "\n", "normalizedEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AFLFastSchedule->PowerSchedule\n", "\n", "\n", "\n", "\n", "\n", "AFLGoSchedule\n", "\n", "\n", "AFLGoSchedule\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "DirectedSchedule\n", "\n", "\n", "DirectedSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "__getFunctions__()\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AFLGoSchedule->DirectedSchedule\n", "\n", "\n", "\n", "\n", "\n", "DirectedSchedule->PowerSchedule\n", "\n", "\n", "\n", "\n", "\n", "DictMutator\n", "\n", "\n", "DictMutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "insert_from_dictionary()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Mutator\n", "\n", "\n", "Mutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "delete_random_character()\n", "\n", "\n", "\n", "flip_random_character()\n", "\n", "\n", "\n", "insert_random_character()\n", "\n", "\n", "\n", "mutate()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "DictMutator->Mutator\n", "\n", "\n", "\n", "\n", "\n", "Seed\n", "\n", "\n", "Seed\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "__repr__()\n", "\n", "\n", "\n", "__str__()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CountingGreyboxFuzzer\n", "\n", "\n", "CountingGreyboxFuzzer\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "reset()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxFuzzer\n", "\n", "\n", "GreyboxFuzzer\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "reset()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CountingGreyboxFuzzer->GreyboxFuzzer\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "AdvancedMutationFuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "create_candidate()\n", "\n", "\n", "\n", "reset()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "GreyboxFuzzer->AdvancedMutationFuzzer\n", "\n", "\n", "\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "Fuzzer\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "fuzz()\n", "\n", "\n", "\n", "run()\n", "\n", "\n", "\n", "runs()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AdvancedMutationFuzzer->Fuzzer\n", "\n", "\n", "\n", "\n", "\n", "AFLFastSchedule\n", "\n", "\n", "AFLFastSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PowerSchedule\n", "\n", "\n", "PowerSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "choose()\n", "\n", "\n", "\n", "normalizedEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AFLFastSchedule->PowerSchedule\n", "\n", "\n", "\n", "\n", "\n", "AFLGoSchedule\n", "\n", "\n", "AFLGoSchedule\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "DirectedSchedule\n", "\n", "\n", "DirectedSchedule\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "__getFunctions__()\n", "\n", "\n", "\n", "assignEnergy()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "AFLGoSchedule->DirectedSchedule\n", "\n", "\n", "\n", "\n", "\n", "DirectedSchedule->PowerSchedule\n", "\n", "\n", "\n", "\n", "\n", "DictMutator\n", "\n", "\n", "DictMutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "insert_from_dictionary()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Mutator\n", "\n", "\n", "Mutator\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "delete_random_character()\n", "\n", "\n", "\n", "flip_random_character()\n", "\n", "\n", "\n", "insert_random_character()\n", "\n", "\n", "\n", "mutate()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "DictMutator->Mutator\n", "\n", "\n", "\n", "\n", "\n", "Seed\n", "\n", "\n", "Seed\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "__repr__()\n", "\n", "\n", "\n", "__str__()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 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, "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-10-26T13:24:20.885202Z", "iopub.status.busy": "2025-10-26T13:24:20.885041Z", "iopub.status.idle": "2025-10-26T13:24:20.887270Z", "shell.execute_reply": "2025-10-26T13:24:20.887006Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils.setup" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:20.888840Z", "iopub.status.busy": "2025-10-26T13:24:20.888725Z", "iopub.status.idle": "2025-10-26T13:24:20.890342Z", "shell.execute_reply": "2025-10-26T13:24:20.890033Z" }, "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-10-26T13:24:20.891984Z", "iopub.status.busy": "2025-10-26T13:24:20.891878Z", "iopub.status.idle": "2025-10-26T13:24:20.893432Z", "shell.execute_reply": "2025-10-26T13:24:20.893172Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import random" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:20.894630Z", "iopub.status.busy": "2025-10-26T13:24:20.894550Z", "iopub.status.idle": "2025-10-26T13:24:21.221063Z", "shell.execute_reply": "2025-10-26T13:24:21.220738Z" }, "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-10-26T13:24:21.223233Z", "iopub.status.busy": "2025-10-26T13:24:21.222994Z", "iopub.status.idle": "2025-10-26T13:24:21.225055Z", "shell.execute_reply": "2025-10-26T13:24:21.224799Z" }, "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-10-26T13:24:21.226381Z", "iopub.status.busy": "2025-10-26T13:24:21.226284Z", "iopub.status.idle": "2025-10-26T13:24:21.228242Z", "shell.execute_reply": "2025-10-26T13:24:21.227913Z" }, "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-10-26T13:24:21.230067Z", "iopub.status.busy": "2025-10-26T13:24:21.229937Z", "iopub.status.idle": "2025-10-26T13:24:21.232087Z", "shell.execute_reply": "2025-10-26T13:24:21.231795Z" }, "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-10-26T13:24:21.233529Z", "iopub.status.busy": "2025-10-26T13:24:21.233414Z", "iopub.status.idle": "2025-10-26T13:24:21.235576Z", "shell.execute_reply": "2025-10-26T13:24:21.235298Z" }, "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-10-26T13:24:21.237148Z", "iopub.status.busy": "2025-10-26T13:24:21.237003Z", "iopub.status.idle": "2025-10-26T13:24:21.239023Z", "shell.execute_reply": "2025-10-26T13:24:21.238747Z" }, "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-10-26T13:24:21.240665Z", "iopub.status.busy": "2025-10-26T13:24:21.240535Z", "iopub.status.idle": "2025-10-26T13:24:21.242910Z", "shell.execute_reply": "2025-10-26T13:24:21.242674Z" }, "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-10-26T13:24:21.244303Z", "iopub.status.busy": "2025-10-26T13:24:21.244188Z", "iopub.status.idle": "2025-10-26T13:24:21.245851Z", "shell.execute_reply": "2025-10-26T13:24:21.245599Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Coverage import Location" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:21.247383Z", "iopub.status.busy": "2025-10-26T13:24:21.247254Z", "iopub.status.idle": "2025-10-26T13:24:21.249390Z", "shell.execute_reply": "2025-10-26T13:24:21.249121Z" }, "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-10-26T13:24:21.251241Z", "iopub.status.busy": "2025-10-26T13:24:21.251103Z", "iopub.status.idle": "2025-10-26T13:24:21.253990Z", "shell.execute_reply": "2025-10-26T13:24:21.253754Z" }, "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-10-26T13:24:21.255372Z", "iopub.status.busy": "2025-10-26T13:24:21.255266Z", "iopub.status.idle": "2025-10-26T13:24:21.257079Z", "shell.execute_reply": "2025-10-26T13:24:21.256809Z" }, "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-10-26T13:24:21.258280Z", "iopub.status.busy": "2025-10-26T13:24:21.258201Z", "iopub.status.idle": "2025-10-26T13:24:21.275990Z", "shell.execute_reply": "2025-10-26T13:24:21.275711Z" }, "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-10-26T13:24:21.277594Z", "iopub.status.busy": "2025-10-26T13:24:21.277424Z", "iopub.status.idle": "2025-10-26T13:24:21.279893Z", "shell.execute_reply": "2025-10-26T13:24:21.279649Z" }, "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-10-26T13:24:21.281286Z", "iopub.status.busy": "2025-10-26T13:24:21.281161Z", "iopub.status.idle": "2025-10-26T13:24:21.292283Z", "shell.execute_reply": "2025-10-26T13:24:21.291859Z" }, "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-10-26T13:24:21.294017Z", "iopub.status.busy": "2025-10-26T13:24:21.293913Z", "iopub.status.idle": "2025-10-26T13:24:21.295824Z", "shell.execute_reply": "2025-10-26T13:24:21.295564Z" }, "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-10-26T13:24:21.297247Z", "iopub.status.busy": "2025-10-26T13:24:21.297132Z", "iopub.status.idle": "2025-10-26T13:24:21.299831Z", "shell.execute_reply": "2025-10-26T13:24:21.299578Z" }, "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-10-26T13:24:21.301211Z", "iopub.status.busy": "2025-10-26T13:24:21.301117Z", "iopub.status.idle": "2025-10-26T13:24:21.302615Z", "shell.execute_reply": "2025-10-26T13:24:21.302399Z" }, "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-10-26T13:24:21.303831Z", "iopub.status.busy": "2025-10-26T13:24:21.303745Z", "iopub.status.idle": "2025-10-26T13:24:21.307006Z", "shell.execute_reply": "2025-10-26T13:24:21.306668Z" }, "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-10-26T13:24:21.308254Z", "iopub.status.busy": "2025-10-26T13:24:21.308175Z", "iopub.status.idle": "2025-10-26T13:24:21.310172Z", "shell.execute_reply": "2025-10-26T13:24:21.309960Z" }, "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-10-26T13:24:21.311428Z", "iopub.status.busy": "2025-10-26T13:24:21.311354Z", "iopub.status.idle": "2025-10-26T13:24:21.313042Z", "shell.execute_reply": "2025-10-26T13:24:21.312715Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import time" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:21.314520Z", "iopub.status.busy": "2025-10-26T13:24:21.314410Z", "iopub.status.idle": "2025-10-26T13:24:21.316062Z", "shell.execute_reply": "2025-10-26T13:24:21.315819Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "n = 30000" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:21.317269Z", "iopub.status.busy": "2025-10-26T13:24:21.317190Z", "iopub.status.idle": "2025-10-26T13:24:25.031171Z", "shell.execute_reply": "2025-10-26T13:24:25.030902Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'It took the blackbox mutation-based fuzzer 3.71 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-10-26T13:24:25.032761Z", "iopub.status.busy": "2025-10-26T13:24:25.032645Z", "iopub.status.idle": "2025-10-26T13:24:28.449753Z", "shell.execute_reply": "2025-10-26T13:24:28.449479Z" }, "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-10-26T13:24:28.451302Z", "iopub.status.busy": "2025-10-26T13:24:28.451176Z", "iopub.status.idle": "2025-10-26T13:24:28.455536Z", "shell.execute_reply": "2025-10-26T13:24:28.455242Z" }, "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-10-26T13:24:28.457514Z", "iopub.status.busy": "2025-10-26T13:24:28.457366Z", "iopub.status.idle": "2025-10-26T13:24:28.460395Z", "shell.execute_reply": "2025-10-26T13:24:28.460088Z" }, "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-10-26T13:24:28.461839Z", "iopub.status.busy": "2025-10-26T13:24:28.461739Z", "iopub.status.idle": "2025-10-26T13:24:32.231661Z", "shell.execute_reply": "2025-10-26T13:24:32.231328Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took the greybox mutation-based fuzzer 3.77 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-10-26T13:24:32.233334Z", "iopub.status.busy": "2025-10-26T13:24:32.233233Z", "iopub.status.idle": "2025-10-26T13:24:35.620547Z", "shell.execute_reply": "2025-10-26T13:24:35.620271Z" }, "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-10-26T13:24:35.622201Z", "iopub.status.busy": "2025-10-26T13:24:35.622087Z", "iopub.status.idle": "2025-10-26T13:24:35.624058Z", "shell.execute_reply": "2025-10-26T13:24:35.623837Z" }, "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-10-26T13:24:35.625574Z", "iopub.status.busy": "2025-10-26T13:24:35.625464Z", "iopub.status.idle": "2025-10-26T13:24:35.633822Z", "shell.execute_reply": "2025-10-26T13:24:35.633561Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:35.635327Z", "iopub.status.busy": "2025-10-26T13:24:35.635225Z", "iopub.status.idle": "2025-10-26T13:24:35.636831Z", "shell.execute_reply": "2025-10-26T13:24:35.636594Z" }, "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-10-26T13:24:35.638141Z", "iopub.status.busy": "2025-10-26T13:24:35.638058Z", "iopub.status.idle": "2025-10-26T13:24:35.762937Z", "shell.execute_reply": "2025-10-26T13:24:35.762652Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/png": "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eooBPVnjpUuXQr058FNkZKQJODeKcAMAcGQX1Y3WbSB8UVAMAAAchXADAAAchXADAAAcJVN6nSBIT50OAADCg+u47ctEf+ku3Jw+fdr8jImJCfWmAACA6ziO586d+6rrRFjpbJ5qnSTowIEDkjNnzjSf3ElTpYam/fv3S65cudL0uZ2GfeU79pXv2Fe+Y1/5h/0V+n2lcUWDTdGiRa85XDzdtdzoDilevHhAX0M/TL78vmFf+Y595Tv2le/YV/5hf4V2X12rxcaFgmIAAOAohBsAAOAohJs0FBUVJcOHDzc/cXXsK9+xr3zHvvId+8o/7K/w2lfprqAYAAA4Gy03AADAUQg3AADAUQg3AADAUQg3AADAUQg3aWTSpElSqlQpyZIli9SvX182bNggTvfiiy+aWZ49L5UqVXLff+HCBenbt6/kz59fcuTIIffcc48cOnTI6zni4uKkTZs2ki1bNilUqJA8+eSTcvnyZa91li1bJjfddJOpvC9XrpzMmDFD7G7FihXStm1bM5Om7pd58+Z53a91/C+88IIUKVJEsmbNKs2bN5fffvvNa51jx45J165dzSRYefLkkR49esiZM2e81vnpp5/k1ltvNd87nRH0tddeS7Yts2fPNp+LrlO9enVZsGCBhNO+evDBB5N9z1q1apUu99WoUaOkbt26ZoZ1/X1p37697Nq1y2udYP7e2fnvni/7qmnTpsm+W3369El3+2rKlClSo0YN96R7DRs2lIULF4b3d0pHS+HGzJo1y4qMjLTee+89a8eOHVavXr2sPHnyWIcOHbKcbPjw4VbVqlWt+Ph49+XIkSPu+/v06WPFxMRYS5YssTZt2mQ1aNDAio2Ndd9/+fJlq1q1albz5s2tzZs3WwsWLLAKFChgDR061L3O77//bmXLls0aNGiQtXPnTuvNN9+0MmbMaH3zzTeWnel7efbZZ60vv/xSRyNac+fO9bp/9OjRVu7cua158+ZZW7dute68806rdOnS1vnz593rtGrVyqpZs6a1bt06a+XKlVa5cuWszp07u+8/efKkVbhwYatr167W9u3brU8//dTKmjWrNW3aNPc6q1evNvvrtddeM/vvueeeszJnzmxt27bNCpd91b17d7MvPL9nx44d81onveyrli1bWu+//755D1u2bLHuuOMOq0SJEtaZM2eC/ntn9797vuyrJk2a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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-10-26T13:24:35.764612Z", "iopub.status.busy": "2025-10-26T13:24:35.764461Z", "iopub.status.idle": "2025-10-26T13:24:35.766243Z", "shell.execute_reply": "2025-10-26T13:24:35.765967Z" }, "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-10-26T13:24:35.767641Z", "iopub.status.busy": "2025-10-26T13:24:35.767543Z", "iopub.status.idle": "2025-10-26T13:24:35.769245Z", "shell.execute_reply": "2025-10-26T13:24:35.769028Z" }, "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-10-26T13:24:35.770544Z", "iopub.status.busy": "2025-10-26T13:24:35.770450Z", "iopub.status.idle": "2025-10-26T13:24:35.772592Z", "shell.execute_reply": "2025-10-26T13:24:35.772366Z" }, "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-10-26T13:24:35.773901Z", "iopub.status.busy": "2025-10-26T13:24:35.773817Z", "iopub.status.idle": "2025-10-26T13:24:35.776202Z", "shell.execute_reply": "2025-10-26T13:24:35.775975Z" }, "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-10-26T13:24:35.777560Z", "iopub.status.busy": "2025-10-26T13:24:35.777477Z", "iopub.status.idle": "2025-10-26T13:24:37.167882Z", "shell.execute_reply": "2025-10-26T13:24:37.167360Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took the fuzzer w/ exponential schedule 1.39 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-10-26T13:24:37.169782Z", "iopub.status.busy": "2025-10-26T13:24:37.169662Z", "iopub.status.idle": "2025-10-26T13:24:37.171733Z", "shell.execute_reply": "2025-10-26T13:24:37.171231Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:37.173342Z", "iopub.status.busy": "2025-10-26T13:24:37.173203Z", "iopub.status.idle": "2025-10-26T13:24:37.208283Z", "shell.execute_reply": "2025-10-26T13:24:37.207998Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/png": "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YsWKFbF06dJ46KGH4tJLL60c37lzZ4wbNy5OOOGEWLduXcyfPz+uvfbauPXWW1v7LQEA7VBd6eAullbw0Y9+NBobG+N73/teZd/5559f9Nj84Ac/KHpxjj322Pjrv/7r+Ju/+ZvieHNzc/Ga2267LS644IIiHA0bNizWrl0bI0aMKGruu++++MhHPhL//d//Xbw+BakvfelLsXXr1ujatWtRM3PmzLj77rtj48aNb6mtKSj16tWr+P6pxwgAOPK91c/vVu/J+eM//uNYuXJl/PSnPy22n3766Xj44Yfj3HPPLbY3b95cBJN0i6osNXTkyJGxevXqYjut0y2qcsBJUn2nTp2Knp9yzejRoysBJ0m9QZs2bYpXX331sG1raWkpLszBCwCQpy6tfcLUm5LCw5AhQ6Jz587FGJ2//du/LW4/JSngJKnn5mBpu3wsrfv371/d0C5dom/fvlU1adzPoecoH+vTp88b2jZnzpz46le/2qrvFwA4MrV6T86dd94Z//RP/xR33HFHPPnkk3H77bfH3//93xfrWps1a1bRtVVetmzZUusmAQDtpSfnqquuKnpz0tiaZPjw4fFf//VfRS/KxRdfHE1NTcX+bdu2FbOrytL2qaeeWnydarZv31513n379hUzrsqvT+v0moOVt8s1h6qvry8WACB/rd6T86tf/aoYO3OwdNvqwIEDxdfpFlMKIWncTlm6vZXG2owaNarYTusdO3YUs6bKVq1aVZwjjd0p16QZV3v37q3UpJlYgwcPPuytKgCgY2n1kHPeeecVY3CWLVsWL7zwQixZsiRuuOGG+MQnPlEcr6uri+nTp8d1110X99xzT6xfvz4mT55czJiaOHFiUTN06NA455xz4pJLLok1a9bEI488EtOmTSt6h1JdctFFFxWDjtPzc9JU88WLF8eCBQtixowZrf2WAID2qNTKdu7cWbriiitKAwcOLHXr1q30nve8p/SlL32p1NLSUqk5cOBA6Zprrik1NjaW6uvrS2PGjClt2rSp6jwvv/xy6cILLyz17Nmz1NDQUJoyZUpp165dVTVPP/106cwzzyzOcdxxx5Xmzp37ttra3Nycps8XawCgfXirn9+t/pyc9sRzcgCg/anZc3IAAI4EQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCy1Sch56aWX4tOf/nT069cvunfvHsOHD48nnniicrxUKsXs2bPjmGOOKY6PHTs2nn/++apzvPLKKzFp0qRoaGiI3r17x9SpU2P37t1VNc8880ycddZZ0a1btxgwYEDMmzevLd4OANAOtXrIefXVV+OMM86Io446KpYvXx7PPfdcXH/99dGnT59KTQojCxcujEWLFsXjjz8ePXr0iPHjx8eePXsqNSngbNiwIVasWBFLly6Nhx56KC699NLK8Z07d8a4cePihBNOiHXr1sX8+fPj2muvjVtvvbW13xIA0B6VWtnVV19dOvPMM3/j8QMHDpSamppK8+fPr+zbsWNHqb6+vvTDH/6w2H7uuedKqWlr166t1CxfvrxUV1dXeumll4rtm2++udSnT59SS0tL1fcePHjwW25rc3Nz8X3SGgBoH97q53er9+Tcc889MWLEiPjTP/3T6N+/f/zRH/1RfOc736kc37x5c2zdurW4RVXWq1evGDlyZKxevbrYTut0iyqdpyzVd+rUqej5KdeMHj06unbtWqlJvUGbNm0qepMOp6WlpegBOngBAPLU6iHnP//zP+OWW26J3//934/7778/vvCFL8Rf/dVfxe23314cTwEnaWxsrHpd2i4fS+sUkA7WpUuX6Nu3b1XN4c5x8Pc41Jw5c4pAVV7SOB4AIE+tHnIOHDgQp512Wvzd3/1d0YuTxtFccsklxfibWps1a1Y0NzdXli1bttS6SQBAewk5acbUsGHDqvYNHTo0XnzxxeLrpqamYr1t27aqmrRdPpbW27dvrzq+b9++YsbVwTWHO8fB3+NQ9fX1xWytgxcAIE+tHnLSzKo0LuZgP/3pT4tZUMmgQYOKELJy5crK8TQ2Jo21GTVqVLGd1jt27ChmTZWtWrWq6CVKY3fKNWnG1d69eys1aSbW4MGDq2ZyAQAdU6uHnCuvvDIee+yx4nbVz372s7jjjjuKad2XXXZZcbyuri6mT58e1113XTFIef369TF58uQ49thjY+LEiZWen3POOae4zbVmzZp45JFHYtq0aXHBBRcUdclFF11UDDpOz89JU80XL14cCxYsiBkzZrT2WwIA2qO2mNp17733lk4++eRiWviQIUNKt9566xumkV9zzTWlxsbGombMmDGlTZs2VdW8/PLLpQsvvLDUs2fPUkNDQ2nKlCmlXbt2VdU8/fTTxXT1dI7jjjuuNHfu3LfVTlPIAaD9eauf33XpP9FBpdtkaZZVGoRsfA4A5PX57XdXAQBZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGSpS60bkKsTZy6rdRPajRfmTqh1EwDIkJ4cACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCwJOQBAloQcACBLQg4AkCUhBwDIkpADAGRJyAEAsiTkAABZEnIAgCy1eciZO3du1NXVxfTp0yv79uzZE5dddln069cvevbsGeeff35s27at6nUvvvhiTJgwId71rndF//7946qrrop9+/ZV1TzwwANx2mmnRX19fZx00klx2223tfXbAQDaiTYNOWvXro1vf/vb8b73va9q/5VXXhn33ntv3HXXXfHggw/Gz3/+8/jkJz9ZOb5///4i4Lz++uvx6KOPxu23314EmNmzZ1dqNm/eXNScffbZ8dRTTxUh6nOf+1zcf//9bfmWAICOHnJ2794dkyZNiu985zvRp0+fyv7m5ub43ve+FzfccEN8+MMfjtNPPz2+//3vF2HmscceK2r+7d/+LZ577rn4wQ9+EKeeemqce+658fWvfz1u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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-10-26T13:24:37.209976Z", "iopub.status.busy": "2025-10-26T13:24:37.209856Z", "iopub.status.idle": "2025-10-26T13:24:37.212320Z", "shell.execute_reply": "2025-10-26T13:24:37.212060Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " path id 'p' : path frequency 'f(p)'\n" ] }, { "data": { "text/plain": [ "{'e014b68ad4f3bc2daf207e2498d14cbf': 5612,\n", " '0a1008773804033d8a4c0e3aba4b96a0': 2607,\n", " 'eae4df5b039511eac56625f47c337d24': 1105,\n", " 'b14f545c3b39716a455034d9a0c61b8c': 457,\n", " '11529f85aaa30be08110f3076748e420': 219}" ] }, "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-10-26T13:24:37.213949Z", "iopub.status.busy": "2025-10-26T13:24:37.213788Z", "iopub.status.idle": "2025-10-26T13:24:38.504139Z", "shell.execute_reply": "2025-10-26T13:24:38.503873Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'It took the fuzzer w/ original schedule 1.29 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-10-26T13:24:38.505677Z", "iopub.status.busy": "2025-10-26T13:24:38.505557Z", "iopub.status.idle": "2025-10-26T13:24:38.538265Z", "shell.execute_reply": "2025-10-26T13:24:38.538007Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAGiCAYAAAAFotdwAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAH2lJREFUeJzt3Q20VWWdP/AfL/ISyKsDaIIyOYMwko5gxKisDAY0coaymVEpHCOdWuKIlAbLJDNnIBgtyBeymnCtckJnDWYwYixIHRUFcVTEIFuDI9UAlXIBiysvZ9bzrP85f66S6XTxcJ/7+ay13Wfv/Zx9nr3Be748L/u2qVQqlQAAKEzbelcAAOBQEHIAgCIJOQBAkYQcAKBIQg4AUCQhBwAokpADABRJyAEAiiTkAABFEnIAgCK95ZDz0EMPxbnnnhvHHHNMtGnTJu65554mx9NviZg5c2YcffTR0blz5xgzZkw8//zzTcq89NJLMXHixOjWrVv06NEjJk+eHLt27WpS5plnnokzzzwzOnXqFP379485c+a8ri533313nHjiibnM0KFD49///d/f6uUAAIV6yyHnlVdeiZNPPjluueWWgx5PYWT+/PmxYMGCePzxx6NLly4xbty42L17d61MCjjr16+P5cuXx5IlS3JwuvTSS2vHd+zYEWPHjo3jjjsu1q5dG3Pnzo3rrrsubr/99lqZRx99NC644IIckP7zP/8zJkyYkJdnn332rd8FAKA8ld9DevvixYtr2/v376/069evMnfu3Nq+7du3Vzp27Fj5l3/5l7z93HPP5fetWbOmVua+++6rtGnTpvKzn/0sb996662Vnj17VhobG2tlPvvZz1YGDRpU2/7rv/7ryvjx45vUZ8SIEZW/+7u/+30uCQAoRPvmDEybNm2KLVu25C6qqu7du8eIESNi1apVcf755+d16qIaPnx4rUwq37Zt29zy86EPfSiXGTVqVHTo0KFWJrUGfelLX4qXX345evbsmctMmzatyeenMq/tPjtQY2NjXqr279+fu8569+6du94AgMNfamfZuXNnHjqT8sNv06whJwWcpG/fvk32p+3qsbTu06dP00q0bx+9evVqUmbgwIGvO0f1WAo5af1Gn3Mws2bNii984Qu/1zUCAIeHzZs3x7HHHvv2hJzD3YwZM5q0/jQ0NMSAAQPyTUqDoAGAw18a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", "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-10-26T13:24:38.539742Z", "iopub.status.busy": "2025-10-26T13:24:38.539616Z", "iopub.status.idle": "2025-10-26T13:24:38.541890Z", "shell.execute_reply": "2025-10-26T13:24:38.541649Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " path id 'p' : path frequency 'f(p)'\n" ] }, { "data": { "text/plain": [ "{'e014b68ad4f3bc2daf207e2498d14cbf': 6581,\n", " '0a1008773804033d8a4c0e3aba4b96a0': 2379,\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-10-26T13:24:38.543322Z", "iopub.status.busy": "2025-10-26T13:24:38.543208Z", "iopub.status.idle": "2025-10-26T13:24:38.545406Z", "shell.execute_reply": "2025-10-26T13:24:38.545055Z" }, "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-10-26T13:24:38.546773Z", "iopub.status.busy": "2025-10-26T13:24:38.546675Z", "iopub.status.idle": "2025-10-26T13:24:38.548698Z", "shell.execute_reply": "2025-10-26T13:24:38.548408Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'e014b68ad4f3bc2daf207e2498d14cbf', 0.00000, 'good'\n", "'0a1008773804033d8a4c0e3aba4b96a0', 0.00000, 'bnd'\n", "'eae4df5b039511eac56625f47c337d24', 0.00030, 'ba.'\n", "'b14f545c3b39716a455034d9a0c61b8c', 0.02464, 'bad.'\n", "'11529f85aaa30be08110f3076748e420', 0.97506, '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-10-26T13:24:38.549905Z", "iopub.status.busy": "2025-10-26T13:24:38.549807Z", "iopub.status.idle": "2025-10-26T13:24:40.882658Z", "shell.execute_reply": "2025-10-26T13:24:40.882283Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAVH5JREFUeJzt3QmcTeX/wPHv2Ma+L2MnZN+SZSZRUUIiJUlRSfVDSCRakELZ4p9shRZSZCmJZM1OKEsU2cKQdayDcf6v71P3du9sZrjruZ/363XM3HPOPec5zzVzvvM83+c5YZZlWQIAAGATafxdAAAAAE8iuAEAALZCcAMAAGyF4AYAANgKwQ0AALAVghsAAGArBDcAAMBWCG4AAICtENwAAABbIbgBgAB31113mQVAyhDcAAFoz5498vzzz8stt9wiGTNmlOzZs8sdd9who0aNkosXL/q7ePCCHTt2SP/+/WXfvn3+LgoQ9MJ4thQQWL777jtp1aqVhIeHS7t27aRSpUpy+fJlWblypXz99dfy1FNPyYQJE/xdTHjYzJkzzee+dOnSBK00+vmrDBky+Kl0QHBJ5+8CAPjP3r175bHHHpPixYvLkiVLpGDBgs5tnTt3lt27d5vgJxBduHBBMmfO7O9iBLTz589LlixZUv0+ghogdeiWAgLIe++9J+fOnZOPP/7YLbBxKF26tHTr1s35+urVqzJw4EApVaqUaekpUaKE9O3bV2JjY537PPDAA6Z7KzGRkZFy++23u637/PPPpUaNGpIpUybJnTu3CbYOHjzoto+2LGiL0s8//yz16tUzQY2eV82dO1eaNm0qhQoVMmXSsmkZ4+LiEpx/zJgxpmx6rlq1aslPP/2UaH6JXk+/fv3M9esxixYtKq+88orbdSZnxowZzmvKmzevPPHEE3Lo0CHn9mHDhklYWJjs378/wXv79OljgotTp045161bt07uv/9+yZEjh7n2+vXry6pVq9zep11Mekztbnr88cclV65cUrdu3UTLN2XKFNNqo+6++27zPl2WLVvmrG/XOtH1uv2rr76SAQMGSOHChSVbtmzyyCOPyJkzZ0y9dO/eXfLnzy9Zs2aVp59+OtG6SslnDQQl7ZYCEBgKFy5s3XLLLSnev3379tqtbD3yyCPWmDFjrHbt2pnXLVq0cO7z6aefmnXr1693e+++ffvM+qFDhzrXvf3221ZYWJjVunVr68MPP7QGDBhg5c2b1ypRooR16tQp537169e3IiIirHz58lkvvviiNX78eGvOnDlmm5770UcfNccdO3as1apVK3Oenj17up1fj6/r77zzTmv06NFWjx49rNy5c1ulSpUyx3eIi4uz7rvvPitz5sxW9+7dzbm6dOlipUuXzmrevPl162jy5MnmPDVr1rRGjhxpvfrqq1amTJncrmn//v3mut97770E79fPo2nTps7XixcvtjJkyGBFRkZaw4cPN8esUqWKWbdu3Trnfv369TPnrVChgimnXq9+RonZs2eP1bVrV7N/3759rc8++8ws0dHRzvp2rZOlS5eafatVq2bKofWn79dreOyxx6zHH3/caty4sTnfk08+afbVz9JVSj9rIBgR3AAB4syZM+YmlJIbttqyZYvZ/9lnn3Vbr0GErl+yZInzuOHh4dbLL7/stp/eyPXmpjd2R7CTNm1a65133nHbb+vWrSaQcF2vN1o9x7hx4xKU68KFCwnWPf/88yY4uXTpknkdGxtr5cmTxwQcV65cce43ZcoUc1zXG7ne5NOkSWP99NNPbsfUc+u+q1atSrKOLl++bOXPn9+qVKmSdfHiRef6efPmmfe++eabznUaJNSoUcPt/RoQ6n4aIKpr165ZZcqUsRo1amS+d73mkiVLWvfee2+C4KZNmzZWSsyYMcPsr4FLfEkFN3pdeo0Oei79TDWwcaXXVrx4cefr1HzWQDCiWwoIEDExMeardi+kxPz5883XHj16uK1/+eWXzVdHbo6OtGrcuLHpwnAdP/Dll19KnTp1pFixYub1rFmz5Nq1a/Loo4/K8ePHnUtERISUKVPGJLq60u4h7e6IT7s4HM6ePWuOceedd5qcnJ07d5r1GzdulBMnTkjHjh0lXbr/Uv/atm1rum/idymVL19eypUr51aue+65x2yPXy5Xep5jx45Jp06dzKgzB+020+O55i+1bt3adLPpSDXXOtLrbN68uXm9ZcsW+eOPP0w3k5bfURbNpWnQoIGsWLHC1KGrF154QbxFE87Tp0/vfF27dm3zGT/zzDNu++l67W7Sbswb+ayBYENCMRAgNAhxBAQpofkhadKkMXkorvQGlTNnTrf8Eb1xz5kzR9asWSNRUVHmBq438vfff9+5j9609caoN7fEuN5EleZ5JJboun37dnn99ddN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W350uz4sNLFt2v3msHLlSrnzzjvN/6GiRYtK165d5fz5887t+rkPHDjQXLM++FL/j3mNFWJS88j0m3Jok2X1y25Zw8ubl41GLreK955nrfj9mHfPC8ArLl68aO3YscN8Na5ds6zYc/5Z9Nwp1L59e6t58+bO12fPnrWef/55q3Tp0lZcXJxZd+7cOatgwYJWy5Ytra1bt1qLFy+2SpYsad7rMGLECCt79uzWF198Ye3cudN65ZVXrPTp01u///672T506FCraNGi1ooVK6x9+/ZZP/30kzVt2jSz7dixY+b37uTJk60jR46Y10r31WNOmTLF2rNnj/XDDz9YJUqUsPr372+2a/kqVapkNWjQwNqyZYu1fPlyq3r16uZYs2fPTvR6jx8/boWFhVmDBw9OUf3osfLnz29NmjTJlGH//v3XLdeqVaustGnTWocPH3YeZ9asWVaWLFlM/ar69etbWbNmtbp162bq6/PPP7cyZ85sTZgwwfmeBx980Cpfvrw5n15fo0aNzOdy+fJls/27774zdbxhwwYrJibGuuWWW6yXXnrputczO4m6SWxbjhw5zOeitOz6+TiWYcOGmTLr/4mrV6+6bdu8ebOVJ08e64033jDv3b17t7n+kSNHmv8TWkf6WT311FPOcxUvXtzUqx5X99fluj9nN3j/JrjxFoIbwFYS/NLVIEN/xv2x6LlTSAMUvRHrjUcX/f2ngczPP//s3EdvuLly5TJBjoPeWNOkSWNFR0eb14UKFbLeeecdt2PXrFnT6tSpk/n+xRdftO655x7rWhKBV2I3Vg1aBg0a5Lbus88+M+VTCxcutNKlS2cdOnTIuf37779P9ga+du1as12DDVd6I3bUgQZmruXq3r17qsqlKlSoYL377rvO182aNXO7kWtwo4GLa3307t3brFMaAOi5NQhwDcwyZcpkffXVV851Wr+33nqr9fjjj1uVK1e2Ll26lOh1u15PxowZndeqi6OurhfcuFqzZo05zpdffplgm/4M1K5d23rggQecAXKHDh2s5557zm0/DXD1/5DjZ0aDmxYtWiRbfk8FN37tllqxYoVpHixUqFCKk82WLVtmmgq1WVQTxRzNaYGeUOwQRkoxAB/TJ1lv2bLFLOvXr5dGjRqZLpD9+/eb7b/99ptUrVrVdMs4aM6KdkXt2rVLYmJi5PDhw2adK32t73Uk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w7qPfq8tJSnplipRooQMGjRInnnmGdOao0HWhAkTnNv37dtnjjd9+nSJiooyXV6VKlWS5cuXO/eZMmWKaX1ypa00+j7H9gEDBsgvv/xi1umi67QxXMup59RrKlSokHTt2jVVdQng5tByA+CGHThwQKpUqWK+v3DhgqRNm9bc4C9evGhu9hocPP744/Lhhx8m+v5Ro0bJ8OHDZfz48VK9enXT9fTggw/K9u3bpUyZMnLkyBFp2LCh3H///dKzZ0/TLZVSetyBAwdK3759ZebMmfK///1P6tevL2XLlnXu06tXL3n//felQoUKMmLECGnWrJns3bs30a6vxLrLtm3bZoKxH3/80azLkSOHfP311zJy5EgTOFWsWFGio6NNAATAd2i5AXDDtFVC801WrFhhXmt3lLa0aM7NDz/8YLZpq0tShg0bJr1795bHHnvMBB3vvvuuab3RgENFRERIunTpTFCj36cmuGnSpIl06tRJSpcubc6RN29eWbp0qds+Xbp0kYcffljKly8vY8eONcHJxx9/nKLjZ8qUyZRHy6dl00XXacCn32tQpq03tWrVko4dO6a43ABuHsENgBumN3btAtIRTTVr1jStONpSUaBAAalXr57ZpkFFYmJiYuTw4cMJuq/0tSYi3yxHi5LSViQNOI4dO+a2T2RkpNu13H777Td97latWpmWq1tuucUENbNnz5arV6/e1DEBpA7dUgBumHa77N+/X65cuWLyZ7QlQ2/kuuj3mgSsXUz+oKOnXGmAo2VMqTRp0pj8GVd6nddTtGhR2bVrl+mqWrRokWk9Gjp0qMnniV8mAN5Byw2AGzZ//nzT9aStIp9//rn5XhNztVtJv9ftScmePbvp1lq1apXben2tOTC+sHbtWuf3GpBpl5p2Ual8+fLJ2bNn5fz588594g/51u63uLi4BMfV7inN3xk9erQsW7bMjCTbunWrV68FwH9ouQFww7RlRruhjh49Ks2bNzetI9pSo3ksBQsWvO77NaG3X79+UqpUKZNrM3nyZBNATJ061SflHzNmjElc1oBGk4B1jh4dYaVq164tmTNnNgnJOtpJ84k0WdqVdrtpArKWWUd86cisL774wgQ8jvdr0KfBTnJD2QF4Fi03AG6Ktkxovo0Op16/fr25yacksFEaNPTo0UNefvllqVy5shl59M0335iAwxeGDBliFp2fR4ev67kdOUI6d44GJtr6pGXToCX+UHQN4nQk1913321aenQfHSE2ceJEkzukeT/aPfXtt9+maAQWAM9ghmIAIUfnuSlZsqRs3rw5yUc6AAhetNwAAABbIbgBAAC2QrcUAACwFVpuAACArRDcAAAAWyG4AQAAtkJwAwAAbIXgBgAA2ArBDQAAsBWCGwAAYCsENwAAwFYIbgAAgNjJ/wPCJ47IiMzW1gAAAABJRU5ErkJggg==", "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-10-26T13:24:40.884326Z", "iopub.status.busy": "2025-10-26T13:24:40.884209Z", "iopub.status.idle": "2025-10-26T13:24:40.886629Z", "shell.execute_reply": "2025-10-26T13:24:40.886372Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from html.parser import HTMLParser" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:40.887964Z", "iopub.status.busy": "2025-10-26T13:24:40.887860Z", "iopub.status.idle": "2025-10-26T13:24:40.889703Z", "shell.execute_reply": "2025-10-26T13:24:40.889352Z" }, "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-10-26T13:24:40.890947Z", "iopub.status.busy": "2025-10-26T13:24:40.890858Z", "iopub.status.idle": "2025-10-26T13:24:40.893643Z", "shell.execute_reply": "2025-10-26T13:24:40.893393Z" }, "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-10-26T13:24:40.894817Z", "iopub.status.busy": "2025-10-26T13:24:40.894730Z", "iopub.status.idle": "2025-10-26T13:24:53.635447Z", "shell.execute_reply": "2025-10-26T13:24:53.635175Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'It took all three fuzzers 12.74 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-10-26T13:24:53.637247Z", "iopub.status.busy": "2025-10-26T13:24:53.637090Z", "iopub.status.idle": "2025-10-26T13:24:56.036883Z", "shell.execute_reply": "2025-10-26T13:24:56.036341Z" }, "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-10-26T13:24:56.038784Z", "iopub.status.busy": "2025-10-26T13:24:56.038654Z", "iopub.status.idle": "2025-10-26T13:24:56.041011Z", "shell.execute_reply": "2025-10-26T13:24:56.040787Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[' H', '', '', '`', ' i', '', '(', 'j ', '', '0']" ] }, "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-10-26T13:24:56.042293Z", "iopub.status.busy": "2025-10-26T13:24:56.042179Z", "iopub.status.idle": "2025-10-26T13:24:56.044842Z", "shell.execute_reply": "2025-10-26T13:24:56.044520Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "['m*\\x08',\n", " 'r.5<)h',\n", " 'iq',\n", " '\\x11G',\n", " '5, [, ]`). 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-10-26T13:24:56.046541Z", "iopub.status.busy": "2025-10-26T13:24:56.046427Z", "iopub.status.idle": "2025-10-26T13:24:56.048063Z", "shell.execute_reply": "2025-10-26T13:24:56.047840Z" }, "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-10-26T13:24:56.049196Z", "iopub.status.busy": "2025-10-26T13:24:56.049108Z", "iopub.status.idle": "2025-10-26T13:24:56.221612Z", "shell.execute_reply": "2025-10-26T13:24:56.221321Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from ControlFlow import generate_maze_code" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:56.223229Z", "iopub.status.busy": "2025-10-26T13:24:56.223023Z", "iopub.status.idle": "2025-10-26T13:24:56.225135Z", "shell.execute_reply": "2025-10-26T13:24:56.224803Z" }, "slideshow": { "slide_type": "fragment" }, "tags": [ "remove-input" ] }, "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-10-26T13:24:56.226526Z", "iopub.status.busy": "2025-10-26T13:24:56.226423Z", "iopub.status.idle": "2025-10-26T13:24:56.228101Z", "shell.execute_reply": "2025-10-26T13:24:56.227882Z" }, "slideshow": { "slide_type": "subslide" }, "tags": [ "remove-input" ] }, "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-10-26T13:24:56.229400Z", "iopub.status.busy": "2025-10-26T13:24:56.229302Z", "iopub.status.idle": "2025-10-26T13:24:56.231083Z", "shell.execute_reply": "2025-10-26T13:24:56.230736Z" }, "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-10-26T13:24:56.232437Z", "iopub.status.busy": "2025-10-26T13:24:56.232356Z", "iopub.status.idle": "2025-10-26T13:24:56.235958Z", "shell.execute_reply": "2025-10-26T13:24:56.235738Z" }, "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-10-26T13:24:56.237505Z", "iopub.status.busy": "2025-10-26T13:24:56.237415Z", "iopub.status.idle": "2025-10-26T13:24:56.239230Z", "shell.execute_reply": "2025-10-26T13:24:56.238963Z" }, "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-10-26T13:24:56.240605Z", "iopub.status.busy": "2025-10-26T13:24:56.240532Z", "iopub.status.idle": "2025-10-26T13:24:56.241995Z", "shell.execute_reply": "2025-10-26T13:24:56.241795Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from ControlFlow import callgraph" ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:24:56.243258Z", "iopub.status.busy": "2025-10-26T13:24:56.243174Z", "iopub.status.idle": "2025-10-26T13:24:57.675930Z", "shell.execute_reply": "2025-10-26T13:24:57.675385Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "G\n", "\n", "\n", "cluster_G\n", "\n", "\n", "\n", "cluster_callgraphX\n", "\n", "callgraph\n", "\n", "\n", "\n", "callgraphX\n", "\n", "callgraph\n", "\n", "\n", "\n", "callgraphX__maze\n", "\n", "maze\n", "(callgraph.py:84)\n", "\n", "\n", "\n", "callgraphX->callgraphX__maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__print_maze\n", "\n", "print_maze\n", "(callgraph.py:2)\n", "\n", "\n", "\n", "callgraphX->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__target_tile\n", "\n", "target_tile\n", "(callgraph.py:358)\n", "\n", "\n", "\n", "callgraphX->callgraphX__target_tile\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_0\n", "\n", "tile_1_0\n", "(callgraph.py:26)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_1\n", "\n", "tile_1_1\n", "(callgraph.py:31)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_2\n", "\n", "tile_1_2\n", "(callgraph.py:36)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_3\n", "\n", "tile_1_3\n", "(callgraph.py:41)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_4\n", "\n", "tile_1_4\n", "(callgraph.py:46)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_5\n", "\n", "tile_1_5\n", "(callgraph.py:51)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_6\n", "\n", "tile_1_6\n", "(callgraph.py:56)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_7\n", "\n", "tile_1_7\n", "(callgraph.py:61)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_8\n", "\n", "tile_1_8\n", "(callgraph.py:66)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_1_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_0\n", "\n", "tile_2_0\n", "(callgraph.py:71)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_1\n", "\n", "tile_2_1\n", "(callgraph.py:76)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_2\n", "\n", "tile_2_2\n", "(callgraph.py:87)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_3\n", "\n", "tile_2_3\n", "(callgraph.py:92)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_4\n", "\n", "tile_2_4\n", "(callgraph.py:100)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_5\n", "\n", "tile_2_5\n", "(callgraph.py:108)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_6\n", "\n", "tile_2_6\n", "(callgraph.py:116)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_7\n", "\n", "tile_2_7\n", "(callgraph.py:124)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_8\n", "\n", "tile_2_8\n", "(callgraph.py:132)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_2_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_0\n", "\n", "tile_3_0\n", "(callgraph.py:137)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_1\n", "\n", "tile_3_1\n", "(callgraph.py:142)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_2\n", "\n", "tile_3_2\n", "(callgraph.py:150)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_3\n", "\n", "tile_3_3\n", "(callgraph.py:155)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_4\n", "\n", "tile_3_4\n", "(callgraph.py:163)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_5\n", "\n", "tile_3_5\n", "(callgraph.py:168)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_6\n", "\n", "tile_3_6\n", "(callgraph.py:173)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_7\n", "\n", "tile_3_7\n", "(callgraph.py:178)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_8\n", "\n", "tile_3_8\n", "(callgraph.py:186)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_3_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_0\n", "\n", "tile_4_0\n", "(callgraph.py:191)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_1\n", "\n", "tile_4_1\n", "(callgraph.py:196)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_2\n", "\n", "tile_4_2\n", "(callgraph.py:204)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_3\n", "\n", "tile_4_3\n", "(callgraph.py:209)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_4\n", "\n", "tile_4_4\n", "(callgraph.py:217)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_5\n", "\n", "tile_4_5\n", "(callgraph.py:225)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_6\n", "\n", "tile_4_6\n", "(callgraph.py:233)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_7\n", "\n", "tile_4_7\n", "(callgraph.py:238)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_8\n", "\n", "tile_4_8\n", "(callgraph.py:246)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_4_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_0\n", "\n", "tile_5_0\n", "(callgraph.py:251)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_1\n", "\n", "tile_5_1\n", "(callgraph.py:256)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_2\n", "\n", "tile_5_2\n", "(callgraph.py:264)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_3\n", "\n", "tile_5_3\n", "(callgraph.py:269)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_4\n", "\n", "tile_5_4\n", "(callgraph.py:274)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_5\n", "\n", "tile_5_5\n", "(callgraph.py:279)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_6\n", "\n", "tile_5_6\n", "(callgraph.py:287)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_7\n", "\n", "tile_5_7\n", "(callgraph.py:292)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_8\n", "\n", "tile_5_8\n", "(callgraph.py:300)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_5_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_0\n", "\n", "tile_6_0\n", "(callgraph.py:305)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_1\n", "\n", "tile_6_1\n", "(callgraph.py:310)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_2\n", "\n", "tile_6_2\n", "(callgraph.py:318)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_3\n", "\n", "tile_6_3\n", "(callgraph.py:326)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_4\n", "\n", "tile_6_4\n", "(callgraph.py:334)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_5\n", "\n", "tile_6_5\n", "(callgraph.py:342)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_6\n", "\n", "tile_6_6\n", "(callgraph.py:350)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_7\n", "\n", "tile_6_7\n", "(callgraph.py:355)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_8\n", "\n", "tile_6_8\n", "(callgraph.py:361)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_6_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_0\n", "\n", "tile_7_0\n", "(callgraph.py:366)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_1\n", "\n", "tile_7_1\n", "(callgraph.py:371)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_2\n", "\n", "tile_7_2\n", "(callgraph.py:376)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_3\n", "\n", "tile_7_3\n", "(callgraph.py:381)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_4\n", "\n", "tile_7_4\n", "(callgraph.py:386)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_5\n", "\n", "tile_7_5\n", "(callgraph.py:391)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_6\n", "\n", "tile_7_6\n", "(callgraph.py:396)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_7\n", "\n", "tile_7_7\n", "(callgraph.py:401)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_8\n", "\n", "tile_7_8\n", "(callgraph.py:406)\n", "\n", "\n", "\n", "callgraphX->callgraphX__tile_7_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__maze->callgraphX__tile_2_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_0->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_1->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_2->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_3->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_4->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_5->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_6->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_7->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_1_8->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_0->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_1->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_1->callgraphX__tile_1_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_1->callgraphX__tile_2_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_1->callgraphX__tile_2_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_1->callgraphX__tile_2_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_1->callgraphX__tile_3_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_2->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_3->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_3->callgraphX__tile_1_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_3->callgraphX__tile_2_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_3->callgraphX__tile_2_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_3->callgraphX__tile_2_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_3->callgraphX__tile_3_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_4->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_4->callgraphX__tile_1_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_4->callgraphX__tile_2_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_4->callgraphX__tile_2_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_4->callgraphX__tile_2_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_4->callgraphX__tile_3_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_5->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_5->callgraphX__tile_1_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_5->callgraphX__tile_2_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_5->callgraphX__tile_2_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_5->callgraphX__tile_2_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_5->callgraphX__tile_3_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_6->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_6->callgraphX__tile_1_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_6->callgraphX__tile_2_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_6->callgraphX__tile_2_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_6->callgraphX__tile_2_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_6->callgraphX__tile_3_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_7->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_7->callgraphX__tile_1_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_7->callgraphX__tile_2_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_7->callgraphX__tile_2_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_7->callgraphX__tile_2_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_7->callgraphX__tile_3_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_2_8->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_0->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_1->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_1->callgraphX__tile_2_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_1->callgraphX__tile_3_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_1->callgraphX__tile_3_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_1->callgraphX__tile_3_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_1->callgraphX__tile_4_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_2->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_3->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_3->callgraphX__tile_2_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_3->callgraphX__tile_3_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_3->callgraphX__tile_3_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_3->callgraphX__tile_3_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_3->callgraphX__tile_4_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_4->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_5->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_6->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_7->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_7->callgraphX__tile_2_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_7->callgraphX__tile_3_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_7->callgraphX__tile_3_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_7->callgraphX__tile_3_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_7->callgraphX__tile_4_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_3_8->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_0->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_1->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_1->callgraphX__tile_3_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_1->callgraphX__tile_4_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_1->callgraphX__tile_4_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_1->callgraphX__tile_4_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_1->callgraphX__tile_5_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_2->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_3->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_3->callgraphX__tile_3_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_3->callgraphX__tile_4_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_3->callgraphX__tile_4_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_3->callgraphX__tile_4_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_3->callgraphX__tile_5_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_4->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_4->callgraphX__tile_3_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_4->callgraphX__tile_4_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_4->callgraphX__tile_4_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_4->callgraphX__tile_4_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_4->callgraphX__tile_5_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_5->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_5->callgraphX__tile_3_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_5->callgraphX__tile_4_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_5->callgraphX__tile_4_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_5->callgraphX__tile_4_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_5->callgraphX__tile_5_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_6->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_7->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_7->callgraphX__tile_3_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_7->callgraphX__tile_4_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_7->callgraphX__tile_4_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_7->callgraphX__tile_4_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_7->callgraphX__tile_5_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_4_8->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_0->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_1->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_1->callgraphX__tile_4_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_1->callgraphX__tile_5_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_1->callgraphX__tile_5_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_1->callgraphX__tile_5_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_1->callgraphX__tile_6_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_2->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_3->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_4->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_5->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_5->callgraphX__tile_4_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_5->callgraphX__tile_5_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_5->callgraphX__tile_5_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_5->callgraphX__tile_5_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_5->callgraphX__tile_6_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_6->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_7->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_7->callgraphX__tile_4_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_7->callgraphX__tile_5_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_7->callgraphX__tile_5_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_7->callgraphX__tile_5_8\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_7->callgraphX__tile_6_7\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_5_8->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_0->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_1->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_1->callgraphX__tile_5_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_1->callgraphX__tile_6_0\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_1->callgraphX__tile_6_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_1->callgraphX__tile_6_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_1->callgraphX__tile_7_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_2->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_2->callgraphX__tile_5_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_2->callgraphX__tile_6_1\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_2->callgraphX__tile_6_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_2->callgraphX__tile_6_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_2->callgraphX__tile_7_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_3->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_3->callgraphX__tile_5_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_3->callgraphX__tile_6_2\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_3->callgraphX__tile_6_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_3->callgraphX__tile_6_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_3->callgraphX__tile_7_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_4->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_4->callgraphX__tile_5_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_4->callgraphX__tile_6_3\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_4->callgraphX__tile_6_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_4->callgraphX__tile_6_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_4->callgraphX__tile_7_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_5->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_5->callgraphX__tile_5_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_5->callgraphX__tile_6_4\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_5->callgraphX__tile_6_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_5->callgraphX__tile_6_6\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_5->callgraphX__tile_7_5\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_6->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_7->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_6_8->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_0->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_1->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_2->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_3->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_4->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_5->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_6->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_7->callgraphX__print_maze\n", "\n", "\n", "\n", "\n", "\n", "callgraphX__tile_7_8->callgraphX__print_maze\n", "\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-10-26T13:24:57.678683Z", "iopub.status.busy": "2025-10-26T13:24:57.678550Z", "iopub.status.idle": "2025-10-26T13:24:57.681153Z", "shell.execute_reply": "2025-10-26T13:24:57.680834Z" }, "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-10-26T13:24:57.683000Z", "iopub.status.busy": "2025-10-26T13:24:57.682846Z", "iopub.status.idle": "2025-10-26T13:24:57.685777Z", "shell.execute_reply": "2025-10-26T13:24:57.685365Z" }, "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-10-26T13:24:57.687514Z", "iopub.status.busy": "2025-10-26T13:24:57.687396Z", "iopub.status.idle": "2025-10-26T13:25:05.055372Z", "shell.execute_reply": "2025-10-26T13:25:05.055015Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took the fuzzer 7.36 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-10-26T13:25:05.057032Z", "iopub.status.busy": "2025-10-26T13:25:05.056905Z", "iopub.status.idle": "2025-10-26T13:25:05.059467Z", "shell.execute_reply": "2025-10-26T13:25:05.059086Z" }, "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-10-26T13:25:05.061087Z", "iopub.status.busy": "2025-10-26T13:25:05.060979Z", "iopub.status.idle": "2025-10-26T13:25:05.075473Z", "shell.execute_reply": "2025-10-26T13:25:05.075201Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Out of 1398 seeds,\n", "* 0 solved the maze,\n", "* 270 were valid but did not solve the maze, and\n", "* 1128 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-10-26T13:25:05.076941Z", "iopub.status.busy": "2025-10-26T13:25:05.076844Z", "iopub.status.idle": "2025-10-26T13:25:05.079260Z", "shell.execute_reply": "2025-10-26T13:25:05.078893Z" }, "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-10-26T13:25:05.080753Z", "iopub.status.busy": "2025-10-26T13:25:05.080636Z", "iopub.status.idle": "2025-10-26T13:25:05.082209Z", "shell.execute_reply": "2025-10-26T13:25:05.081985Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import networkx as nx # type: ignore" ] }, { "cell_type": "code", "execution_count": 73, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:25:05.083343Z", "iopub.status.busy": "2025-10-26T13:25:05.083258Z", "iopub.status.idle": "2025-10-26T13:25:05.084876Z", "shell.execute_reply": "2025-10-26T13:25:05.084630Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from ControlFlow import get_callgraph" ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:25:05.086409Z", "iopub.status.busy": "2025-10-26T13:25:05.086315Z", "iopub.status.idle": "2025-10-26T13:25:05.124041Z", "shell.execute_reply": "2025-10-26T13:25:05.123602Z" }, "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-10-26T13:25:05.126483Z", "iopub.status.busy": "2025-10-26T13:25:05.126216Z", "iopub.status.idle": "2025-10-26T13:25:05.128888Z", "shell.execute_reply": "2025-10-26T13:25:05.128563Z" }, "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-10-26T13:25:05.130762Z", "iopub.status.busy": "2025-10-26T13:25:05.130631Z", "iopub.status.idle": "2025-10-26T13:25:05.133969Z", "shell.execute_reply": "2025-10-26T13:25:05.133338Z" }, "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-10-26T13:25:05.138018Z", "iopub.status.busy": "2025-10-26T13:25:05.137277Z", "iopub.status.idle": "2025-10-26T13:25:05.141100Z", "shell.execute_reply": "2025-10-26T13:25:05.140732Z" }, "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-10-26T13:25:05.144723Z", "iopub.status.busy": "2025-10-26T13:25:05.144086Z", "iopub.status.idle": "2025-10-26T13:25:05.148733Z", "shell.execute_reply": "2025-10-26T13:25:05.147796Z" }, "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-10-26T13:25:05.150933Z", "iopub.status.busy": "2025-10-26T13:25:05.150821Z", "iopub.status.idle": "2025-10-26T13:25:15.137073Z", "shell.execute_reply": "2025-10-26T13:25:15.136797Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took the fuzzer 9.98 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-10-26T13:25:15.138654Z", "iopub.status.busy": "2025-10-26T13:25:15.138551Z", "iopub.status.idle": "2025-10-26T13:25:15.164804Z", "shell.execute_reply": "2025-10-26T13:25:15.164510Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Out of 2708 seeds,\n", "* 0 solved the maze,\n", "* 1020 were valid but did not solve the maze, and\n", "* 1688 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-10-26T13:25:15.166345Z", "iopub.status.busy": "2025-10-26T13:25:15.166240Z", "iopub.status.idle": "2025-10-26T13:25:15.224272Z", "shell.execute_reply": "2025-10-26T13:25:15.223976Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAGwCAYAAAC0HlECAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAehlJREFUeJztnQecFOX5x5+9stf7cY16gJSjFymiKIogELH9E1sMViJiEhsqJgqoCUaTaKIoligm0agkYkUUKSrSQeoBoRxwwBWOq1wv8/887zHr7N7M7uzu7O3s3u/7+ezt7c477/u8ZWaefcvvtUiSJBEAAAAAAHBKiPPDAAAAAACAgdMEAAAAAKADOE0AAAAAADqA0wQAAAAAoAM4TQAAAAAAOoDTBAAAAACgAzhNAAAAAAA6CNMTCLimpaWFTp06RXFxcWSxWPxtDgAAAAB0wHKVVVVVlJWVRSEhzvuS4DQZBDtMXbt29bcZAAAAAPCA/Px86tKli9MwcJoMgnuY5EKPj4/3Kq7NR0rp9re3eG3TmzPOp1E9k72OBwAAAAhWKisrRaeH/Bx3Bpwmg5CH5Nhh8tZpmjA4jjqnHaaCijrPbCGijIRImjC4O4WGYKgQAAAAcIWeqTWYCG5C2NGZd2WOcH7cRT6Hz4fDBAAAABgHnCaTcsXATHrl58MpMyHSrfO4h4nP4/MBAAAAYBwYnjMx7PhcnpNBm/NK6URpNX2VW0S1jS3UPSWKJuVkUHltI6XGRIjupZKz9ZQWF0mjspPRwwQAAAD4ADhNJocdoLG9Uoh6pdBPz+/mb3MAAACADguG5wAAAAAAdACnCQAAAABAB3CaAAAAAAB0AKcJAAAAAEAHcJoAAAAAAHQApwkAAAAAQAdwmgAAAAAAdACnCQAAAABAB3CaAAAAAAB0AKcJAAAAAEAHcJoAAAAAAHQApwkAAAAAQAdwmgAAAAAAdACnCQAAAABAB3CaAAAAAAB0AKcJAAAAAEAHcJoAAAAAAHQApwkAAAAAQAdwmgAAAAAAdACnCQAAAABAB3CaAAAAAADM7jS98sorNHjwYIqPjxevsWPH0hdffGE7XldXR7Nnz6aUlBSKjY2l6667joqKiuziOH78OE2bNo2io6MpLS2N5syZQ01NTXZh1q5dS8OHD6eIiAjq3bs3LVmypI0tixYtoh49elBkZCSNHj2aNm/e7MOcAwAAACDQ8KvT1KVLF3rmmWdo27ZttHXrVrr00kvpqquuor1794rj999/P3366ae0dOlS+uabb+jUqVN07bXX2s5vbm4WDlNDQwOtX7+e3n77beEQPfHEE7YweXl5IsyECRNox44ddN9999Gdd95JX375pS3M+++/Tw888ADNmzePtm/fTkOGDKHJkydTcXFxO5cIAAAAAEyLZDKSkpKkN954QyovL5fCw8OlpUuX2o7t27dPYpM3bNggPi9fvlwKCQmRCgsLbWFeeeUVKT4+XqqvrxefH374YWnAgAF2aVx//fXS5MmTbZ9HjRolzZ492/a5ublZysrKkhYuXKhpZ11dnVRRUWF75efnC9v4fwAAAAAEBvzc1vv8Ns2cJu41eu+996i6uloM03HvU2NjI02cONEWpl+/ftStWzfasGGD+MzvgwYNovT0dFsY7iGqrKy09VZxGGUcchg5Du6l4rSUYUJCQsRnOYwaCxcupISEBNura9euBpYGAAAAAMyG352m3bt3i/lKPN/o7rvvpmXLllFOTg4VFhaS1WqlxMREu/DsIPExht+VDpN8XD7mLAw7VrW1tVRSUiIcNrUwchxqzJ07lyoqKmyv/Px8L0sCAAAAAGYmzN8G9O3bV8w1YsfjP//5D82YMUPMXzI77OTxCwAAAAAdA787TdybxCvamBEjRtCWLVvor3/9K11//fVi6Ky8vNyut4lXz2VkZIj/+d1xlZu8uk4ZxnHFHX/m1XpRUVEUGhoqXmph5DgAAAAAAPw+POdIS0sL1dfXCwcqPDycVq1aZTt24MABITHAc54YfufhPeUqt5UrVwqHiIf45DDKOOQwchzstHFayjBsA3+WwwAAAAAA+LWniecFTZkyRUzurqqqonfffVdoKrEcAE+uvuOOO4QUQHJysnCEfvWrXwlHZsyYMeL8SZMmCefolltuoWeffVbMQfrd734ntJ3koTOeJ/XSSy/Rww8/TLfffjutXr2aPvjgA/r8889tdnAaPCw4cuRIGjVqFL3wwgtiQvptt93mt7IBAAAAgMmQ/Mjtt98ude/eXbJarVKnTp2kyy67TPrqq69sx2tra6V77rlHyBBER0dL11xzjVRQUGAXx9GjR6UpU6ZIUVFRUmpqqvTggw9KjY2NdmHWrFkjDR06VKTTs2dP6a233mpjy4svvih169ZNhGEJ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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-10-26T13:25:15.226118Z", "iopub.status.busy": "2025-10-26T13:25:15.225985Z", "iopub.status.idle": "2025-10-26T13:25:15.228811Z", "shell.execute_reply": "2025-10-26T13:25:15.228574Z" }, "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-10-26T13:25:15.230344Z", "iopub.status.busy": "2025-10-26T13:25:15.230246Z", "iopub.status.idle": "2025-10-26T13:25:30.754158Z", "shell.execute_reply": "2025-10-26T13:25:30.753794Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'It took the fuzzer 15.52 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-10-26T13:25:30.755614Z", "iopub.status.busy": "2025-10-26T13:25:30.755512Z", "iopub.status.idle": "2025-10-26T13:25:30.800523Z", "shell.execute_reply": "2025-10-26T13:25:30.800189Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "First solution: DP{\u00049DsDmD)$gPRROR\"jRUUHtLFLPUU=DURRR/RDDDDD\n", "Out of 3918 seeds,\n", "* 680 solved the maze,\n", "* 348 were valid but did not solve the maze, and\n", "* 2890 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-10-26T13:25:30.802329Z", "iopub.status.busy": "2025-10-26T13:25:30.802225Z", "iopub.status.idle": "2025-10-26T13:25:30.809479Z", "shell.execute_reply": "2025-10-26T13:25:30.809219Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DDDDRRRRUULLUUDURRRRDDDDD\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": { "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-10-26T13:25:33.480043Z", "iopub.status.busy": "2025-10-26T13:25:33.479905Z", "iopub.status.idle": "2025-10-26T13:25:33.481919Z", "shell.execute_reply": "2025-10-26T13:25:33.481614Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os" ] }, { "cell_type": "code", "execution_count": 95, "metadata": { "execution": { "iopub.execute_input": "2025-10-26T13:25:33.483318Z", "iopub.status.busy": "2025-10-26T13:25:33.483157Z", "iopub.status.idle": "2025-10-26T13:25:33.485501Z", "shell.execute_reply": "2025-10-26T13:25:33.485169Z" }, "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-10-26T13:25:33.486963Z", "iopub.status.busy": "2025-10-26T13:25:33.486848Z", "iopub.status.idle": "2025-10-26T13:25:33.488641Z", "shell.execute_reply": "2025-10-26T13:25:33.488318Z" }, "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.13.4" }, "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 }