├── .gitignore ├── .gitmodules ├── .hexhive ├── make_html.sh └── present ├── 01-native_fuzzing ├── 01-setup-workdir.sh ├── 02-native-fuzz-loadpng.sh ├── 03-native-fuzz-storepng.sh ├── 04-cleanup.sh ├── README.md ├── aflinaction.png └── gdb.png ├── 02-coverage_fuzzing ├── 01-instrument_symb.sh ├── 02-symb-fuzzing-loadpng.sh ├── 03-symb-fuzzing-storepng.sh ├── 04-cleanup.sh ├── README.md ├── aflinaction.png └── gdb.png ├── 03-coverage_sanitized_fuzzing ├── 01-instrument_asan.sh ├── 02-asan-fuzzing-loadpng.sh ├── 03-asan-fuzzing-storepng.sh ├── 04-cleanup.sh ├── README.md ├── aflinaction.png └── gdb.png ├── README.md ├── playground ├── README.md ├── bin │ ├── loadpng │ └── storepng ├── inputs │ ├── loadpng │ │ ├── palette.png │ │ └── rgba.png │ └── storepng │ │ ├── testcase01 │ │ ├── testcase02 │ │ ├── testcase03 │ │ └── testcase04 └── src │ ├── Makefile │ ├── crc.c │ ├── crc.h │ ├── loadpng.c │ ├── pngparser.c │ ├── pngparser.h │ └── storepng.c ├── setup.sh └── tools /.gitignore: -------------------------------------------------------------------------------- 1 | aflplusplus/ 2 | *.a 3 | *.o 4 | *~ 5 | .sw* 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "retrowrite"] 2 | path = retrowrite 3 | url = https://git@github.com/HexHive/retrowrite.git 4 | -------------------------------------------------------------------------------- /.hexhive/make_html.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | pip3 install markdown 3 | python3 -m markdown -x fenced_code README.md > README.html 4 | python3 -m markdown -x fenced_code 01-native_fuzzing/README.md > Phase01.html 5 | python3 -m markdown -x fenced_code 02-coverage_fuzzing/README.md > Phase02.html 6 | python3 -m markdown -x fenced_code 03-coverage_sanitized_fuzzing/README.md > Phase03.html 7 | 8 | -------------------------------------------------------------------------------- /.hexhive/present: -------------------------------------------------------------------------------- 1 | export PS1="$ " 2 | -------------------------------------------------------------------------------- /01-native_fuzzing/01-setup-workdir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy 4 | set -ex 5 | 6 | # locate this script: 7 | SCRIPT=$(readlink -f $0) 8 | SCRIPTDIR=$(dirname $SCRIPT) 9 | 10 | # cd into the right place 11 | cd "$SCRIPTDIR/../playground" 12 | # make directory 13 | mkdir -p work-native-storepng 14 | mkdir -p work-native-loadpng 15 | -------------------------------------------------------------------------------- /01-native_fuzzing/02-native-fuzz-loadpng.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # be noisy 3 | set -ex 4 | 5 | # locate this script: 6 | SCRIPT=$(readlink -f $0) 7 | SCRIPTDIR_THIS=$(dirname $SCRIPT) 8 | 9 | # if the user didn't source tools, let's do it for them: 10 | # but if they did, don't do it twice. 11 | if ! command -v afl-fuzz; then 12 | cd $SCRIPTDIR_THIS/.. 13 | source tools 14 | fi 15 | 16 | # cd into the right place 17 | cd "$SCRIPTDIR_THIS/../playground/work-native-loadpng" 18 | 19 | # exec afl 20 | afl-fuzz -Q -i ../inputs/loadpng -o ../fuzz-native-loadpng/ -- ../bin/loadpng @@ 21 | -------------------------------------------------------------------------------- /01-native_fuzzing/03-native-fuzz-storepng.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # be noisy 3 | set -ex 4 | 5 | # locate this script: 6 | SCRIPT=$(readlink -f $0) 7 | SCRIPTDIR_THIS=$(dirname $SCRIPT) 8 | 9 | # if the user didn't source tools, let's do it for them: 10 | # but if they did, don't do it twice. 11 | if ! command -v afl-fuzz; then 12 | cd $SCRIPTDIR_THIS/.. 13 | source tools 14 | fi 15 | 16 | # cd into the right place 17 | cd "$SCRIPTDIR_THIS/../playground/work-native-storepng" 18 | 19 | # exec afl 20 | afl-fuzz -Q -i ../inputs/storepng -o ../fuzz-native-storepng/ -- ../bin/storepng @@ 21 | -------------------------------------------------------------------------------- /01-native_fuzzing/04-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy: 4 | set -ex 5 | 6 | # locate this script: 7 | SCRIPT=$(readlink -f $0) 8 | SCRIPTDIR=$(dirname $SCRIPT) 9 | 10 | # cd into the right place 11 | cd $SCRIPTDIR/../playground 12 | # remove work directory 13 | rm -rf work-native-loadpng 14 | rm -rf work-native-storepng 15 | -------------------------------------------------------------------------------- /01-native_fuzzing/README.md: -------------------------------------------------------------------------------- 1 | # Phase 01: Fuzzing native binaries at snails pace 2 | 3 | ## Introduction 4 | 5 | This section aims to introduce you to standard tools for fuzzing binaries. 6 | Fuzzing can be augmented by the use of source code if available, but we 7 | assume in this section that you do not have access to the source you wish to 8 | fuzz. This might happen if you are testing commercial or closed source software 9 | for issues on your systems, for example. 10 | 11 | One of the original fuzzing tools is called American Fuzzy Lop. Written by 12 | a Google Engineer, this tool has several modes to support fuzzing with and 13 | without access to the source code. 14 | 15 | A community version of the project, AFL++, includes compatibility fixes for 16 | more modern operating systems and later modes. This tutorial will use AFL++. 17 | We strongly recommend you use AF++, as you will likely need to do things like 18 | patch qemu and find python2 binaries otherwise. 19 | 20 | In the rest of this documentation, TUTORIAL_REPO_DIR refers to the location 21 | you found your repository in. 22 | 23 | __**ATTENTION**__ 24 | 25 | Before you begin copying and pasting commands from this section, ready-made 26 | scripts exist in this folder for all commands you can see here. For each section you 27 | will find a script that performs the commands from that part, so that you 28 | do not need to copy and paste commands from the readme. This documentation 29 | exists so you can see what is done and why. 30 | 31 | ## Download and Build AFL++ 32 | 33 | The first step of this tutorial is to download and build AFL. The script in the 34 | root of this tutorial will do this for you. 35 | 36 | First we check out the latest version of AFL++: 37 | 38 | ``` 39 | git clone https://github.com/AFLplusplus/AFLplusplus aflplusplus 40 | git checkout "2.66c" 41 | ``` 42 | 43 | into a directory `aflplusplus`. We have used the optional output name in git 44 | to remove the capital letters, which is easier on Linux. 45 | We now need to build the tool: 46 | 47 | ``` 48 | cd afplusplus 49 | make 50 | ``` 51 | 52 | Wait a little while. This will build afl locally after some time. Once this is 53 | done we also need to build support for Qemu mode: 54 | 55 | ``` 56 | cd qemu_mode 57 | ./build_qemu_support.sh 58 | ``` 59 | 60 | Again, this will take some time, but once done you will see a success message. 61 | 62 | **Script to run**: [TUTORIAL_REPO_DIR/setup.sh](../setup.sh) 63 | 64 | ## Learn how to use AFL++ with storepng 65 | 66 | We are now going to demonstrate a working fuzz project with AFL++. 67 | 68 | To make your life easier, we have a script to add AFL to the path. In the 69 | root of the tutorial repository you should see a plain file called "tools". 70 | Simply run: 71 | 72 | ```shell 73 | source tools 74 | ``` 75 | 76 | in your shell. Now you should be able to run: 77 | 78 | ``` 79 | afl-fuzz 80 | ``` 81 | 82 | and it will execute the fuzzing command from the correct directory. 83 | 84 | Now, enter the playground folder if you are not there already: 85 | 86 | ``` 87 | cd playground 88 | ``` 89 | 90 | Now that you are in the playground directory, create a working directory where 91 | we will fuzz from: 92 | 93 | ``` 94 | mkdir -p work-native 95 | ``` 96 | 97 | **Script**: [01-setup-workdir.sh](01-setup-workdir.sh) 98 | 99 | We will come back to this. Now you should be able to examine the folders 100 | you have available. Here is an annotated listing of the playground 101 | directory: 102 | 103 | ``` 104 | ├── bin - binaries 105 | │   ├── loadpng - the loadpng program 106 | │   └── storepng - the storepng program 107 | ├── inputs - valid inputs the fuzzer can use to train 108 | │   ├── loadpng - loadpng input cases 109 | │   │   ├── palette.png 110 | │   │   └── rgba.png 111 | │   └── storepng - storepng input cases 112 | │   ├── testcase01 113 | │   ├── testcase02 114 | │   ├── testcase03 115 | │   └── testcase04 116 | ├── README.md - a readme file 117 | └── src - the source used to create the binaries 118 | ├── crc.c 119 | ├── crc.h 120 | ├── libz.a 121 | ├── loadpng.c 122 | ├── Makefile 123 | ├── pngparser.c 124 | ├── pngparser.h 125 | └── storepng.c 126 | ``` 127 | 128 | As you can see, we have provided the source to the binaries. We will not use 129 | this, but it is available if you wish to experiment. 130 | 131 | Now to run `afl-fuzz` we need several pieces of information: 132 | 133 | - The binary we will fuzz. 134 | - A directory of input test cases. 135 | - A storage directory for fuzzing results 136 | - The current working directory. 137 | 138 | `storepng` takes a single argument, the source of randomness used to render a 139 | PNG. Using this we can provide fuzz testcases. 140 | 141 | Without further ado, let us execute a fuzzing run: 142 | 143 | ``` 144 | cd work-native 145 | afl-fuzz -Q -i ../inputs/storepng -o ../fuzz-native/ -- ../bin/storepng @@ 146 | ``` 147 | 148 | The commands to `afl-fuzz` are as follows: 149 | 150 | - `-Q` instructs AFL++ to run in Qemu mode. We are pretending we do not have 151 | the source for this test case, so this is necessary here. 152 | - `-i ../inputs/storepng` loads the input test cases from the input directory. 153 | - `-o ../fuzz-native/` tells afl++ where to store its information. 154 | - `-- ../bin/storepng @@` is a bit special. There are three parts to this 155 | command: `--`, which terminates the argument list, the path to the 156 | program to be fuzzed, and `@@`. This is a placeholder which tells AFL++ 157 | which argument may be substituted for input by the fuzzer. In other words, 158 | this is how the test cases are supplied. 159 | 160 | If all goes well, you should see output like this: 161 | 162 | ![Image showing terminal with AFL running, presenting crash statistics](aflinaction.png) 163 | 164 | Whenever you want to stop the fuzzing operation, you can press `CTRL+C` 165 | as you would to exit any terminal program. Fuzzing will then terminate. 166 | 167 | This may take some time. 168 | 169 | **Script**: [02-native-fuzz.sh](02-native-fuzz-loadpng.sh) 170 | 171 | ## Examining bugs 172 | 173 | There is no script for this section, but it is interesting to examine crashes 174 | sometimes. AFL++ stores in its fuzz output directory an input that lead to 175 | each unique crash. How do we look at this? Well, we can find the crashes 176 | from the playground as follows: 177 | 178 | ```shell 179 | ls playground/fuzz-native/crashes 180 | ``` 181 | 182 | These are inputs that were provided to the program in place of the `@@`. 183 | The name gives you some information as to the strategy AFL used to find this 184 | particular crash. If we want to actually look at the crash, we can do this: 185 | 186 | ```shell 187 | cd playground 188 | gdb bin/storepng 189 | run fuzz-native/crashes/... 190 | ``` 191 | 192 | where `fuzz-native/crashes/...` is the name of a particular crash in question. 193 | This will run the command with that particular file as an argument, exactly 194 | what we want. Under gdb we can then see the stack trace: 195 | 196 | ![Image showing GDB with a stack trace of crashing storepng using the 197 | bt command](gdb.png) 198 | 199 | ## Cleanup 200 | 201 | To clean up, we simply remove the work directory. Since storepng outputs a 202 | lot of files, the easiest method to remove it is to remove the entire directory 203 | in one go: 204 | 205 | ```sh 206 | cd .. 207 | rm -rf work-native 208 | ``` 209 | 210 | **Script**: [04-cleanup.sh](04-cleanup.sh) 211 | -------------------------------------------------------------------------------- /01-native_fuzzing/aflinaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/01-native_fuzzing/aflinaction.png -------------------------------------------------------------------------------- /01-native_fuzzing/gdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/01-native_fuzzing/gdb.png -------------------------------------------------------------------------------- /02-coverage_fuzzing/01-instrument_symb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy 4 | set -ex 5 | 6 | cd ../playground/bin 7 | 8 | retrowrite loadpng loadpng_symb.s 9 | AFL_AS_FORCE_INSTRUMENT=1 $AFL_PATH/afl-gcc loadpng_symb.s -o loadpng_symb_inst -lz 10 | retrowrite storepng storepng_symb.s 11 | AFL_AS_FORCE_INSTRUMENT=1 $AFL_PATH/afl-gcc storepng_symb.s -o storepng_symb_inst -lz 12 | 13 | -------------------------------------------------------------------------------- /02-coverage_fuzzing/02-symb-fuzzing-loadpng.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy 4 | set -ex 5 | 6 | cd ../playground/ 7 | 8 | mkdir -p work-symb-loadpng 9 | cd work-symb-loadpng 10 | ../../aflplusplus/afl-fuzz -i ../inputs/loadpng -o ../02-fuzz-sym-loadpng/ -- ../bin/loadpng_symb_inst @@ 11 | -------------------------------------------------------------------------------- /02-coverage_fuzzing/03-symb-fuzzing-storepng.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy 4 | set -ex 5 | 6 | cd ../playground/ 7 | 8 | mkdir -p work-symb-storepng 9 | cd work-symb-storepng 10 | ../../aflplusplus/afl-fuzz -i ../inputs/storepng -o ../02-fuzz-sym-storepng/ -- ../bin/storepng_symb_inst @@ 11 | -------------------------------------------------------------------------------- /02-coverage_fuzzing/04-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy: 4 | set -ex 5 | 6 | # locate this script: 7 | SCRIPT=$(readlink -f $0) 8 | SCRIPTDIR=$(dirname $SCRIPT) 9 | 10 | # cd into the right place 11 | cd $SCRIPTDIR/../playground 12 | # remove work directory 13 | rm -rf work-symb-loadpng 14 | rm -rf work-symb-storepng 15 | -------------------------------------------------------------------------------- /02-coverage_fuzzing/README.md: -------------------------------------------------------------------------------- 1 | # Phase 02: instrumenting native binaries to collect coverage 2 | 3 | ## Introduction 4 | 5 | This section aims to introduce you to static binaries rewriting using the tool [retrowrite](https://github.com/HexHive/retrowrite) 6 | 7 | For more information about retrowrite tool and fuzzing, please refer to the presentation or read the retrowrite documentation (available on the public repository). 8 | 9 | In the rest of this documentation, TUTORIAL_REPO_DIR refers to the location 10 | you found your repository in. 11 | 12 | __**ATTENTION**__ 13 | 14 | Before you begin copying and pasting commands from this section, ready-made 15 | scripts exist in this folder for all commands you can see here. For each section you 16 | will find a script that performs the commands from that part, so that you 17 | do not need to copy and paste commands from the readme. This documentation 18 | exists so you can see what is done and why. 19 | 20 | ## Downstore and Build AFL++ 21 | 22 | The installation from the 01-native-fuzzing step should correctly set up the tools needed for this part. 23 | 24 | In case you didn't run the script during 01-native-fuzzing phase, you can run the following script: 25 | **Script to run**: [TUTORIAL_REPO_DIR/setup.sh](../setup.sh) 26 | 27 | 28 | ## Learn how to instrument a binary 29 | 30 | ```shell 31 | source tools 32 | cd playground/bin 33 | ``` 34 | 35 | To generate symbolized assembly you can use the following command: 36 | ```shell 37 | retrowrite storepng storepng_symb.s 38 | ``` 39 | 40 | You will get an ASCII file containing ASM instruction of the binary. 41 | 42 | Now you will need to recompile a binary with AFL instrumentations. 43 | 44 | In order to do that you will need to use afl-gcc command: 45 | ```shell 46 | AFL_AS_FORCE_INSTRUMENT=1 afl-gcc storepng_symb.s -o storepng_symb_inst -lz 47 | 48 | # to verify that the recompilation went good (no direct crash) 49 | ./storepng_symb_inst 50 | 51 | cd ../ 52 | ``` 53 | 54 | **Script**: [01-instrument_symb.sh](01-instrument_symb.sh) 55 | 56 | ## Learn how to use AFL++ with storepng_symb_inst 57 | 58 | Now, we are going to demonstrate a working fuzzing campaign with AFL++ project. 59 | 60 | In your shell you should be able to run: 61 | 62 | ``` 63 | afl-fuzz 64 | ``` 65 | 66 | And it will execute the fuzzing command from the correct directory. 67 | 68 | 69 | Now that you are in the playground directory, create a working directory where 70 | we will fuzz from: 71 | 72 | ``` 73 | mkdir -p work-symb-storepng 74 | ``` 75 | 76 | As you can see, we have provided the source to the binaries. We will not use this, but it is available if you wish to experiment. 77 | 78 | Now to run `afl-fuzz` we need several pieces of information: 79 | 80 | - The binary we will fuzz. 81 | - A directory of input test cases. 82 | - A storage directory for fuzzing results 83 | - The current working directory. 84 | 85 | 86 | Without further ado, let us execute a fuzzing run: 87 | 88 | ``` 89 | cd work-symb-storepng 90 | afl-fuzz -i ../inputs/storepng -o ../02-fuzz-sym-storepng/ -- ../bin/storepng_symb_inst @@ 91 | ``` 92 | 93 | The commands to `afl-fuzz` are as follows: 94 | 95 | - `-i ../inputs/storepng` stores the input test cases from the input directory. 96 | - `-o ../02-fuzz-sym-storepng/` tells afl++ where to store its information. 97 | - `-- ../bin/storepng_symb_inst @@` is a bit special. There are three parts to this 98 | command: `--`, which terminates the argument list, the path to the 99 | program to be fuzzed, and `@@`. This is a placeholder which tells AFL++ 100 | which argument may be substituted for input by the fuzzer. In other words, 101 | this is how the test cases are supplied. 102 | 103 | If all goes well, you should see output like this: 104 | 105 | ![Image showing terminal with AFL running, presenting crash statistics](aflinaction.png) 106 | 107 | Whenever you want to stop the fuzzing operation, you can press `CTRL+C` 108 | as you would to exit any terminal program. Fuzzing will then terminate. 109 | 110 | This may take some time. 111 | 112 | **Script**: [02-symb-fuzzing-loadpng.sh](02-symb-fuzzing-loadpng.sh) 113 | 114 | **Script**: [03-symb-fuzzing-storepng.sh](03-symb-fuzzing-storepng.sh) 115 | 116 | ## Examining bugs 117 | 118 | There is no script for this section, but it is interesting to examine crashes 119 | sometimes. AFL++ stores in its fuzz output directory an input that lead to 120 | each unique crash. How do we look at this? Well, we can find the crashes 121 | from the playground as follows: 122 | 123 | ```shell 124 | ls playground/02-fuzz-sym-storepng/crashes 125 | ``` 126 | 127 | These are inputs that were provided to the program in place of the `@@`. 128 | The name gives you some information as to the strategy AFL used to find this 129 | particular crash. If we want to actually look at the crash, we can do this: 130 | 131 | ```shell 132 | cd playground 133 | gdb bin/storepng_symb_inst 134 | run 02-fuzz-sym-storepng/crashes/... 135 | ``` 136 | 137 | where `02-fuzz-sym-storepng/crashes/...` is the name of a particular crash in question. 138 | This will run the command with that particular file as an argument, exactly 139 | what we want. Under gdb we can then see the stack trace: 140 | 141 | ![Image showing GDB with a stack trace of crashing storepng using the 142 | bt command](gdb.png) 143 | 144 | 145 | ## Cleanup 146 | 147 | To clean up, we simply remove the work directory. Since storepng outputs a 148 | lot of files, the easiest method to remove it is to remove the entire directory 149 | in one go: 150 | 151 | ```sh 152 | cd .. 153 | rm -r work-symb-loadpng 154 | rm -r work-symb-storepng 155 | 156 | ``` 157 | 158 | **Script**: [04-cleanup.sh](04-cleanup.sh) 159 | -------------------------------------------------------------------------------- /02-coverage_fuzzing/aflinaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/02-coverage_fuzzing/aflinaction.png -------------------------------------------------------------------------------- /02-coverage_fuzzing/gdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/02-coverage_fuzzing/gdb.png -------------------------------------------------------------------------------- /03-coverage_sanitized_fuzzing/01-instrument_asan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy 4 | set -ex 5 | 6 | cd ../playground/bin 7 | retrowrite loadpng loadpng_asan.s --asan 8 | sed -i 's/asan_init_v4/asan_init/g' loadpng_asan.s 9 | AFL_AS_FORCE_INSTRUMENT=1 $AFL_PATH/afl-gcc loadpng_asan.s -o loadpng_asan_inst -lz -fsanitize=address 10 | 11 | 12 | retrowrite storepng storepng_asan.s -a 13 | sed -i 's/asan_init_v4/asan_init/g' storepng_asan.s 14 | AFL_AS_FORCE_INSTRUMENT=1 $AFL_PATH/afl-gcc storepng_asan.s -o storepng_asan_inst -lz -fsanitize=address 15 | 16 | -------------------------------------------------------------------------------- /03-coverage_sanitized_fuzzing/02-asan-fuzzing-loadpng.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy 4 | set -ex 5 | 6 | cd ../playground/ 7 | 8 | mkdir -p work-asan-loadpng 9 | cd work-asan-loadpng 10 | ../../aflplusplus/afl-fuzz -i ../inputs/loadpng -o ../03-fuzz-asan-loadpng/ -- ../bin/loadpng_asan_inst @@ 11 | -------------------------------------------------------------------------------- /03-coverage_sanitized_fuzzing/03-asan-fuzzing-storepng.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy 4 | set -ex 5 | 6 | cd ../playground/ 7 | 8 | mkdir -p work-asan-storepng 9 | cd work-asan-storepng 10 | ../../aflplusplus/afl-fuzz -i ../inputs/storepng -o ../03-fuzz-asan-storepng/ -- ../bin/storepng_asan_inst @@ 11 | -------------------------------------------------------------------------------- /03-coverage_sanitized_fuzzing/04-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # be noisy: 4 | set -ex 5 | 6 | # locate this script: 7 | SCRIPT=$(readlink -f $0) 8 | SCRIPTDIR=$(dirname $SCRIPT) 9 | 10 | # cd into the right place 11 | cd $SCRIPTDIR/../playground 12 | # remove work directory 13 | rm -rf work-asan-loadpng 14 | rm -rf work-asan-storepng 15 | -------------------------------------------------------------------------------- /03-coverage_sanitized_fuzzing/README.md: -------------------------------------------------------------------------------- 1 | # Phase 03: instrumenting native binaries with coverage tracking and sanitization 2 | 3 | ## Introduction 4 | 5 | This section aims to introduce you to static binaries rewriting using the tool [retrowrite](https://github.com/HexHive/retrowrite) 6 | 7 | For more information about retrowrite tool and fuzzing, please refer to the presentation or read the retrowrite documentation (available in the public repository). 8 | 9 | In the rest of this documentation, TUTORIAL_REPO_DIR refers to the location 10 | you found your repository in. 11 | 12 | __**ATTENTION**__ 13 | 14 | Before you begin copying and pasting commands from this section, ready-made 15 | scripts exist in this folder for all commands you can see here. For each section you 16 | will find a script that performs the commands from that part, so that you 17 | do not need to copy and paste commands from the readme. This documentation 18 | exists so you can see what is done and why. 19 | 20 | ## Download and Build AFL++ 21 | 22 | The installation from the 01-native-fuzzing step should correctly set up the tools needed for this part. 23 | 24 | In case you didn't run the script during 01-native-fuzzing phase, you can run the following script: 25 | 26 | **Script to run**: [TUTORIAL_REPO_DIR/setup.sh](../setup.sh) 27 | 28 | ## Learn how to instrument a binary with ASan 29 | 30 | ```shell 31 | source tools 32 | cd playground/bin 33 | ``` 34 | To generate symbolized assembly you can use the following command: 35 | ```shell 36 | retrowrite storepng storepng_asan.s --asan 37 | ``` 38 | 39 | You will get an ASCII file containing the assembler instructions of the binary. 40 | 41 | Now you will need to recompile the binary with AFL instrumentations. 42 | 43 | In order to do that you will need to use the following commands: 44 | ```shell 45 | sed -i 's/asan_init_v4/asan_init/g' storepng_asan.s 46 | AFL_AS_FORCE_INSTRUMENT=1 afl-gcc storepng_asan.s -o storepng_asan_inst -lz -fsanitize=address 47 | 48 | # to verify that the recompilation worked, run the binary 49 | ./storepng_asan_inst 50 | 51 | cd ../ 52 | ``` 53 | **Script**: [01-instrument_asan.sh](01-instrument_asan.sh) 54 | 55 | ## Learn how to use AFL++ with storepng_asan_inst 56 | 57 | Now, we are going to demonstrate a working fuzzing campaign with AFL++. 58 | 59 | In your shell you should be able to run: 60 | 61 | ``` 62 | afl-fuzz 63 | ``` 64 | 65 | And it will execute the fuzzing command from the correct directory. 66 | 67 | 68 | Now that you are in the playground directory, create a working directory where we will fuzz from: 69 | 70 | ``` 71 | mkdir -p work-asan-storepng 72 | ``` 73 | 74 | As you can see, we have provided the source to the binaries. We will not use this, but it is available if you wish to experiment. 75 | 76 | Now to run `afl-fuzz` we need several pieces of information: 77 | 78 | - The binary we will fuzz. 79 | - A directory of input test cases. 80 | - A storage directory for fuzzing results 81 | - The current working directory. 82 | 83 | 84 | Without further ado, let us execute a fuzzing run: 85 | 86 | ``` 87 | cd work-asan 88 | afl-fuzz -i ../inputs/storepng -o ../03-fuzz-asan-storepng/ -- ../bin/storepng_asan_inst @@ 89 | ``` 90 | 91 | The commands to `afl-fuzz` are as follows: 92 | 93 | - `-i ../inputs/storepng` stores the input test cases from the input directory. 94 | - `-o ../03-fuzz-asan-storepng/` tells afl++ where to store its information. 95 | - `-- ../bin/storepng_asan_inst @@` is a bit special. There are three parts to this 96 | command: `--`, which terminates the argument list, the path to the 97 | program to be fuzzed, and `@@`. This is a placeholder which tells AFL++ 98 | which argument may be substituted for input by the fuzzer. In other words, 99 | this is how the test cases are supplied. 100 | 101 | If all goes well, you should see output like this: 102 | 103 | ![Image showing terminal with AFL running, presenting crash statistics](aflinaction.png) 104 | 105 | Whenever you want to stop the fuzzing operation, you can press `CTRL+C` 106 | as you would to exit any terminal program. Fuzzing will then terminate. 107 | 108 | This may take some time. 109 | 110 | **Script**: [02-asan-fuzzing-loadpng.sh](02-asan-fuzzing-loadpng.sh) 111 | 112 | **Script**: [03-asan-fuzzing-storepng.sh](03-asan-fuzzing-storepng.sh) 113 | 114 | 115 | ## Examining bugs 116 | 117 | There is no script for this section, but it is interesting to examine crashes 118 | sometimes. AFL++ stores in its fuzz output directory an input that lead to 119 | each unique crash. How do we look at this? Well, we can find the crashes 120 | from the playground as follows: 121 | 122 | ```shell 123 | ls playground/03-fuzz-asan-storepng/crashes 124 | ``` 125 | 126 | These are inputs that were provided to the program in place of the `@@`. 127 | The name gives you some information as to the strategy AFL used to find this 128 | particular crash. If we want to actually look at the crash, we can do this: 129 | 130 | ```shell 131 | cd playground 132 | gdb bin/storepng_asan_inst 133 | run 03-fuzz-asan-storepng/crashes/... 134 | ``` 135 | 136 | where `03-fuzz-asan-storepng/crashes/...` is the name of a particular crash in question. 137 | This will run the command with that particular file as an argument, exactly 138 | what we want. Under gdb we can then see the stack trace: 139 | 140 | ![Image showing GDB with a stack trace of crashing storepng using the 141 | bt command](gdb.png) 142 | 143 | 144 | ## Cleanup 145 | 146 | To clean up, we simply remove the work directory. Since storepng outputs a 147 | lot of files, the easiest method to remove it is to remove the entire directory 148 | in one go: 149 | 150 | ```sh 151 | cd .. 152 | rm -r work-asan-loadpng 153 | rm -r work-asan-storepng 154 | 155 | ``` 156 | 157 | **Script**: [04-cleanup.sh](04-cleanup.sh) 158 | -------------------------------------------------------------------------------- /03-coverage_sanitized_fuzzing/aflinaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/03-coverage_sanitized_fuzzing/aflinaction.png -------------------------------------------------------------------------------- /03-coverage_sanitized_fuzzing/gdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/03-coverage_sanitized_fuzzing/gdb.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RetroWrite: fuzzing native binaries at lightning speed 2 | 3 | Modern grey-box fuzzers are the most effective way of finding bugs in complex 4 | code bases, and instrumentation is fundamental to their effectiveness. Existing 5 | instrumentation techniques either require source code (e.g., afl-gcc, ASan) or 6 | have a high runtime performance cost (roughly 10x slowdown for e.g., afl-qemu). 7 | 8 | We introduce Retrowrite, a binary rewriting framework that enables direct static 9 | instrumentation for both user-mode binaries and Linux kernel modules. Unlike 10 | dynamic translation and trampolining, rewriting code with Retrowrite does not 11 | introduce a performance penalty. We show the effectiveness of Retrowrite for 12 | fuzzing by implementing binary-only coverage tracking and ASan instrumentation 13 | passes. Our binary instrumentation achieves performance similar to 14 | compiler-based instrumentation. 15 | 16 | This tutorial consists of three practical phases: 17 | 18 | * Fuzzing native binaries without instrumentation 19 | * Instrumenting native binaries with coverage tracking 20 | * Instrumenting native binaries with coverage tracking and address sanitization 21 | 22 | 23 | ## Research background 24 | 25 | Fuzzing is the method of choice for finding security vulnerabilities in software 26 | due to its simplicity and scalability, but it struggles to find deep paths in 27 | complex programs, and only detects bugs when the target crashes. Instrumentation 28 | greatly helps with both issues by (i) collecting coverage feedback, which drives 29 | fuzzing deeper into the target, and (ii) crashing the target immediately when 30 | bugs are detected, which lets the fuzzer detect more bugs and produce more 31 | precise reports. One of the main difficulties of fuzzing closed-source software 32 | is that instrumenting compiled binaries comes at a huge performance cost. For 33 | example, simple coverage instrumentation through dynamic binary translation 34 | already incurs between 10x and 100x slowdown, which prevents the fuzzer from 35 | finding interesting inputs and bugs. 36 | 37 | We show how we used static binary rewriting for instrumentation: our approach 38 | has low overhead (comparable to compile-time instrumentation) but works on 39 | binaries. There are three main techniques to rewrite binaries: recompilation, 40 | trampoline insertion and reassembleable assembly. Recompilation is the most 41 | powerful but it requires expensive analysis and type recovery, which is an 42 | open/unsolved problem. Trampolines add a level of indirection and increase the 43 | size of the code, both of which have a negative impact on performance. 44 | Reassembleable assembly, the technique that we use, suffers from neither 45 | problem. In order to produce reassembleable assembly, we first disassemble the 46 | binary and then symbolize all code and data references (replacing offsets and 47 | references with references to unique labels). The output can then be fed to a 48 | standard assembler to produce a binary. Because symbolization replaces all 49 | references with labels, we can now insert instrumentation in the code and the 50 | assembler will fix the references for us when reassembling the binary. 51 | Symbolization is possible because references to code and global data always use 52 | RIP-relative addressing in the class of binaries that we support 53 | (position-independent x86\_64 binaries). This makes it easy to distinguish 54 | between references and integer constants in the disassembly. 55 | 56 | See the following resources for more details: 57 | 58 | * [RetroWrite homepage](https://github.com/HexHive/RetroWrite) 59 | * [RetroWrite paper](http://nebelwelt.net/files/20Oakland.pdf) 60 | 61 | 62 | ## Tutorial outline 63 | 64 | We present Retrowrite, a framework for static binary rewriting that can add 65 | efficient instrumentation to compiled binaries and scales to real-world code. 66 | Retrowrite symbolizes x86_64 position-independent Linux binaries and emits 67 | reassembleable assembly, which can be fed to instrumentation passes. We 68 | implement a binary version of Address Sanitizer (ASan) that integrates with the 69 | source-based version. Retrowrite’s output can also be fed directly to AFL’s 70 | afl-gcc to produce a binary with coverage-tracking instrumentation. RetroWrite 71 | is openly available for everyone to use and we will demo it during the 72 | presentation. 73 | 74 | Based on the open-source release of RetroWrite, we will rewrite several binaries 75 | (starting with small examples to larger real programs and libraries) and prepare 76 | them for fuzzing and sanitization. Using the demo programs the attendees will 77 | discover bugs in these binaries and learn how to patch them alongside. 78 | 79 | 80 | The tutorial consists of three phases: 81 | 82 | * [Phase 01: fuzzing native binaries without instrumentation](./01-native_fuzzing/README.md) 83 | * [Phase 02: instrumenting native binaries with coverage tracking](./02-coverage_fuzzing/README.md) 84 | * [Phase 03: instrumenting native binaries with coverage tracking and address sanitization](./03-coverage_sanitized_fuzzing/README.md) 85 | 86 | The `playground` directory contains a set of buggy binaries to play with along 87 | with their source code. The tutorial expects that retrowrite is installed and 88 | compiled in the `retrowrite` directory and the fuzzer in the `fuzzer` directory. 89 | You can set up the necessary tools by executing `./setup.sh`. 90 | 91 | 92 | ## Contact 93 | 94 | This tutorial is created by the HexHive group at EPFL and is first presented at 95 | [SSSS'20](./https://www.cerias.purdue.edu/site/ssss20). In case of questions, 96 | please contact [Mathias Payer](mailto:mathias.payer@epfl.ch), Jean-Michel 97 | Crepel, or Antony Vennard. 98 | -------------------------------------------------------------------------------- /playground/README.md: -------------------------------------------------------------------------------- 1 | # Playground that provides buggy binaries 2 | 3 | In this directory you will find: 4 | 5 | * bin/loadpng - a binary that exercises png reading code when supplied with a 6 | single argument - the PNG file to read. 7 | * bin/storepng - a binary that writes a png file based on a single provided 8 | file as a source of random bytes (don't use /dev/urandom 9 | directly for this, if you want test cases extract at least 1kb 10 | of bytes from there). 11 | * inputs/ - example valid inputs for the binaries. 12 | * src/ - the source for the binaries, for your inspection (but we 13 | don't rely on having this as part of the fuzzing campaign). 14 | 15 | 16 | -------------------------------------------------------------------------------- /playground/bin/loadpng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/bin/loadpng -------------------------------------------------------------------------------- /playground/bin/storepng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/bin/storepng -------------------------------------------------------------------------------- /playground/inputs/loadpng/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/inputs/loadpng/palette.png -------------------------------------------------------------------------------- /playground/inputs/loadpng/rgba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/inputs/loadpng/rgba.png -------------------------------------------------------------------------------- /playground/inputs/storepng/testcase01: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/inputs/storepng/testcase01 -------------------------------------------------------------------------------- /playground/inputs/storepng/testcase02: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/inputs/storepng/testcase02 -------------------------------------------------------------------------------- /playground/inputs/storepng/testcase03: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/inputs/storepng/testcase03 -------------------------------------------------------------------------------- /playground/inputs/storepng/testcase04: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexHive/RetroWrite-Tutorial/1346441f023eb199461c86c7aa9998f19d7db590/playground/inputs/storepng/testcase04 -------------------------------------------------------------------------------- /playground/src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | CC?=clang 4 | CFLAGS=-fPIC -O2 -fPIE 5 | LDFLAGS=-fPIC -fPIE -pie `pkg-config --cflags --libs zlib` 6 | 7 | %.o: %.c 8 | $(CC) $(CFLAGS) -c $< -o $@ 9 | 10 | all: libpngparser.a loadpng storepng 11 | cp loadpng storepng ../bin 12 | 13 | clean: 14 | rm -f libpngparser.a loadpng storepng *.o 15 | 16 | .PHONY: all clean s 17 | 18 | libpngparser.a: pngparser.o crc.o 19 | ar rcs libpngparser.a $^ 20 | 21 | loadpng: loadpng.o libpngparser.a 22 | $(CC) $(LDFLAGS) -o $@ $^ 23 | 24 | storepng: storepng.o libpngparser.a 25 | $(CC) $(LDFLAGS) -o $@ $^ 26 | 27 | -------------------------------------------------------------------------------- /playground/src/crc.c: -------------------------------------------------------------------------------- 1 | /* This code is obtained from: 2 | * https://www.w3.org/TR/PNG-CRCAppendix.html 3 | * 4 | */ 5 | 6 | /* Table of CRCs of all 8-bit messages. */ 7 | unsigned long crc_table[256]; 8 | 9 | /* Flag: has the table been computed? Initially false. */ 10 | int crc_table_computed = 0; 11 | 12 | /* Make the table for a fast CRC. */ 13 | static void make_crc_table(void) 14 | { 15 | unsigned long c; 16 | int n, k; 17 | 18 | for (n = 0; n < 256; n++) { 19 | c = (unsigned long) n; 20 | for (k = 0; k < 8; k++) { 21 | if (c & 1) 22 | c = 0xedb88320L ^ (c >> 1); 23 | else 24 | c = c >> 1; 25 | } 26 | crc_table[n] = c; 27 | } 28 | crc_table_computed = 1; 29 | } 30 | 31 | /* Update a running CRC with the bytes buf[0..len-1]--the CRC 32 | should be initialized to all 1's, and the transmitted value 33 | is the 1's complement of the final running CRC (see the 34 | crc() routine below)). */ 35 | 36 | unsigned long update_crc(unsigned long crc, unsigned char *buf, 37 | int len) 38 | { 39 | unsigned long c = crc; 40 | int n; 41 | 42 | if (!crc_table_computed) 43 | make_crc_table(); 44 | for (n = 0; n < len; n++) { 45 | c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); 46 | } 47 | return c; 48 | } 49 | 50 | /* Return the CRC of the bytes buf[0..len-1]. */ 51 | unsigned long crc(unsigned char *buf, int len) 52 | { 53 | return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL; 54 | } -------------------------------------------------------------------------------- /playground/src/crc.h: -------------------------------------------------------------------------------- 1 | #ifndef CRC_H 2 | #define CRC_H 3 | 4 | unsigned long update_crc(unsigned long crc, unsigned char *buf, 5 | int len); 6 | 7 | unsigned long crc(unsigned char *buf, int len); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /playground/src/loadpng.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "pngparser.h" 5 | 6 | int main(int argc, char** argv) { 7 | struct image *test_img; 8 | 9 | if ( argc != 2 ) { 10 | printf("I need an input. Please provide a path to the PNG to load.\n"); 11 | exit(1); 12 | } 13 | char* filename = argv[1]; 14 | // What would happen if we run multiple fuzzing processes at the same time? 15 | // Take a look at the name of the file. 16 | load_png(filename, &test_img); 17 | 18 | // Always return 0 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /playground/src/pngparser.c: -------------------------------------------------------------------------------- 1 | #include "zlib.h" 2 | #include "crc.h" 3 | #include "pngparser.h" 4 | #include 5 | #include 6 | 7 | #define PNG_OUTPUT_CHUNK_SIZE (1 << 14) 8 | 9 | #define PNG_IHDR_COLOR_GRAYSCALE 0 10 | #define PNG_IHDR_COLOR_RGB 2 11 | #define PNG_IHDR_COLOR_PALETTE 3 12 | #define PNG_IHDR_COLOR_GRAYSCALE_ALPHA 4 13 | #define PNG_IHDR_COLOR_RGB_ALPHA 6 14 | 15 | #define PNG_IHDR_INTERLACE_NO_INTERLACE 0 16 | #define PNG_IHDR_INTERLACE_ADAM7 1 17 | 18 | #define to_little_endian(x) (change_endianness(x)) 19 | #define to_big_endian(x) (change_endianness(x)) 20 | 21 | /** Changes the endianness of the data 22 | */ 23 | uint32_t change_endianness(uint32_t x) 24 | { 25 | int i; 26 | uint32_t result = 0; 27 | 28 | for (i = 0; i < 4; i++) 29 | { 30 | result |= ((x >> (8*i)) & 0xff) << (8 * (3 - i)); 31 | } 32 | 33 | return result; 34 | } 35 | 36 | /** 37 | * Y0l0 PNG data is divided into chunks. Every chunk consists of: 38 | * 1) the length (in bytes) 39 | * 2) a code denoting the type of the chunk 40 | * 3) the chunk data 41 | * 4) CRC checksum 42 | */ 43 | struct __attribute__((__packed__)) png_chunk 44 | { 45 | uint32_t length; 46 | uint32_t chunk_type; 47 | void * chunk_data; 48 | uint32_t crc; 49 | }; 50 | 51 | /** 52 | * Different chunk types 53 | */ 54 | typedef struct png_chunk png_chunk_ihdr; 55 | typedef struct png_chunk png_chunk_plte; 56 | typedef struct png_chunk png_chunk_idat; 57 | typedef struct png_chunk png_chunk_iend; 58 | 59 | /* The header that carries image metadata 60 | */ 61 | struct __attribute__((__packed__)) png_header_ihdr 62 | { 63 | uint32_t width; 64 | uint32_t height; 65 | uint8_t bit_depth; 66 | uint8_t color_type; 67 | uint8_t compression; 68 | uint8_t filter; 69 | uint8_t interlace; 70 | }; 71 | 72 | /* Color description in the palette 73 | */ 74 | struct __attribute__((__packed__)) plte_entry 75 | { 76 | uint8_t red; 77 | uint8_t green; 78 | uint8_t blue; 79 | }; 80 | 81 | /* Starting bytes of an image 82 | */ 83 | struct __attribute__((__packed__)) png_header_filesig 84 | { 85 | uint8_t filesig[8]; 86 | }; 87 | 88 | /* The current implementation supports only some of the PNG color types 89 | */ 90 | int is_color_type_valid(uint8_t color_type) 91 | { 92 | switch (color_type) 93 | { 94 | 95 | case PNG_IHDR_COLOR_PALETTE: 96 | case PNG_IHDR_COLOR_RGB_ALPHA: 97 | return 1; 98 | case PNG_IHDR_COLOR_GRAYSCALE: 99 | case PNG_IHDR_COLOR_RGB: 100 | case PNG_IHDR_COLOR_GRAYSCALE_ALPHA: 101 | default: 102 | return 0; 103 | } 104 | } 105 | 106 | /* Currently, we support only some of the bidepths */ 107 | int is_bit_depth_valid(uint8_t color_type, int8_t bitdepth) 108 | { 109 | if (color_type == PNG_IHDR_COLOR_PALETTE && bitdepth == 8) 110 | return 1; 111 | 112 | if (color_type == PNG_IHDR_COLOR_RGB_ALPHA && bitdepth == 8) 113 | return 1; 114 | 115 | return 0; 116 | } 117 | 118 | /* The only supported method is deflate */ 119 | int is_compression_valid(uint8_t compression) 120 | { 121 | return !compression; 122 | } 123 | 124 | /* We support only the default PNG filtering method 125 | * Filtering is a step before compression. 126 | * Compression may be better if we store differences between pixels 127 | * instead of the actual pixel values. 128 | */ 129 | int is_filter_valid(uint8_t filter) 130 | { 131 | return !filter; 132 | } 133 | 134 | /* The only filter type that we support is no filter at all */ 135 | int is_filter_type_valid(uint8_t filter_type) 136 | { 137 | return !filter_type; 138 | } 139 | 140 | /* Y0L0 PNG stores all of its data as a sequence of rows. 141 | * Some other, barbaric standards (e.g. PNG) also provide storage sequences 142 | * that give lower resolution approximations of the image while streaming the data 143 | */ 144 | int is_interlace_valid(uint8_t interlace) 145 | { 146 | switch (interlace) 147 | { 148 | case PNG_IHDR_INTERLACE_NO_INTERLACE: 149 | return 1; 150 | 151 | // We don't support interlacing 152 | // 153 | case PNG_IHDR_INTERLACE_ADAM7: 154 | return 0; 155 | 156 | default: 157 | return 0; 158 | } 159 | } 160 | 161 | 162 | /* Check if the metadata header is valid */ 163 | int is_png_ihdr_valid(struct png_header_ihdr *ihdr) 164 | { 165 | if (!is_color_type_valid(ihdr->color_type)) 166 | return 0; 167 | 168 | if (!is_bit_depth_valid(ihdr->color_type, ihdr->bit_depth)) 169 | return 0; 170 | 171 | if (!is_compression_valid(ihdr->compression)) 172 | return 0; 173 | 174 | if (!is_filter_valid(ihdr->filter)) 175 | return 0; 176 | 177 | if (!is_interlace_valid(ihdr->interlace)) 178 | return 0; 179 | 180 | return 1; 181 | } 182 | 183 | /* Check if we have the IHDR chunk */ 184 | int is_chunk_ihdr(struct png_chunk *chunk) 185 | { 186 | return !memcmp(&chunk->chunk_type, "IHDR", 4); 187 | } 188 | 189 | /* Convert a freshly read generic chunk to IHDR */ 190 | png_chunk_ihdr *format_ihdr_chunk(struct png_chunk *chunk) 191 | { 192 | png_chunk_ihdr *ihdr; 193 | struct png_header_ihdr *ihdr_header; 194 | 195 | if (!is_chunk_ihdr(chunk)) 196 | return NULL; 197 | 198 | if (chunk->length != sizeof(struct png_header_ihdr)) 199 | return NULL; 200 | 201 | ihdr = (png_chunk_ihdr *) chunk; 202 | 203 | if (!is_png_ihdr_valid(ihdr->chunk_data)) 204 | return NULL; 205 | 206 | ihdr_header = (struct png_header_ihdr *) ihdr->chunk_data; 207 | ihdr_header->height = to_little_endian(ihdr_header->height); 208 | ihdr_header->width = to_little_endian(ihdr_header->width); 209 | 210 | return ihdr; 211 | } 212 | 213 | /* Check if this is the IEND chunk */ 214 | int is_chunk_iend(struct png_chunk *chunk) 215 | { 216 | return !memcmp(&chunk->chunk_type, "IEND", 4); 217 | } 218 | 219 | /* Format a freshly read chunk as an IEND chunk */ 220 | png_chunk_iend *format_iend_chunk(struct png_chunk *chunk) 221 | { 222 | if (!is_chunk_iend(chunk)) { 223 | return NULL; 224 | } 225 | 226 | if (chunk->length) { 227 | return NULL; 228 | } 229 | 230 | if (chunk->chunk_data) { 231 | return NULL; 232 | } 233 | 234 | return (png_chunk_iend *) chunk; 235 | } 236 | 237 | /* Read the signature of a file */ 238 | int read_png_filesig(FILE *file, struct png_header_filesig *filesig) 239 | { 240 | return fread(filesig, sizeof(*filesig), 1, file) != 1; 241 | } 242 | 243 | /* Checks if the first bytes have the correct values */ 244 | int is_png_filesig_valid(struct png_header_filesig *filesig) 245 | { 246 | return !memcmp(filesig, "\211PNG\r\n\032\n", 8); 247 | } 248 | 249 | /* CRC for a chunk. This prevents data corruption. 250 | * 251 | * EDIT THIS FUNCTION BEFORE FUZZING! 252 | */ 253 | int is_png_chunk_valid(struct png_chunk *chunk) 254 | { 255 | /*uint32_t crc_value = crc((unsigned char *) &chunk->chunk_type, sizeof(int32_t)); 256 | 257 | if (chunk->length) { 258 | crc_value = update_crc(crc_value ^ 0xffffffffL, (unsigned char *) chunk->chunk_data, chunk->length) ^ 0xffffffffL; 259 | } 260 | 261 | return chunk->crc == crc_value;*/ 262 | return 1; 263 | } 264 | 265 | /* Fill the chunk with the data from the file.*/ 266 | int read_png_chunk(FILE *file, struct png_chunk *chunk) 267 | { 268 | chunk->chunk_data = NULL; 269 | 270 | if (fread(chunk, sizeof(int32_t), 2, file) != 2) { 271 | goto error; 272 | } 273 | 274 | chunk->length = to_little_endian(chunk->length); 275 | 276 | if (chunk->length) { 277 | chunk->chunk_data = malloc(chunk->length); 278 | if (!chunk->chunk_data) 279 | goto error; 280 | 281 | if (fread(chunk->chunk_data, chunk->length, 1, file) != 1) { 282 | goto error; 283 | } 284 | } 285 | 286 | if (fread(&chunk->crc, sizeof(int32_t), 1, file) != 1) { 287 | goto error; 288 | } 289 | 290 | chunk->crc = to_little_endian(chunk->crc); 291 | 292 | if (!is_png_chunk_valid(chunk)) { 293 | goto error; 294 | } 295 | 296 | chunk->chunk_type = chunk->chunk_type; 297 | 298 | return 0; 299 | 300 | error: 301 | if (chunk->chunk_data) { 302 | free(chunk->chunk_data); 303 | chunk->chunk_data = NULL; 304 | } 305 | return 1; 306 | } 307 | 308 | /* Does the chunk represent a palette of colors?*/ 309 | int is_chunk_plte(struct png_chunk *chunk) 310 | { 311 | return !memcmp(&chunk->chunk_type, "PLTE", 4); 312 | } 313 | 314 | /* Reinterpret a chunk to the PLTE chunk, if possible */ 315 | png_chunk_plte *format_plte_chunk(struct png_chunk *chunk) 316 | { 317 | if (!is_chunk_plte(chunk)) 318 | return NULL; 319 | 320 | if (chunk->length % 3) 321 | return NULL; 322 | 323 | return (png_chunk_plte *) chunk; 324 | } 325 | 326 | /* Does the chunk represent image data? */ 327 | int is_chunk_idat(struct png_chunk *chunk) 328 | { 329 | return !memcmp(&chunk->chunk_type, "IDAT", 4); 330 | } 331 | 332 | /* Query the metadata for the interlacing type */ 333 | int is_interlaced(png_chunk_ihdr *ihdr_chunk) 334 | { 335 | struct png_header_ihdr *ihdr_header = (struct png_header_ihdr *) ihdr_chunk->chunk_data; 336 | return ihdr_header->interlace; 337 | } 338 | 339 | /* Reinterpret a generic chunk as an IDAT chunk */ 340 | png_chunk_idat *format_idat_chunk(struct png_chunk *chunk) { 341 | if (!is_chunk_idat(chunk)) { 342 | return NULL; 343 | } 344 | 345 | return (png_chunk_idat *) chunk; 346 | } 347 | 348 | /* Take a deflate stream and decompress it. This is used to obtain image data from an IDAT train. */ 349 | int decompress_png_data(uint8_t *compressed_data, uint32_t input_length, uint8_t **decompressed_data, uint32_t *decompressed_length) 350 | { 351 | int ret; 352 | unsigned have; 353 | z_stream strm; 354 | unsigned char out[PNG_OUTPUT_CHUNK_SIZE]; 355 | uint8_t *output_buffer = NULL; 356 | uint32_t output_length = 0; 357 | 358 | /* allocate inflate state */ 359 | strm.zalloc = Z_NULL; 360 | strm.zfree = Z_NULL; 361 | strm.opaque = Z_NULL; 362 | 363 | strm.avail_in = input_length; 364 | strm.next_in = Z_NULL; 365 | ret = inflateInit(&strm); 366 | if (ret != Z_OK) 367 | return ret; 368 | 369 | 370 | strm.next_in = compressed_data; 371 | 372 | /* run inflate() on input until output buffer not full */ 373 | do { 374 | strm.avail_out = PNG_OUTPUT_CHUNK_SIZE; 375 | strm.next_out = out; 376 | ret = inflate(&strm, Z_NO_FLUSH); 377 | 378 | switch (ret) { 379 | case Z_STREAM_ERROR: 380 | case Z_NEED_DICT: 381 | case Z_DATA_ERROR: 382 | case Z_MEM_ERROR: 383 | goto error; 384 | } 385 | 386 | have = PNG_OUTPUT_CHUNK_SIZE - strm.avail_out; 387 | 388 | output_buffer = realloc(output_buffer, output_length + have); 389 | 390 | memcpy(output_buffer + output_length, out, have); 391 | 392 | output_length += have; 393 | } while (strm.avail_out == 0); 394 | 395 | if (ret != Z_STREAM_END) { 396 | goto error; 397 | } 398 | 399 | /* clean up and return */ 400 | (void)inflateEnd(&strm); 401 | 402 | *decompressed_data = output_buffer; 403 | *decompressed_length = output_length; 404 | return 0; 405 | 406 | error: 407 | inflateEnd(&strm); 408 | if (output_buffer) free(output_buffer); 409 | return 1; 410 | } 411 | 412 | /* Combine image metadata, palette and a decompressed image data buffer (with palette entries) into an image */ 413 | struct image *convert_color_palette_to_image(png_chunk_ihdr *ihdr_chunk, png_chunk_plte *plte_chunk, uint8_t *inflated_buf, uint32_t inflated_size) 414 | { 415 | struct png_header_ihdr *ihdr_header = (struct png_header_ihdr *) ihdr_chunk->chunk_data; 416 | uint32_t height = ihdr_header->height; 417 | uint32_t width = ihdr_header->width; 418 | uint32_t palette_idx = 0; 419 | 420 | struct plte_entry *plte_entries = (struct plte_entry *) plte_chunk->chunk_data; 421 | 422 | struct image * img = malloc(sizeof(struct image)); 423 | img->size_y = height; 424 | img->size_x = width; 425 | img->px = malloc(sizeof(struct pixel) * img->size_x * img->size_y); 426 | 427 | for (uint32_t idy = 0; idy < height; idy++) { 428 | // Filter byte at the start of every scanline needs to be 0 429 | if (inflated_buf[idy * (1 + width)]) { 430 | free(img->px); 431 | free(img); 432 | return NULL; 433 | } 434 | for (uint32_t idx = 0; idx < width; idx++) { 435 | palette_idx = inflated_buf[idy * (1 + width) + idx + 1]; 436 | img->px[idy * img->size_x + idx].red = plte_entries[palette_idx].red; 437 | img->px[idy * img->size_x + idx].green = plte_entries[palette_idx].green; 438 | img->px[idy * img->size_x + idx].blue = plte_entries[palette_idx].blue; 439 | img->px[idy * img->size_x + idx].alpha = 0xff; 440 | } 441 | } 442 | 443 | return img; 444 | } 445 | 446 | /* Combine image metadata and decompressed image data (RGBA) into an image */ 447 | struct image *convert_rgb_alpha_to_image(png_chunk_ihdr *ihdr_chunk, uint8_t *inflated_buf, uint32_t inflated_size) 448 | { 449 | struct png_header_ihdr *ihdr_header = (struct png_header_ihdr *) ihdr_chunk->chunk_data; 450 | uint32_t height = ihdr_header->height; 451 | uint32_t width = ihdr_header->width; 452 | 453 | uint32_t pixel_idx = 0; 454 | uint32_t r_idx, g_idx, b_idx, a_idx; 455 | 456 | struct image * img = malloc(sizeof(struct image)); 457 | 458 | if (!img) { 459 | goto error; 460 | } 461 | 462 | img->size_y = height; 463 | img->size_x = width; 464 | img->px = malloc(sizeof(struct pixel) * img->size_x * img->size_y); 465 | 466 | if (!img->px) { 467 | goto error; 468 | } 469 | 470 | for (uint32_t idy = 0; idy < height; idy++) { 471 | // The filter byte at the start of every scanline needs to be 0 472 | if (inflated_buf[idy * (1 + 4 * width)]) { 473 | goto error; 474 | } 475 | 476 | for (uint32_t idx = 0; idx < width; idx++) { 477 | pixel_idx = idy * (1 + 4 * width) + 1 + 4*idx; 478 | 479 | r_idx = pixel_idx; 480 | g_idx = pixel_idx + 1; 481 | b_idx = pixel_idx + 2; 482 | a_idx = pixel_idx + 3; 483 | 484 | img->px[idy * img->size_x + idx].red = inflated_buf[r_idx]; 485 | img->px[idy * img->size_x + idx].green = inflated_buf[g_idx]; 486 | img->px[idy * img->size_x + idx].blue = inflated_buf[b_idx]; 487 | img->px[idy * img->size_x + idx].alpha = inflated_buf[a_idx]; 488 | } 489 | } 490 | 491 | return img; 492 | 493 | error: 494 | if (img) { 495 | if (img->px) { 496 | free(img->px); 497 | } 498 | free(img); 499 | } 500 | } 501 | 502 | /* Creates magic unicorns */ 503 | void reverse_filter_on_scanlines(png_chunk_ihdr *ihdr_chunk, uint8_t *inflated_buf, uint32_t inflated_size) 504 | { 505 | return; 506 | } 507 | 508 | /* Dispatch function for converting decompressed data into an image */ 509 | struct image *convert_data_to_image(png_chunk_ihdr *ihdr_chunk, png_chunk_plte *plte_chunk, uint8_t *inflated_buf, uint32_t inflated_size) 510 | { 511 | struct png_header_ihdr *ihdr_header = (struct png_header_ihdr *) ihdr_chunk->chunk_data; 512 | switch (ihdr_header->color_type) 513 | { 514 | case PNG_IHDR_COLOR_PALETTE: 515 | return convert_color_palette_to_image(ihdr_chunk, plte_chunk, inflated_buf, inflated_size); 516 | case PNG_IHDR_COLOR_RGB_ALPHA: 517 | return convert_rgb_alpha_to_image(ihdr_chunk, inflated_buf, inflated_size); 518 | default: 519 | return NULL; 520 | } 521 | } 522 | 523 | /* Parses a Y0L0 PNG with no interlacing */ 524 | struct image *parse_png_no_interlace(png_chunk_ihdr *ihdr_chunk, png_chunk_plte *plte_chunk, uint8_t *inflated_buf, uint32_t inflated_size) 525 | { 526 | reverse_filter_on_scanlines(ihdr_chunk, inflated_buf, inflated_size); 527 | 528 | return convert_data_to_image(ihdr_chunk, plte_chunk, inflated_buf, inflated_size); 529 | } 530 | 531 | /* Parses a Y0L0 PNG from read data. Returns NULL if the image is interlaced. */ 532 | struct image *parse_png(png_chunk_ihdr *ihdr_chunk, png_chunk_plte *plte_chunk, uint8_t *inflated_buf, uint32_t inflated_size) 533 | { 534 | if (!ihdr_chunk) { 535 | return NULL; 536 | } 537 | 538 | struct png_header_ihdr *ihdr_header = (struct png_header_ihdr *) ihdr_chunk->chunk_data; 539 | 540 | switch (ihdr_header->interlace) { 541 | case PNG_IHDR_INTERLACE_NO_INTERLACE: 542 | return parse_png_no_interlace(ihdr_chunk, plte_chunk, inflated_buf, inflated_size); 543 | case PNG_IHDR_INTERLACE_ADAM7: 544 | default: 545 | return NULL; 546 | } 547 | } 548 | 549 | /* Reads a Y0l0 PNG from file and parses it into an image */ 550 | int load_png(const char *filename, struct image **img) 551 | { 552 | struct png_header_filesig filesig; 553 | png_chunk_ihdr *ihdr_chunk = NULL; 554 | png_chunk_plte *plte_chunk = NULL; 555 | png_chunk_iend *iend_chunk = NULL; 556 | 557 | uint32_t deflated_data_length = 0; 558 | uint32_t deflated_data_idx = 0; 559 | uint8_t *deflated_buf = NULL; 560 | 561 | uint8_t *inflated_buf = NULL; 562 | uint32_t inflated_size = 0; 563 | 564 | int idat_train_started = 0; 565 | int idat_train_finished = 0; 566 | 567 | int chunk_idx = -1; 568 | 569 | struct png_chunk *current_chunk = malloc(sizeof(struct png_chunk)); 570 | 571 | // libFuzzer example bug demo: 572 | current_chunk->chunk_data = NULL; 573 | 574 | FILE *input = fopen(filename, "rb"); 575 | 576 | // Has the file been open properly? 577 | if (!input) { 578 | goto error; 579 | } 580 | 581 | // Did we read the starting bytes properly? 582 | if (read_png_filesig(input, &filesig)) { 583 | goto error; 584 | } 585 | 586 | // Are the starting bytes correct? 587 | if (!is_png_filesig_valid(&filesig)) { 588 | goto error; 589 | } 590 | 591 | // Read all PNG chunks 592 | for (;!read_png_chunk(input, current_chunk); current_chunk = malloc(sizeof(struct png_chunk))) 593 | { 594 | chunk_idx++; 595 | // We have more chunks after IEND for some reason 596 | // IEND must be the last chunk 597 | if (iend_chunk) 598 | goto error; 599 | 600 | // All IDAT chunks need to occur in sequence 601 | // We end the IDAT sequence here if we encounter a different chunk 602 | if (idat_train_started && !is_chunk_idat(current_chunk)) { 603 | idat_train_finished = 1; 604 | idat_train_started = 0; 605 | } 606 | 607 | // The first iteration: We must have IHDR! 608 | if (!chunk_idx) { 609 | if (!is_chunk_ihdr(current_chunk)) { 610 | goto error; 611 | } 612 | } 613 | 614 | if (is_chunk_ihdr(current_chunk)) { 615 | // The second IHDR? 616 | if (ihdr_chunk) { 617 | goto error; 618 | } 619 | 620 | ihdr_chunk = format_ihdr_chunk(current_chunk); 621 | 622 | if (!ihdr_chunk) { 623 | goto error; 624 | } 625 | 626 | continue; 627 | } 628 | 629 | // PLTE chunk encountered 630 | if (is_chunk_plte(current_chunk)) { 631 | // Only 1 PLTE is allowed 632 | if (plte_chunk) { 633 | goto error; 634 | } 635 | 636 | plte_chunk = format_plte_chunk(current_chunk); 637 | 638 | if (!plte_chunk) { 639 | goto error; 640 | } 641 | 642 | continue; 643 | } 644 | 645 | // IEND chunk 646 | if (is_chunk_iend(current_chunk)) { 647 | iend_chunk = format_iend_chunk(current_chunk); 648 | 649 | if (!iend_chunk) { 650 | goto error; 651 | } 652 | 653 | continue; 654 | } 655 | 656 | // Aggregate all IDAT data together before decompressing 657 | if (is_chunk_idat(current_chunk)) { 658 | png_chunk_idat *idat_chunk; 659 | 660 | // If we have already processed a sequence of IDATs, why do we see another one here? 661 | if (idat_train_finished) { 662 | goto error; 663 | } 664 | idat_train_started = 1; 665 | 666 | idat_chunk = format_idat_chunk(current_chunk); 667 | 668 | deflated_data_length += idat_chunk->length; 669 | deflated_buf = realloc(deflated_buf, deflated_data_length); 670 | 671 | memcpy(deflated_buf + deflated_data_idx, idat_chunk->chunk_data, idat_chunk->length); 672 | 673 | if (idat_chunk->chunk_data) { 674 | free(idat_chunk->chunk_data); 675 | } 676 | 677 | free(idat_chunk); 678 | } 679 | } 680 | 681 | // After we finish looping, we should have processed IEND 682 | if (!iend_chunk) { 683 | goto error; 684 | } 685 | 686 | // Decompress IDAT data 687 | if (decompress_png_data(deflated_buf, deflated_data_length, &inflated_buf, &inflated_size)) { 688 | goto error; 689 | } 690 | 691 | // Process decompressed data 692 | *img = parse_png(ihdr_chunk, plte_chunk, inflated_buf, inflated_size); 693 | 694 | if (!*img) { 695 | goto error; 696 | } 697 | 698 | success: 699 | fclose(input); 700 | 701 | if (deflated_buf) free(deflated_buf); 702 | 703 | if (current_chunk) { 704 | if (current_chunk->chunk_data) { 705 | free(current_chunk->chunk_data); 706 | } 707 | free(current_chunk); 708 | } 709 | 710 | if (plte_chunk) free(plte_chunk); 711 | 712 | if (ihdr_chunk) free(ihdr_chunk); 713 | 714 | return 0; 715 | error: 716 | fclose(input); 717 | 718 | if (deflated_buf) free(deflated_buf); 719 | 720 | if (current_chunk) { 721 | if (current_chunk->chunk_data) { 722 | // LibFuzzer says there is a bug here! 723 | // Let's fix it. 724 | free(current_chunk->chunk_data); 725 | } 726 | free(current_chunk); 727 | } 728 | 729 | if (plte_chunk) free(plte_chunk); 730 | 731 | if (ihdr_chunk) free(ihdr_chunk); 732 | 733 | return 1; 734 | } 735 | 736 | // Store a valid file signature 737 | int store_filesig(FILE *output) 738 | { 739 | return fwrite("\211PNG\r\n\032\n", 8, 1, output) != 1; 740 | } 741 | 742 | // Create a header for a RGBA image 743 | struct png_header_ihdr fill_ihdr_rgb_alpha(struct image *img) 744 | { 745 | struct png_header_ihdr ihdr; 746 | ihdr.bit_depth = 8; 747 | ihdr.color_type = PNG_IHDR_COLOR_RGB_ALPHA; 748 | ihdr.compression = 0; 749 | ihdr.filter = 0; 750 | ihdr.interlace = 0; 751 | ihdr.height = to_big_endian(img->size_y); 752 | ihdr.width = to_big_endian(img->size_x); 753 | 754 | return ihdr; 755 | } 756 | 757 | // Create a header for a PLTE image 758 | struct png_header_ihdr fill_ihdr_plte(struct image *img) 759 | { 760 | struct png_header_ihdr ihdr; 761 | ihdr.bit_depth = 8; 762 | ihdr.color_type = PNG_IHDR_COLOR_PALETTE; 763 | ihdr.compression = 0; 764 | ihdr.filter = 0; 765 | ihdr.interlace = 0; 766 | ihdr.height = to_big_endian(img->size_y); 767 | ihdr.width = to_big_endian(img->size_x); 768 | 769 | return ihdr; 770 | } 771 | 772 | // Expects the length in the little endian format and converts it to big endian 773 | // Calculates and fills the CRC value for a chunk 774 | void fill_chunk_crc(struct png_chunk *chunk) 775 | { 776 | chunk->crc = crc((unsigned char *) &chunk->chunk_type, sizeof(chunk->chunk_type)); 777 | if (chunk->length) { 778 | chunk->crc = update_crc(chunk->crc ^ 0xffffffffL, (unsigned char *) chunk->chunk_data, chunk->length) ^ 0xffffffffL; 779 | } 780 | chunk->crc = to_big_endian(chunk->crc); 781 | chunk->length = to_big_endian(chunk->length); 782 | } 783 | 784 | // Fills the IHDR image metadata chunk 785 | png_chunk_ihdr fill_ihdr_chunk(struct png_header_ihdr *ihdr) 786 | { 787 | png_chunk_ihdr ihdr_chunk; 788 | memcpy(&ihdr_chunk.chunk_type, "IHDR", 4); 789 | ihdr_chunk.length = sizeof(*ihdr); 790 | ihdr_chunk.chunk_data = ihdr; 791 | fill_chunk_crc((struct png_chunk *) &ihdr_chunk); 792 | return ihdr_chunk; 793 | } 794 | 795 | // Chunk needs to already be in the big endian format 796 | // Writes a chunk to the file 797 | int store_png_chunk(FILE *output, struct png_chunk *chunk) 798 | { 799 | fwrite(&chunk->length, 4, 1, output); 800 | fwrite(&chunk->chunk_type, 4, 1, output); 801 | fwrite(chunk->chunk_data, to_little_endian(chunk->length), 1, output); 802 | fwrite(&chunk->crc, 4, 1, output); 803 | } 804 | 805 | // Writes a RGBA metadata chunk 806 | int store_ihdr_rgb_alpha(FILE *output, struct image *img) 807 | { 808 | struct png_header_ihdr ihdr = fill_ihdr_rgb_alpha(img); 809 | png_chunk_ihdr ihdr_chunk = fill_ihdr_chunk(&ihdr); 810 | store_png_chunk(output, (struct png_chunk *) &ihdr_chunk); 811 | return 0; 812 | } 813 | 814 | // Writes a palette metadata chunk 815 | int store_ihdr_plte(FILE *output, struct image *img) 816 | { 817 | struct png_header_ihdr ihdr = fill_ihdr_plte(img); 818 | png_chunk_ihdr ihdr_chunk = fill_ihdr_chunk(&ihdr); 819 | store_png_chunk(output, (struct png_chunk *) &ihdr_chunk); 820 | return 0; 821 | } 822 | 823 | // Compresses image data using deflate 824 | int compress_png_data(uint8_t *decompressed_data, uint32_t decompressed_length, uint8_t **compressed_data, uint32_t *compressed_length) 825 | { 826 | int ret, flush; 827 | unsigned have; 828 | z_stream strm; 829 | unsigned char out[PNG_OUTPUT_CHUNK_SIZE]; 830 | int level = 1; 831 | 832 | *compressed_data = NULL; 833 | *compressed_length = 0; 834 | /* allocate deflate state */ 835 | strm.zalloc = Z_NULL; 836 | strm.zfree = Z_NULL; 837 | strm.opaque = Z_NULL; 838 | ret = deflateInit(&strm, level); 839 | if (ret != Z_OK) 840 | return ret; 841 | 842 | /* compress until end of file */ 843 | 844 | strm.avail_in = decompressed_length; 845 | flush = Z_FINISH; 846 | strm.next_in = decompressed_data; 847 | 848 | /* run deflate() on input until output buffer not full, finish 849 | compression if all of source has been read in */ 850 | do { 851 | strm.avail_out = PNG_OUTPUT_CHUNK_SIZE; 852 | strm.next_out = out; 853 | ret = deflate(&strm, flush); /* no bad return value */ 854 | if (ret == Z_STREAM_ERROR) { 855 | goto error; 856 | } 857 | have = PNG_OUTPUT_CHUNK_SIZE - strm.avail_out; 858 | 859 | *compressed_data = realloc(*compressed_data, *compressed_length + have); 860 | memcpy(*compressed_data + *compressed_length, out, have); 861 | *compressed_length += have; 862 | 863 | } while (strm.avail_out == 0); 864 | if (strm.avail_in != 0) { 865 | goto error; 866 | } 867 | 868 | if (ret != Z_STREAM_END) { 869 | goto error; 870 | } 871 | 872 | /* clean up and return */ 873 | (void)deflateEnd(&strm); 874 | return 0; 875 | 876 | error: 877 | if (*compressed_data) { 878 | free(*compressed_data); 879 | } 880 | } 881 | 882 | // Fills IDAT with compressed data 883 | png_chunk_idat fill_idat_chunk(uint8_t *data, uint32_t length) 884 | { 885 | png_chunk_idat idat; 886 | memcpy(&idat.chunk_type, "IDAT", 4); 887 | idat.chunk_data = data; 888 | idat.length = length; 889 | fill_chunk_crc(&idat); 890 | return idat; 891 | } 892 | 893 | // Writes an IDAT chunk from image data to a file 894 | int store_idat_rgb_alpha(FILE *output, struct image *img) 895 | { 896 | uint32_t non_compressed_length = img->size_y * (1 + img->size_x * 4); 897 | uint8_t *non_compressed_buf = malloc(non_compressed_length); 898 | 899 | for (uint32_t id_y = 0; id_y < img->size_y; id_y++) { 900 | non_compressed_buf[id_y * (1 + img->size_x * 4)] = 0; 901 | for (uint32_t id_x = 0; id_x < img->size_x; id_x++) { 902 | uint32_t id_pix_buf = id_y * (1 + img->size_x * 4) + 1 + 4*id_x; 903 | uint32_t id_pix = id_y * img->size_x + id_x; 904 | 905 | non_compressed_buf[id_pix_buf] = img->px[id_pix].red; 906 | non_compressed_buf[id_pix_buf + 1] = img->px[id_pix].green; 907 | non_compressed_buf[id_pix_buf + 2] = img->px[id_pix].blue; 908 | non_compressed_buf[id_pix_buf + 3] = img->px[id_pix].alpha; 909 | } 910 | } 911 | 912 | uint8_t * compressed_data_buf; 913 | uint32_t compressed_length; 914 | 915 | compress_png_data(non_compressed_buf, non_compressed_length, &compressed_data_buf, &compressed_length); 916 | 917 | png_chunk_idat idat = fill_idat_chunk(compressed_data_buf, compressed_length); 918 | store_png_chunk(output, (struct png_chunk *) &idat); 919 | return 0; 920 | } 921 | 922 | // Finds a color in a palette and returns its index 923 | int find_color(struct pixel *palette, uint32_t palette_length, struct pixel *target) 924 | { 925 | for (int idx = 0; idx < palette_length; idx++) { 926 | if (palette[idx].red == target->red && 927 | palette[idx].green == target->green && 928 | palette[idx].blue == target->blue) { 929 | return idx; 930 | } 931 | } 932 | 933 | return -1; 934 | } 935 | 936 | // Writes an IDAT chunk for a palette image 937 | int store_idat_plte(FILE *output, struct image *img, struct pixel *palette, uint32_t palette_length) 938 | { 939 | uint32_t non_compressed_length = img->size_y * (1 + img->size_x); 940 | uint8_t *non_compressed_buf = malloc(non_compressed_length); 941 | 942 | for (uint32_t id_y = 0; id_y < img->size_y; id_y++) { 943 | non_compressed_buf[id_y * (1 + img->size_x)] = 0; 944 | for (uint32_t id_x = 0; id_x < img->size_x; id_x++) { 945 | uint32_t id_pix_buf = id_y * (1 + img->size_x) + 1 + id_x; 946 | uint32_t id_pix = id_y * img->size_x + id_x; 947 | int code = find_color(palette, palette_length, &img->px[id_pix]); 948 | if (code < 0) { 949 | goto error; 950 | } 951 | non_compressed_buf[id_pix_buf] = code; 952 | 953 | } 954 | } 955 | 956 | uint8_t * compressed_data_buf = NULL; 957 | uint32_t compressed_length; 958 | 959 | compress_png_data(non_compressed_buf, non_compressed_length, &compressed_data_buf, &compressed_length); 960 | 961 | png_chunk_idat idat = fill_idat_chunk(compressed_data_buf, compressed_length); 962 | store_png_chunk(output, (struct png_chunk *) &idat); 963 | return 0; 964 | 965 | error: 966 | if (compressed_data_buf) { 967 | free(compressed_data_buf); 968 | } 969 | return 1; 970 | } 971 | 972 | // Writes the first two chunks for a RGBA image 973 | int store_png_rgb_alpha(FILE *output, struct image *img) 974 | { 975 | store_ihdr_rgb_alpha(output, img); 976 | store_idat_rgb_alpha(output, img); 977 | } 978 | 979 | // Creates a PLTE chunk from PLTE entries (colors) 980 | png_chunk_plte fill_plte_chunk(struct plte_entry * plte_data, uint32_t color_count) 981 | { 982 | png_chunk_plte plte; 983 | 984 | memcpy(&plte.chunk_type, "PLTE", 4); 985 | plte.chunk_data = plte_data; 986 | plte.length = 3 * color_count; 987 | fill_chunk_crc((struct png_chunk *) &plte); 988 | return plte; 989 | } 990 | 991 | // Writes a palette to the file 992 | int store_plte(FILE *output, struct pixel *palette, uint32_t palette_length) 993 | { 994 | struct plte_entry plte_data[256]; 995 | 996 | for (int idx = 0; idx < palette_length; idx++) { 997 | plte_data[idx].red = palette[idx].red; 998 | plte_data[idx].green = palette[idx].green; 999 | plte_data[idx].blue = palette[idx].blue; 1000 | } 1001 | 1002 | png_chunk_plte plte_chunk = fill_plte_chunk(plte_data, palette_length); 1003 | store_png_chunk(output, (struct png_chunk *) &plte_chunk); 1004 | } 1005 | 1006 | // Writes the first 3 chunks for a palette Y0L0 PNG image 1007 | int store_png_palette(FILE *output, struct image *img, struct pixel *palette, uint32_t palette_length) 1008 | { 1009 | store_ihdr_plte(output, img); 1010 | store_plte(output, palette, palette_length); 1011 | store_idat_plte(output, img, palette, palette_length); 1012 | } 1013 | 1014 | // Stores an IEND chunk to a file 1015 | int store_png_chunk_iend(FILE *output) 1016 | { 1017 | png_chunk_iend iend; 1018 | memcpy(&iend.chunk_type,"IEND", 4); 1019 | iend.length = 0; 1020 | fill_chunk_crc(&iend); 1021 | store_png_chunk(output, &iend); 1022 | } 1023 | 1024 | // Store a Y0L0 PNG to a file. Provide an array of pixels if you want to use a palette format. 1025 | // If it is NULL, RGBA is selected. 1026 | int store_png(const char *filename, struct image *img, struct pixel *palette, uint8_t palette_length) 1027 | { 1028 | int result = 0; 1029 | FILE *output = fopen(filename, "wb"); 1030 | 1031 | store_filesig(output); 1032 | 1033 | if (palette) { 1034 | store_png_palette(output, img, palette, palette_length); 1035 | } else { 1036 | store_png_rgb_alpha(output, img); 1037 | } 1038 | 1039 | store_png_chunk_iend(output); 1040 | fclose(output); 1041 | return 0; 1042 | } 1043 | -------------------------------------------------------------------------------- /playground/src/pngparser.h: -------------------------------------------------------------------------------- 1 | #ifndef PNG_PARSER_H 2 | #define PNG_PARSER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | /* Each pixel consists of four channels: RGBA 10 | * 0 denotes black, while 0xff represents the maximum value the color can take 11 | * 12 | * Alpha channel is a separate channel that denotes transparency. 13 | */ 14 | struct pixel { 15 | uint8_t red; 16 | uint8_t green; 17 | uint8_t blue; 18 | uint8_t alpha; 19 | }; 20 | 21 | /* The image comprises its dimensions and its pixels 22 | * Pixels are stored as an 1D array. 23 | * 24 | * The image: 25 | * 26 | * ABC 27 | * DEF 28 | * GHI 29 | * 30 | * is linearized as: ABCDEFGHI 31 | * 32 | * Pixel coordinates can take values [0, size_x - 1] and [0, size_y - 1] 33 | * 34 | * We can access the pixel [y][x] by accessing the pixel at the index [y * size_x + x] 35 | * 36 | * However, it is easier to use a C99 feature VLA (variable length arrays) 37 | * We will cast img->px into "struct image (*)[img->width_x]" 38 | * 39 | * This will dereference the correct pixel, but the code using it will neeed to be in 40 | * a scope of its own (i.e. just add curly braces around it) if we want to use unstructured control flow 41 | * (i.e. goto statements). 42 | * 43 | */ 44 | 45 | struct image { 46 | uint16_t size_x; 47 | uint16_t size_y; 48 | struct pixel *px; 49 | }; 50 | 51 | /* load_png loads a png file denoted by filename and writes a pointer to struct image 52 | * into the memory pointed to by img. 53 | * 54 | * Please remember to free both img and img->px after you are finished using the image. 55 | * Please also remember to do it in the correct order :) 56 | * 57 | * This function returns 0 on success and a non-zero value on failure. 58 | */ 59 | 60 | 61 | int load_png(const char *filename, struct image **img); 62 | 63 | 64 | /* store_png stores an image pointed to by img into a file whose name is passed as a filename argument 65 | * If the argument palette is NULL, the file will be stored in the RGBA format. 66 | * Otherwise, we will try to represent the image using the provided palette pixels. If this cannot be 67 | * accomplished, the function will fail. 68 | * 69 | * This function returns 0 on success and a non-zero value on failure. 70 | */ 71 | 72 | 73 | int store_png(const char *filename, struct image *img, struct pixel *palette, uint8_t palette_length); 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /playground/src/storepng.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "pngparser.h" 7 | 8 | #define SIZE_X 10 9 | #define SIZE_Y 10 10 | #define SIZE_PALETTE 10 11 | // Load stub 12 | // 13 | int main(int argc, char* argv[]) 14 | { 15 | // Set up the image structures 16 | struct image img; 17 | struct pixel pixels[SIZE_Y][SIZE_X]; 18 | struct pixel palette[SIZE_PALETTE] = {0}; 19 | char randomByte; 20 | 21 | img.size_x = SIZE_X; 22 | img.size_y = SIZE_Y; 23 | 24 | img.px = (struct pixel*) pixels; 25 | 26 | if (argc != 2) { 27 | return 1; 28 | } 29 | 30 | // The output file will be generated based on the PID 31 | char outputFilename[256]; 32 | snprintf(outputFilename, sizeof(outputFilename), "storepng-%d", getpid()); 33 | 34 | // Input filename is passed as the first argument 35 | const char* filename = argv[1]; 36 | 37 | FILE *f = fopen(filename, "rb"); 38 | if (!f) 39 | return 1; 40 | 41 | fseek(f, 0, SEEK_END); // seek to end of file 42 | size_t size = ftell(f); // get current file pointer 43 | 44 | // We need enough data to fill the image, palette and generate a random byte 45 | if (size < 1000) 46 | return 1; 47 | 48 | // Reset file 49 | fseek(f, 0, SEEK_SET); 50 | 51 | // Read and fill random data 52 | fread(pixels, sizeof(pixels), 1, f); 53 | fread(palette, sizeof(palette), 1, f); 54 | fread(&randomByte, sizeof(randomByte), 1, f); 55 | fclose(f); 56 | 57 | // Call without or with palette 58 | if (randomByte & 0x01) { 59 | store_png(outputFilename, &img, NULL, 0); 60 | } else { 61 | store_png(outputFilename, &img, palette, SIZE_PALETTE); 62 | } 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Display all commands on the command line so users can see 4 | # what we're doing and why 5 | set -ex 6 | 7 | # variables (how to find things) go here: 8 | AFLPP=$(pwd)/aflplusplus 9 | DISTRO=`awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"'` 10 | 11 | if [[ "Ubuntu" == "$DISTRO" ]]; then 12 | # package install 13 | sudo apt-get install -y make autoconf automake libtool shtool wget curl \ 14 | xz-utils gcc g++ cmake \ 15 | ninja-build zlib1g make python \ 16 | build-essential git ca-certificates \ 17 | tar gzip vim libelf-dev libelf1 libiberty-dev \ 18 | libboost-all-dev python3-pip python3-venv \ 19 | libpcap-dev libbz2-dev liblzo2-dev liblzma-dev liblz4-dev libz-dev \ 20 | libxml2-dev libssl-dev libacl1-dev libattr1-dev zip \ 21 | unzip libtool-bin bison flex libpixman-1-dev 22 | fi 23 | 24 | # trigger download of retrowrite and other submodules 25 | git submodule update --init --recursive 26 | 27 | # Tune system to work with AFL++ 28 | echo 'core' | sudo tee /proc/sys/kernel/core_pattern 29 | if [[ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]]; then 30 | pushd /sys/devices/system/cpu 31 | echo performance | sudo tee cpu*/cpufreq/scaling_governor 32 | popd 33 | fi 34 | 35 | 36 | # build AFL++: 37 | echo "[*] Cloning AFL++ into $AFLPP" 38 | if [[ ! -d $AFLPP ]]; then 39 | git clone https://github.com/AFLplusplus/AFLplusplus $AFLPP 40 | fi 41 | 42 | pushd $AFLPP 43 | git checkout "2.66c" 44 | 45 | echo "[*] Building AFL++" 46 | export AFL_USE_ASAN=1 47 | make ASAN_BUILD=1 || exit 1 48 | echo "[*] Building AFL++ qemu_mode support" 49 | pushd $AFLPP/qemu_mode 50 | ./build_qemu_support.sh || exit 1 51 | popd && popd 52 | 53 | echo "[*] Building Retrowrite using Retrowrite's helper script" 54 | pushd retrowrite 55 | ./setup.sh 56 | popd 57 | -------------------------------------------------------------------------------- /tools: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # locate this script: 4 | SCRIPT="${BASH_SOURCE[0]}" 5 | SCRIPTDIR="" 6 | if [ -z "$SCRIPT" ]; then 7 | SCRIPT=$(readlink -f $0) 8 | SCRIPTDIR=$(dirname $SCRIPT) 9 | else 10 | SCRIPTDIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 ; pwd -P )" 11 | fi 12 | if ! test -f $SCRIPTDIR/aflplusplus/afl-fuzz; then 13 | echo "Please run the setup.sh script first." 14 | fi 15 | if ! test -f "$SCRIPTDIR/retrowrite/retro/bin/activate"; then 16 | echo "Please run the setup.sh script first." 17 | fi 18 | 19 | 20 | source "$SCRIPTDIR/retrowrite/retro/bin/activate" 21 | 22 | # ensure AFL can find itself as well: 23 | export AFL_PATH="$SCRIPTDIR/aflplusplus" 24 | 25 | # Add AFL to the path: 26 | export PATH="$PATH:$SCRIPTDIR/aflplusplus" 27 | 28 | # some useful aliases: 29 | alias playg="cd $SCRIPTDIR/playground" 30 | alias tutorial="cd $SCRIPTDIR" 31 | alias phase1="cd $SCRIPTDIR/01-native_fuzzing" 32 | alias phase2="cd $SCRIPTDIR/02-coverage_fuzzing" 33 | alias phase3="cd $SCRIPTDIR/03-coverage_sanitized_fuzzing" 34 | 35 | function afl-configure-system() { 36 | # Tune system to work with AFL++ 37 | echo 'core' | sudo tee /proc/sys/kernel/core_pattern 38 | if [[ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]]; then 39 | pushd /sys/devices/system/cpu 40 | echo performance | sudo tee cpu*/cpufreq/scaling_governor 41 | popd 42 | fi 43 | } 44 | --------------------------------------------------------------------------------