Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
2,737 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = dataAddr; | ||
uc_reg_write(uc, UC_MIPS_REG_A0, ®); | ||
|
||
reg = START; | ||
uc_reg_write(uc, UC_MIPS_REG_T9, ®); | ||
|
||
reg = RA; | ||
uc_reg_write(uc, UC_MIPS_REG_RA, ®); | ||
|
||
// 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Oops, something went wrong.