Skip to content

Commit

Permalink
add code
Browse files Browse the repository at this point in the history
  • Loading branch information
rk700 committed Aug 1, 2019
1 parent db8aa4c commit 28163e4
Show file tree
Hide file tree
Showing 28 changed files with 2,737 additions and 0 deletions.
34 changes: 34 additions & 0 deletions Makefile
@@ -0,0 +1,34 @@
CC := clang
CFLAGS := -Wall -fPIC
#CFLAGS += -O2
CFLAGS += -g -DUF_DEBUG
LDFLAGS := -fsanitize=fuzzer -lunicorn -pthread

OUT := uf
.DEFAULT_GOAL := all

SRC := $(wildcard callback/*.c) \
$(wildcard uniFuzzer/*.c) \
$(wildcard uniFuzzer/elfLoader/*.c)
OBJ := $(SRC:.c=.o)

MAIN_SRC := uniFuzzer/uniFuzzer.c
MAIN_OBJ := uniFuzzer/uniFuzzer.o
$(MAIN_OBJ): CFLAGS += -fsanitize=fuzzer -IuniFuzzer/elfLoader

OTHER_SRC := $(filter-out $(MAIN_SRC),$(SRC))
OTHER_OBJ := $(OTHER_SRC:.c=.o)

%.o:%.c
$(CC) -o $@ $(CFLAGS) -c $<

all:$(OUT)

$(OUT):$(OBJ)
$(CC) -o $@ $(LDFLAGS) $^

clean:
rm -f $(OUT) $(OBJ)

.PHONY: all clean

85 changes: 85 additions & 0 deletions README.md
@@ -0,0 +1,85 @@
uniFuzzer
---------

uniFuzzer is a fuzzing tool for closed-source binaries based on [Unicorn](https://github.com/unicorn-engine/unicorn) and [LibFuzzer](https://llvm.org/docs/LibFuzzer.html). Currently it supports fuzzing 32-bits LSB ELF files on ARM/MIPS, which are usually seen in IoT devices.


# Features

- very little [hack](#-hack-on-unicorn) and easy to build
- can target any specified function or code snippet
- coverage-guided fuzzing with considerable speed
- dependence resolved and loaded automatically
- library function override by PRELOAD


# Build

1. Reverse the target binary and find interesting functions for fuzzing.
2. Create a `.c` file in the directory `callback`, which should contain the following callbacks:

* `void onLibLoad(const char *libName, void *baseAddr, void *ucBaseAddr)`: It's invoked each time an dependent library is loaded in Unicorn.
* `int uniFuzzerInit(uc_engine *uc)`: It's invoked just after all the binaries been loaded in Unicorn. Stack/heap/registers can be setup up here.
* `int uniFuzzerBeforeExec(uc_engine *uc, const uint8_t *data, size_t len)`: It's invoked before each round of fuzzing execution.
* `int uniFuzzerAfterExec(uc_engine *uc)`: It's invoked after each round of fuzzing execution.

3. Run `make` and get the fuzzing tool named `uf`.


# Run

uniFuzzer uses the following environment variables as parameters:

- `UF_TARGET`: Path of the target ELF file
- `UF_PRELOAD`: Path of the preload library. Please make sure that the library has the same architecture as the target.
- `UF_LIBPATH`: Paths in which the dependent libraries reside. Use `:` to separate multiple paths.

And the fuzzing can be started using the following command:

```bash
UF_TARGET=<target> [UF_PRELOAD=<preload>] UF_LIBPATH=<libPath> ./uf
```


# Demo

There comes a demo for basic usage. The demo contains the following files:

- demo-vuln.c: This is the target for fuzzing. It contains a simple function named `vuln()` which is vulnerable to stack/heap overflow.
- demo-libcpreload.c: This is for PRELOAD hooking. It defines an empty `printf()` and simplified `malloc()/free()`.
- callback/demo-callback.c: This defines the necessary callbacks for fuzzing the demo `vuln()` function.

First, please install gcc for mipsel (package `gcc-mipsel-linux-gnu` on Debian) to build the demo:

```bash
# the target binary
# '-Xlinker --hash-style=sysv' tells gcc to use 'DT_HASH' instead of 'DT_GNU_HASH' for symbol lookup
# since currently uniFuzzer does not support 'DT_GNU_HASH'
mipsel-linux-gnu-gcc demo-vuln.c -Xlinker --hash-style=sysv -no-pie -o demo-vuln

# the preload library
mipsel-linux-gnu-gcc -shared -fPIC -nostdlib -Xlinker --hash-style=sysv demo-libcpreload.c -o demo-libcpreload.so
```

Or you can just use the file `demo-vuln` and `demo-libcpreload.so`, which are compiled using the commands above.

Next, run `make` to build uniFuzzer. Please note that if you compiled the MIPS demo by yourself, then some addresses might be different from the prebuilt one and `demo-callback.c` should be updated accordingly.

Finally, make sure that the libc library of MIPS is ready. On Debian it's in `/usr/mipsel-linux-gnu/lib/` after installing the package `libc6-mipsel-cross`, and that's what `UF_LIBPATH` should be:

```bash
UF_TARGET=<path to demo-vuln> UF_PRELOAD=<path to demo-libcpreload.so> UF_LIBPATH=<lib path for MIPS> ./uf
```

# Hack on Unicorn

Unicorn clears the JIT cache of QEMU due to this [issue](https://github.com/unicorn-engine/unicorn/issues/1043), which slows down the speed of fuzzing since the target binary would have to be JIT re-compiled during each round of execution.

We can comment out `tb_flush(env);` as stated in that issue for performance.

# TODO

* support for syscall
* support for other architectures and binary formats
* support `GNU_HASH`
* integrate environment setup and provide APIs
179 changes: 179 additions & 0 deletions callback/demo-callback.c
@@ -0,0 +1,179 @@
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <unicorn/unicorn.h>

#include <assert.h>

static uc_context *context;

// CHANGE ME!
// start addr of the emulation (entry point of function vuln)
#define START 0x4007f0
// end addr of the emulation (return addr in main)
#define END 0x400970
// return addr of the target function
#define RA 0x400970

// CHANGE ME!
// name of the preload library
#define PRELOAD_LIB "demo-libcpreload.so"

// CHANGE ME!
// readelf -sW demo-libcpreload.so | grep heap_boundary
#define HEAP_BOUNDARY_GOT_OFFSET 0x10380

#define HEAP_SIZE 1024*1024*32
#define STACK_SIZE 1024*1024*8
#define DATA_SIZE 0x1000

static char *heapBase;
static char *stackTop;
static char *dataAddr;

// heap_boundary@got for the simplified malloc() in demo-preload
static uint32_t *heapBoundaryGOT;

#define HEAP_CANARY 0xdeadbeef

// callback: invoked when ELFs(target binary and dependent libs) are loaded
void onLibLoad(const char *libName, void *baseAddr, void *ucBaseAddr) {
fprintf(stderr, "loading %s at %p, uc addr: %p\n", libName, baseAddr, ucBaseAddr);

if(strlen(libName)+1 >= sizeof(PRELOAD_LIB)) {
// libname ends with "demo-libcpreload.so"
if(strcmp(libName+strlen(libName)-sizeof(PRELOAD_LIB)+1, PRELOAD_LIB) == 0) {
heapBoundaryGOT = (char *)baseAddr + HEAP_BOUNDARY_GOT_OFFSET;
fprintf(stderr, "demo-libcpreload.so is at %p, heap_boundary@got is at %p\n", baseAddr, heapBoundaryGOT);
}
}
}

// callback: setup the env before emulation starts
int uniFuzzerInit(uc_engine *uc) {

// setup heap area
heapBase = mmap(NULL, HEAP_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_32BIT, -1, 0);
if(heapBase == MAP_FAILED) {
perror("mapping heap");
return 1;
}
if(uc_mem_map_ptr(uc, heapBase, HEAP_SIZE, UC_PROT_READ | UC_PROT_WRITE, heapBase) != UC_ERR_OK) {
fprintf(stderr, "uc mapping heap failed\n");
return 1;
}
printf("heap is at %p\n", heapBase);


// setup stack area
stackTop = mmap(NULL, STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_32BIT, -1, 0);
if(stackTop == MAP_FAILED) {
perror("mapping stack");
return 1;
}
if(uc_mem_map_ptr(uc, stackTop, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE, stackTop) != UC_ERR_OK) {
fprintf(stderr, "uc mapping stack failed\n");
return 1;
}
printf("stack is at %p\n", stackTop+STACK_SIZE);


// setup data area
dataAddr = mmap(NULL, DATA_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_32BIT, -1, 0);
if(dataAddr == MAP_FAILED) {
perror("mapping data");
return 1;
}
if(uc_mem_map_ptr(uc, dataAddr, DATA_SIZE, UC_PROT_READ | UC_PROT_WRITE, dataAddr) != UC_ERR_OK) {
fprintf(stderr, "uc mapping data failed\n");
return 1;
}
printf("data is at %p\n", dataAddr);

// for the registers
uint32_t reg;

reg = stackTop+STACK_SIZE - 0x200;
uc_reg_write(uc, UC_MIPS_REG_SP, &reg);

reg = dataAddr;
uc_reg_write(uc, UC_MIPS_REG_A0, &reg);

reg = START;
uc_reg_write(uc, UC_MIPS_REG_T9, &reg);

reg = RA;
uc_reg_write(uc, UC_MIPS_REG_RA, &reg);

// alloc and save cpu context for restore
if(uc_context_alloc(uc, &context) != UC_ERR_OK) {
fprintf(stderr, "uc_context_alloc failed\n");
return 1;
}

uc_context_save(uc, context);

return 0;
}

// callback: invoked before each round of fuzzing
int uniFuzzerBeforeExec(uc_engine *uc, const uint8_t *data, size_t len) {
// filter on input size
if(len == 0 || len > 256) return 0;

// reset heap base addr in preload library
*heapBoundaryGOT = heapBase;

// restore cpu context
uc_context_restore(uc, context);

// copy input to buffer
memcpy(dataAddr, data, len);

// uncomment the following line to ignore heap overflow in the function vuln
// memset((char *)dataAddr+4, 0, 1);

// uncomment the following line to ignore stack overflow in the function vuln
// memset(dataAddr, 0x20, 1);

uc_err err;

// start emulation of the target function
if((err = uc_emu_start(uc, START, END, 0, 0)) != UC_ERR_OK) {
fprintf(stderr, "uc_emu_start failed: %s\n", uc_strerror(err));
return 1;
}
else {
return 0;
}
}

// callback: invoked after each round of fuzzing
int uniFuzzerAfterExec(uc_engine *uc) {
// check all heap allocations to see if there's an overflow

// current boundary for used heap area
uint32_t *boundary = *heapBoundaryGOT;

// start addr for used heap are
uint32_t *start = heapBase;

size_t chunk_len;
char *canary;

// check canary for all chunks
while(start < boundary) {
chunk_len = *start;
canary = (char *)start + chunk_len + 4;// with header

// overflow
if(*(uint32_t *)canary != HEAP_CANARY) {
fprintf(stderr, "heap overflow!\n");
return 1;
}

start = (char *)start + chunk_len + 8;
}

return 0;
}
31 changes: 31 additions & 0 deletions demo-libcpreload.c
@@ -0,0 +1,31 @@
#include <stddef.h>
#include <stdint.h>

#define HEAP_CANARY 0xdeadbeef


// very simple allocator that just cut and return memory from mmap-ed area
// | chunk size | chunk | canary |
// |--------------|------------------|---------|
// | 4 bytes | ... | 4 bytes |

void *malloc(size_t size) {
static char *heap_boundary = 0x05000000;
size_t chunk_len = ((size+7)/8)*8;

*((uint32_t *)heap_boundary) = chunk_len; // header
*((uint32_t *)(heap_boundary+4+chunk_len)) = HEAP_CANARY;

void *chunk = heap_boundary + 4;

heap_boundary += chunk_len + 8; // with header and canary

return chunk;
}

void free(void *ptr) {
}

int printf() {
return 0;
}
Binary file added demo-libcpreload.so
Binary file not shown.
Binary file added demo-vuln
Binary file not shown.
32 changes: 32 additions & 0 deletions demo-vuln.c
@@ -0,0 +1,32 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int vuln(unsigned char *input) {
size_t input_len = *input;
printf("the input size is %d\n", input_len);
char stack_buf[128];
char *heap_buf = malloc(60);

// heap overflow
strcpy(heap_buf, input+1);
// stack overflow
memcpy(stack_buf, input+1, input_len);

free(heap_buf);
return input_len;
}

int main(int argc, char *argv[]) {
char input[256];
int fin = open(argv[1], O_RDONLY);
read(fin, input, sizeof(input));

int res = vuln(input);
return res;
}

0 comments on commit 28163e4

Please sign in to comment.