diff options
author | van Hauser <vh@thc.org> | 2024-07-01 08:55:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-01 08:55:00 +0200 |
commit | e27e3622d465778aba4e75dc623bf1348c46785d (patch) | |
tree | 4a1108a4ffe96db28a31b5fcda1b49c65c806551 /qemu_mode/hooking_bridge | |
parent | a7f928ac31bd75d91f93631790d95a86d13e134f (diff) | |
parent | b169629dbd1cd0b78b541c7fa8eccf6a6a7394b1 (diff) | |
download | afl++-e27e3622d465778aba4e75dc623bf1348c46785d.tar.gz |
Merge pull request #2140 from CowBoy4mH3LL/dev
Adding of QEMU hooking bridge
Diffstat (limited to 'qemu_mode/hooking_bridge')
-rw-r--r-- | qemu_mode/hooking_bridge/Makefile | 18 | ||||
-rw-r--r-- | qemu_mode/hooking_bridge/README.md | 101 | ||||
-rw-r--r-- | qemu_mode/hooking_bridge/inc/common.h | 11 | ||||
-rw-r--r-- | qemu_mode/hooking_bridge/inc/exports.h | 29 | ||||
-rw-r--r-- | qemu_mode/hooking_bridge/src/main.c | 36 | ||||
-rw-r--r-- | qemu_mode/hooking_bridge/src/patching.c | 173 |
6 files changed, 368 insertions, 0 deletions
diff --git a/qemu_mode/hooking_bridge/Makefile b/qemu_mode/hooking_bridge/Makefile new file mode 100644 index 00000000..dcc6f12a --- /dev/null +++ b/qemu_mode/hooking_bridge/Makefile @@ -0,0 +1,18 @@ +.PHONY: clean + +all: plugin +SRC=./src +BLD=./build +INC=-I./inc -I../qemuafl/include -I$(GLIB_H) -I$(GLIB_CONFIG_H) +# CC=gcc + +$(BLD)/patching.o:$(SRC)/patching.c + $(CC) -c -fPIC $(INC) -o $(BLD)/patching.o $(SRC)/patching.c + +plugin:$(SRC)/main.c $(BLD)/patching.o + $(CC) -c -fPIC $(INC) -o $(BLD)/plugin.o $(SRC)/main.c + $(CC) -shared -o $(BLD)/plugin.so $(BLD)/plugin.o $(BLD)/patching.o + +clean: + rm -rf $(BLD)/*.o + rm -rf $(BLD)/*.so \ No newline at end of file diff --git a/qemu_mode/hooking_bridge/README.md b/qemu_mode/hooking_bridge/README.md new file mode 100644 index 00000000..ae8e62e4 --- /dev/null +++ b/qemu_mode/hooking_bridge/README.md @@ -0,0 +1,101 @@ +# Native hooking support into QEMUAFL +* The essential idea is to have inbuilt hooking support into QEMU, instead of relying on the more expensive options UNICORN and its children. +* This solution comprises a bridge (QEMU plugin) that connects your hooks (in a shared library (.so)) with the QEMU usermode ecosystem. +* Currently, LINUX only + +## Bridge compilation +Run build_qemu_support.sh as you do to compile qemuafl, additionally with three args namely: +* `ENABLE_HOOKING=1` to compile the bridge +* `GLIB_H` and `GLIB_CONFIG_H` point to headers `glib.h` and `glibconfig.h` to wherever they are installed on your system + +## Writting hooks +1. Create one or more hooking functions in a shared library, say `hook.so`. +2. Include `exports.h` in your hook build. You can find this header at `<your AFL++ path>/qemu_mode/hooking_bridge/inc`. +3. Shown below is an example which will use to walkthrough hook creation + ```C + struct ret* hook_000000400deadc08(){ + memset (buf, 0, 8); + scanf("%s",buf); + r_reg(RSI,(void *)&h_addr); + w_mem(h_addr,8, buf); + to_ret = (struct ret){0x400deadcab, 0}; + return &to_ret; + } + ``` + i. Hook functions must be named as `hook_<left padded hook location>`. Here, `<left padded hook location>` means `<hook location>` left padded with 0's to until the `(system word length)/4` number of hex characters, e.g. 16 on a 64 bit machine. The unpaded part of `<hook location>` is the absolute address where you want to place the hook. It is basically the file base address (which does not change in QEMU as of now) plus the instruction offset where the hooks is to be placed. The hook function must return a `struct ret *`, which is touched upon later. + + ii. Most likely you will need to access memory or registers in the hook. So we provide four functions + ```C + // Read memory (from address, num. bytes, destination buffer) -> returns 0 on success + int r_mem(unsigned long long addr, unsigned long long len, void *dest); + // Write memory (to address, num. bytes, source buffer) -> returns 0 on success + int w_mem(unsigned long long addr, unsigned long long len, void *src); + // Read register (identifier, destination buffer) -> returns number of bytes read + int r_reg(unsigned char reg, void *dest); + // Read register (identifier, source buffer) -> returns number of bytes written + int w_reg(unsigned char reg, char *src); + ``` + When operating on registers, the functions require a `reg` identifier. This is basically a number gdb uses to lookup a register and can be found under `<qemu(afl) path>/gdb-xml` in the architecture specific xml files. For the example case from above, `RSI` is 4 as obtained from `i386-64bit.xml`. + + iii. Once done with the processing, the hooks needs to return a `struct ret` type pointer, the struct format being + ```C + struct ret{ + unsigned long long addr; + char remove_bp; + }; + ``` + As we can see, there are two fields: first that indicates the address to return to and second that indicates whether the installed hook should be removed after the return. The second field becomes critical if the hook is within an ongoing loop and should be kept intact for future references. + + iv. Finally, mention the list of hooks in a `configure` function that we can call and install your hooks + ```C + struct conf config; + struct conf* configure(){ + config.IP_reg_num = 16; + config.entry_addr = 0x4000001000; + config.num_hooks = NUMHOOKS; //1,2,3... + hooks[0] = 0x400deadc08; + // hooks[1] = 0xcafecace + // .... + config.hooks = hooks; + + //Any other processing stuff you need done before fuzztime + + return &config; + } + ``` + The `configure` function must have the signature `struct conf* configure()` i.e. it must return a pointer to the `config` object. The format of the `config` object is + ```C + struct conf{ + unsigned char IP_reg_num; //found in <qemudir>/gdb-xml + unsigned long long entry_addr; //Main, init, or any entry point that is executed by QEMU prior to hooking targets + unsigned long long* hooks; //list of hooked addresses + unsigned long long num_hooks; // Number of hooks + }; + ``` + `IP_reg_num` here is the register number assigned under the architecture specific xml file under `<qemu(afl) path>/gdb-xml` to the instruction pointer. + +## Running with hooks +Set `QEMU_PLUGIN="file=<AFL download path>qemu_mode/hooking_bridge/build/plugin.so,arg=<your hook .so>"` before running AFL++ in QEMU mode. Note `<your hook .so>` is the absolute path to your hooks library. + +## Contributing +* If you want to enable debugging + * Compile with an additional `DEBUG=1` switch. + * Akin to QEMU's own documentation, set `QEMU_LOG=plugin QEMU_LOG_FILENAME=<your plugin log path>` before you run. + +## Current limitations +1. Cannot be used to debug (-g option) when using the bridge as it uses the gdbstub internally. This is not a problem if used with AFL++, so not such a big issue. +2. Cannot put a hook on the first block after `<entry point>`. Not typically a hookable location. +3. The current implementation can only function on Linux. We have tested on the following configuration + ```Bash + lsb_release -a + --------------- + Distributor ID: Ubuntu + Description: Ubuntu 22.04.3 LTS + Release: 22.04 + Codename: jammy + ``` + ```Bash + uname -a + ---------- + Linux someone 6.5.0-28-generic #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr 4 14:39:20 UTC 2 x86_64 x86_64 x86_64 GNU/Linux + ``` diff --git a/qemu_mode/hooking_bridge/inc/common.h b/qemu_mode/hooking_bridge/inc/common.h new file mode 100644 index 00000000..f2260977 --- /dev/null +++ b/qemu_mode/hooking_bridge/inc/common.h @@ -0,0 +1,11 @@ +#ifndef COMMON_H +#define COMMON_H + +#include <qemu/qemu-plugin.h> + +void patch_finish_cb(void *userdata); +void patch_block_trans_cb(struct qemu_plugin_tb *tb); +void patch_vpu_init_cb(unsigned int vcpu_index); +void patch_init(char *hook_library); + +#endif \ No newline at end of file diff --git a/qemu_mode/hooking_bridge/inc/exports.h b/qemu_mode/hooking_bridge/inc/exports.h new file mode 100644 index 00000000..186976f4 --- /dev/null +++ b/qemu_mode/hooking_bridge/inc/exports.h @@ -0,0 +1,29 @@ +#ifndef API_H +#define API_H + +//# EXPORTS +// Returns 0 on success +int r_mem(unsigned long long addr, unsigned long long len, void *dest); +// // Returns 0 on success +int w_mem(unsigned long long addr, unsigned long long len, void *src); +// Returns num of bytes read; +int r_reg(unsigned char reg, void *dest); +// // Returns num of bytes written +int w_reg(unsigned char reg, char *src); + + +//NOTE hook function must be named hook_<16 hex character at_addr> +//NOTE must define function `struct conf* configure()` +struct conf{ + unsigned char IP_reg_num; + unsigned long long entry_addr; + unsigned long long* hooks; + unsigned long long num_hooks; +}conf; + +struct ret{ + unsigned long long addr; + char remove_bp; +}; + +#endif \ No newline at end of file diff --git a/qemu_mode/hooking_bridge/src/main.c b/qemu_mode/hooking_bridge/src/main.c new file mode 100644 index 00000000..98e8da98 --- /dev/null +++ b/qemu_mode/hooking_bridge/src/main.c @@ -0,0 +1,36 @@ +#include <stdio.h> +#include <stdlib.h> +#include "common.h" + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +static void finish_cb(qemu_plugin_id_t id, void *userdata) { + + patch_finish_cb(userdata); + +} + +static void block_trans_cb(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) { + + patch_block_trans_cb(tb); + +} + +static void vpu_init_cb(qemu_plugin_id_t id, unsigned int vcpu_index) { + + patch_vpu_init_cb(vcpu_index); + +} + +QEMU_PLUGIN_EXPORT +int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, int argc, + char **argv) { + + patch_init(argv[0]); + qemu_plugin_register_vcpu_init_cb(id, vpu_init_cb); + qemu_plugin_register_vcpu_tb_trans_cb(id, block_trans_cb); + qemu_plugin_register_atexit_cb(id, finish_cb, NULL); + return 0; + +} + diff --git a/qemu_mode/hooking_bridge/src/patching.c b/qemu_mode/hooking_bridge/src/patching.c new file mode 100644 index 00000000..123ad99d --- /dev/null +++ b/qemu_mode/hooking_bridge/src/patching.c @@ -0,0 +1,173 @@ +#include <stdlib.h> +#include <stdio.h> +#include <dlfcn.h> +#include <glib.h> +#include "common.h" +#include "exports.h" + +void *handle; +struct conf *config; +struct conf *(*configure)(); +GByteArray *out; +void *cpu; +char cbuf[100]; + +// region GDB Imports +#pragma region GDB Imports +void cpu_single_step(void *cpu, int enabled); +int get_sstep_flags(void); +void gdb_accept_init(int fd); +int gdb_breakpoint_insert(int type, unsigned long long addr, + unsigned long long len); +int gdb_breakpoint_remove(int type, unsigned long long addr, + unsigned long long len); +void *qemu_get_cpu(int index); +int target_memory_rw_debug(void *cpu, unsigned long long addr, void *ptr, + unsigned long long len, char is_write); +int gdb_read_register(void *cs, GByteArray *mem_buf, int n); +int gdb_write_register(void *cs, char *mem_buf, int n); +void gdb_set_cpu_pc(unsigned long long pc); +void gdb_continue(void); +#pragma endregion GDB Imports + +// region API +int r_mem(unsigned long long addr, unsigned long long len, void *dest) { + + return target_memory_rw_debug(cpu, addr, dest, len, 0); + +} + +int w_mem(unsigned long long addr, unsigned long long len, void *src) { + + return target_memory_rw_debug(cpu, addr, src, len, 1); + +} + +int r_reg(unsigned char reg, void *dest) { + + g_byte_array_steal(out, NULL); + int op = gdb_read_register(cpu, out, reg); + memcpy(dest, out->data, out->len); + return op; + +} + +int w_reg(unsigned char reg, char *src) { + + return gdb_write_register(cpu, src, reg); + +} + +// region Breakpoint handling +char single_stepped; +unsigned long long gen_addr; +struct ret *(*hook)(); +struct ret *returned; +// Defined and imported gdbstub.c +void set_signal_callback(void (*cb)(int)); +// Breakpoints are set here +void patch_block_trans_cb(struct qemu_plugin_tb *tb) { + + unsigned long long addr; + addr = qemu_plugin_tb_vaddr(tb); + + if (addr == config->entry_addr) { + + // NOTE This means we cannot put a BP in the first basic block + gdb_accept_init(-1); + for (int i = 0; i < config->num_hooks; i++) { + + gdb_breakpoint_insert(0, config->hooks[i], 1); + + } + + } + +} + +void handle_signal_callback(int sig) { + + if (single_stepped) { + + single_stepped = 0; + gdb_breakpoint_insert(0, gen_addr, 1); + cpu_single_step(cpu, 0); + gdb_continue(); + return; + + } + + r_reg(config->IP_reg_num, cbuf); + gen_addr = *(unsigned long long *)cbuf; + + sprintf(cbuf, "hook_%016llx", gen_addr); + // TODO maybe find a way to put the hook function pointers in the TCG data + // structure instead of this dlsym call + *(unsigned long long **)(&hook) = dlsym(handle, cbuf); + if (!hook) { + + exit(-1); + + } + + returned = hook(); + + if (returned->remove_bp || + (returned->addr == + gen_addr)) { //* force removal of bp in returning to the same address, + //otherwise hook will be called again + gdb_breakpoint_remove(0, gen_addr, 1); + + } + + if (returned->addr == gen_addr) { + + single_stepped = 1; + cpu_single_step(cpu, get_sstep_flags()); + + } else { + + //* no need to rexecute the IP instruction + gdb_set_cpu_pc(returned->addr); + + } + + gdb_continue(); + +} + +// region Constructor/Destructor +void patch_finish_cb(void *userdata) { + + g_byte_array_free(out, 1); + dlclose(handle); + +} + +void patch_vpu_init_cb(unsigned int vcpu_index) { + + cpu = qemu_get_cpu(vcpu_index); + +} + +void patch_init(char *hook_lib) { + + // TODO make OS agnostic, remove dlopen + handle = dlopen(hook_lib, RTLD_NOW); + if (!handle) { + + fprintf(stderr, "DLOPEN Error: %s\n", dlerror()); + exit(-1); + + } + + single_stepped = 0; + + *(void **)(&configure) = dlsym(handle, "configure"); + config = configure(); + + set_signal_callback(handle_signal_callback); + out = g_byte_array_new(); + +} + |