diff options
-rw-r--r-- | frida_mode/GNUmakefile | 2 | ||||
-rw-r--r-- | frida_mode/src/instrument/instrument_arm64.c | 2 | ||||
-rw-r--r-- | frida_mode/src/instrument/instrument_x64.c | 3 | ||||
-rw-r--r-- | frida_mode/src/instrument/instrument_x86.c | 2 | ||||
-rw-r--r-- | frida_mode/src/prefetch.c | 106 | ||||
-rw-r--r-- | frida_mode/src/stalker.c | 2 | ||||
-rw-r--r-- | frida_mode/test/dynamic/GNUmakefile | 76 | ||||
-rw-r--r-- | frida_mode/test/dynamic/Makefile | 19 | ||||
-rw-r--r-- | frida_mode/test/dynamic/testinstr.c | 98 | ||||
-rw-r--r-- | frida_mode/test/dynamic/testinstrlib.c | 14 |
10 files changed, 320 insertions, 4 deletions
diff --git a/frida_mode/GNUmakefile b/frida_mode/GNUmakefile index 0b5f52cb..014dcca5 100644 --- a/frida_mode/GNUmakefile +++ b/frida_mode/GNUmakefile @@ -116,7 +116,7 @@ ifndef OS $(error "Operating system unsupported") endif -GUM_DEVKIT_VERSION=15.1.15 +GUM_DEVKIT_VERSION=15.1.22 GUM_DEVKIT_FILENAME=frida-gumjs-devkit-$(GUM_DEVKIT_VERSION)-$(OS)-$(ARCH).tar.xz GUM_DEVKIT_URL="https://github.com/frida/frida/releases/download/$(GUM_DEVKIT_VERSION)/$(GUM_DEVKIT_FILENAME)" diff --git a/frida_mode/src/instrument/instrument_arm64.c b/frida_mode/src/instrument/instrument_arm64.c index 2bc8f8aa..fab9eee2 100644 --- a/frida_mode/src/instrument/instrument_arm64.c +++ b/frida_mode/src/instrument/instrument_arm64.c @@ -157,11 +157,13 @@ static gboolean instrument_is_deterministic(const cs_insn *from_insn) { } static void instrument_coverage_switch(GumStalkerObserver *self, + gpointer from_address, gpointer start_address, const cs_insn * from_insn, gpointer * target) { UNUSED_PARAMETER(self); + UNUSED_PARAMETER(from_address); UNUSED_PARAMETER(start_address); gsize fixup_offset; diff --git a/frida_mode/src/instrument/instrument_x64.c b/frida_mode/src/instrument/instrument_x64.c index f02c971e..a764b054 100644 --- a/frida_mode/src/instrument/instrument_x64.c +++ b/frida_mode/src/instrument/instrument_x64.c @@ -172,12 +172,13 @@ void instrument_coverage_optimize_init(void) { } static void instrument_coverage_switch(GumStalkerObserver *self, + gpointer from_address, gpointer start_address, const cs_insn * from_insn, gpointer * target) { UNUSED_PARAMETER(self); - UNUSED_PARAMETER(start_address); + UNUSED_PARAMETER(from_address); cs_x86 * x86; cs_x86_op *op; diff --git a/frida_mode/src/instrument/instrument_x86.c b/frida_mode/src/instrument/instrument_x86.c index 6a899248..1baa98ca 100644 --- a/frida_mode/src/instrument/instrument_x86.c +++ b/frida_mode/src/instrument/instrument_x86.c @@ -84,11 +84,13 @@ gboolean instrument_is_coverage_optimize_supported(void) { } static void instrument_coverage_switch(GumStalkerObserver *self, + gpointer from_address, gpointer start_address, const cs_insn * from_insn, gpointer * target) { UNUSED_PARAMETER(self); + UNUSED_PARAMETER(from_address); UNUSED_PARAMETER(start_address); cs_x86 * x86; diff --git a/frida_mode/src/prefetch.c b/frida_mode/src/prefetch.c index fa0288cc..59bfecc2 100644 --- a/frida_mode/src/prefetch.c +++ b/frida_mode/src/prefetch.c @@ -32,6 +32,8 @@ gboolean prefetch_backpatch = TRUE; static prefetch_data_t *prefetch_data = NULL; static int prefetch_shm_id = -1; +static GHashTable *cant_prefetch = NULL; + static void gum_afl_stalker_backpatcher_notify(GumStalkerObserver *self, const GumBackpatch *backpatch, gsize size) { @@ -40,6 +42,18 @@ static void gum_afl_stalker_backpatcher_notify(GumStalkerObserver *self, if (!entry_run) { return; } gsize remaining = sizeof(prefetch_data->backpatch_data) - prefetch_data->backpatch_size; + + gpointer from = gum_stalker_backpatch_get_from(backpatch); + gpointer to = gum_stalker_backpatch_get_to(backpatch); + + /* Stop reporting patches which can't be prefetched */ + if (g_hash_table_contains(cant_prefetch, GSIZE_TO_POINTER(from)) || + g_hash_table_contains(cant_prefetch, GSIZE_TO_POINTER(to))) { + + return; + + } + if (sizeof(gsize) + size > remaining) { return; } gsize *dst_backpatch_size = @@ -68,6 +82,9 @@ void prefetch_write(void *addr) { /* Bail if we aren't initialized */ if (prefetch_data == NULL) return; + /* Stop reporting blocks which can't be prefetched */ + if (g_hash_table_contains(cant_prefetch, GSIZE_TO_POINTER(addr))) { return; } + /* * Our shared memory IPC is large enough for about 1000 entries, we can fine * tune this if we need to. But if we have more new blocks that this in a @@ -84,6 +101,38 @@ void prefetch_write(void *addr) { } +typedef struct { + + GumAddress address; + gboolean executable; + +} check_executable_t; + +static gboolean prefetch_find_executable(const GumRangeDetails *details, + gpointer user_data) { + + check_executable_t *ctx = (check_executable_t *)user_data; + if (GUM_MEMORY_RANGE_INCLUDES(details->range, ctx->address)) { + + ctx->executable = TRUE; + return FALSE; + + } + + return TRUE; + +} + +static gboolean prefetch_is_executable(void *address) { + + check_executable_t ctx = {.address = GUM_ADDRESS(address), + .executable = FALSE}; + gum_process_enumerate_ranges(GUM_PAGE_EXECUTE, prefetch_find_executable, + &ctx); + return ctx.executable; + +} + static void prefetch_read_blocks(void) { GumStalker *stalker = stalker_get(); @@ -92,7 +141,24 @@ static void prefetch_read_blocks(void) { for (size_t i = 0; i < prefetch_data->count; i++) { void *addr = prefetch_data->entry[i]; - gum_stalker_prefetch(stalker, addr, 1); + + if (prefetch_is_executable(addr)) { + + gum_stalker_prefetch(stalker, addr, 1); + + } else { + + /* + * If our child process creates a new executable mapping, e.g. by + * dynamically loading a new DSO, then this won't appear in our parent + * process' memory map and hence we can't prefetch it. Add it to a + * hashtable which the child will inherit on the next fork to prevent the + * child from keep reporting it and exhausting the shared memory buffers + * used to pass new blocks from the child back to the parent. + */ + g_hash_table_add(cant_prefetch, GSIZE_TO_POINTER(addr)); + + } } @@ -125,7 +191,36 @@ static void prefetch_read_patches(void) { } backpatch = (GumBackpatch *)&prefetch_data->backpatch_data[offset]; - gum_stalker_prefetch_backpatch(stalker, backpatch); + + gpointer from = gum_stalker_backpatch_get_from(backpatch); + gpointer to = gum_stalker_backpatch_get_to(backpatch); + + /* + * If our child process creates a new executable mapping, e.g. by + * dynamically loading a new DSO, then this won't appear in our parent + * process' memory map and hence we can't prefetch it. Add it to a + * hashtable which the child will inherit on the next fork to prevent the + * child from keep reporting it and exhausting the shared memory buffers + * used to pass new blocks from the child back to the parent. + */ + if (!prefetch_is_executable(from)) { + + g_hash_table_add(cant_prefetch, GSIZE_TO_POINTER(from)); + + } + + if (!prefetch_is_executable(to)) { + + g_hash_table_add(cant_prefetch, GSIZE_TO_POINTER(to)); + + } + + if (prefetch_is_executable(from) && prefetch_is_executable(to)) { + + gum_stalker_prefetch_backpatch(stalker, backpatch); + + } + offset += size; } @@ -215,6 +310,13 @@ void prefetch_init(void) { prefetch_hook_fork(); + cant_prefetch = g_hash_table_new(g_direct_hash, g_direct_equal); + if (cant_prefetch == NULL) { + + FFATAL("Failed to g_hash_table_new, errno: %d", errno); + + } + if (!prefetch_backpatch) { return; } GumStalkerObserver * observer = stalker_get_observer(); diff --git a/frida_mode/src/stalker.c b/frida_mode/src/stalker.c index 80e4e707..ddc17b81 100644 --- a/frida_mode/src/stalker.c +++ b/frida_mode/src/stalker.c @@ -131,6 +131,8 @@ void stalker_init(void) { } + gum_stalker_activate_experimental_unwind_support(); + #if defined(__x86_64__) || defined(__i386__) stalker = g_object_new(GUM_TYPE_STALKER, "ic-entries", stalker_ic_entries, "adjacent-blocks", stalker_adjacent_blocks, NULL); diff --git a/frida_mode/test/dynamic/GNUmakefile b/frida_mode/test/dynamic/GNUmakefile new file mode 100644 index 00000000..f43416f7 --- /dev/null +++ b/frida_mode/test/dynamic/GNUmakefile @@ -0,0 +1,76 @@ +PWD:=$(shell pwd)/ +ROOT:=$(PWD)../../../ +BUILD_DIR:=$(PWD)build/ +TESTINSTR_DATA_DIR:=$(BUILD_DIR)in/ +TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)in +AFLPP_DRIVER_DUMMY_INPUT:=$(BUILD_DIR)dummy.dat + +TESTINSTBIN:=$(BUILD_DIR)testinstr +TESTINSTSRC:=$(PWD)testinstr.c + +TESTINSTLIB:=$(BUILD_DIR)testinstrlib.so +TESTINSTLIBSRC:=$(PWD)testinstrlib.c + +QEMU_OUT:=$(BUILD_DIR)qemu-out +FRIDA_OUT:=$(BUILD_DIR)frida-out + +AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so + +ADDR_BIN:=$(ROOT)frida_mode/build/addr +GET_SYMBOL_ADDR:=$(ROOT)frida_mode/util/get_symbol_addr.sh + +AFL_FRIDA_BASE_ADDR:=$(shell $(ADDR_BIN)) +AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TESTINSTBIN) testinstr $(AFL_FRIDA_BASE_ADDR)) + +CFLAGS+=-D_GNU_SOURCE=1 +LDFLAGS+=-ldl + +.PHONY: all clean qemu frida + +all: $(TESTINSTBIN) $(TESTINSTLIB) + make -C $(ROOT)frida_mode/ + +$(BUILD_DIR): + mkdir -p $@ + +$(AFLPP_DRIVER_DUMMY_INPUT): | $(BUILD_DIR) + dd if=/dev/zero bs=1048576 count=1 of=$@ + +$(TESTINSTR_DATA_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR) + echo -n "000" > $@ + +$(TESTINSTLIB): $(TESTINSTLIBSRC) | $(BUILD_DIR) + $(CC) \ + $(CFLAGS) \ + -shared \ + -o $@ \ + $(LDFLAGS) \ + $< + +$(TESTINSTBIN): $(TESTINSTSRC) | $(BUILD_DIR) + $(CC) \ + $(CFLAGS) \ + -o $@ \ + $< \ + $(LDFLAGS) \ + -Wl,-rpath,'$$ORIGIN' + +clean: + rm -rf $(BUILD_DIR) + + +frida: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE) $(AFLPP_DRIVER_DUMMY_INPUT) + AFL_FRIDA_PERSISTENT_HOOK=$(AFLPP_FRIDA_DRIVER_HOOK_OBJ) \ + AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \ + AFL_ENTRYPOINT=$(AFL_FRIDA_PERSISTENT_ADDR) \ + $(ROOT)afl-fuzz \ + -D \ + -O \ + -i $(TESTINSTR_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -- \ + $(TESTINSTBIN) $(AFLPP_DRIVER_DUMMY_INPUT) + diff --git a/frida_mode/test/dynamic/Makefile b/frida_mode/test/dynamic/Makefile new file mode 100644 index 00000000..f843af19 --- /dev/null +++ b/frida_mode/test/dynamic/Makefile @@ -0,0 +1,19 @@ +all: + @echo trying to use GNU make... + @gmake all || echo please install GNUmake + +32: + @echo trying to use GNU make... + @gmake 32 || echo please install GNUmake + +clean: + @gmake clean + +qemu: + @gmake qemu + +frida: + @gmake frida + +debug: + @gmake debug diff --git a/frida_mode/test/dynamic/testinstr.c b/frida_mode/test/dynamic/testinstr.c new file mode 100644 index 00000000..ad26d060 --- /dev/null +++ b/frida_mode/test/dynamic/testinstr.c @@ -0,0 +1,98 @@ +/* + american fuzzy lop++ - a trivial program to test the build + -------------------------------------------------------- + Originally written by Michal Zalewski + Copyright 2014 Google Inc. All rights reserved. + Copyright 2019-2022 AFLplusplus Project. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + http://www.apache.org/licenses/LICENSE-2.0 + */ +#include <dlfcn.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +typedef void (*fntestinstrlib)(char *buf, int len); + +void testinstr(char *buf, int len) { + void *lib = dlopen("testinstrlib.so", RTLD_NOW); + if (lib == NULL) { + puts("Library not found"); + abort(); + } + + fntestinstrlib fn = (fntestinstrlib)(dlsym(lib, "testinstrlib")); + if (fn == NULL) { + puts("Function not found"); + abort(); + } + + fn(buf, len); +} + +int main(int argc, char **argv) { + char * file; + int fd = -1; + off_t len; + char * buf = NULL; + size_t n_read; + int result = -1; + + if (argc != 2) { return 1; } + + do { + file = argv[1]; + printf("file: %s\n", file); + + dprintf(STDERR_FILENO, "Running: %s\n", file); + + fd = open(file, O_RDONLY); + if (fd < 0) { + perror("open"); + break; + } + + len = lseek(fd, 0, SEEK_END); + if (len < 0) { + perror("lseek (SEEK_END)"); + break; + } + + if (lseek(fd, 0, SEEK_SET) != 0) { + perror("lseek (SEEK_SET)"); + break; + } + + printf("len: %ld\n", len); + + buf = malloc(len); + if (buf == NULL) { + perror("malloc"); + break; + } + + n_read = read(fd, buf, len); + if (n_read != len) { + perror("read"); + break; + } + + dprintf(STDERR_FILENO, "Running: %s: (%zd bytes)\n", file, n_read); + + testinstr(buf, len); + dprintf(STDERR_FILENO, "Done: %s: (%zd bytes)\n", file, n_read); + + result = 0; + + } while (false); + + if (buf != NULL) { free(buf); } + + if (fd != -1) { close(fd); } + + return result; +} diff --git a/frida_mode/test/dynamic/testinstrlib.c b/frida_mode/test/dynamic/testinstrlib.c new file mode 100644 index 00000000..987cbf91 --- /dev/null +++ b/frida_mode/test/dynamic/testinstrlib.c @@ -0,0 +1,14 @@ +#include <stdio.h> + +void testinstrlib(char *buf, int len) { + if (len < 1) return; + buf[len] = 0; + + // we support three input cases + if (buf[0] == '0') + printf("Looks like a zero to me!\n"); + else if (buf[0] == '1') + printf("Pretty sure that is a one!\n"); + else + printf("Neither one or zero? How quaint!\n"); +} |