Initial import unicornafl

This commit is contained in:
lazymio
2021-10-25 00:51:16 +02:00
parent 91451aa2f5
commit dd7476a9bd
42 changed files with 2269 additions and 1 deletions

328
uc.c
View File

@ -26,6 +26,17 @@
#include "qemu/include/qemu/queue.h"
#ifdef UNICORN_HAS_AFL
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/shm.h>
#include "afl/config.h"
#include "afl/types.h"
#endif
UNICORN_EXPORT
unsigned int uc_version(unsigned int *major, unsigned int *minor)
{
@ -93,6 +104,14 @@ const char *uc_strerror(uc_err code)
return "Insufficient resource (UC_ERR_RESOURCE)";
case UC_ERR_EXCEPTION:
return "Unhandled CPU exception (UC_ERR_EXCEPTION)";
case UC_ERR_AFL_RET_ERROR:
return "Something went horribly wrong in the parent (UC_ERR_AFL_RET_ERROR)";
case UC_ERR_AFL_RET_NO_AFL:
return "No AFL, no need to fork (UC_ERR_AFL_RET_NO_AFL)";
case UC_ERR_AFL_RET_CALLED_TWICE:
return "AFL has already been started before (UC_ERR_AFL_RET_CALLED_TWICE)";
case UC_ERR_AFL_RET_FINISHED:
return "We forked before but now parent is gone (UC_ERR_AFL_REG_FINISHED)";
}
}
@ -748,6 +767,315 @@ uc_err uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t time
}
static inline uc_afl_ret uc_afl_forkserver_start(uc_engine *uc, uint64_t *exits, size_t exit_count)
{
/*
Why we need exits as parameter to forkserver:
In the original unicorn-afl, Unicorn needed to flush the tb cache for every iteration.
This is super slow.
Problem was, that the original forked server doesn't know about possible future exits.
The cached blocks, in the next child, therefore would have no exit set and run forever.
Also it's nice to have multiple exits, so let's just do it right.
*/
if (!uc) {
fprintf(stderr, "[!] Unicorn Engine passed to uc_afl_fuzz is NULL!\n");
return UC_AFL_RET_ERROR;
}
if (!exits) {
fprintf(stderr, "[!] Nullptr provided for exits.\n");
return UC_AFL_RET_ERROR;
}
if (!exit_count) {
fprintf(stderr, "[!] No exits provided (exit_count was 0).\n");
return UC_AFL_RET_ERROR;
}
if (unlikely(uc->afl_area_ptr)) {
#if defined(AFL_DEBUG)
fprintf(stderr, "[!] forkserver_start(...) called twice. Already fuzzing!\n");
#endif
return UC_AFL_RET_CALLED_TWICE; // AFL has already been started before.
}
/* Copy exits to unicorn env buffer */
uc->exits = g_realloc(uc->exits, sizeof(exits[0]) * exit_count);
if (uc->exits == NULL) {
perror("[!] malloc failed when starting forkserver.");
return UC_AFL_RET_ERROR;
}
memcpy(uc->exits, exits, sizeof(exits[0]) * exit_count);
uc->exit_count = exit_count;
// Set addr_end to make sure unicorn will not stop at addr 0x0.
uc->addr_end = uc->exits[0];
/* Fork() :) */
return uc->afl_forkserver_start(uc);
}
/* AFL++ supports testcase forwarding via shared map.
If the env variable is set, get the shared map here.
returns true if we enabled shmap fuzzing, false otherwise. */
static bool uc_afl_enable_shm_testcases(uc_engine *uc) {
char *id_str = getenv(SHM_FUZZ_ENV_VAR);
if (id_str) {
int shm_id = atoi(id_str);
char *map = (char *)shmat(shm_id, NULL, 0);
if (!map || map == (void *)-1) {
perror("[!] could not access fuzzing shared memory");
exit(1);
}
uc->afl_testcase_size_p = (u32 *)map;
uc->afl_testcase_ptr = (map + sizeof(u32));
#if defined(AFL_DEBUG)
printf("[d] successfully opened shared memory for testcases with id %d\n", shm_id);
#endif
return true;
} else {
#if defined(AFL_DEBUG)
printf("[d] SHM_FUZZ_ENV_VAR not set - not using shared map fuzzing.\n");
#endif
return false;
}
}
/* returns the filesize in bytes, -1 or error. */
static inline off_t uc_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;
*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;
}
/* A start with "less features" for our afl use-case */
/* this is largely copied from uc_emu_start, just without setting the entry point, counter and timeout. */
int uc_afl_emu_start(uc_engine *uc) {
uc->emu_counter = 0;
uc->invalid_error = UC_ERR_OK;
uc->emulation_done = false;
uc->stop_request = false;
// remove count hook if counting isn't necessary
if (uc->count_hook != 0) {
uc_hook_del(uc, uc->count_hook);
uc->count_hook = 0;
}
uc->vm_start(uc);
// emulation is done
uc->emulation_done = true;
return uc->invalid_error;
}
/* afl_next that expects you know what you're doing
Specifically, it won't check for afl_area_ptr and next to be set. */
static inline uc_afl_ret uc_afl_next_inl(uc_engine *uc, bool crash_found)
{
// Tell the parent we need a new testcase, then stop until testcase is available.
if (uc->afl_child_request_next(uc, crash_found) == UC_AFL_RET_ERROR) return UC_AFL_RET_ERROR;
return UC_AFL_RET_CHILD;
}
/* similar to __afl_persistent loop */
static inline uc_afl_ret uc_afl_next(uc_engine *uc, bool crash_found)
{
if (unlikely(!uc->afl_area_ptr)) {
fprintf(stderr, "[!] uc_afl_next(...) called before forkserver_start(...).");
return UC_AFL_RET_ERROR;
}
// Tell the parent we need a new testcase, then stop until testcase is available.
if (uc->afl_child_request_next) {
return uc_afl_next_inl(uc, crash_found);
}
return UC_AFL_RET_NO_AFL;
}
UNICORN_EXPORT
uc_err uc_afl_fuzz(
uc_engine *uc,
char* input_file,
uc_afl_cb_place_input_t place_input_callback,
uint64_t *exits,
size_t exit_count,
uc_afl_cb_validate_crash_t validate_crash_callback,
bool always_validate,
uint32_t persistent_iters,
void *data
){
#ifndef UNICORN_HAS_AFL
return UC_ERR_MODE;
#else
if (!uc) {
fprintf(stderr, "[!] Unicorn Engine passed to uc_afl_fuzz is NULL!\n");
return UC_AFL_RET_ERROR;
}
if (!input_file || input_file[0] == 0) {
fprintf(stderr, "[!] No input file provided to uc_afl_fuzz.\n");
return UC_AFL_RET_ERROR;
}
if (!place_input_callback) {
fprintf(stderr, "[!] no place_input_callback set.\n");
return UC_AFL_RET_ERROR;
}
if (always_validate && !validate_crash_callback) {
fprintf(stderr, "[!] always_validate set but validate_crash_callback is missing.\n");
return UC_AFL_RET_ERROR;
}
if (!exit_count) {
fprintf(stderr, "[!] Nullptr provided for exits.\n");
return UC_AFL_RET_ERROR;
}
uint32_t mmap_in_len = 0;
char *in_buf = NULL;
uint32_t *in_len_p = NULL;
bool use_shmap_input = uc_afl_enable_shm_testcases(uc);
if (use_shmap_input) {
/* For shared map fuzzing, the ptr stays the same */
in_buf = uc->afl_testcase_ptr;
in_len_p = uc->afl_testcase_size_p;
} else {
in_len_p = &mmap_in_len;
}
uc_afl_ret afl_ret = uc_afl_forkserver_start(uc, exits, exit_count);
switch(afl_ret) {
case UC_AFL_RET_CHILD:
break;
case UC_AFL_RET_NO_AFL:
// Not running in AFL.
persistent_iters = 1;
break;
case UC_AFL_RET_FINISHED:
// Nothing more to do
return afl_ret;
case UC_AFL_RET_ERROR:
case UC_AFL_RET_CALLED_TWICE:
// Nothing more we can do
return afl_ret;
default:
// What have we done
fprintf(stderr, "[!] Unexpected forkserver return: %d", afl_ret);
return UC_AFL_RET_ERROR;
}
bool first_round = true;
bool crash_found = false;
#if defined(AFL_DEBUG)
if (uc->afl_testcase_ptr) {
printf("[d] uc->afl_testcase_ptr = %p, len = %d\n", uc->afl_testcase_ptr, *uc->afl_testcase_size_p);
}
#endif
// 0 means never stop child in persistence mode.
uint32_t i;
for (i = 0; persistent_iters == 0 || i < persistent_iters; i++) {
// The main fuzz loop starts here :)
if (first_round) {
first_round = false;
} else {
if (uc_afl_next_inl(uc, crash_found) == UC_AFL_RET_ERROR) {
/* parent is probably gone */
exit(1);
}
crash_found = false;
}
/* get input, call place input callback, emulate, unmap input (if needed) */
if (unlikely(!use_shmap_input)) {
/* in_buf and the len are not in a shared map (as it would be for sharedmem fuzzing
No shmap fuzzing involved - Let's read a "normal" file. */
off_t in_len = uc_afl_mmap_file(input_file, &in_buf);
if (unlikely(in_len < 0)) {
fprintf(stderr, "[!] Unable to mmap file: %s (return was %ld)\n", input_file, (long int) in_len);
perror("mmap");
fflush(stderr);
return UC_AFL_RET_ERROR;
}
mmap_in_len = in_len;
}
bool input_accepted = place_input_callback(uc, in_buf, *in_len_p, i, data);
if (unlikely(!input_accepted)) {
// Apparently the input was not to the users' liking. Let's continue.
goto next_iter;
}
uc_err uc_emu_ret = uc_afl_emu_start(uc);
if (unlikely((uc_emu_ret != UC_ERR_OK) || (always_validate && validate_crash_callback))) {
if (validate_crash_callback != NULL && validate_crash_callback(
uc, uc_emu_ret, in_buf, *in_len_p, i, data) != true) {
// The callback thinks this is not a valid crash. Ignore.
goto next_iter;
}
if (persistent_iters != 1) {
// We're inpersistent mode and can report the crash via afl_next. No reason to die.
crash_found = true;
goto next_iter;
}
fprintf(stderr, "[!] UC returned Error: '%s' - let's abort().\n", uc_strerror(uc_emu_ret));
fflush(stderr);
abort();
}
next_iter:
if (!use_shmap_input) munmap(in_buf, mmap_in_len);
}
// UC_AFL_RET_CHILD -> We looped through all iters.
// We are still in the child, nothing good will come after this.
// Exit and let the next generation run.
if (likely(afl_ret == UC_AFL_RET_CHILD)) {
exit(0);
}
if (uc->afl_area_ptr) {
// Nothing should ever come after this but clean it up still.
// shmdt(uc->afl_area_ptr);
uc->afl_area_ptr = NULL;
uc->afl_testcase_ptr = NULL;
}
// UC_AFL_RET_NO_AFL -> Not fuzzing. We ran once.
return UC_AFL_RET_NO_AFL;
#endif
}
UNICORN_EXPORT
uc_err uc_emu_stop(uc_engine *uc)
{