mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-15 03:18:07 +00:00
unicorn speedtest initial commit
This commit is contained in:
6
unicorn_mode/samples/speedtest/.gitignore
vendored
Normal file
6
unicorn_mode/samples/speedtest/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
output
|
||||
harness
|
||||
harness-debug
|
||||
target
|
||||
target.o
|
||||
target.offsets.*
|
17
unicorn_mode/samples/speedtest/Makefile
Normal file
17
unicorn_mode/samples/speedtest/Makefile
Normal file
@ -0,0 +1,17 @@
|
||||
CFLAGS += -Wall -Werror -Wextra -Wpedantic -Og -g -fPIE
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: target target.offsets.main
|
||||
|
||||
clean:
|
||||
rm -rf *.o target target.offsets.*
|
||||
|
||||
target.o: target.c
|
||||
${CC} ${CFLAGS} -c target.c -o $@
|
||||
|
||||
target: target.o
|
||||
${CC} ${CFLAGS} target.o -o $@
|
||||
|
||||
target.offsets.main: target
|
||||
./get_offsets.py
|
65
unicorn_mode/samples/speedtest/README.md
Normal file
65
unicorn_mode/samples/speedtest/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Speedtest
|
||||
|
||||
This is a simple sample harness for a non-crashing file,
|
||||
to show the raw speed of C, Rust, and Python harnesses.
|
||||
|
||||
## Compiling...
|
||||
|
||||
Make sure, you built unicornafl first (`../../build_unicorn_support.sh`).
|
||||
Then, follow these individual steps:
|
||||
|
||||
### Rust
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
cargo build --release
|
||||
../../../afl-fuzz -i ../sample_inputs -o out -- ./target/release/harness @@
|
||||
```
|
||||
|
||||
### C
|
||||
|
||||
```bash
|
||||
cd c
|
||||
make
|
||||
../../../afl-fuzz -i ../sample_inputs -o out -- ./harness @@
|
||||
```
|
||||
|
||||
### python
|
||||
|
||||
```bash
|
||||
cd python
|
||||
../../../afl-fuzz -i ../sample_inputs -o out -U -- python3 ./harness.py @@
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
TODO: add results here.
|
||||
|
||||
|
||||
## Compiling speedtest_target.c
|
||||
|
||||
You shouldn't need to compile simple_target.c since a X86_64 binary version is
|
||||
pre-built and shipped in this sample folder. This file documents how the binary
|
||||
was built in case you want to rebuild it or recompile it for any reason.
|
||||
|
||||
The pre-built binary (simple_target_x86_64.bin) was built using -g -O0 in gcc.
|
||||
|
||||
We then load the binary and execute the main function directly.
|
||||
|
||||
## Addresses for the harness:
|
||||
To find the address (in hex) of main, run:
|
||||
```bash
|
||||
objdump -M intel -D target | grep '<main>:' | cut -d" " -f1
|
||||
```
|
||||
To find all call sites to magicfn, run:
|
||||
```bash
|
||||
objdump -M intel -D target | grep '<magicfn>$' | cut -d":" -f1
|
||||
```
|
||||
For malloc callsites:
|
||||
```bash
|
||||
objdump -M intel -D target | grep '<malloc@plt>$' | cut -d":" -f1
|
||||
```
|
||||
And free callsites:
|
||||
```bash
|
||||
objdump -M intel -D target | grep '<free@plt>$' | cut -d":" -f1
|
||||
```
|
53
unicorn_mode/samples/speedtest/c/Makefile
Normal file
53
unicorn_mode/samples/speedtest/c/Makefile
Normal file
@ -0,0 +1,53 @@
|
||||
# UnicornAFL Usage
|
||||
# Original Unicorn Example Makefile by Nguyen Anh Quynh <aquynh@gmail.com>, 2015
|
||||
# Adapted for AFL++ by domenukk <domenukk@gmail.com>, 2020
|
||||
.POSIX:
|
||||
UNAME_S =$(shell uname -s)# GNU make
|
||||
UNAME_S:sh=uname -s # BSD make
|
||||
_UNIQ=_QINU_
|
||||
|
||||
LIBDIR = ../../../unicornafl
|
||||
BIN_EXT =
|
||||
AR_EXT = a
|
||||
|
||||
# Verbose output?
|
||||
V ?= 0
|
||||
|
||||
CFLAGS += -Wall -Werror -Wextra -Wno-unused-parameter -I../../../unicornafl/include
|
||||
|
||||
LDFLAGS += -L$(LIBDIR) -lpthread -lm
|
||||
|
||||
_LRT = $(_UNIQ)$(UNAME_S:Linux=)
|
||||
__LRT = $(_LRT:$(_UNIQ)=-lrt)
|
||||
LRT = $(__LRT:$(_UNIQ)=)
|
||||
|
||||
LDFLAGS += $(LRT)
|
||||
|
||||
_CC = $(_UNIQ)$(CROSS)
|
||||
__CC = $(_CC:$(_UNIQ)=$(CC))
|
||||
MYCC = $(__CC:$(_UNIQ)$(CROSS)=$(CROSS)gcc)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: fuzz
|
||||
|
||||
clean:
|
||||
rm -rf *.o harness harness-debug
|
||||
|
||||
harness.o: harness.c ../../../unicornafl/include/unicorn/*.h
|
||||
${MYCC} ${CFLAGS} -O3 -c harness.c -o $@
|
||||
|
||||
harness-debug.o: harness.c ../../../unicornafl/include/unicorn/*.h
|
||||
${MYCC} ${CFLAGS} -fsanitize=address -g -Og -c harness.c -o $@
|
||||
|
||||
harness: harness.o
|
||||
${MYCC} -L${LIBDIR} harness.o ../../../unicornafl/libunicornafl.a $(LDFLAGS) -o $@
|
||||
|
||||
harness-debug: harness-debug.o
|
||||
${MYCC} -fsanitize=address -g -Og -L${LIBDIR} harness-debug.o ../../../unicornafl/libunicornafl.a $(LDFLAGS) -o harness-debug
|
||||
|
||||
../target:
|
||||
$(MAKE) -C ..
|
||||
|
||||
fuzz: ../target harness
|
||||
SKIP_BINCHECK=1 ../../../../afl-fuzz -i ../sample_inputs -o ./output -- ./harness @@
|
390
unicorn_mode/samples/speedtest/c/harness.c
Normal file
390
unicorn_mode/samples/speedtest/c/harness.c
Normal file
@ -0,0 +1,390 @@
|
||||
/*
|
||||
Simple test harness for AFL++'s unicornafl c mode.
|
||||
|
||||
This loads the simple_target_x86_64 binary into
|
||||
Unicorn's memory map for emulation, places the specified input into
|
||||
argv[1], sets up argv, and argc and executes 'main()'.
|
||||
If run inside AFL, afl_fuzz automatically does the "right thing"
|
||||
|
||||
Run under AFL as follows:
|
||||
|
||||
$ cd <afl_path>/unicorn_mode/samples/simple/
|
||||
$ make
|
||||
$ ../../../afl-fuzz -m none -i sample_inputs -o out -- ./harness @@
|
||||
*/
|
||||
|
||||
// This is not your everyday Unicorn.
|
||||
#define UNICORN_AFL
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <unicorn/unicorn.h>
|
||||
|
||||
// Path to the file containing the binary to emulate
|
||||
#define BINARY_FILE ("../target")
|
||||
|
||||
// Memory map for the code to be tested
|
||||
// Arbitrary address where code to test will be loaded
|
||||
static const int64_t BASE_ADDRESS = 0x0;
|
||||
// Max size for the code (64kb)
|
||||
static const int64_t CODE_SIZE_MAX = 0x00010000;
|
||||
// Location where the input will be placed (make sure the emulated program knows this somehow, too ;) )
|
||||
static const int64_t INPUT_ADDRESS = 0x00100000;
|
||||
// Maximum size for our input
|
||||
static const int64_t INPUT_MAX = 0x00100000;
|
||||
// Where our pseudo-heap is at
|
||||
static const int64_t HEAP_ADDRESS = 0x00200000;
|
||||
// Maximum allowable size for the heap
|
||||
static const int64_t HEAP_SIZE_MAX = 0x000F0000;
|
||||
// Address of the stack (Some random address again)
|
||||
static const int64_t STACK_ADDRESS = 0x00400000;
|
||||
// Size of the stack (arbitrarily chosen, just make it big enough)
|
||||
static const int64_t STACK_SIZE = 0x000F0000;
|
||||
|
||||
// Alignment for unicorn mappings (seems to be needed)
|
||||
static const int64_t ALIGNMENT = 0x1000;
|
||||
|
||||
static void hook_block(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
|
||||
printf(">>> Tracing basic block at 0x%"PRIx64 ", block size = 0x%x\n", address, size);
|
||||
}
|
||||
|
||||
static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
|
||||
printf(">>> Tracing instruction at 0x%"PRIx64 ", instruction size = 0x%x\n", address, size);
|
||||
}
|
||||
|
||||
/* Unicorn page needs to be 0x1000 aligned, apparently */
|
||||
static uint64_t pad(uint64_t size) {
|
||||
if (size % ALIGNMENT == 0) { return size; }
|
||||
return ((size / ALIGNMENT) + 1) * ALIGNMENT;
|
||||
}
|
||||
|
||||
/* returns the filesize in bytes, -1 or error. */
|
||||
static off_t afl_mmap_file(char *filename, char **buf_ptr) {
|
||||
|
||||
off_t ret = -1;
|
||||
|
||||
int fd = open(filename, O_RDONLY);
|
||||
|
||||
struct stat st = {0};
|
||||
if (fstat(fd, &st)) goto exit;
|
||||
|
||||
off_t in_len = st.st_size;
|
||||
if (in_len == -1) {
|
||||
/* This can only ever happen on 32 bit if the file is exactly 4gb. */
|
||||
fprintf(stderr, "Filesize of %s too large\n", filename);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
*buf_ptr = mmap(0, in_len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
|
||||
if (*buf_ptr != MAP_FAILED) ret = in_len;
|
||||
|
||||
exit:
|
||||
close(fd);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/* Place the input at the right spot inside unicorn.
|
||||
This code path is *HOT*, do as little work as possible! */
|
||||
static bool place_input_callback(
|
||||
uc_engine *uc,
|
||||
char *input,
|
||||
size_t input_len,
|
||||
uint32_t persistent_round,
|
||||
void *data
|
||||
){
|
||||
// printf("Placing input with len %ld to %x\n", input_len, DATA_ADDRESS);
|
||||
if (input_len >= INPUT_MAX) {
|
||||
// Test input too short or too long, ignore this testcase
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need a valid c string, make sure it never goes out of bounds.
|
||||
input[input_len-1] = '\0';
|
||||
|
||||
// Write the testcase to unicorn.
|
||||
uc_mem_write(uc, INPUT_ADDRESS, input, input_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// exit in case the unicorn-internal mmap fails.
|
||||
static void mem_map_checked(uc_engine *uc, uint64_t addr, size_t size, uint32_t mode) {
|
||||
size = pad(size);
|
||||
//printf("SIZE %llx, align: %llx\n", size, ALIGNMENT);
|
||||
uc_err err = uc_mem_map(uc, addr, size, mode);
|
||||
if (err != UC_ERR_OK) {
|
||||
printf("Error mapping %ld bytes at 0x%lx: %s (mode: %d)\n", size, addr, uc_strerror(err), mode);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// allocates an array, reads all addrs to the given array ptr, returns a size
|
||||
ssize_t read_all_addrs(char *path, uint64_t *addrs, size_t max_count) {
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) {
|
||||
perror("fopen");
|
||||
fprintf(stderr, "Could not read %s, make sure you ran ./get_offsets.py\n", path);
|
||||
exit(-1);
|
||||
}
|
||||
for (size_t i = 0; i < max_count; i++) {
|
||||
bool end = false;
|
||||
if(fscanf(f, "%lx", &addrs[i]) == EOF) {
|
||||
end = true;
|
||||
i--;
|
||||
} else if (fgetc(f) == EOF) {
|
||||
end = true;
|
||||
}
|
||||
if (end) {
|
||||
printf("Set %ld addrs for %s\n", i + 1, path);
|
||||
fclose(f);
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return max_count;
|
||||
}
|
||||
|
||||
// Read all addresses from the given file, and set a hook for them.
|
||||
void set_all_hooks(uc_engine *uc, char *hook_file, void *hook_fn) {
|
||||
|
||||
FILE *f = fopen(hook_file, "r");
|
||||
if (!f) {
|
||||
fprintf(stderr, "Could not read %s, make sure you ran ./get_offsets.py\n", hook_file);
|
||||
exit(-1);
|
||||
}
|
||||
uint64_t hook_addr;
|
||||
for (int hook_count = 0; 1; hook_count++) {
|
||||
if(fscanf(f, "%lx", &hook_addr) == EOF) {
|
||||
printf("Set %d hooks for %s\n", hook_count, hook_file);
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
printf("got new hook addr %lx (count: %d) ohbytw: sizeof %lx\n", hook_addr, hook_count, sizeof(uc_hook));
|
||||
hook_addr += BASE_ADDRESS;
|
||||
// We'll leek these hooks like a good citizen.
|
||||
uc_hook *hook = calloc(1, sizeof(uc_hook));
|
||||
if (!hook) {
|
||||
perror("calloc");
|
||||
exit(-1);
|
||||
}
|
||||
uc_hook_add(uc, hook, UC_HOOK_CODE, hook_fn, NULL, hook_addr, hook_addr);
|
||||
// guzzle up newline
|
||||
if (fgetc(f) == EOF) {
|
||||
printf("Set %d hooks for %s\n", hook_count, hook_file);
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This is a fancy print function that we're just going to skip for fuzzing.
|
||||
static void hook_magicfn(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
|
||||
address += size;
|
||||
uc_reg_write(uc, UC_X86_REG_RIP, &address);
|
||||
}
|
||||
|
||||
static bool already_allocated = false;
|
||||
|
||||
// We use a very simple malloc/free stub here, that only works for exactly one allocation at a time.
|
||||
static void hook_malloc(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
|
||||
if (already_allocated) {
|
||||
printf("Double malloc, not supported right now!\n");
|
||||
abort();
|
||||
}
|
||||
// read the first param.
|
||||
uint64_t malloc_size;
|
||||
uc_reg_read(uc, UC_X86_REG_RDI, &malloc_size);
|
||||
if (malloc_size > HEAP_SIZE_MAX) {
|
||||
printf("Tried to allocated %ld bytes, but we only support up to %ld\n", malloc_size, HEAP_SIZE_MAX);
|
||||
abort();
|
||||
}
|
||||
uc_reg_write(uc, UC_X86_REG_RAX, &HEAP_ADDRESS);
|
||||
address += size;
|
||||
uc_reg_write(uc, UC_X86_REG_RIP, &address);
|
||||
already_allocated = true;
|
||||
}
|
||||
|
||||
// No real free, just set the "used"-flag to false.
|
||||
static void hook_free(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
|
||||
if (!already_allocated) {
|
||||
printf("Double free detected. Real bug?\n");
|
||||
abort();
|
||||
}
|
||||
// read the first param.
|
||||
uint64_t free_ptr;
|
||||
uc_reg_read(uc, UC_X86_REG_RDI, &free_ptr);
|
||||
if (free_ptr != HEAP_ADDRESS) {
|
||||
printf("Tried to free wrong mem region: 0x%lx at code loc 0x%lx\n", free_ptr, address);
|
||||
abort();
|
||||
}
|
||||
address += size;
|
||||
uc_reg_write(uc, UC_X86_REG_RIP, &address);
|
||||
already_allocated = false;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
if (argc == 1) {
|
||||
printf("Test harness to measure speed against Rust and python. Usage: harness [-t] <inputfile>\n");
|
||||
exit(1);
|
||||
}
|
||||
bool tracing = false;
|
||||
char *filename = argv[1];
|
||||
if (argc > 2 && !strcmp(argv[1], "-t")) {
|
||||
tracing = true;
|
||||
filename = argv[2];
|
||||
}
|
||||
|
||||
uc_engine *uc;
|
||||
uc_err err;
|
||||
uc_hook hooks[2];
|
||||
char *file_contents;
|
||||
|
||||
// Initialize emulator in X86_64 mode
|
||||
err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc);
|
||||
if (err) {
|
||||
printf("Failed on uc_open() with error returned: %u (%s)\n",
|
||||
err, uc_strerror(err));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If we want tracing output, set the callbacks here
|
||||
if (tracing) {
|
||||
// tracing all basic blocks with customized callback
|
||||
uc_hook_add(uc, &hooks[0], UC_HOOK_BLOCK, hook_block, NULL, 1, 0);
|
||||
uc_hook_add(uc, &hooks[1], UC_HOOK_CODE, hook_code, NULL, 1, 0);
|
||||
}
|
||||
|
||||
printf("The input testcase is set to %s\n", filename);
|
||||
|
||||
|
||||
printf("Loading target from %s\n", BINARY_FILE);
|
||||
off_t len = afl_mmap_file(BINARY_FILE, &file_contents);
|
||||
printf("Binary file size: %lx\n", len);
|
||||
if (len < 0) {
|
||||
perror("Could not read binary to emulate");
|
||||
return -2;
|
||||
}
|
||||
if (len == 0) {
|
||||
fprintf(stderr, "File at '%s' is empty\n", BINARY_FILE);
|
||||
return -3;
|
||||
}
|
||||
if (len > CODE_SIZE_MAX) {
|
||||
fprintf(stderr, "Binary too large, increase CODE_SIZE_MAX\n");
|
||||
return -4;
|
||||
}
|
||||
|
||||
// Map memory.
|
||||
mem_map_checked(uc, BASE_ADDRESS, len, UC_PROT_ALL);
|
||||
fflush(stdout);
|
||||
|
||||
// write machine code to be emulated to memory
|
||||
if (uc_mem_write(uc, BASE_ADDRESS, file_contents, len) != UC_ERR_OK) {
|
||||
puts("Error writing to CODE");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Release copied contents
|
||||
munmap(file_contents, len);
|
||||
|
||||
// Set the program counter to the start of the code
|
||||
FILE *f = fopen("../target.offsets.main", "r");
|
||||
if (!f) {
|
||||
perror("fopen");
|
||||
puts("Could not read offset to main function, make sure you ran ./get_offsets.py");
|
||||
exit(-1);
|
||||
}
|
||||
uint64_t start_address;
|
||||
if(fscanf(f, "%lx", &start_address) == EOF) {
|
||||
puts("Start address not found in target.offests.main");
|
||||
exit(-1);
|
||||
}
|
||||
fclose(f);
|
||||
start_address += BASE_ADDRESS;
|
||||
printf("Execution will start at 0x%lx", start_address);
|
||||
// Set the program counter to the start of the code
|
||||
uc_reg_write(uc, UC_X86_REG_RIP, &start_address); // address of entry point of main()
|
||||
|
||||
// Setup the Stack
|
||||
mem_map_checked(uc, STACK_ADDRESS, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE);
|
||||
// Setup the stack pointer, but allocate two pointers for the pointers to input
|
||||
uint64_t val = STACK_ADDRESS + STACK_SIZE - 16;
|
||||
//printf("Stack at %lu\n", stack_val);
|
||||
uc_reg_write(uc, UC_X86_REG_RSP, &val);
|
||||
|
||||
// reserve some space for our input data
|
||||
mem_map_checked(uc, INPUT_ADDRESS, INPUT_MAX, UC_PROT_READ);
|
||||
|
||||
// argc = 2
|
||||
val = 2;
|
||||
uc_reg_write(uc, UC_X86_REG_RDI, &val);
|
||||
//RSI points to our little 2 QWORD space at the beginning of the stack...
|
||||
val = STACK_ADDRESS + STACK_SIZE - 16;
|
||||
uc_reg_write(uc, UC_X86_REG_RSI, &val);
|
||||
|
||||
//... which points to the Input. Write the ptr to mem in little endian.
|
||||
uint32_t addr_little = STACK_ADDRESS;
|
||||
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
// The chances you are on a big_endian system aren't too high, but still...
|
||||
__builtin_bswap32(addr_little);
|
||||
#endif
|
||||
|
||||
uc_mem_write(uc, STACK_ADDRESS + STACK_SIZE - 16, (char *)&addr_little, 4);
|
||||
|
||||
set_all_hooks(uc, "../target.offsets.malloc", hook_malloc);
|
||||
set_all_hooks(uc, "../target.offsets.magicfn", hook_magicfn);
|
||||
set_all_hooks(uc, "../target.offsets.free", hook_free);
|
||||
|
||||
int exit_count_max = 100;
|
||||
// we don't need more exits for now.
|
||||
uint64_t exits[exit_count_max];
|
||||
|
||||
ssize_t exit_count = read_all_addrs("../target.offsets.main_ends", exits, exit_count_max);
|
||||
if (exit_count < 1) {
|
||||
printf("Could not find exits! aborting.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
printf("Starting to fuzz. Running from addr %ld to one of these %ld exits:\n", start_address, exit_count);
|
||||
for (ssize_t i = 0; i < exit_count; i++) {
|
||||
printf(" exit %ld: %ld\n", i, exits[i]);
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
// let's gooo
|
||||
uc_afl_ret afl_ret = uc_afl_fuzz(
|
||||
uc, // The unicorn instance we prepared
|
||||
filename, // Filename of the input to process. In AFL this is usually the '@@' placeholder, outside it's any input file.
|
||||
place_input_callback, // Callback that places the input (automatically loaded from the file at filename) in the unicorninstance
|
||||
exits, // Where to exit (this is an array)
|
||||
exit_count, // Count of end addresses
|
||||
NULL, // Optional calback to run after each exec
|
||||
false, // true, if the optional callback should be run also for non-crashes
|
||||
1000, // For persistent mode: How many rounds to run
|
||||
NULL // additional data pointer
|
||||
);
|
||||
switch(afl_ret) {
|
||||
case UC_AFL_RET_ERROR:
|
||||
printf("Error starting to fuzz");
|
||||
return -3;
|
||||
break;
|
||||
case UC_AFL_RET_NO_AFL:
|
||||
printf("No AFL attached - We are done with a single run.");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
77
unicorn_mode/samples/speedtest/get_offsets.py
Normal file
77
unicorn_mode/samples/speedtest/get_offsets.py
Normal file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""This simple script uses objdump to parse important addresses from the target"""
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
objdump_output = subprocess.check_output(
|
||||
shlex.split("objdump -M intel -D target")
|
||||
).decode()
|
||||
main_loc = None
|
||||
main_ends = []
|
||||
main_ended = False
|
||||
magicfn_calls = []
|
||||
malloc_calls = []
|
||||
free_calls = []
|
||||
strlen_calls = []
|
||||
|
||||
|
||||
def line2addr(line):
|
||||
return "0x" + line.split(":", 1)[0].strip()
|
||||
|
||||
|
||||
last_line = None
|
||||
for line in objdump_output.split("\n"):
|
||||
line = line.strip()
|
||||
|
||||
def read_addr_if_endswith(findme, list_to):
|
||||
"""
|
||||
Look, for example, for the addr like:
|
||||
12a9: e8 f2 fd ff ff call 10a0 <free@plt>
|
||||
"""
|
||||
if line.endswith(findme):
|
||||
list_to.append(line2addr(line))
|
||||
|
||||
if main_loc is not None and main_ended is False:
|
||||
# We want to know where main ends. An empty line in objdump.
|
||||
if len(line) == 0:
|
||||
main_ends.append(line2addr(last_line))
|
||||
main_ended = True
|
||||
elif "ret" in line:
|
||||
main_ends.append(line2addr(line))
|
||||
|
||||
if "<main>:" in line:
|
||||
if main_loc is not None:
|
||||
raise Exception("Found multiple main functions, odd target!")
|
||||
# main_loc is the label, so it's parsed differntly (i.e. `0000000000001220 <main>:`)
|
||||
main_loc = "0x" + line.strip().split(" ", 1)[0].strip()
|
||||
else:
|
||||
[
|
||||
read_addr_if_endswith(*x)
|
||||
for x in [
|
||||
("<free@plt>", free_calls),
|
||||
("<malloc@plt>", malloc_calls),
|
||||
("<strlen@plt>", strlen_calls),
|
||||
("<magicfn>", magicfn_calls),
|
||||
]
|
||||
]
|
||||
|
||||
last_line = line
|
||||
|
||||
if main_loc is None:
|
||||
raise (
|
||||
"Could not find main in ./target! Make sure objdump is installed and the target is compiled."
|
||||
)
|
||||
|
||||
with open("target.offsets.main", "w") as f:
|
||||
f.write(main_loc)
|
||||
with open("target.offsets.main_ends", "w") as f:
|
||||
f.write("\n".join(main_ends))
|
||||
with open("target.offsets.magicfn", "w") as f:
|
||||
f.write("\n".join(magicfn_calls))
|
||||
with open("target.offsets.malloc", "w") as f:
|
||||
f.write("\n".join(malloc_calls))
|
||||
with open("target.offsets.free", "w") as f:
|
||||
f.write("\n".join(free_calls))
|
||||
with open("target.offsets.strlen", "w") as f:
|
||||
f.write("\n".join(strlen_calls))
|
8
unicorn_mode/samples/speedtest/python/Makefile
Normal file
8
unicorn_mode/samples/speedtest/python/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
all: fuzz
|
||||
|
||||
../target:
|
||||
$(MAKE) -C ..
|
||||
|
||||
fuzz: ../target
|
||||
rm -rf ./ouptput
|
||||
../../../../afl-fuzz -s 1 -U -i ../sample_inputs -o ./output -- python3 harness.py @@
|
277
unicorn_mode/samples/speedtest/python/harness.py
Normal file
277
unicorn_mode/samples/speedtest/python/harness.py
Normal file
@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test harness for AFL's Unicorn Mode.
|
||||
|
||||
This loads the speedtest target binary (precompiled X64 code) into
|
||||
Unicorn's memory map for emulation, places the specified input into
|
||||
Argv, and executes main.
|
||||
There should not be any crashes - it's a speedtest against Rust and c.
|
||||
|
||||
Before running this harness, call make in the parent folder.
|
||||
|
||||
Run under AFL as follows:
|
||||
|
||||
$ cd <afl_path>/unicorn_mode/samples/speedtest/python
|
||||
$ ../../../../afl-fuzz -U -i ../sample_inputs -o ./output -- python3 harness.py @@
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import struct
|
||||
|
||||
from unicornafl import *
|
||||
from unicornafl.unicorn_const import UC_ARCH_X86, UC_HOOK_CODE, UC_MODE_64
|
||||
from unicornafl.x86_const import (
|
||||
UC_X86_REG_RAX,
|
||||
UC_X86_REG_RDI,
|
||||
UC_X86_REG_RIP,
|
||||
UC_X86_REG_RSI,
|
||||
UC_X86_REG_RSP,
|
||||
)
|
||||
|
||||
# Memory map for the code to be tested
|
||||
BASE_ADDRESS = 0x0 # Arbitrary address where the (PIE) target binary will be loaded to
|
||||
CODE_SIZE_MAX = 0x00010000 # Max size for the code (64kb)
|
||||
INPUT_ADDRESS = 0x00100000 # where we put our stuff
|
||||
INPUT_MAX = 0x00100000 # max size for our input
|
||||
HEAP_ADDRESS = 0x00200000 # Heap addr
|
||||
HEAP_SIZE_MAX = 0x000F0000 # Maximum allowable size for the heap
|
||||
STACK_ADDRESS = 0x00400000 # Address of the stack (arbitrarily chosen)
|
||||
STACK_SIZE = 0x000F0000 # Size of the stack (arbitrarily chosen)
|
||||
|
||||
target_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
||||
)
|
||||
target_bin = os.path.join(target_path, "target")
|
||||
|
||||
|
||||
def get_offsets_for(name):
|
||||
full_path = os.path.join(target_path, f"target.offsets.{name}")
|
||||
with open(full_path) as f:
|
||||
return [int(x, 16) + BASE_ADDRESS for x in f.readlines()]
|
||||
|
||||
|
||||
# Read all offsets from our objdump file
|
||||
main_offset = get_offsets_for("main")[0]
|
||||
main_ends = get_offsets_for("main_ends")
|
||||
malloc_callsites = get_offsets_for("malloc")
|
||||
free_callsites = get_offsets_for("free")
|
||||
magicfn_callsites = get_offsets_for("magicfn")
|
||||
# Joke's on me: strlen got inlined by my compiler
|
||||
strlen_callsites = get_offsets_for("strlen")
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
|
||||
already_allocated = False
|
||||
|
||||
|
||||
def malloc_hook(uc, address, size, user_data):
|
||||
"""
|
||||
We use a very simple malloc/free stub here, that only works for exactly one allocation at a time.
|
||||
"""
|
||||
global already_allocated
|
||||
if already_allocated:
|
||||
print("Double malloc, not supported right now!")
|
||||
os.abort()
|
||||
# read the first param
|
||||
malloc_size = uc.reg_read(UC_X86_REG_RDI)
|
||||
if malloc_size > HEAP_SIZE_MAX:
|
||||
print(
|
||||
f"Tried to allocate {malloc_size} bytes, aint't nobody got space for that! (We may only allocate up to {HEAP_SIZE_MAX})"
|
||||
)
|
||||
os.abort()
|
||||
uc.reg_write(UC_X86_REG_RAX, HEAP_ADDRESS)
|
||||
uc.reg_write(UC_X86_REG_RIP, address + size)
|
||||
already_allocated = True
|
||||
|
||||
|
||||
def free_hook(uc, address, size, user_data):
|
||||
"""
|
||||
No real free, just set the "used"-flag to false.
|
||||
"""
|
||||
global already_allocated
|
||||
if not already_allocated:
|
||||
print("Double free detected. Real bug?")
|
||||
os.abort()
|
||||
# read the first param
|
||||
free_ptr = uc.reg_read(UC_X86_REG_RDI)
|
||||
if free_ptr != HEAP_ADDRESS:
|
||||
print(
|
||||
f"Tried to free wrong mem region: {hex(free_ptr)} at code loc {hex(address)}"
|
||||
)
|
||||
os.abort()
|
||||
uc.reg_write(UC_X86_REG_RIP, address + size)
|
||||
already_allocated = False
|
||||
|
||||
|
||||
# def strlen_hook(uc, address, size, user_data):
|
||||
# """
|
||||
# No real strlen, we know the len is == our input.
|
||||
# This completely ignores '\0', but for this target, do we really care?
|
||||
# """
|
||||
# global input_len
|
||||
# print(f"Returning len {input_len}")
|
||||
# uc.reg_write(UC_X86_REG_RAX, input_len)
|
||||
# uc.reg_write(UC_X86_REG_RIP, address + size)
|
||||
|
||||
|
||||
def magicfn_hook(uc, address, size, user_data):
|
||||
"""
|
||||
This is a fancy print function that we're just going to skip for fuzzing.
|
||||
"""
|
||||
uc.reg_write(UC_X86_REG_RIP, address + size)
|
||||
|
||||
|
||||
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(
|
||||
"-t",
|
||||
"--trace",
|
||||
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_X86, UC_MODE_64)
|
||||
|
||||
if args.trace:
|
||||
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,
|
||||
)
|
||||
|
||||
print("The input testcase is set to {}".format(args.input_file))
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Load the binary to emulate and map it into memory
|
||||
with open(target_bin, "rb") as f:
|
||||
binary_code = f.read()
|
||||
|
||||
# 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 binary to its place in mem
|
||||
uc.mem_map(BASE_ADDRESS, CODE_SIZE_MAX)
|
||||
uc.mem_write(BASE_ADDRESS, binary_code)
|
||||
|
||||
# Set the program counter to the start of the code
|
||||
uc.reg_write(UC_X86_REG_RIP, main_offset)
|
||||
|
||||
# Setup the stack.
|
||||
uc.mem_map(STACK_ADDRESS, STACK_SIZE)
|
||||
# Setup the stack pointer, but allocate two pointers for the pointers to input.
|
||||
uc.reg_write(UC_X86_REG_RSP, STACK_ADDRESS + STACK_SIZE - 16)
|
||||
|
||||
# Setup our input space, and push the pointer to it in the function params
|
||||
uc.mem_map(INPUT_ADDRESS, INPUT_MAX)
|
||||
# We have argc = 2
|
||||
uc.reg_write(UC_X86_REG_RDI, 2)
|
||||
# RSI points to our little 2 QWORD space at the beginning of the stack...
|
||||
uc.reg_write(UC_X86_REG_RSI, STACK_ADDRESS + STACK_SIZE - 16)
|
||||
# ... which points to the Input. Write the ptr to mem in little endian.
|
||||
uc.mem_write(STACK_ADDRESS + STACK_SIZE - 16, struct.pack("<Q", INPUT_ADDRESS))
|
||||
|
||||
for addr in malloc_callsites:
|
||||
uc.hook_add(UC_HOOK_CODE, malloc_hook, begin=addr, end=addr)
|
||||
|
||||
for addr in free_callsites:
|
||||
uc.hook_add(UC_HOOK_CODE, free_hook, begin=addr, end=addr)
|
||||
|
||||
if len(strlen_callsites):
|
||||
# strlen got inlined for my compiler.
|
||||
print(
|
||||
"Oops, your compiler emitted strlen as function. You may have to change the harness."
|
||||
)
|
||||
# for addr in strlen_callsites:
|
||||
# uc.hook_add(UC_HOOK_CODE, strlen_hook, begin=addr, end=addr)
|
||||
|
||||
for addr in magicfn_callsites:
|
||||
uc.hook_add(UC_HOOK_CODE, magicfn_hook, begin=addr, end=addr + 1)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Set up a callback to place input data (do little work here, it's called for every single iteration! This code is *HOT*)
|
||||
# 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):
|
||||
# Apply constraints to the mutated input
|
||||
input_len = len(input)
|
||||
# global input_len
|
||||
if input_len > INPUT_MAX:
|
||||
#print("Test input is too long (> {} bytes)")
|
||||
return False
|
||||
|
||||
# print(f"Placing input: {input} in round {persistent_round}")
|
||||
|
||||
# Make sure the string is always 0-terminated (as it would be "in the wild")
|
||||
input[-1] = b'\0'
|
||||
|
||||
# Write the mutated command into the data buffer
|
||||
uc.mem_write(INPUT_ADDRESS, input)
|
||||
#uc.reg_write(UC_X86_REG_RIP, main_offset)
|
||||
|
||||
print(f"Starting to fuzz. Running from addr {main_offset} to one of {main_ends}")
|
||||
# Start the fuzzer.
|
||||
uc.afl_fuzz(args.input_file, place_input_callback, main_ends, persistent_iters=1000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
1
unicorn_mode/samples/speedtest/rust/.gitignore
vendored
Normal file
1
unicorn_mode/samples/speedtest/rust/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
target
|
80
unicorn_mode/samples/speedtest/rust/Cargo.lock
generated
Normal file
80
unicorn_mode/samples/speedtest/rust/Cargo.lock
generated
Normal file
@ -0,0 +1,80 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "build-helper"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "capstone"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031ba51c39151a1d6336ec859646153187204b0147c7b3f6fe2de636f1b8dbb3"
|
||||
dependencies = [
|
||||
"capstone-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "capstone-sys"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fae25eddcb80e24f98c35952c37a91ff7f8d0f60dbbdafb9763e8d5cc566b8d7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "unicornafl"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"build-helper",
|
||||
"capstone",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicornafl_harness"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"capstone",
|
||||
"libc",
|
||||
"unicornafl",
|
||||
]
|
13
unicorn_mode/samples/speedtest/rust/Cargo.toml
Normal file
13
unicorn_mode/samples/speedtest/rust/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "unicornafl_harness"
|
||||
version = "0.1.0"
|
||||
authors = ["Dominik Maier <domenukk@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
unicornafl = { path = "../../../unicornafl/bindings/rust/", version="1.0.0" }
|
||||
capstone="0.6.0"
|
||||
libc="0.2.66"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
231
unicorn_mode/samples/speedtest/rust/src/main.rs
Normal file
231
unicorn_mode/samples/speedtest/rust/src/main.rs
Normal file
@ -0,0 +1,231 @@
|
||||
extern crate capstone;
|
||||
extern crate libc;
|
||||
|
||||
use core::cell::{Cell, RefCell};
|
||||
use libc::{c_void, munmap};
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
process::abort,
|
||||
};
|
||||
|
||||
use unicornafl::{
|
||||
unicorn_const::{uc_error, Arch, Mode, Permission},
|
||||
utils::*,
|
||||
RegisterX86::*,
|
||||
};
|
||||
|
||||
const BINARY: &str = &"../target";
|
||||
|
||||
// Memory map for the code to be tested
|
||||
// Arbitrary address where code to test will be loaded
|
||||
const BASE_ADDRESS: u64 = 0x0;
|
||||
// Max size for the code (64kb)
|
||||
const CODE_SIZE_MAX: u64 = 0x00010000;
|
||||
// Location where the input will be placed (make sure the uclated program knows this somehow, too ;) )
|
||||
const INPUT_ADDRESS: u64 = 0x00100000;
|
||||
// Maximum size for our input
|
||||
const INPUT_MAX: u64 = 0x00100000;
|
||||
// Where our pseudo-heap is at
|
||||
const HEAP_ADDRESS: u64 = 0x00200000;
|
||||
// Maximum allowable size for the heap
|
||||
const HEAP_SIZE_MAX: u64 = 0x000F0000;
|
||||
// Address of the stack (Some random address again)
|
||||
const STACK_ADDRESS: u64 = 0x00400000;
|
||||
// Size of the stack (arbitrarily chosen, just make it big enough)
|
||||
const STACK_SIZE: u64 = 0x000F0000;
|
||||
|
||||
macro_rules! hook {
|
||||
($addr:expr, $func:expr) => {
|
||||
uc.add_code_hook($addr, $addr, Box::new($func))
|
||||
.expect(&format!("failed to set {} hook", stringify!($func)));
|
||||
};
|
||||
($addr:expr, $func:expr, $opt_name:expr) => {
|
||||
uc.add_code_hook($addr, $addr, Box::new($func))
|
||||
.expect(&format!("failed to set {} hook", $opt_name));
|
||||
};
|
||||
}
|
||||
|
||||
fn read_file(filename: &str) -> Result<Vec<u8>, io::Error> {
|
||||
let mut f = File::open(filename)?;
|
||||
let mut buffer = Vec::new();
|
||||
f.read_to_end(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Our location parser
|
||||
fn parse_locs(loc_name: &str) -> Result<Vec<u64>, io::Error> {
|
||||
let contents = &read_file(&format!("../target.offsets.{}", loc_name))?;
|
||||
str_from_u8_unchecked(&contents)
|
||||
.split("\n")
|
||||
.filter_map(|x| u64::from_str_radix(x, 16))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// find null terminated string in vec
|
||||
pub unsafe fn str_from_u8_unchecked(utf8_src: &[u8]) -> &str {
|
||||
let nul_range_end = utf8_src
|
||||
.iter()
|
||||
.position(|&c| c == b'\0')
|
||||
.unwrap_or(utf8_src.len());
|
||||
::std::str::from_utf8_unchecked(&utf8_src[0..nul_range_end])
|
||||
}
|
||||
|
||||
fn align(size: u64) -> u64 {
|
||||
const ALIGNMENT: u64 = 0x1000;
|
||||
if size % ALIGNMENT == 0 {
|
||||
size
|
||||
} else {
|
||||
((size / ALIGNMENT) + 1) * ALIGNMENT
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() == 1 {
|
||||
println!("Missing parameter <uclation_input> (@@ for AFL)");
|
||||
return;
|
||||
}
|
||||
let input_file = &args[1];
|
||||
println!("The input testcase is set to {}", input_file);
|
||||
uclate(input_file).unwrap();
|
||||
}
|
||||
|
||||
fn uclate(input_file: &str) -> Result<(), io::Error> {
|
||||
let mut uc = Unicorn::new(Arch::X86, Mode::MODE_64, 0)?;
|
||||
|
||||
let binary = read_file(BINARY).expect(&format!("Could not read modem image: {}", BINARY));
|
||||
let aligned_binary_size = align(binary.len() as u64);
|
||||
// Apply constraints to the mutated input
|
||||
if binary.len() as u64 > CODE_SIZE_MAX {
|
||||
println!("Binary code is too large (> {} bytes)", CODE_SIZE_MAX);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Write the binary to its place in mem
|
||||
uc.mem_map(
|
||||
BASE_ADDRESS,
|
||||
CODE_SIZE_MAX,
|
||||
Permission::READ | Permission::WRITE,
|
||||
)?;
|
||||
uc.mem_write(BASE_ADDR, binary);
|
||||
|
||||
// Set the program counter to the start of the code
|
||||
let main_locs = parse_locs("main")?;
|
||||
uc.reg_write(RIP, main_locs[0])?;
|
||||
|
||||
// Setup the stack.
|
||||
uc.mem_map(
|
||||
STACK_ADDRESS,
|
||||
STACK_SIZE as usize,
|
||||
Permission::READ | Permission::WRITE,
|
||||
)?;
|
||||
// Setup the stack pointer, but allocate two pointers for the pointers to input.
|
||||
uc.reg_write(RSP, STACK_ADDRESS + STACK_SIZE - 16)?;
|
||||
|
||||
// Setup our input space, and push the pointer to it in the function params
|
||||
uc.mem_map(INPUT_ADDRESS, INPUT_MAX as usize, Permission::READ)?;
|
||||
// We have argc = 2
|
||||
uc.reg_write(RDI, 2)?;
|
||||
// RSI points to our little 2 QWORD space at the beginning of the stack...
|
||||
uc.reg_write(RSI, STACK_ADDRESS + STACK_SIZE - 16)?;
|
||||
// ... which points to the Input. Write the ptr to mem in little endian.
|
||||
uc.mem_write(
|
||||
STACK_ADDRESS + STACK_SIZE - 16,
|
||||
(INPUT_ADDRESS as u32).to_le_bytes(),
|
||||
)?;
|
||||
|
||||
let already_allocated = Cell::new(false);
|
||||
|
||||
let already_allocated_malloc = already_allocated.clone();
|
||||
let hook_malloc = move |mut uc: Unicorn, addr: u64, size: u32| {
|
||||
if already_allocated_malloc.get() {
|
||||
println!("Double malloc, not supported right now!");
|
||||
abort();
|
||||
}
|
||||
// read the first param
|
||||
let malloc_size = uc.reg_read(RDI).unwrap();
|
||||
if malloc_size > HEAP_SIZE_MAX {
|
||||
println!(
|
||||
"Tried to allocate {} bytes, but we may only allocate up to {}",
|
||||
malloc_size, HEAP_SIZE_MAX
|
||||
);
|
||||
abort();
|
||||
}
|
||||
uc.reg_write(RAX, HEAP_ADDRESS).unwrap();
|
||||
uc.reg_write(RIP, addr + size as u64).unwrap();
|
||||
already_allocated_malloc.set(true);
|
||||
};
|
||||
|
||||
let already_allocated_free = already_allocated.clone();
|
||||
let hook_free = move |mut uc: Unicorn, addr: u64, size: u32| {
|
||||
if already_allocated_free.get() {
|
||||
println!("Double free detected. Real bug?");
|
||||
abort();
|
||||
}
|
||||
// read the first param
|
||||
let free_ptr = uc.reg_read(RDI).unwrap();
|
||||
if free_ptr != HEAP_ADDRESS {
|
||||
println!(
|
||||
"Tried to free wrong mem region {:x} at code loc {:x}",
|
||||
free_ptr, addr
|
||||
);
|
||||
abort();
|
||||
}
|
||||
uc.reg_write(RIP, addr + size as u64);
|
||||
already_allocated_free.set(false);
|
||||
};
|
||||
|
||||
/*
|
||||
BEGIN FUNCTION HOOKS
|
||||
*/
|
||||
|
||||
let hook_magicfn =
|
||||
move |mut uc: Unicorn, addr: u64, size: u32| uc.reg_write(RIP, address + size as u64);
|
||||
|
||||
for addr in parse_locs("malloc")? {
|
||||
hook!(addr, hook_malloc, "malloc");
|
||||
}
|
||||
|
||||
for addr in parse_locs("free")? {
|
||||
hook!(addr, hook_free, "free");
|
||||
}
|
||||
|
||||
for addr in parse_locs("magicfn")? {
|
||||
hook!(addr, hook_magicfn, "magicfn");
|
||||
}
|
||||
|
||||
let place_input_callback = |mut uc: Unicorn, afl_input: &[u8], _persistent_round: i32| {
|
||||
// apply constraints to the mutated input
|
||||
if afl_input.len() > INPUT_MAX as usize {
|
||||
//println!("Skipping testcase with leng {}", afl_input.len());
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: afl_input[-1] = b'\0'
|
||||
uc.mem_write(INPUT_ADDRESS, afl_input).unwrap();
|
||||
true
|
||||
};
|
||||
|
||||
let crash_validation_callback =
|
||||
|uc: Unicorn, result: uc_error, _input: &[u8], _: i32| result != uc_error::OK;
|
||||
|
||||
end_addrs = parse_locs("main_ends")?;
|
||||
|
||||
let ret = uc.afl_fuzz(
|
||||
input_file,
|
||||
Box::new(place_input_callback),
|
||||
&end_addrs,
|
||||
Box::new(crash_validation_callback),
|
||||
false,
|
||||
1,
|
||||
);
|
||||
|
||||
match ret {
|
||||
Ok(_) => {}
|
||||
Err(e) => panic!(format!("found non-ok unicorn exit: {:?}", e)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
1
unicorn_mode/samples/speedtest/sample_inputs/a
Normal file
1
unicorn_mode/samples/speedtest/sample_inputs/a
Normal file
@ -0,0 +1 @@
|
||||
a
|
77
unicorn_mode/samples/speedtest/target.c
Normal file
77
unicorn_mode/samples/speedtest/target.c
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Sample target file to test afl-unicorn fuzzing capabilities.
|
||||
* This is a very trivial example that will, however, never crash.
|
||||
* Crashing would change the execution speed.
|
||||
*
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Random print function we can hook in our harness to test hook speeds.
|
||||
char magicfn(char to_print) {
|
||||
puts("Printing a char, just minding my own business: ");
|
||||
putchar(to_print);
|
||||
putchar('\n');
|
||||
return to_print;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
printf("Gimme input pl0x!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Make sure the hooks work...
|
||||
char *test = malloc(1024);
|
||||
if (!test) {
|
||||
printf("Uh-Oh, malloc doesn't work!");
|
||||
abort();
|
||||
}
|
||||
free(test);
|
||||
|
||||
char *data_buf = argv[1];
|
||||
// We can start the unicorn hooking here.
|
||||
uint64_t data_len = strlen(data_buf);
|
||||
if (data_len < 20) return -2;
|
||||
|
||||
for (; data_len --> 0 ;) {
|
||||
char *buf_cpy = NULL;
|
||||
if (data_len) {
|
||||
buf_cpy = malloc(data_len);
|
||||
if (!buf_cpy) {
|
||||
puts("Oof, malloc failed! :/");
|
||||
abort();
|
||||
}
|
||||
memcpy(buf_cpy, data_buf, data_len);
|
||||
}
|
||||
if (data_len >= 18) {
|
||||
free(buf_cpy);
|
||||
continue;
|
||||
}
|
||||
if (data_len > 2 && data_len < 18) {
|
||||
buf_cpy[data_len - 1] = (char) 0x90;
|
||||
} else if (data_buf[9] == (char) 0x90 && data_buf[10] != 0x00 && buf_cpy[11] == (char) 0x90) {
|
||||
// Cause a crash if data[10] is not zero, but [9] and [11] are zero
|
||||
unsigned char valid_read = buf_cpy[10];
|
||||
if (magicfn(valid_read) != valid_read) {
|
||||
puts("Oof, the hook for data_buf[10] is broken?");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
free(buf_cpy);
|
||||
}
|
||||
if (data_buf[0] > 0x10 && data_buf[0] < 0x20 && data_buf[1] > data_buf[2]) {
|
||||
// Cause an 'invalid read' crash if (0x10 < data[0] < 0x20) and data[1] > data[2]
|
||||
unsigned char valid_read = data_buf[0];
|
||||
if (magicfn(valid_read) != valid_read) {
|
||||
puts("Oof, the hook for data_buf[0] is broken?");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
magicfn('q');
|
||||
|
||||
return 0;
|
||||
}
|
Submodule unicorn_mode/unicornafl updated: 83d1b426be...0dd17c58d5
Reference in New Issue
Block a user