diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | qemu_mode/libcompcov/Makefile | 4 | ||||
-rw-r--r-- | qemu_mode/unsigaction/Makefile | 2 | ||||
-rwxr-xr-x | test/test.sh | 70 | ||||
-rw-r--r-- | unicorn_mode/README.md | 36 | ||||
-rwxr-xr-x | unicorn_mode/build_unicorn_support.sh | 97 | ||||
-rwxr-xr-x | unicorn_mode/samples/compcov_x64/compcov_target.elf | bin | 13200 -> 13896 bytes | |||
-rw-r--r-- | unicorn_mode/samples/compcov_x64/compcov_test_harness.py | 63 | ||||
-rw-r--r-- | unicorn_mode/samples/simple/simple_test_harness.py | 69 | ||||
-rw-r--r-- | unicorn_mode/samples/simple/simple_test_harness_alt.py | 179 | ||||
m--------- | unicorn_mode/unicorn | 0 |
13 files changed, 321 insertions, 214 deletions
diff --git a/.gitignore b/.gitignore index ac3a653b..1e653a08 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,4 @@ afl-whatsup.8 qemu_mode/libcompcov/compcovtest as qemu_mode/qemu-* -unicorn_mode/unicorn -unicorn_mode/unicorn-* -unicorn_mode/*.tar.gz +core\.* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..05bd3b04 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "unicorn_mode/unicorn"] + path = unicorn_mode/unicorn + url = https://github.com/vanhauser-thc/unicorn.git diff --git a/Makefile b/Makefile index 880dfc67..1f75e45d 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ help: @echo "distrib: everything (for both binary-only and source code fuzzing)" @echo "man: creates simple man pages from the help option of the programs" @echo "install: installs everything you have compiled with the build option above" - @echo "clean: cleans everything. for qemu_mode and unicorn_mode it means it deletes all downloads as well" + @echo "clean: cleans everything. for qemu_mode it means it deletes all downloads as well" @echo "code-format: format the code, do this before you commit and send a PR please!" @echo "tests: this runs the test framework. It is more catered for the developers, but if you run into problems this helps pinpointing the problem" @echo "document: creates afl-fuzz-document which will only do one run and save all manipulated inputs into out/queue/mutations" @@ -278,7 +278,6 @@ code-format: ./.custom-format.py -i qemu_mode/libcompcov/*.c ./.custom-format.py -i qemu_mode/libcompcov/*.cc ./.custom-format.py -i qemu_mode/libcompcov/*.h - ./.custom-format.py -i unicorn_mode/patches/*.h ./.custom-format.py -i *.h ./.custom-format.py -i *.c @@ -311,8 +310,8 @@ all_done: test_build .NOTPARALLEL: clean clean: - rm -f $(PROGS) libradamsa.so afl-as as afl-g++ afl-clang afl-clang++ *.o src/*.o *~ a.out core core.[1-9][0-9]* *.stackdump .test .test1 .test2 test-instr .test-instr0 .test-instr1 qemu_mode/qemu-3.1.1.tar.xz afl-qemu-trace afl-gcc-fast afl-gcc-pass.so afl-gcc-rt.o afl-g++-fast *.so unicorn_mode/24f55a7973278f20f0de21b904851d99d4716263.tar.gz *.8 - rm -rf out_dir qemu_mode/qemu-3.1.1 unicorn_mode/unicorn *.dSYM */*.dSYM + rm -f $(PROGS) libradamsa.so afl-as as afl-g++ afl-clang afl-clang++ *.o src/*.o *~ a.out core core.[1-9][0-9]* *.stackdump .test .test1 .test2 test-instr .test-instr0 .test-instr1 qemu_mode/qemu-3.1.1.tar.xz afl-qemu-trace afl-gcc-fast afl-gcc-pass.so afl-gcc-rt.o afl-g++-fast *.so *.8 + rm -rf out_dir qemu_mode/qemu-3.1.1 *.dSYM */*.dSYM -$(MAKE) -C llvm_mode clean -$(MAKE) -C gcc_plugin clean $(MAKE) -C libdislocator clean @@ -320,6 +319,7 @@ clean: $(MAKE) -C qemu_mode/unsigaction clean $(MAKE) -C qemu_mode/libcompcov clean $(MAKE) -C src/third_party/libradamsa/ clean + $(MAKE) -C unicorn_mode/unicorn clean distrib: all radamsa -$(MAKE) -C llvm_mode diff --git a/qemu_mode/libcompcov/Makefile b/qemu_mode/libcompcov/Makefile index d078ae06..e827cbd8 100644 --- a/qemu_mode/libcompcov/Makefile +++ b/qemu_mode/libcompcov/Makefile @@ -22,7 +22,7 @@ CFLAGS ?= -O3 -funroll-loops -I ../../include/ CFLAGS += -Wall -Wno-unused-result -D_FORTIFY_SOURCE=2 -g -Wno-pointer-sign LDFLAGS += -ldl -all: libcompcov.so compcovtest +all: libcompcov.so libcompcov.so: libcompcov.so.c ../../config.h $(CC) $(CFLAGS) -shared -fPIC $< -o ../../$@ $(LDFLAGS) @@ -34,7 +34,7 @@ clean: rm -f ../../libcompcov.so compcovtest compcovtest: compcovtest.cc - $(CXX) $< -o $@ + $(CXX) -std=c++11 $< -o $@ install: all install -m 755 ../../libcompcov.so $${DESTDIR}$(HELPER_PATH) diff --git a/qemu_mode/unsigaction/Makefile b/qemu_mode/unsigaction/Makefile index 9aa96330..02dc2c79 100644 --- a/qemu_mode/unsigaction/Makefile +++ b/qemu_mode/unsigaction/Makefile @@ -18,7 +18,7 @@ ifndef AFL_NO_X86 all: lib_i386 lib_amd64 lib_i386: - $(CC) -m32 -fPIC -shared unsigaction.c -o unsigaction32.so + $(CC) -m32 -fPIC -shared unsigaction.c -o unsigaction32.so || echo "Cannot build unsigation32" lib_amd64: $(CC) -fPIC -shared unsigaction.c -o unsigaction64.so diff --git a/test/test.sh b/test/test.sh index ee497298..a75f991d 100755 --- a/test/test.sh +++ b/test/test.sh @@ -550,44 +550,50 @@ test -d ../unicorn_mode/unicorn && { test -e ../unicorn_mode/samples/simple/simple_target.bin -a -e ../unicorn_mode/samples/compcov_x64/compcov_target.bin && { { # travis workaround - PY=`which python2.7` - test "$PY" = "/opt/pyenv/shims/python2.7" -a -x /usr/bin/python2.7 && PY=/usr/bin/python2.7 + PY=`which python` + test "$PY" = "/opt/pyenv/shims/python" -a -x /usr/bin/python && PY=/usr/bin/python mkdir -p in echo 0 > in/in $ECHO "$GREY[*] Using python binary $PY" - $ECHO "$GREY[*] running afl-fuzz for unicorn_mode, this will take approx 25 seconds" + if ! $PY -c 'import unicornafl' 2> /dev/null ; then + $ECHO "$YELLOW[-] we cannot test unicorn_mode because it is not present" + else { - ../afl-fuzz -V25 -U -i in -o out -d -- "$PY" ../unicorn_mode/samples/simple/simple_test_harness.py @@ >>errors 2>&1 - } >>errors 2>&1 - test -n "$( ls out/queue/id:000002* 2> /dev/null )" && { - $ECHO "$GREEN[+] afl-fuzz is working correctly with unicorn_mode" - } || { - echo CUT------------------------------------------------------------------CUT - cat errors - echo CUT------------------------------------------------------------------CUT - $ECHO "$RED[!] afl-fuzz is not working correctly with unicorn_mode" - CODE=1 - } - rm -f errors + $ECHO "$GREY[*] running afl-fuzz for unicorn_mode, this will take approx 25 seconds" + { + ../afl-fuzz -V25 -U -i in -o out -d -- "$PY" ../unicorn_mode/samples/simple/simple_test_harness.py @@ >>errors 2>&1 + } >>errors 2>&1 + test -n "$( ls out/queue/id:000002* 2> /dev/null )" && { + $ECHO "$GREEN[+] afl-fuzz is working correctly with unicorn_mode" + } || { + echo CUT------------------------------------------------------------------CUT + cat errors + echo CUT------------------------------------------------------------------CUT + $ECHO "$RED[!] afl-fuzz is not working correctly with unicorn_mode" + CODE=1 + } + rm -f errors - printf '\x01\x01' > in/in - # This seed is close to the first byte of the comparison. - # If CompCov works, a new tuple will appear in the map => new input in queue - $ECHO "$GREY[*] running afl-fuzz for unicorn_mode compcov, this will take approx 35 seconds" - { - export AFL_COMPCOV_LEVEL=2 - ../afl-fuzz -V35 -U -i in -o out -d -- "$PY" ../unicorn_mode/samples/compcov_x64/compcov_test_harness.py @@ >>errors 2>&1 - } >>errors 2>&1 - test -n "$( ls out/queue/id:000001* 2> /dev/null )" && { - $ECHO "$GREEN[+] afl-fuzz is working correctly with unicorn_mode compcov" - } || { - echo CUT------------------------------------------------------------------CUT - cat errors - echo CUT------------------------------------------------------------------CUT - $ECHO "$RED[!] afl-fuzz is not working correctly with unicorn_mode compcov" - CODE=1 + printf '\x01\x01' > in/in + # This seed is close to the first byte of the comparison. + # If CompCov works, a new tuple will appear in the map => new input in queue + $ECHO "$GREY[*] running afl-fuzz for unicorn_mode compcov, this will take approx 35 seconds" + { + export AFL_COMPCOV_LEVEL=2 + ../afl-fuzz -V35 -U -i in -o out -d -- "$PY" ../unicorn_mode/samples/compcov_x64/compcov_test_harness.py @@ >>errors 2>&1 + } >>errors 2>&1 + test -n "$( ls out/queue/id:000001* 2> /dev/null )" && { + $ECHO "$GREEN[+] afl-fuzz is working correctly with unicorn_mode compcov" + } || { + echo CUT------------------------------------------------------------------CUT + cat errors + echo CUT------------------------------------------------------------------CUT + $ECHO "$RED[!] afl-fuzz is not working correctly with unicorn_mode compcov" + CODE=1 + } + rm -rf in out errors } - rm -rf in out errors + fi } } || { $ECHO "$RED[-] missing sample binaries in unicorn_mode/samples/ - what is going on??" diff --git a/unicorn_mode/README.md b/unicorn_mode/README.md index 8f381b59..904ea624 100644 --- a/unicorn_mode/README.md +++ b/unicorn_mode/README.md @@ -20,7 +20,7 @@ but at least we're able to use AFL on these binaries, right? ## 2) How to use -Requirements: you need an installed python2 environment. +Requirements: you need an installed python environment. ### Building AFL's Unicorn Mode @@ -31,11 +31,8 @@ features: $ cd unicorn_mode $ ./build_unicorn_support.sh -NOTE: This script downloads a Unicorn Engine commit that has been tested -and is stable-ish from the Unicorn github page. If you are offline, you'll need -to hack up this script a little bit and supply your own copy of Unicorn's latest -stable release. It's not very hard, just check out the beginning of the -build_unicorn_support.sh script and adjust as necessary. +NOTE: This script checks out a Unicorn Engine fork as submodule that has been tested +and is stable-ish, based on the unicorn engine master. Building Unicorn will take a little bit (~5-10 minutes). Once it completes it automatically compiles a sample application and verify that it works. @@ -51,11 +48,10 @@ To really use unicorn-mode effectively you need to prepare the following: + Quality/speed of results will depend greatly on quality of starting samples + See AFL's guidance on how to create a sample corpus - * Unicorn-based test harness which: + * Unicornafl-based test harness which: + Adds memory map regions + Loads binary code into memory - + Emulates at least one instruction* - + Yeah, this is lame. See 'Gotchas' section below for more info + + Calls uc.afl_fuzz() / uc.afl_start_forkserver + Loads and verifies data to fuzz from a command-line specified file + AFL will provide mutated inputs by changing the file passed to the test harness @@ -103,16 +99,20 @@ for the x86, x86_64 and ARM targets. ## 4) Gotchas, feedback, bugs -To make sure that AFL's fork server starts up correctly the Unicorn test -harness script must emulate at least one instruction before loading the -data that will be fuzzed from the input file. It doesn't matter what the -instruction is, nor if it is valid. This is an artifact of how the fork-server -is started and could likely be fixed with some clever re-arranging of the -patches applied to Unicorn. +Running the build script builds Unicornafl and its python bindings and installs +them on your system. +This installation will leave any existing Unicorn installations untouched. +If you want to use unicornafl instead of unicorn in a script, +replace all `unicorn` imports with `unicornafl` inputs, everything else should "just work". +If you use 3rd party code depending on unicorn, you can use unicornafl monkeypatching: +Before importing anything that depends on unicorn, do: -Running the build script builds Unicorn and its python bindings and installs -them on your system. This installation will supersede any existing Unicorn -installation with the patched afl-unicorn version. +```python +import unicornafl +unicornafl.monkeypatch() +``` + +This will replace all unicorn imports with unicornafl inputs. Refer to the unicorn_mode/samples/arm_example/arm_tester.c for an example of how to do this properly! If you don't get this right, AFL will not diff --git a/unicorn_mode/build_unicorn_support.sh b/unicorn_mode/build_unicorn_support.sh index e987e15a..707e47bb 100755 --- a/unicorn_mode/build_unicorn_support.sh +++ b/unicorn_mode/build_unicorn_support.sh @@ -33,9 +33,6 @@ # You must make sure that Unicorn Engine is not already installed before # running this script. If it is, please uninstall it first. -UNICORN_URL="https://github.com/unicorn-engine/unicorn/archive/24f55a7973278f20f0de21b904851d99d4716263.tar.gz" -UNICORN_SHA384="7180d47ca52c99b4c073a343a2ead91da1a829fdc3809f3ceada5d872e162962eab98873a8bc7971449d5f34f41fdb93" - echo "=================================================" echo "Unicorn-AFL build script" echo "=================================================" @@ -52,7 +49,7 @@ if [ ! "$PLT" = "Linux" ] && [ ! "$PLT" = "Darwin" ] && [ ! "$PLT" = "FreeBSD" ] fi -if [ ! -f "patches/afl-unicorn-cpu-inl.h" -o ! -f "../config.h" ]; then +if [ ! -f "../config.h" ]; then echo "[-] Error: key files not found - wrong working directory?" exit 1 @@ -66,40 +63,30 @@ if [ ! -f "../afl-showmap" ]; then fi +PYTHONBIN=python +MAKECMD=make +EASY_INSTALL='easy_install' +TARCMD=tar + if [ "$PLT" = "Linux" ]; then - CKSUMCMD='sha384sum --' - PYTHONBIN=python2 - MAKECMD=make CORES=`nproc` - TARCMD=tar - EASY_INSTALL=easy_install fi if [ "$PLT" = "Darwin" ]; then - CKSUMCMD="shasum -a 384" - PYTHONBIN=python2.7 - MAKECMD=make CORES=`sysctl hw.ncpu | cut -d' ' -f2` TARCMD=tar - EASY_INSTALL=easy_install-2.7 fi if [ "$PLT" = "FreeBSD" ]; then - CKSUMCMD="sha384 -q" - PYTHONBIN=python2.7 MAKECMD=gmake CORES=`sysctl hw.ncpu | cut -d' ' -f2` TARCMD=gtar - EASY_INSTALL=easy_install-2.7 fi if [ "$PLT" = "NetBSD" ] || [ "$PLT" = "OpenBSD" ]; then - CKSUMCMD="cksum -a sha384 -q" - PYTHONBIN=python2.7 MAKECMD=gmake CORES=`sysctl hw.ncpu | cut -d'=' -f2` TARCMD=gtar - EASY_INSTALL=easy_install-2.7 fi for i in wget $PYTHONBIN automake autoconf $MAKECMD $TARCMD; do @@ -108,7 +95,7 @@ for i in wget $PYTHONBIN automake autoconf $MAKECMD $TARCMD; do if [ "$T" = "" ]; then - echo "[-] Error: '$i' not found. Run 'sudo apt-get install $i'." + echo "[-] Error: '$i' not found. Run 'sudo apt-get install $i' or similar." exit 1 fi @@ -136,51 +123,13 @@ fi echo "[+] All checks passed!" -ARCHIVE="`basename -- "$UNICORN_URL"`" - -CKSUM=`$CKSUMCMD "$ARCHIVE" 2>/dev/null | cut -d' ' -f1` - -if [ ! "$CKSUM" = "$UNICORN_SHA384" ]; then - - echo "[*] Downloading Unicorn v1.0.1 from the web..." - rm -f "$ARCHIVE" - OK= - while [ -z "$OK" ]; do - wget -c -O "$ARCHIVE" -- "$UNICORN_URL" && OK=1 - done - - CKSUM=`$CKSUMCMD "$ARCHIVE" 2>/dev/null | cut -d' ' -f1` - -fi - -if [ "$CKSUM" = "$UNICORN_SHA384" ]; then - - echo "[+] Cryptographic signature on $ARCHIVE checks out." - -else - - echo "[-] Error: signature mismatch on $ARCHIVE (perhaps download error?)." - exit 1 - -fi - -echo "[*] Uncompressing archive (this will take a while)..." - -rm -rf "unicorn" || exit 1 -mkdir "unicorn" || exit 1 -$TARCMD xzf "$ARCHIVE" -C ./unicorn --strip-components=1 || exit 1 - -echo "[+] Unpacking successful." - -#rm -rf "$ARCHIVE" || exit 1 - -echo "[*] Applying patches..." - -cp patches/*.h unicorn || exit 1 -patch -p1 --directory unicorn < patches/patches.diff || exit 1 -patch -p1 --directory unicorn < patches/compcov.diff || exit 1 +echo "[*] Making sure unicornafl is checked out" +git submodule init || exit 1 +git submodule update || exit 1 +echo "[+] Got unicornafl." -echo "[+] Patching done." +echo "[*] making sure config.h matches" +cp "../config.h" "./unicorn/" || exit 1 echo "[*] Configuring Unicorn build..." @@ -188,8 +137,9 @@ cd "unicorn" || exit 1 echo "[+] Configuration complete." -echo "[*] Attempting to build Unicorn (fingers crossed!)..." +echo "[*] Attempting to build unicornafl (fingers crossed!)..." +$MAKECMD clean # make doesn't seem to work for unicorn UNICORN_QEMU_FLAGS="--python=$PYTHONBIN" $MAKECMD -j$CORES || exit 1 echo "[+] Build process successful!" @@ -197,20 +147,21 @@ echo "[+] Build process successful!" echo "[*] Installing Unicorn python bindings..." cd bindings/python || exit 1 if [ -z "$VIRTUAL_ENV" ]; then - echo "[*] Info: Installing python unicorn using --user" - $PYTHONBIN setup.py install --user --prefix=|| exit 1 + echo "[*] Info: Installing python unicornafl using --user" + $PYTHONBIN setup.py install --user --force --prefix=|| exit 1 else - echo "[*] Info: Installing python unicorn to virtualenv: $VIRTUAL_ENV" - $PYTHONBIN setup.py install || exit 1 + echo "[*] Info: Installing python unicornafl to virtualenv: $VIRTUAL_ENV" + $PYTHONBIN setup.py install --force || exit 1 fi -export LIBUNICORN_PATH='$(pwd)' # in theory, this allows to switch between afl-unicorn and unicorn so files. +# export LIBUNICORN_PATH='$(pwd)' # in theory, this allows to switch between afl-unicorn and unicorn so files. +echo '[*] If needed, you can (re)install the bindigns from `./unicorn/bindings/python` using `python setup.py install`' cd ../../ || exit 1 -echo "[+] Unicorn bindings installed successfully." +echo "[*] Unicornafl bindings installed successfully." # Compile the sample, run it, verify that it works! -echo "[*] Testing unicorn-mode functionality by running a sample test harness under afl-unicorn" +echo "[*] Testing unicornafl python functionality by running a sample test harness" cd ../samples/simple || exit 1 @@ -222,6 +173,8 @@ if [ -s .test-instr0 ] then echo "[+] Instrumentation tests passed. " + echo '[+] Make sure to adapt older scripts to `import unicornafl` and use `uc.afl_forkserver_start`' + echo ' or `uc.afl_fuzz` to kick off fuzzing.' echo "[+] All set, you can now use Unicorn mode (-U) in afl-fuzz!" RETVAL=0 diff --git a/unicorn_mode/samples/compcov_x64/compcov_target.elf b/unicorn_mode/samples/compcov_x64/compcov_target.elf index 0f1ad916..abe972dd 100755 --- a/unicorn_mode/samples/compcov_x64/compcov_target.elf +++ b/unicorn_mode/samples/compcov_x64/compcov_target.elf Binary files differdiff --git a/unicorn_mode/samples/compcov_x64/compcov_test_harness.py b/unicorn_mode/samples/compcov_x64/compcov_test_harness.py index 5698cbc8..9a5da520 100644 --- a/unicorn_mode/samples/compcov_x64/compcov_test_harness.py +++ b/unicorn_mode/samples/compcov_x64/compcov_test_harness.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """ Simple test harness for AFL's Unicorn Mode. @@ -17,8 +18,8 @@ import argparse import os import signal -from unicorn import * -from unicorn.x86_const import * +from unicornafl import * +from unicornafl.x86_const import * # Path to the file containing the binary to emulate BINARY_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'compcov_target.bin') @@ -120,51 +121,39 @@ def main(): uc.mem_map(STACK_ADDRESS, STACK_SIZE) uc.reg_write(UC_X86_REG_RSP, STACK_ADDRESS + STACK_SIZE) - #----------------------------------------------------- - # Emulate 1 instruction to kick off AFL's fork server - # THIS MUST BE DONE BEFORE LOADING USER DATA! - # If this isn't done every single run, the AFL fork server - # will not be started appropriately and you'll get erratic results! - # It doesn't matter what this returns with, it just has to execute at - # least one instruction in order to get the fork server started. - - # Execute 1 instruction just to startup the forkserver - print("Starting the AFL forkserver by executing 1 instruction") - try: - uc.emu_start(uc.reg_read(UC_X86_REG_RIP), 0, 0, count=1) - except UcError as e: - print("ERROR: Failed to execute a single instruction (error: {})!".format(e)) - return + # Mapping a location to write our buffer to + uc.mem_map(DATA_ADDRESS, DATA_SIZE_MAX) + #----------------------------------------------- # Load the mutated input and map it into memory - # Load the mutated input from disk - print("Loading data input from {}".format(args.input_file)) - input_file = open(args.input_file, 'rb') - input = input_file.read() - input_file.close() + def place_input_callback(uc, input, _, data): + """ + Callback that loads the mutated input into memory. + """ + # Load the mutated input from disk + input_file = open(args.input_file, 'rb') + input = input_file.read() + input_file.close() - # Apply constraints to the mutated input - if len(input) > DATA_SIZE_MAX: - print("Test input is too long (> {} bytes)".format(DATA_SIZE_MAX)) - return + # Apply constraints to the mutated input + if len(input) > DATA_SIZE_MAX: + return - # Write the mutated command into the data buffer - uc.mem_map(DATA_ADDRESS, DATA_SIZE_MAX) - uc.mem_write(DATA_ADDRESS, input) + # Write the mutated command into the data buffer + uc.mem_write(DATA_ADDRESS, input) #------------------------------------------------------------ # Emulate the code, allowing it to process the mutated input - print("Executing until a crash or execution reaches 0x{0:016x}".format(end_address)) - try: - result = uc.emu_start(uc.reg_read(UC_X86_REG_RIP), end_address, timeout=0, count=0) - except UcError as e: - print("Execution failed with error: {}".format(e)) - force_crash(e) - - print("Done.") + print("Starting the AFL fuzz") + uc.afl_fuzz( + input_file=args.input_file, + place_input_callback=place_input_callback, + exits=[end_address], + persistent_iters=1 + ) if __name__ == "__main__": main() diff --git a/unicorn_mode/samples/simple/simple_test_harness.py b/unicorn_mode/samples/simple/simple_test_harness.py index 6d0fb399..d85ec9f5 100644 --- a/unicorn_mode/samples/simple/simple_test_harness.py +++ b/unicorn_mode/samples/simple/simple_test_harness.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """ Simple test harness for AFL's Unicorn Mode. @@ -17,8 +18,8 @@ import argparse import os import signal -from unicorn import * -from unicorn.mips_const import * +from unicornafl import * +from unicornafl.mips_const import * # Path to the file containing the binary to emulate BINARY_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'simple_target.bin') @@ -120,51 +121,29 @@ def main(): uc.mem_map(STACK_ADDRESS, STACK_SIZE) uc.reg_write(UC_MIPS_REG_SP, STACK_ADDRESS + STACK_SIZE) - #----------------------------------------------------- - # Emulate 1 instruction to kick off AFL's fork server - # THIS MUST BE DONE BEFORE LOADING USER DATA! - # If this isn't done every single run, the AFL fork server - # will not be started appropriately and you'll get erratic results! - # It doesn't matter what this returns with, it just has to execute at - # least one instruction in order to get the fork server started. - - # Execute 1 instruction just to startup the forkserver - print("Starting the AFL forkserver by executing 1 instruction") - try: - uc.emu_start(uc.reg_read(UC_MIPS_REG_PC), 0, 0, count=1) - except UcError as e: - print("ERROR: Failed to execute a single instruction (error: {})!".format(e)) - return - - #----------------------------------------------- - # Load the mutated input and map it into memory - - # Load the mutated input from disk - print("Loading data input from {}".format(args.input_file)) - input_file = open(args.input_file, 'rb') - input = input_file.read() - input_file.close() - - # Apply constraints to the mutated input - if len(input) > DATA_SIZE_MAX: - print("Test input is too long (> {} bytes)".format(DATA_SIZE_MAX)) - return - - # Write the mutated command into the data buffer + # reserve some space for data uc.mem_map(DATA_ADDRESS, DATA_SIZE_MAX) - uc.mem_write(DATA_ADDRESS, input) - #------------------------------------------------------------ - # Emulate the code, allowing it to process the mutated input - - print("Executing until a crash or execution reaches 0x{0:016x}".format(end_address)) - try: - result = uc.emu_start(uc.reg_read(UC_MIPS_REG_PC), end_address, timeout=0, count=0) - except UcError as e: - print("Execution failed with error: {}".format(e)) - force_crash(e) - - print("Done.") + #----------------------------------------------------- + # Set up a callback to place input data (do little work here, it's called for every single iteration) + # We did not pass in any data and don't use persistent mode, so we can ignore these params. + # Be sure to check out the docstrings for the uc.afl_* functions. + def place_input_callback(uc, input, persistent_round, data): + # Load the mutated input from disk + input_file = open(args.input_file, 'rb') + input = input_file.read() + input_file.close() + + # Apply constraints to the mutated input + if len(input) > DATA_SIZE_MAX: + #print("Test input is too long (> {} bytes)") + return False + + # Write the mutated command into the data buffer + uc.mem_write(DATA_ADDRESS, input) + + # Start the fuzzer. + uc.afl_fuzz(args.input_file, place_input_callback, [end_address]) if __name__ == "__main__": main() diff --git a/unicorn_mode/samples/simple/simple_test_harness_alt.py b/unicorn_mode/samples/simple/simple_test_harness_alt.py new file mode 100644 index 00000000..9c3dbc93 --- /dev/null +++ b/unicorn_mode/samples/simple/simple_test_harness_alt.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +""" + Alternative simple test harness for Unicornafl. + It is slower but compatible with anything that uses unicorn. + + Have a look at `unicornafl.monkeypatch()` for an easy way to fuzz unicorn projects. + + This loads the simple_target.bin binary (precompiled as MIPS code) into + Unicorn's memory map for emulation, places the specified input into + simple_target's buffer (hardcoded to be at 0x300000), and executes 'main()'. + If any crashes occur during emulation, this script throws a matching signal + to tell AFL that a crash occurred. + + Run under AFL as follows: + + $ cd <afl_path>/unicorn_mode/samples/simple/ + $ ../../../afl-fuzz -U -m none -i ./sample_inputs -o ./output -- python simple_test_harness_alt.py @@ +""" + +import argparse +import os +import signal + +from unicornafl import * +from unicornafl.mips_const import * + +# Path to the file containing the binary to emulate +BINARY_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'simple_target.bin') + +# Memory map for the code to be tested +CODE_ADDRESS = 0x00100000 # Arbitrary address where code to test will be loaded +CODE_SIZE_MAX = 0x00010000 # Max size for the code (64kb) +STACK_ADDRESS = 0x00200000 # Address of the stack (arbitrarily chosen) +STACK_SIZE = 0x00010000 # Size of the stack (arbitrarily chosen) +DATA_ADDRESS = 0x00300000 # Address where mutated data will be placed +DATA_SIZE_MAX = 0x00010000 # Maximum allowable size of mutated data + +try: + # If Capstone is installed then we'll dump disassembly, otherwise just dump the binary. + from capstone import * + cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + CS_MODE_BIG_ENDIAN) + def unicorn_debug_instruction(uc, address, size, user_data): + mem = uc.mem_read(address, size) + for (cs_address, cs_size, cs_mnemonic, cs_opstr) in cs.disasm_lite(bytes(mem), size): + print(" Instr: {:#016x}:\t{}\t{}".format(address, cs_mnemonic, cs_opstr)) +except ImportError: + def unicorn_debug_instruction(uc, address, size, user_data): + print(" Instr: addr=0x{0:016x}, size=0x{1:016x}".format(address, size)) + +def unicorn_debug_block(uc, address, size, user_data): + print("Basic Block: addr=0x{0:016x}, size=0x{1:016x}".format(address, size)) + +def unicorn_debug_mem_access(uc, access, address, size, value, user_data): + if access == UC_MEM_WRITE: + print(" >>> Write: addr=0x{0:016x} size={1} data=0x{2:016x}".format(address, size, value)) + else: + print(" >>> Read: addr=0x{0:016x} size={1}".format(address, size)) + +def unicorn_debug_mem_invalid_access(uc, access, address, size, value, user_data): + if access == UC_MEM_WRITE_UNMAPPED: + print(" >>> INVALID Write: addr=0x{0:016x} size={1} data=0x{2:016x}".format(address, size, value)) + else: + print(" >>> INVALID Read: addr=0x{0:016x} size={1}".format(address, size)) + +def force_crash(uc_error): + # This function should be called to indicate to AFL that a crash occurred during emulation. + # Pass in the exception received from Uc.emu_start() + mem_errors = [ + UC_ERR_READ_UNMAPPED, UC_ERR_READ_PROT, UC_ERR_READ_UNALIGNED, + UC_ERR_WRITE_UNMAPPED, UC_ERR_WRITE_PROT, UC_ERR_WRITE_UNALIGNED, + UC_ERR_FETCH_UNMAPPED, UC_ERR_FETCH_PROT, UC_ERR_FETCH_UNALIGNED, + ] + if uc_error.errno in mem_errors: + # Memory error - throw SIGSEGV + os.kill(os.getpid(), signal.SIGSEGV) + elif uc_error.errno == UC_ERR_INSN_INVALID: + # Invalid instruction - throw SIGILL + os.kill(os.getpid(), signal.SIGILL) + else: + # Not sure what happened - throw SIGABRT + os.kill(os.getpid(), signal.SIGABRT) + +def main(): + + parser = argparse.ArgumentParser(description="Test harness for simple_target.bin") + parser.add_argument('input_file', type=str, help="Path to the file containing the mutated input to load") + parser.add_argument('-d', '--debug', default=False, action="store_true", help="Enables debug tracing") + args = parser.parse_args() + + # Instantiate a MIPS32 big endian Unicorn Engine instance + uc = Uc(UC_ARCH_MIPS, UC_MODE_MIPS32 + UC_MODE_BIG_ENDIAN) + + if args.debug: + uc.hook_add(UC_HOOK_BLOCK, unicorn_debug_block) + uc.hook_add(UC_HOOK_CODE, unicorn_debug_instruction) + uc.hook_add(UC_HOOK_MEM_WRITE | UC_HOOK_MEM_READ, unicorn_debug_mem_access) + uc.hook_add(UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_READ_INVALID, unicorn_debug_mem_invalid_access) + + #--------------------------------------------------- + # Load the binary to emulate and map it into memory + + print("Loading data input from {}".format(args.input_file)) + binary_file = open(BINARY_FILE, 'rb') + binary_code = binary_file.read() + binary_file.close() + + # Apply constraints to the mutated input + if len(binary_code) > CODE_SIZE_MAX: + print("Binary code is too large (> {} bytes)".format(CODE_SIZE_MAX)) + return + + # Write the mutated command into the data buffer + uc.mem_map(CODE_ADDRESS, CODE_SIZE_MAX) + uc.mem_write(CODE_ADDRESS, binary_code) + + # Set the program counter to the start of the code + start_address = CODE_ADDRESS # Address of entry point of main() + end_address = CODE_ADDRESS + 0xf4 # Address of last instruction in main() + uc.reg_write(UC_MIPS_REG_PC, start_address) + + #----------------- + # Setup the stack + + uc.mem_map(STACK_ADDRESS, STACK_SIZE) + uc.reg_write(UC_MIPS_REG_SP, STACK_ADDRESS + STACK_SIZE) + + # reserve some space for data + uc.mem_map(DATA_ADDRESS, DATA_SIZE_MAX) + + #----------------------------------------------------- + # Kick off AFL's fork server + # THIS MUST BE DONE BEFORE LOADING USER DATA! + # If this isn't done every single run, the AFL fork server + # will not be started appropriately and you'll get erratic results! + + print("Starting the AFL forkserver") + + afl_mode = uc.afl_forkserver_start([end_address]) + if afl_mode != UC_AFL_RET_NO_AFL: + # Disable prints for speed + out = lambda x, y: None + else: + out = lambda x, y: print(x.format(y)) + + #----------------------------------------------- + # Load the mutated input and map it into memory + + # Load the mutated input from disk + out("Loading data input from {}", args.input_file) + input_file = open(args.input_file, 'rb') + input = input_file.read() + input_file.close() + + # Apply constraints to the mutated input + if len(input) > DATA_SIZE_MAX: + out("Test input is too long (> {} bytes)", DATA_SIZE_MAX) + return + + # Write the mutated command into the data buffer + uc.mem_write(DATA_ADDRESS, input) + + #------------------------------------------------------------ + # Emulate the code, allowing it to process the mutated input + + out("Executing until a crash or execution reaches 0x{0:016x}", end_address) + try: + uc.emu_start(uc.reg_read(UC_MIPS_REG_PC), end_address, timeout=0, count=0) + except UcError as e: + out("Execution failed with error: {}", e) + force_crash(e) + + # UC_AFL_RET_ERROR = 0 + # UC_AFL_RET_CHILD = 1 + # UC_AFL_RET_NO_AFL = 2 + # UC_AFL_RET_FINISHED = 3 + out("Done. AFL Mode is {}", afl_mode) + +if __name__ == "__main__": + main() diff --git a/unicorn_mode/unicorn b/unicorn_mode/unicorn new file mode 160000 +Subproject aa5ebf5e16f4f5781cfe94229b41eee7ff93b35 |