diff options
Diffstat (limited to 'unicorn_mode/samples')
-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 |
4 files changed, 229 insertions, 82 deletions
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() |