diff options
Diffstat (limited to 'frida_mode')
50 files changed, 4843 insertions, 0 deletions
diff --git a/frida_mode/MapDensity.md b/frida_mode/MapDensity.md new file mode 100644 index 00000000..f4ae3ace --- /dev/null +++ b/frida_mode/MapDensity.md @@ -0,0 +1,147 @@ +# Map Density + +# How Coverage Works +The coverage in AFL++ works by assigning each basic block of code a unique ID +and during execution when transitioning between blocks (e.g. by calls or jumps) +assigning each of these edges an ID based upon the source and destination block +ID. + +For each individual execution of the target, a single dimensional byte array +indexed by the edge ID is used to count how many times each edge is traversed. + +A single dimensional cumulative byte array is also constructed where each byte +again represents an individual edge ID, but this time, the value of the byte +represents a range of how many times that edge has been traversed. + +```1, 2, 3, 4-7, 8-15, 16-31, 32-127, 128+``` + +The theory is that a new path isn't particularly interesting if an edge has been +traversed `23` instead of `24` times for example, but is interesting if an edge +has been traversed for the very first time, or the number of times fits within a different bucket. + +After each run, the count of times each edge is hit is compared to the values in +the cumulative map and if it is different, then the input is kept as a new seed +and the cumulative map is updated. + +This mechanism is described in greater detail in the seminal +[paper](https://lcamtuf.coredump.cx/afl/technical_details.txt) on AFL by +[lcamtuf](https://github.com/lcamtuf). + +# Collisions +In black-box fuzzing, we must assume that control may flow from any block to any +other block, since we don't know any better. Thus for a target with `n` basic +blocks of code, there are `n * n` potential edges. As we can see, even with a +small number of edges, a very large map will be required so that we have space +to fit them all. Even if our target only had `1024` blocks, this would require a +map containing `1048576` entries (or 1Mb in size). + +Whilst this may not seem like a lot of memory, it causes problems for two reasons. Firstly, the processing step after each execution must now process much more +data, and secondly a map this size is unlikely to fit within the L2 cache of the processor. Since this is a very hot code path, we are likely to pay a very heavy +performance cost. + +Therefore, we must accept that not all edges can have a unique and that +therefore there will be collisions. This means that if the fuzzer finds a new +path by uncovering an edge which was not previously found, but that the same +edge ID is used by another edge, then it may go completely unnoticed. This is +obviously undesirable, but equally if our map is too large, then we will not be +able to process as many potential inputs in the same time and hence not uncover +edges for that reason. Thus a careful trade-off of map size must be made. + +# Block & Edge Numbering +Since the original AFL, blocks and edges have always been numbered in the same +way as we can see from the following C snippet from the whitepaper. + +```c + cur_location = (block_address >> 4) ^ (block_address << 8); + shared_mem[cur_location ^ prev_location]++; + prev_location = cur_location >> 1; + +``` + +Each block ID is generated by performing a shift and XOR on its address. Then +the edge ID is calculated as `E = B ^ (B' >> 1)`. Here, we can make two +observations. In fact, the edge ID is also masked to ensure it is less than the +size of the map being used. + +## Block IDs +Firstly, the block ID doesn't have very good entropy. If we consider the address +of the block, then whilst each block has a unique ID, it isn't necessarily very +evenly distributed. + +We start with a large address, and need to discard a large number of the bits to +generate a block ID which is within range. But how do we choose the unique bits +of the address verus those which are the same for every block? The high bits of +the address may simply be all `0s` or all `1s` to make the address cannonical, +the middle portion of the address may be the same for all blocks (since if they +are all within the same binary, then they will all be adjacent in memory), and +on some systems, even the low bits may have poor entropy as some use fixed +length aligned instructions. Then we need to consider that a portion of each +binary may contain the `.data` or `.bss` sections and so may not contain any +blocks of code at all. + +## Edge IDs +Secondly, we can observe that when we generate an edge ID from the source and +destination block IDs, we perform a right shift on the source block ID. Whilst +there are good reasons as set out in the whitepaper why such a transform is +applied, in so doing, we dispose of `1` bit of precious entropy in our source +block ID. + +All together, this means that some edge IDs may be more popular than others. +This means that some portions of the map may be very densly populated with large +numbers of edges, whilst others may be very sparsely populated, or not populated +at all. + +# Improvements +One of the main reaons why this algorithm selected, is performance. All of the +operations are very quick to perform and given we may be carrying this out for +every block of code we execute, performance is critical. + +However, the design of the binary instrumentation modes of AFL++ has moved on. +Both QEMU and FRIDA modes use a two stage process when executing a target +application. Each block is first compiled or instrumented, and then it is +executed. The compiled blocks can be re-used each time the target executes them. + +Since a blocks ID is based on its address, and this is known at compile time, we +only need to generate this ID once per block and so this ID generation no longer +needs to be as performant. We can therefore use a hash algorithm to generate +this ID and therefore ensure that the block IDs are more evenly distributed. + +Edge IDs however, can only be determined at run-time. Since we don't know which +blocks a given input will traverse until we run it. However, given our block IDs +are now evenly distributed, generating an evenly distributed edge ID becomes +simple. Here, the only change we make is to use a rotate operation rather than +a shift operation so we don't lose a bit of entropy from the source ID. + +So our new algorithm becomes: +```c + cur_location = hash(block_address) + shared_mem[cur_location ^ prev_location]++; + prev_location = rotate(cur_location, 1); +``` + +Lastly, in the original design, the `cur_location` was always set to `0`, at the +beginning of a run, we instead set the value of `cur_location` to `hash(0)`. + +# Parallel Fuzzing +Another sub-optimal aspect of the original design is that no matter how many +instances of the fuzzer you ran in parallel, each instance numbered each block +and so each edge with the same ID. Each instance would therefore find the same +subset of edges collide with each other. In the event of a collision, all +instances will hit the same road block. + +However, if we instead use a different seed for our hashing function for each +instance, then each will ascribe each block a different ID and hence each edge +will be given a different edge ID. This means that whilst one instance of the +fuzzer may find a given pair of edges collide, it is very unlikely that another +instance will find the same pair also collide. + +Due to the collaborative nature of parallel fuzzing, this means that whilst one +instance may struggle to find a particular new path because the new edge +collides, another instance will likely not encounter the same collision and thus +be able to differentiate this new path and share it with the other instances. + +If only a single new edge is found, and the new path is shared with an instance +for which that edge collides, that instance may disregard it as irrelevant. In +practice, however, the discovery of a single new edge, likely leads to several +more edges beneath it also being found and therefore the likelihood of all of +these being collisions is very slim. diff --git a/frida_mode/Scripting.md b/frida_mode/Scripting.md new file mode 100644 index 00000000..5467db99 --- /dev/null +++ b/frida_mode/Scripting.md @@ -0,0 +1,863 @@ +# Scripting +FRIDA now supports the ability to configure itself using JavaScript. This allows +the user to make use of the convenience of FRIDA's scripting engine (along with +it's support for debug symbols and exports) to configure all of the things which +were traditionally configured using environment variables. + +By default FRIDA mode will look for the file `afl.js` in the current working +directory of the target. Alternatively, a script file can be configured using +the environment variable `AFL_FRIDA_JS_SCRIPT`. + +This script can make use of all of the standard [frida api functions](https://frida.re/docs/javascript-api/), but FRIDA mode adds some additional functions to allow +you to interact with FRIDA mode itself. These can all be accessed via the global +`Afl` parameter. e.g. `Afl.print("HELLO WORLD");`, + +If you encounter a problem with your script, then you should set the environment +variable `AFL_DEBUG_CHILD=1` to view any diagnostic information. + + +# Example +Most of the time, users will likely be wanting to call the functions which configure an address (e.g. for the entry point, or the persistent address). + +The example below uses the API [`DebugSymbol.fromName()`](https://frida.re/docs/javascript-api/#debugsymbol). Another use API is [`Module.getExportByName()`](https://frida.re/docs/javascript-api/#module). + +```js +/* Use Afl.print instead of console.log */ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +/* Print some useful diagnostics stuff */ +Afl.print(`PID: ${Process.id}`); + +new ModuleMap().values().forEach(m => { + Afl.print(`${m.base}-${m.base.add(m.size)} ${m.name}`); +}); + +/* + * Configure entry-point, persistence etc. This will be what most + * people want to do. + */ +const persistent_addr = DebugSymbol.fromName('main'); +Afl.print(`persistent_addr: ${persistent_addr.address}`); + +if (persistent_addr.address.equals(ptr(0))) { + Afl.error('Cannot find symbol main'); +} + +const persistent_ret = DebugSymbol.fromName('slow'); +Afl.print(`persistent_ret: ${persistent_ret.address}`); + +if (persistent_ret.address.equals(ptr(0))) { + Afl.error('Cannot find symbol slow'); +} + +Afl.setPersistentAddress(persistent_addr.address); +Afl.setPersistentReturn(persistent_ret.address); +Afl.setPersistentCount(1000000); + +/* Control instrumentation, you may want to do this too */ +Afl.setInstrumentLibraries(); +const mod = Process.findModuleByName("libc-2.31.so") +Afl.addExcludedRange(mod.base, mod.size); + +/* Some useful options to configure logging */ +Afl.setStdOut("/tmp/stdout.txt"); +Afl.setStdErr("/tmp/stderr.txt"); + +/* Show the address layout. Sometimes helpful */ +Afl.setDebugMaps(); + +/* + * If you are using these options, then things aren't going + * very well for you. + */ +Afl.setInstrumentDebugFile("/tmp/instr.log"); +Afl.setPrefetchDisable(); +Afl.setInstrumentNoOptimize(); +Afl.setInstrumentEnableTracing(); +Afl.setInstrumentTracingUnique(); +Afl.setStatsFile("/tmp/stats.txt"); +Afl.setStatsInterval(1); +Afl.setStatsTransitions(); + +/* *ALWAYS* call this when you have finished all your configuration */ +Afl.done(); +Afl.print("done"); +``` + +# Stripped Binaries + +Lastly, if the binary you attempting to fuzz has no symbol information, and no +exports, then the following approach can be used. + +```js +const module = Process.getModuleByName('target.exe'); +/* Hardcoded offset within the target image */ +const address = module.base.add(0xdeadface); +Afl.setPersistentAddress(address); +``` + +# Persisent Hook +A persistent hook can be implemented using a conventional shared object, sample +source code for a hook suitable for the prototype of `LLVMFuzzerTestOneInput` +can be found [here](hook/hook.c). This can be configured using code similar to +the following. + +```js +const path = Afl.module.path; +const dir = path.substring(0, path.lastIndexOf("/")); +const mod = Module.load(`${dir}/frida_mode/build/hook.so`); +const hook = mod.getExportByName('afl_persistent_hook'); +Afl.setPersistentHook(hook); +``` + +Alternatively, the hook can be provided by using FRIDAs built in support for `CModule`, powered by TinyCC. + +```js +const cm = new CModule(` + + #include <string.h> + #include <gum/gumdefs.h> + + void afl_persistent_hook(GumCpuContext *regs, uint8_t *input_buf, + uint32_t input_buf_len) { + + memcpy((void *)regs->rdi, input_buf, input_buf_len); + regs->rsi = input_buf_len; + + } + `, + { + memcpy: Module.getExportByName(null, 'memcpy') + }); +Afl.setPersistentHook(cm.afl_persistent_hook); +``` + +# Advanced Persistence +Consider the following target code... +```c + +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +void LLVMFuzzerTestOneInput(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"); + +} + +int run(char *file) { + + int fd = -1; + off_t len; + char * buf = NULL; + size_t n_read; + int result = -1; + + do { + + 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; + + } + + 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); + + LLVMFuzzerTestOneInput(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; + +} + +void slow() { + + usleep(100000); + +} + +int main(int argc, char **argv) { + + if (argc != 2) { return 1; } + slow(); + return run(argv[1]); + +} +``` + +FRIDA mode supports the replacement of any function, with an implementation +generated by CModule. This allows for a bespoke harness to be written as +follows: + +``` +const slow = DebugSymbol.fromName('slow').address; +Afl.print(`slow: ${slow}`); + +const LLVMFuzzerTestOneInput = DebugSymbol.fromName('LLVMFuzzerTestOneInput').address; +Afl.print(`LLVMFuzzerTestOneInput: ${LLVMFuzzerTestOneInput}`); + +const cm = new CModule(` + + extern unsigned char * __afl_fuzz_ptr; + extern unsigned int * __afl_fuzz_len; + extern void LLVMFuzzerTestOneInput(char *buf, int len); + + void slow(void) { + + LLVMFuzzerTestOneInput(__afl_fuzz_ptr, *__afl_fuzz_len); + } + `, + { + LLVMFuzzerTestOneInput: LLVMFuzzerTestOneInput, + __afl_fuzz_ptr: Afl.getAflFuzzPtr(), + __afl_fuzz_len: Afl.getAflFuzzLen() + }); + +Afl.setEntryPoint(cm.slow); +Afl.setPersistentAddress(cm.slow); +Afl.setInMemoryFuzzing(); +Interceptor.replace(slow, cm.slow); +Afl.print("done"); +Afl.done(); +``` + +Here, we replace the function `slow` with our own code. This code is then +selected as the entry point as well as the persistent loop address. + +**WARNING** There are two key limitations in replacing a function in this way: +- The function which is to be replaced must not be `main` this is because this +is the point at which FRIDA mode is initialized and at the point the the JS has +been run, the start of the `main` function has already been instrumented and +cached. +- The replacement function must not call itself. e.g. in this example we +couldn't replace `LLVMFuzzerTestOneInput` and call itself. + +# Patching +Consider the [following](test/js/test2.c) test code... + +```c +/* + american fuzzy lop++ - a trivial program to test the build + -------------------------------------------------------- + Originally written by Michal Zalewski + Copyright 2014 Google Inc. All rights reserved. + Copyright 2019-2020 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 <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +const uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + + ... + + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +uint32_t +crc32(const void *buf, size_t size) +{ + const uint8_t *p = buf; + uint32_t crc; + crc = ~0U; + while (size--) + crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + return crc ^ ~0U; +} + +/* + * Don't you hate those contrived examples which CRC their data. We can use + * FRIDA to patch this function out and always return success. Otherwise, we + * could change it to actually correct the checksum. + */ +int crc32_check (char * buf, int len) { + if (len < sizeof(uint32_t)) { return 0; } + uint32_t expected = *(uint32_t *)&buf[len - sizeof(uint32_t)]; + uint32_t calculated = crc32(buf, len - sizeof(uint32_t)); + return expected == calculated; +} + +/* + * So you've found a really boring bug in an earlier campaign which results in + * a NULL dereference or something like that. That bug can get in the way, + * causing the persistent loop to exit whenever it is triggered, and can also + * cloud your output unnecessarily. Again, we can use FRIDA to patch it out. + */ +void some_boring_bug(char c) { + switch (c) { + case 'A'...'Z': + case 'a'...'z': + __builtin_trap(); + break; + } +} + +void LLVMFuzzerTestOneInput(char *buf, int len) { + + if (!crc32_check(buf, len)) return; + + some_boring_bug(buf[0]); + + 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 if (buf[0] == '2') { + if (buf[1] == '3') { + if (buf[2] == '4') { + printf("Oh we, weren't expecting that!"); + __builtin_trap(); + } + } + } + else + printf("Neither one or zero? How quaint!\n"); + +} + +int main(int argc, char **argv) { + + int fd = -1; + off_t len; + char * buf = NULL; + size_t n_read; + int result = -1; + + if (argc != 2) { return 1; } + + printf("Running: %s\n", argv[1]); + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { return 1; } + + len = lseek(fd, 0, SEEK_END); + if (len < 0) { return 1; } + + if (lseek(fd, 0, SEEK_SET) != 0) { return 1; } + + buf = malloc(len); + if (buf == NULL) { return 1; } + + n_read = read(fd, buf, len); + if (n_read != len) { return 1; } + + printf("Running: %s: (%zd bytes)\n", argv[1], n_read); + + LLVMFuzzerTestOneInput(buf, len); + printf("Done: %s: (%zd bytes)\n", argv[1], n_read); + + return 0; +} +``` + +There are a couple of obstacles with our target application. Unlike when fuzzing +source code, though, we can't simply edit it and recompile it. The following +script shows how we can use the normal functionality of FRIDA to modify any +troublesome behaviour. + +```js +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +const main = DebugSymbol.fromName('main').address; +Afl.print(`main: ${main}`); +Afl.setEntryPoint(main); +Afl.setPersistentAddress(main); +Afl.setPersistentCount(10000000); + +const crc32_check = DebugSymbol.fromName('crc32_check').address; +const crc32_replacement = new NativeCallback( + (buf, len) => { + Afl.print(`len: ${len}`); + if (len < 4) { + return 0; + } + + return 1; + }, + 'int', + ['pointer', 'int']); +Interceptor.replace(crc32_check, crc32_replacement); + +const some_boring_bug = DebugSymbol.fromName('some_boring_bug').address +const boring_replacement = new NativeCallback( + (c) => { }, + 'void', + ['char']); +Interceptor.replace(some_boring_bug, boring_replacement); + +Afl.done(); +Afl.print("done"); +``` + +# Advanced Patching +Consider the following code fragment... +```c +extern void some_boring_bug2(char c); + +__asm__ ( + ".text \n" + "some_boring_bug2: \n" + ".global some_boring_bug2 \n" + ".type some_boring_bug2, @function \n" + "mov %edi, %eax \n" + "cmp $0xb4, %al \n" + "jne ok \n" + "ud2 \n" + "ok: \n" + "ret \n"); + +void LLVMFuzzerTestOneInput(char *buf, int len) { + + ... + + some_boring_bug2(buf[0]); + + ... + +} +``` + +Rather than using FRIDAs `Interceptor.replace` or `Interceptor.attach` APIs, it +is possible to apply much more fine grained modification to the target +application by means of using the Stalker APIs. + +The following code locates the function of interest and patches out the UD2 +instruction signifying a crash. + +```js +/* Modify the instructions */ +const some_boring_bug2 = DebugSymbol.fromName('some_boring_bug2').address +const pid = Memory.alloc(4); +pid.writeInt(Process.id); + +const cm = new CModule(` + #include <stdio.h> + #include <gum/gumstalker.h> + + typedef int pid_t; + + #define STDERR_FILENO 2 + #define BORING2_LEN 10 + + extern int dprintf(int fd, const char *format, ...); + extern void some_boring_bug2(char c); + extern pid_t getpid(void); + extern pid_t pid; + + gboolean js_stalker_callback(const cs_insn *insn, gboolean begin, + gboolean excluded, GumStalkerOutput *output) + { + pid_t my_pid = getpid(); + GumX86Writer *cw = output->writer.x86; + + if (GUM_ADDRESS(insn->address) < GUM_ADDRESS(some_boring_bug2)) { + + return TRUE; + + } + + if (GUM_ADDRESS(insn->address) >= + GUM_ADDRESS(some_boring_bug2) + BORING2_LEN) { + + return TRUE; + + } + + if (my_pid == pid) { + + if (begin) { + + dprintf(STDERR_FILENO, "\n> 0x%016lX: %s %s\n", insn->address, + insn->mnemonic, insn->op_str); + + } else { + + dprintf(STDERR_FILENO, " 0x%016lX: %s %s\n", insn->address, + insn->mnemonic, insn->op_str); + + } + + } + + if (insn->id == X86_INS_UD2) { + + gum_x86_writer_put_nop(cw); + return FALSE; + + } else { + + return TRUE; + + } + } + `, + { + dprintf: Module.getExportByName(null, 'dprintf'), + getpid: Module.getExportByName(null, 'getpid'), + some_boring_bug2: some_boring_bug2, + pid: pid + }); +Afl.setStalkerCallback(cm.js_stalker_callback) +Afl.setStdErr("/tmp/stderr.txt"); +``` + +Note that you will more likely want to find the +patch address by using: + +```js +const module = Process.getModuleByName('target.exe'); +/* Hardcoded offset within the target image */ +const address = module.base.add(0xdeadface); +``` +OR +``` +const address = DebugSymbol.fromName("my_function").address.add(0xdeadface); +``` +OR +``` +const address = Module.getExportByName(null, "my_function").add(0xdeadface); +``` + +The function `js_stalker_callback` should return `TRUE` if the original +instruction should be emitted in the instrumented code, or `FALSE` otherwise. +In the example above, we can see it is replaced with a `NOP`. + +Lastly, note that the same callback will be called when compiling instrumented +code both in the child of the forkserver (as it is executed) and also in the +parent of the forserver (when prefetching is enabled) so that it can be +inherited by the next forked child. It is **VERY** important that the same +instructions be generated in both the parent and the child, or if prefetching is +disabled that the same instructions are generated every time the block is +compiled. Failure to do so will likely lead to bugs which are incredibly +difficult to diagnose. The code above only prints the instructions when running +in the parent process (the one provided by `Process.id` when the JS script is +executed). + +# OSX +Note that the JavaScript debug symbol api for OSX makes use of the +`CoreSymbolication` APIs and as such the `CoreFoundation` module must be loaded +into the target to make use of it. This can be done by setting: + +``` +AFL_PRELOAD=/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation +``` + +It should be noted that `CoreSymbolication` API may take a while to initialize +and build its caches. For this reason, it may be nescessary to also increase the +value of the `-t` flag passed to `afl-fuzz`. + +# API +```js +class Afl { + + /** + * Field containing the `Module` object for `afl-frida-trace.so` (the FRIDA mode + * implementation). + */ + public static module: Module = Process.getModuleByName("afl-frida-trace.so"); + + /** + * This is equivalent to setting a value in `AFL_FRIDA_EXCLUDE_RANGES`, + * it takes as arguments a `NativePointer` and a `number`. It can be + * called multiple times to exclude several ranges. + */ + public static addExcludedRange(addressess: NativePointer, size: number): void { + Afl.jsApiAddExcludeRange(addressess, size); + } + + /** + * This is equivalent to setting a value in `AFL_FRIDA_INST_RANGES`, + * it takes as arguments a `NativePointer` and a `number`. It can be + * called multiple times to include several ranges. + */ + public static addIncludedRange(addressess: NativePointer, size: number): void { + Afl.jsApiAddIncludeRange(addressess, size); + } + + /** + * This must always be called at the end of your script. This lets + * FRIDA mode know that your configuration is finished and that + * execution has reached the end of your script. Failure to call + * this will result in a fatal error. + */ + public static done(): void { + Afl.jsApiDone(); + } + + /** + * This function can be called within your script to cause FRIDA + * mode to trigger a fatal error. This is useful if for example you + * discover a problem you weren't expecting and want everything to + * stop. The user will need to enable `AFL_DEBUG_CHILD=1` to view + * this error message. + */ + public static error(msg: string): void { + const buf = Memory.allocUtf8String(msg); + Afl.jsApiError(buf); + } + + /** + * Function used to provide access to `__afl_fuzz_ptr`, which contains the length of + * fuzzing data when using in-memory test case fuzzing. + */ + public static getAflFuzzLen(): NativePointer { + + return Afl.jsApiGetSymbol("__afl_fuzz_len"); + } + + /** + * Function used to provide access to `__afl_fuzz_ptr`, which contains the fuzzing + * data when using in-memory test case fuzzing. + */ + public static getAflFuzzPtr(): NativePointer { + + return Afl.jsApiGetSymbol("__afl_fuzz_ptr"); + } + + /** + * Print a message to the STDOUT. This should be preferred to + * FRIDA's `console.log` since FRIDA will queue it's log messages. + * If `console.log` is used in a callback in particular, then there + * may no longer be a thread running to service this queue. + */ + public static print(msg: string): void { + const STDOUT_FILENO = 2; + const log = `${msg}\n`; + const buf = Memory.allocUtf8String(log); + Afl.jsApiWrite(STDOUT_FILENO, buf, log.length); + } + + /** + * See `AFL_FRIDA_DEBUG_MAPS`. + */ + public static setDebugMaps(): void { + Afl.jsApiSetDebugMaps(); + } + + /** + * This has the same effect as setting `AFL_ENTRYPOINT`, but has the + * convenience of allowing you to use FRIDAs APIs to determine the + * address you would like to configure, rather than having to grep + * the output of `readelf` or something similarly ugly. This + * function should be called with a `NativePointer` as its + * argument. + */ + public static setEntryPoint(address: NativePointer): void { + Afl.jsApiSetEntryPoint(address); + } + + /** + * Function used to enable in-memory test cases for fuzzing. + */ + public static setInMemoryFuzzing(): void { + Afl.jsApiAflSharedMemFuzzing.writeInt(1); + } + + /** + * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as + * an argument. + */ + public static setInstrumentDebugFile(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetInstrumentDebugFile(buf); + } + + /** + * See `AFL_FRIDA_INST_TRACE`. + */ + public static setInstrumentEnableTracing(): void { + Afl.jsApiSetInstrumentTrace(); + } + + /** + * See `AFL_INST_LIBS`. + */ + public static setInstrumentLibraries(): void { + Afl.jsApiSetInstrumentLibraries(); + } + + /** + * See `AFL_FRIDA_INST_NO_OPTIMIZE` + */ + public static setInstrumentNoOptimize(): void { + Afl.jsApiSetInstrumentNoOptimize(); + } + + /** + * See `AFL_FRIDA_INST_TRACE_UNIQUE`. + */ + public static setInstrumentTracingUnique(): void { + Afl.jsApiSetInstrumentTraceUnique(); + } + + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_ADDR`, again a + * `NativePointer` should be provided as it's argument. + */ + public static setPersistentAddress(address: NativePointer): void { + Afl.jsApiSetPersistentAddress(address); + } + + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_CNT`, a + * `number` should be provided as it's argument. + */ + public static setPersistentCount(count: number): void { + Afl.jsApiSetPersistentCount(count); + } + + /** + * See `AFL_FRIDA_PERSISTENT_DEBUG`. + */ + public static setPersistentDebug(): void { + Afl.jsApiSetPersistentDebug(); + } + + /** + * See `AFL_FRIDA_PERSISTENT_ADDR`. This function takes a NativePointer as an + * argument. See above for examples of use. + */ + public static setPersistentHook(address: NativePointer): void { + Afl.jsApiSetPersistentHook(address); + } + + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_RET`, again a + * `NativePointer` should be provided as it's argument. + */ + public static setPersistentReturn(address: NativePointer): void { + Afl.jsApiSetPersistentReturn(address); + } + + /** + * See `AFL_FRIDA_INST_NO_PREFETCH`. + */ + public static setPrefetchDisable(): void { + Afl.jsApiSetPrefetchDisable(); + } + + /* + * Set a function to be called for each instruction which is instrumented + * by AFL FRIDA mode. + */ + public static setStalkerCallback(callback: NativePointer): void { + Afl.jsApiSetStalkerCallback(callback); + } + + /** + * See `AFL_FRIDA_STATS_FILE`. This function takes a single `string` as + * an argument. + */ + public static setStatsFile(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStatsFile(buf); + } + + /** + * See `AFL_FRIDA_STATS_INTERVAL`. This function takes a `number` as an + * argument + */ + public static setStatsInterval(interval: number): void { + Afl.jsApiSetStatsInterval(interval); + } + + /** + * See `AFL_FRIDA_STATS_TRANSITIONS` + */ + public static setStatsTransitions(): void { + Afl.jsApiSetStatsTransitions(); + } + + /** + * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as + * an argument. + */ + public static setStdErr(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStdErr(buf); + } + + /** + * See `AFL_FRIDA_OUTPUT_STDOUT`. This function takes a single `string` as + * an argument. + */ + public static setStdOut(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStdOut(buf); + } + +} + +``` diff --git a/frida_mode/frida.map b/frida_mode/frida.map new file mode 100644 index 00000000..7223d50e --- /dev/null +++ b/frida_mode/frida.map @@ -0,0 +1,35 @@ +{ + global: + __afl_fuzz_len; + __afl_fuzz_ptr; + __afl_sharedmem_fuzzing; + afl_frida_start; + js_api_add_exclude_range; + js_api_add_include_range; + js_api_done; + js_api_error; + js_api_set_debug_maps; + js_api_set_entrypoint; + js_api_set_instrument_debug_file; + js_api_set_instrument_jit; + js_api_set_instrument_libraries; + js_api_set_instrument_no_optimize; + js_api_set_instrument_seed; + js_api_set_instrument_trace; + js_api_set_instrument_trace_unique; + js_api_set_persistent_address; + js_api_set_persistent_count; + js_api_set_persistent_debug; + js_api_set_persistent_hook; + js_api_set_persistent_return; + js_api_set_prefetch_disable; + js_api_set_stalker_callback; + js_api_set_stats_file; + js_api_set_stats_interval; + js_api_set_stats_transitions; + js_api_set_stderr; + js_api_set_stdout; + + local: + *; +}; diff --git a/frida_mode/hook/frida_hook.c b/frida_mode/hook/frida_hook.c new file mode 100644 index 00000000..3bfdb207 --- /dev/null +++ b/frida_mode/hook/frida_hook.c @@ -0,0 +1,64 @@ +/* + * + * Modify this file to set the right registers with the fuzz input and length. + * It is a good idea to check input_buf_len to be not larger than the + * destination buffer! + * + */ + +#include <stdint.h> +#include <string.h> + +#include "frida-gumjs.h" + +#if defined(__x86_64__) + +__attribute__((visibility("default"))) void afl_persistent_hook( + GumCpuContext *regs, uint8_t *input_buf, uint32_t input_buf_len) { + + // do a length check matching the target! + + memcpy((void *)regs->rdi, input_buf, input_buf_len); + regs->rsi = input_buf_len; + +} + +#elif defined(__i386__) + +__attribute__((visibility("default"))) void afl_persistent_hook( + GumCpuContext *regs, uint8_t *input_buf, uint32_t input_buf_len) { + + // do a length check matching the target! + + void **esp = (void **)regs->esp; + void * arg1 = esp[0]; + void **arg2 = &esp[1]; + memcpy(arg1, input_buf, input_buf_len); + *arg2 = (void *)input_buf_len; + +} + +#elif defined(__aarch64__) + +__attribute__((visibility("default"))) void afl_persistent_hook( + GumCpuContext *regs, uint8_t *input_buf, uint32_t input_buf_len) { + + // do a length check matching the target! + + memcpy((void *)regs->x[0], input_buf, input_buf_len); + regs->x[1] = input_buf_len; + +} + +#else + #pragma error "Unsupported architecture" +#endif + +__attribute__((visibility("default"))) int afl_persistent_hook_init(void) { + + // 1 for shared memory input (faster), 0 for normal input (you have to use + // read(), input_buf will be NULL) + return 1; + +} + diff --git a/frida_mode/hook/qemu_hook.c b/frida_mode/hook/qemu_hook.c new file mode 100644 index 00000000..56e787e3 --- /dev/null +++ b/frida_mode/hook/qemu_hook.c @@ -0,0 +1,195 @@ +#include <stdint.h> +#include <string.h> + +#if defined(__x86_64__) + +struct x86_64_regs { + + uint64_t rax, rbx, rcx, rdx, rdi, rsi, rbp, r8, r9, r10, r11, r12, r13, r14, + r15; + + union { + + uint64_t rip; + uint64_t pc; + + }; + + union { + + uint64_t rsp; + uint64_t sp; + + }; + + union { + + uint64_t rflags; + uint64_t flags; + + }; + + uint8_t zmm_regs[32][64]; + +}; + +void afl_persistent_hook(struct x86_64_regs *regs, uint64_t guest_base, + uint8_t *input_buf, uint32_t input_buf_len) { + + (void)guest_base; /* unused */ + memcpy((void *)regs->rdi, input_buf, input_buf_len); + regs->rsi = input_buf_len; + +} + +#elif defined(__i386__) + +struct x86_regs { + + uint32_t eax, ebx, ecx, edx, edi, esi, ebp; + + union { + + uint32_t eip; + uint32_t pc; + + }; + + union { + + uint32_t esp; + uint32_t sp; + + }; + + union { + + uint32_t eflags; + uint32_t flags; + + }; + + uint8_t xmm_regs[8][16]; + +}; + +void afl_persistent_hook(struct x86_regs *regs, uint64_t guest_base, + uint8_t *input_buf, uint32_t input_buf_len) { + + (void)guest_base; /* unused */ + void **esp = (void **)regs->esp; + void * arg1 = esp[1]; + void **arg2 = &esp[2]; + memcpy(arg1, input_buf, input_buf_len); + *arg2 = (void *)input_buf_len; + +} +#elif defined(__aarch64__) + +struct arm64_regs { + + uint64_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10; + + union { + + uint64_t x11; + uint32_t fp_32; + + }; + + union { + + uint64_t x12; + uint32_t ip_32; + + }; + + union { + + uint64_t x13; + uint32_t sp_32; + + }; + + union { + + uint64_t x14; + uint32_t lr_32; + + }; + + union { + + uint64_t x15; + uint32_t pc_32; + + }; + + union { + + uint64_t x16; + uint64_t ip0; + + }; + + union { + + uint64_t x17; + uint64_t ip1; + + }; + + uint64_t x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28; + + union { + + uint64_t x29; + uint64_t fp; + + }; + + union { + + uint64_t x30; + uint64_t lr; + + }; + + union { + + uint64_t x31; + uint64_t sp; + + }; + + // the zero register is not saved here ofc + + uint64_t pc; + + uint32_t cpsr; + + uint8_t vfp_zregs[32][16 * 16]; + uint8_t vfp_pregs[17][32]; + uint32_t vfp_xregs[16]; + +}; + +void afl_persistent_hook(struct arm64_regs *regs, uint64_t guest_base, + uint8_t *input_buf, uint32_t input_buf_len) { + + (void)guest_base; /* unused */ + memcpy((void *)regs->x0, input_buf, input_buf_len); + regs->x1 = input_buf_len; +} + +#else + #pragma error "Unsupported architecture" +#endif + +int afl_persistent_hook_init(void) { + + // 1 for shared memory input (faster), 0 for normal input (you have to use + // read(), input_buf will be NULL) + return 1; + +} diff --git a/frida_mode/include/intercept.h b/frida_mode/include/intercept.h new file mode 100644 index 00000000..8fe93b10 --- /dev/null +++ b/frida_mode/include/intercept.h @@ -0,0 +1,11 @@ +#ifndef _INTERCEPTOR_H +#define _INTERCEPTOR_H + +#include "frida-gumjs.h" + +void intercept_hook(void *address, gpointer replacement, gpointer user_data); +void intercept_unhook(void *address); +void intercept_unhook_self(void); + +#endif + diff --git a/frida_mode/include/js.h b/frida_mode/include/js.h new file mode 100644 index 00000000..a5ecb712 --- /dev/null +++ b/frida_mode/include/js.h @@ -0,0 +1,26 @@ +#ifndef _JS_H +#define _JS_H + +#include "frida-gumjs.h" + +typedef gboolean (*js_api_stalker_callback_t)(const cs_insn *insn, + gboolean begin, gboolean excluded, + GumStalkerOutput *output); + +extern unsigned char api_js[]; +extern unsigned int api_js_len; + +extern gboolean js_done; +extern js_api_stalker_callback_t js_user_callback; + +/* Frida Mode */ + +void js_config(void); + +void js_start(void); + +gboolean js_stalker_callback(const cs_insn *insn, gboolean begin, + gboolean excluded, GumStalkerOutput *output); + +#endif + diff --git a/frida_mode/many-linux/Dockerfile b/frida_mode/many-linux/Dockerfile new file mode 100644 index 00000000..2cd56bc8 --- /dev/null +++ b/frida_mode/many-linux/Dockerfile @@ -0,0 +1,24 @@ +FROM fridadotre/manylinux-x86_64 + +COPY realpath /bin/realpath +RUN chmod +x /bin/realpath + +RUN yum -y install xz +RUN yum -y install vim-common + +WORKDIR / +RUN git clone https://github.com/AFLplusplus/AFLplusplus.git + +WORKDIR /AFLplusplus +RUN mkdir -p /AFLplusplus/frida_mode/build/frida/ +RUN curl -L -o /AFLplusplus/frida_mode/build/frida/frida-gumjs-devkit-15.0.0-linux-x86_64.tar.xz "https://github.com/frida/frida/releases/download/15.0.0/frida-gumjs-devkit-15.0.0-linux-x86_64.tar.xz" + +WORKDIR /AFLplusplus +RUN git checkout dev +WORKDIR /AFLplusplus/frida_mode +ENV CFLAGS="\ + -DADDR_NO_RANDOMIZE=0x0040000 \ + -Wno-implicit-function-declaration \ + " +ENV CXX=$CC +RUN make diff --git a/frida_mode/many-linux/GNUmakefile b/frida_mode/many-linux/GNUmakefile new file mode 100644 index 00000000..2860f20c --- /dev/null +++ b/frida_mode/many-linux/GNUmakefile @@ -0,0 +1,21 @@ +PWD:=$(shell pwd)/ +BUILD_DIR:=$(PWD)build/ + +.PHONY: all clean shell + +all: | $(BUILD_DIR) + docker build --tag many-afl-frida . + docker run --rm \ + -v $(PWD)build/:/export \ + many-afl-frida \ + cp /AFLplusplus/afl-frida-trace.so /export + +$(BUILD_DIR): + mkdir -p $@ + +clean: + rm -rf $(BUILD_DIR) + docker images --filter 'dangling=true' -q --no-trunc | xargs -L1 docker rmi --force + +shell: + docker run -ti --rm many-afl-frida /bin/bash diff --git a/frida_mode/many-linux/Makefile b/frida_mode/many-linux/Makefile new file mode 100644 index 00000000..f3c3cd55 --- /dev/null +++ b/frida_mode/many-linux/Makefile @@ -0,0 +1,9 @@ +all: + @echo trying to use GNU make... + @gmake all || echo please install GNUmake + +clean: + @gmake clean + +shell: + @gmake shell diff --git a/frida_mode/many-linux/README.md b/frida_mode/many-linux/README.md new file mode 100644 index 00000000..2c7b6823 --- /dev/null +++ b/frida_mode/many-linux/README.md @@ -0,0 +1,8 @@ +# many-linux + +This folder contains a Docker image to allow the building of +`afl-frida-trace.so` using the `many-linux` docker image. This docker image is +based on CentOS Linux 5. By building `afl-frida-trace.so` for such an old +version of Linux, given the strong backward compatibility of Linux, this should +work on the majority of Linux environments. This may be useful for targetting +Linux distributions other than your development environment. \ No newline at end of file diff --git a/frida_mode/many-linux/realpath b/frida_mode/many-linux/realpath new file mode 100644 index 00000000..1fdc49a7 --- /dev/null +++ b/frida_mode/many-linux/realpath @@ -0,0 +1,2 @@ +#!/bin/sh +readlink -f -- "$@" diff --git a/frida_mode/src/asan/asan_arm32.c b/frida_mode/src/asan/asan_arm32.c new file mode 100644 index 00000000..f5fa4713 --- /dev/null +++ b/frida_mode/src/asan/asan_arm32.c @@ -0,0 +1,28 @@ +#include "frida-gumjs.h" + +#include "debug.h" + +#include "asan.h" +#include "util.h" + +#if defined(__arm__) +void asan_instrument(const cs_insn *instr, GumStalkerIterator *iterator) { + + UNUSED_PARAMETER(instr); + UNUSED_PARAMETER(iterator); + if (asan_initialized) { + + FATAL("ASAN mode not supported on this architecture"); + + } + +} + +void asan_arch_init(void) { + + FATAL("ASAN mode not supported on this architecture"); + +} + +#endif + diff --git a/frida_mode/src/cmplog/cmplog_arm32.c b/frida_mode/src/cmplog/cmplog_arm32.c new file mode 100644 index 00000000..ac703408 --- /dev/null +++ b/frida_mode/src/cmplog/cmplog_arm32.c @@ -0,0 +1,19 @@ +#include "frida-gumjs.h" + +#include "debug.h" + +#include "frida_cmplog.h" +#include "util.h" + +#if defined(__arm__) +void cmplog_instrument(const cs_insn *instr, GumStalkerIterator *iterator) { + + UNUSED_PARAMETER(instr); + UNUSED_PARAMETER(iterator); + if (__afl_cmp_map == NULL) { return; } + FATAL("CMPLOG mode not supported on this architecture"); + +} + +#endif + diff --git a/frida_mode/src/ctx/ctx_arm32.c b/frida_mode/src/ctx/ctx_arm32.c new file mode 100644 index 00000000..9fc70fb4 --- /dev/null +++ b/frida_mode/src/ctx/ctx_arm32.c @@ -0,0 +1,16 @@ +#include "frida-gumjs.h" + +#include "debug.h" + +#include "ctx.h" + +#if defined(__arm__) + +gsize ctx_read_reg(GumArmCpuContext *ctx, arm_reg reg) { + + FATAL("ctx_read_reg unimplemented for this architecture"); + +} + +#endif + diff --git a/frida_mode/src/ctx/ctx_arm64.c b/frida_mode/src/ctx/ctx_arm64.c new file mode 100644 index 00000000..a735401b --- /dev/null +++ b/frida_mode/src/ctx/ctx_arm64.c @@ -0,0 +1,303 @@ +#include "frida-gumjs.h" + +#include "debug.h" + +#include "ctx.h" + +#if defined(__aarch64__) + + #define ARM64_REG_8(LABEL, REG) \ + case LABEL: { \ + \ + return REG & GUM_INT8_MASK; \ + \ + } + + #define ARM64_REG_16(LABEL, REG) \ + case LABEL: { \ + \ + return (REG & GUM_INT16_MASK); \ + \ + } + + #define ARM64_REG_32(LABEL, REG) \ + case LABEL: { \ + \ + return (REG & GUM_INT32_MASK); \ + \ + } + + #define ARM64_REG_64(LABEL, REG) \ + case LABEL: { \ + \ + return (REG); \ + \ + } + +gsize ctx_read_reg(GumArm64CpuContext *ctx, arm64_reg reg) { + + switch (reg) { + + case ARM64_REG_WZR: + case ARM64_REG_XZR: + return 0; + + ARM64_REG_8(ARM64_REG_B0, ctx->x[0]) + ARM64_REG_8(ARM64_REG_B1, ctx->x[1]) + ARM64_REG_8(ARM64_REG_B2, ctx->x[2]) + ARM64_REG_8(ARM64_REG_B3, ctx->x[3]) + ARM64_REG_8(ARM64_REG_B4, ctx->x[4]) + ARM64_REG_8(ARM64_REG_B5, ctx->x[5]) + ARM64_REG_8(ARM64_REG_B6, ctx->x[6]) + ARM64_REG_8(ARM64_REG_B7, ctx->x[7]) + ARM64_REG_8(ARM64_REG_B8, ctx->x[8]) + ARM64_REG_8(ARM64_REG_B9, ctx->x[9]) + ARM64_REG_8(ARM64_REG_B10, ctx->x[10]) + ARM64_REG_8(ARM64_REG_B11, ctx->x[11]) + ARM64_REG_8(ARM64_REG_B12, ctx->x[12]) + ARM64_REG_8(ARM64_REG_B13, ctx->x[13]) + ARM64_REG_8(ARM64_REG_B14, ctx->x[14]) + ARM64_REG_8(ARM64_REG_B15, ctx->x[15]) + ARM64_REG_8(ARM64_REG_B16, ctx->x[16]) + ARM64_REG_8(ARM64_REG_B17, ctx->x[17]) + ARM64_REG_8(ARM64_REG_B18, ctx->x[18]) + ARM64_REG_8(ARM64_REG_B19, ctx->x[19]) + ARM64_REG_8(ARM64_REG_B20, ctx->x[20]) + ARM64_REG_8(ARM64_REG_B21, ctx->x[21]) + ARM64_REG_8(ARM64_REG_B22, ctx->x[22]) + ARM64_REG_8(ARM64_REG_B23, ctx->x[23]) + ARM64_REG_8(ARM64_REG_B24, ctx->x[24]) + ARM64_REG_8(ARM64_REG_B25, ctx->x[25]) + ARM64_REG_8(ARM64_REG_B26, ctx->x[26]) + ARM64_REG_8(ARM64_REG_B27, ctx->x[27]) + ARM64_REG_8(ARM64_REG_B28, ctx->x[28]) + ARM64_REG_8(ARM64_REG_B29, ctx->fp) + ARM64_REG_8(ARM64_REG_B30, ctx->lr) + ARM64_REG_8(ARM64_REG_B31, ctx->sp) + + ARM64_REG_16(ARM64_REG_H0, ctx->x[0]) + ARM64_REG_16(ARM64_REG_H1, ctx->x[1]) + ARM64_REG_16(ARM64_REG_H2, ctx->x[2]) + ARM64_REG_16(ARM64_REG_H3, ctx->x[3]) + ARM64_REG_16(ARM64_REG_H4, ctx->x[4]) + ARM64_REG_16(ARM64_REG_H5, ctx->x[5]) + ARM64_REG_16(ARM64_REG_H6, ctx->x[6]) + ARM64_REG_16(ARM64_REG_H7, ctx->x[7]) + ARM64_REG_16(ARM64_REG_H8, ctx->x[8]) + ARM64_REG_16(ARM64_REG_H9, ctx->x[9]) + ARM64_REG_16(ARM64_REG_H10, ctx->x[10]) + ARM64_REG_16(ARM64_REG_H11, ctx->x[11]) + ARM64_REG_16(ARM64_REG_H12, ctx->x[12]) + ARM64_REG_16(ARM64_REG_H13, ctx->x[13]) + ARM64_REG_16(ARM64_REG_H14, ctx->x[14]) + ARM64_REG_16(ARM64_REG_H15, ctx->x[15]) + ARM64_REG_16(ARM64_REG_H16, ctx->x[16]) + ARM64_REG_16(ARM64_REG_H17, ctx->x[17]) + ARM64_REG_16(ARM64_REG_H18, ctx->x[18]) + ARM64_REG_16(ARM64_REG_H19, ctx->x[19]) + ARM64_REG_16(ARM64_REG_H20, ctx->x[20]) + ARM64_REG_16(ARM64_REG_H21, ctx->x[21]) + ARM64_REG_16(ARM64_REG_H22, ctx->x[22]) + ARM64_REG_16(ARM64_REG_H23, ctx->x[23]) + ARM64_REG_16(ARM64_REG_H24, ctx->x[24]) + ARM64_REG_16(ARM64_REG_H25, ctx->x[25]) + ARM64_REG_16(ARM64_REG_H26, ctx->x[26]) + ARM64_REG_16(ARM64_REG_H27, ctx->x[27]) + ARM64_REG_16(ARM64_REG_H28, ctx->x[28]) + ARM64_REG_16(ARM64_REG_H29, ctx->fp) + ARM64_REG_16(ARM64_REG_H30, ctx->lr) + ARM64_REG_16(ARM64_REG_H31, ctx->sp) + + ARM64_REG_32(ARM64_REG_W0, ctx->x[0]) + ARM64_REG_32(ARM64_REG_W1, ctx->x[1]) + ARM64_REG_32(ARM64_REG_W2, ctx->x[2]) + ARM64_REG_32(ARM64_REG_W3, ctx->x[3]) + ARM64_REG_32(ARM64_REG_W4, ctx->x[4]) + ARM64_REG_32(ARM64_REG_W5, ctx->x[5]) + ARM64_REG_32(ARM64_REG_W6, ctx->x[6]) + ARM64_REG_32(ARM64_REG_W7, ctx->x[7]) + ARM64_REG_32(ARM64_REG_W8, ctx->x[8]) + ARM64_REG_32(ARM64_REG_W9, ctx->x[9]) + ARM64_REG_32(ARM64_REG_W10, ctx->x[10]) + ARM64_REG_32(ARM64_REG_W11, ctx->x[11]) + ARM64_REG_32(ARM64_REG_W12, ctx->x[12]) + ARM64_REG_32(ARM64_REG_W13, ctx->x[13]) + ARM64_REG_32(ARM64_REG_W14, ctx->x[14]) + ARM64_REG_32(ARM64_REG_W15, ctx->x[15]) + ARM64_REG_32(ARM64_REG_W16, ctx->x[16]) + ARM64_REG_32(ARM64_REG_W17, ctx->x[17]) + ARM64_REG_32(ARM64_REG_W18, ctx->x[18]) + ARM64_REG_32(ARM64_REG_W19, ctx->x[19]) + ARM64_REG_32(ARM64_REG_W20, ctx->x[20]) + ARM64_REG_32(ARM64_REG_W21, ctx->x[21]) + ARM64_REG_32(ARM64_REG_W22, ctx->x[22]) + ARM64_REG_32(ARM64_REG_W23, ctx->x[23]) + ARM64_REG_32(ARM64_REG_W24, ctx->x[24]) + ARM64_REG_32(ARM64_REG_W25, ctx->x[25]) + ARM64_REG_32(ARM64_REG_W26, ctx->x[26]) + ARM64_REG_32(ARM64_REG_W27, ctx->x[27]) + ARM64_REG_32(ARM64_REG_W28, ctx->x[28]) + ARM64_REG_32(ARM64_REG_W29, ctx->fp) + ARM64_REG_32(ARM64_REG_W30, ctx->lr) + + ARM64_REG_64(ARM64_REG_X0, ctx->x[0]) + ARM64_REG_64(ARM64_REG_X1, ctx->x[1]) + ARM64_REG_64(ARM64_REG_X2, ctx->x[2]) + ARM64_REG_64(ARM64_REG_X3, ctx->x[3]) + ARM64_REG_64(ARM64_REG_X4, ctx->x[4]) + ARM64_REG_64(ARM64_REG_X5, ctx->x[5]) + ARM64_REG_64(ARM64_REG_X6, ctx->x[6]) + ARM64_REG_64(ARM64_REG_X7, ctx->x[7]) + ARM64_REG_64(ARM64_REG_X8, ctx->x[8]) + ARM64_REG_64(ARM64_REG_X9, ctx->x[9]) + ARM64_REG_64(ARM64_REG_X10, ctx->x[10]) + ARM64_REG_64(ARM64_REG_X11, ctx->x[11]) + ARM64_REG_64(ARM64_REG_X12, ctx->x[12]) + ARM64_REG_64(ARM64_REG_X13, ctx->x[13]) + ARM64_REG_64(ARM64_REG_X14, ctx->x[14]) + ARM64_REG_64(ARM64_REG_X15, ctx->x[15]) + ARM64_REG_64(ARM64_REG_X16, ctx->x[16]) + ARM64_REG_64(ARM64_REG_X17, ctx->x[17]) + ARM64_REG_64(ARM64_REG_X18, ctx->x[18]) + ARM64_REG_64(ARM64_REG_X19, ctx->x[19]) + ARM64_REG_64(ARM64_REG_X20, ctx->x[20]) + ARM64_REG_64(ARM64_REG_X21, ctx->x[21]) + ARM64_REG_64(ARM64_REG_X22, ctx->x[22]) + ARM64_REG_64(ARM64_REG_X23, ctx->x[23]) + ARM64_REG_64(ARM64_REG_X24, ctx->x[24]) + ARM64_REG_64(ARM64_REG_X25, ctx->x[25]) + ARM64_REG_64(ARM64_REG_X26, ctx->x[26]) + ARM64_REG_64(ARM64_REG_X27, ctx->x[27]) + ARM64_REG_64(ARM64_REG_X28, ctx->x[28]) + ARM64_REG_64(ARM64_REG_FP, ctx->fp) + ARM64_REG_64(ARM64_REG_LR, ctx->lr) + ARM64_REG_64(ARM64_REG_SP, ctx->sp) + + default: + FATAL("Failed to read register: %d", reg); + return 0; + + } + +} + +size_t ctx_get_size(const cs_insn *instr, cs_arm64_op *operand) { + + uint8_t num_registers; + uint8_t count_byte; + char vas_digit; + size_t mnemonic_len; + + switch (instr->id) { + + case ARM64_INS_STP: + case ARM64_INS_STXP: + case ARM64_INS_STNP: + case ARM64_INS_STLXP: + case ARM64_INS_LDP: + case ARM64_INS_LDXP: + case ARM64_INS_LDNP: + num_registers = 2; + break; + default: + num_registers = 1; + break; + + } + + mnemonic_len = strlen(instr->mnemonic); + if (mnemonic_len == 0) { FATAL("No mnemonic found"); }; + + char last = instr->mnemonic[mnemonic_len - 1]; + switch (last) { + + case 'b': + return 1; + case 'h': + return 2; + case 'w': + return 4 * num_registers; + + } + + if (operand->vas == ARM64_VAS_INVALID) { + + if (operand->type == ARM64_OP_REG) { + + switch (operand->reg) { + + case ARM64_REG_WZR: + case ARM64_REG_WSP: + case ARM64_REG_W0 ... ARM64_REG_W30: + case ARM64_REG_S0 ... ARM64_REG_S31: + return 4 * num_registers; + case ARM64_REG_D0 ... ARM64_REG_D31: + return 8 * num_registers; + case ARM64_REG_Q0 ... ARM64_REG_Q31: + return 16; + default: + return 8 * num_registers; + ; + + } + + } + + return 8 * num_registers; + + } + + if (g_str_has_prefix(instr->mnemonic, "st") || + g_str_has_prefix(instr->mnemonic, "ld")) { + + if (mnemonic_len < 3) { + + FATAL("VAS Mnemonic too short: %s\n", instr->mnemonic); + + } + + vas_digit = instr->mnemonic[2]; + if (vas_digit < '0' || vas_digit > '9') { + + FATAL("VAS Mnemonic digit out of range: %s\n", instr->mnemonic); + + } + + count_byte = vas_digit - '0'; + + } else { + + count_byte = 1; + + } + + switch (operand->vas) { + + case ARM64_VAS_1B: + return 1 * count_byte; + case ARM64_VAS_1H: + return 2 * count_byte; + case ARM64_VAS_4B: + case ARM64_VAS_1S: + case ARM64_VAS_1D: + case ARM64_VAS_2H: + return 4 * count_byte; + case ARM64_VAS_8B: + case ARM64_VAS_4H: + case ARM64_VAS_2S: + case ARM64_VAS_2D: + case ARM64_VAS_1Q: + return 8 * count_byte; + case ARM64_VAS_8H: + case ARM64_VAS_4S: + case ARM64_VAS_16B: + return 16 * count_byte; + default: + FATAL("Unexpected VAS type: %s %d", instr->mnemonic, operand->vas); + + } + +} + +#endif + diff --git a/frida_mode/src/intercept.c b/frida_mode/src/intercept.c new file mode 100644 index 00000000..ed8d27bd --- /dev/null +++ b/frida_mode/src/intercept.c @@ -0,0 +1,35 @@ +#include "frida-gumjs.h" + +#include "debug.h" + +#include "intercept.h" + +void intercept_hook(void *address, gpointer replacement, gpointer user_data) { + + GumInterceptor *interceptor = gum_interceptor_obtain(); + gum_interceptor_begin_transaction(interceptor); + GumReplaceReturn ret = + gum_interceptor_replace(interceptor, address, replacement, user_data); + if (ret != GUM_REPLACE_OK) { FATAL("gum_interceptor_attach: %d", ret); } + gum_interceptor_end_transaction(interceptor); + +} + +void intercept_unhook(void *address) { + + GumInterceptor *interceptor = gum_interceptor_obtain(); + + gum_interceptor_begin_transaction(interceptor); + gum_interceptor_revert(interceptor, address); + gum_interceptor_end_transaction(interceptor); + gum_interceptor_flush(interceptor); + +} + +void intercept_unhook_self(void) { + + GumInvocationContext *ctx = gum_interceptor_get_current_invocation(); + intercept_unhook(ctx->function); + +} + diff --git a/frida_mode/src/js/api.js b/frida_mode/src/js/api.js new file mode 100644 index 00000000..b8f2d39a --- /dev/null +++ b/frida_mode/src/js/api.js @@ -0,0 +1,257 @@ +"use strict"; +class Afl { + /** + * This is equivalent to setting a value in `AFL_FRIDA_EXCLUDE_RANGES`, + * it takes as arguments a `NativePointer` and a `number`. It can be + * called multiple times to exclude several ranges. + */ + static addExcludedRange(addressess, size) { + Afl.jsApiAddExcludeRange(addressess, size); + } + /** + * This is equivalent to setting a value in `AFL_FRIDA_INST_RANGES`, + * it takes as arguments a `NativePointer` and a `number`. It can be + * called multiple times to include several ranges. + */ + static addIncludedRange(addressess, size) { + Afl.jsApiAddIncludeRange(addressess, size); + } + /** + * This must always be called at the end of your script. This lets + * FRIDA mode know that your configuration is finished and that + * execution has reached the end of your script. Failure to call + * this will result in a fatal error. + */ + static done() { + Afl.jsApiDone(); + } + /** + * This function can be called within your script to cause FRIDA + * mode to trigger a fatal error. This is useful if for example you + * discover a problem you weren't expecting and want everything to + * stop. The user will need to enable `AFL_DEBUG_CHILD=1` to view + * this error message. + */ + static error(msg) { + const buf = Memory.allocUtf8String(msg); + Afl.jsApiError(buf); + } + /** + * Function used to provide access to `__afl_fuzz_ptr`, which contains the length of + * fuzzing data when using in-memory test case fuzzing. + */ + static getAflFuzzLen() { + return Afl.jsApiGetSymbol("__afl_fuzz_len"); + } + /** + * Function used to provide access to `__afl_fuzz_ptr`, which contains the fuzzing + * data when using in-memory test case fuzzing. + */ + static getAflFuzzPtr() { + return Afl.jsApiGetSymbol("__afl_fuzz_ptr"); + } + /** + * Print a message to the STDOUT. This should be preferred to + * FRIDA's `console.log` since FRIDA will queue it's log messages. + * If `console.log` is used in a callback in particular, then there + * may no longer be a thread running to service this queue. + */ + static print(msg) { + const STDOUT_FILENO = 2; + const log = `${msg}\n`; + const buf = Memory.allocUtf8String(log); + Afl.jsApiWrite(STDOUT_FILENO, buf, log.length); + } + /** + * See `AFL_FRIDA_DEBUG_MAPS`. + */ + static setDebugMaps() { + Afl.jsApiSetDebugMaps(); + } + /** + * This has the same effect as setting `AFL_ENTRYPOINT`, but has the + * convenience of allowing you to use FRIDAs APIs to determine the + * address you would like to configure, rather than having to grep + * the output of `readelf` or something similarly ugly. This + * function should be called with a `NativePointer` as its + * argument. + */ + static setEntryPoint(address) { + Afl.jsApiSetEntryPoint(address); + } + /** + * Function used to enable in-memory test cases for fuzzing. + */ + static setInMemoryFuzzing() { + Afl.jsApiAflSharedMemFuzzing.writeInt(1); + } + /** + * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as + * an argument. + */ + static setInstrumentDebugFile(file) { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetInstrumentDebugFile(buf); + } + /** + * See `AFL_FRIDA_INST_TRACE`. + */ + static setInstrumentEnableTracing() { + Afl.jsApiSetInstrumentTrace(); + } + /** + * See `AFL_FRIDA_INST_JIT`. + */ + static setInstrumentJit() { + Afl.jsApiSetInstrumentJit(); + } + /** + * See `AFL_INST_LIBS`. + */ + static setInstrumentLibraries() { + Afl.jsApiSetInstrumentLibraries(); + } + /** + * See `AFL_FRIDA_INST_NO_OPTIMIZE` + */ + static setInstrumentNoOptimize() { + Afl.jsApiSetInstrumentNoOptimize(); + } + /* + * See `AFL_FRIDA_INST_SEED` + */ + static setInstrumentSeed(seed) { + Afl.jsApiSetInstrumentSeed(seed); + } + /** + * See `AFL_FRIDA_INST_TRACE_UNIQUE`. + */ + static setInstrumentTracingUnique() { + Afl.jsApiSetInstrumentTraceUnique(); + } + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_ADDR`, again a + * `NativePointer` should be provided as it's argument. + */ + static setPersistentAddress(address) { + Afl.jsApiSetPersistentAddress(address); + } + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_CNT`, a + * `number` should be provided as it's argument. + */ + static setPersistentCount(count) { + Afl.jsApiSetPersistentCount(count); + } + /** + * See `AFL_FRIDA_PERSISTENT_DEBUG`. + */ + static setPersistentDebug() { + Afl.jsApiSetPersistentDebug(); + } + /** + * See `AFL_FRIDA_PERSISTENT_ADDR`. This function takes a NativePointer as an + * argument. See above for examples of use. + */ + static setPersistentHook(address) { + Afl.jsApiSetPersistentHook(address); + } + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_RET`, again a + * `NativePointer` should be provided as it's argument. + */ + static setPersistentReturn(address) { + Afl.jsApiSetPersistentReturn(address); + } + /** + * See `AFL_FRIDA_INST_NO_PREFETCH`. + */ + static setPrefetchDisable() { + Afl.jsApiSetPrefetchDisable(); + } + /* + * Set a function to be called for each instruction which is instrumented + * by AFL FRIDA mode. + */ + static setStalkerCallback(callback) { + Afl.jsApiSetStalkerCallback(callback); + } + /** + * See `AFL_FRIDA_STATS_FILE`. This function takes a single `string` as + * an argument. + */ + static setStatsFile(file) { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStatsFile(buf); + } + /** + * See `AFL_FRIDA_STATS_INTERVAL`. This function takes a `number` as an + * argument + */ + static setStatsInterval(interval) { + Afl.jsApiSetStatsInterval(interval); + } + /** + * See `AFL_FRIDA_STATS_TRANSITIONS` + */ + static setStatsTransitions() { + Afl.jsApiSetStatsTransitions(); + } + /** + * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as + * an argument. + */ + static setStdErr(file) { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStdErr(buf); + } + /** + * See `AFL_FRIDA_OUTPUT_STDOUT`. This function takes a single `string` as + * an argument. + */ + static setStdOut(file) { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStdOut(buf); + } + static jsApiGetFunction(name, retType, argTypes) { + const addr = Afl.module.getExportByName(name); + return new NativeFunction(addr, retType, argTypes); + } + static jsApiGetSymbol(name) { + return Afl.module.getExportByName(name); + } +} +/** + * Field containing the `Module` object for `afl-frida-trace.so` (the FRIDA mode + * implementation). + */ +Afl.module = Process.getModuleByName("afl-frida-trace.so"); +Afl.jsApiAddExcludeRange = Afl.jsApiGetFunction("js_api_add_exclude_range", "void", ["pointer", "size_t"]); +Afl.jsApiAddIncludeRange = Afl.jsApiGetFunction("js_api_add_include_range", "void", ["pointer", "size_t"]); +Afl.jsApiAflSharedMemFuzzing = Afl.jsApiGetSymbol("__afl_sharedmem_fuzzing"); +Afl.jsApiDone = Afl.jsApiGetFunction("js_api_done", "void", []); +Afl.jsApiError = Afl.jsApiGetFunction("js_api_error", "void", ["pointer"]); +Afl.jsApiSetDebugMaps = Afl.jsApiGetFunction("js_api_set_debug_maps", "void", []); +Afl.jsApiSetEntryPoint = Afl.jsApiGetFunction("js_api_set_entrypoint", "void", ["pointer"]); +Afl.jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction("js_api_set_instrument_debug_file", "void", ["pointer"]); +Afl.jsApiSetInstrumentJit = Afl.jsApiGetFunction("js_api_set_instrument_jit", "void", []); +Afl.jsApiSetInstrumentLibraries = Afl.jsApiGetFunction("js_api_set_instrument_libraries", "void", []); +Afl.jsApiSetInstrumentNoOptimize = Afl.jsApiGetFunction("js_api_set_instrument_no_optimize", "void", []); +Afl.jsApiSetInstrumentSeed = Afl.jsApiGetFunction("js_api_set_instrument_seed", "void", ["uint64"]); +Afl.jsApiSetInstrumentTrace = Afl.jsApiGetFunction("js_api_set_instrument_trace", "void", []); +Afl.jsApiSetInstrumentTraceUnique = Afl.jsApiGetFunction("js_api_set_instrument_trace_unique", "void", []); +Afl.jsApiSetPersistentAddress = Afl.jsApiGetFunction("js_api_set_persistent_address", "void", ["pointer"]); +Afl.jsApiSetPersistentCount = Afl.jsApiGetFunction("js_api_set_persistent_count", "void", ["uint64"]); +Afl.jsApiSetPersistentDebug = Afl.jsApiGetFunction("js_api_set_persistent_debug", "void", []); +Afl.jsApiSetPersistentHook = Afl.jsApiGetFunction("js_api_set_persistent_hook", "void", ["pointer"]); +Afl.jsApiSetPersistentReturn = Afl.jsApiGetFunction("js_api_set_persistent_return", "void", ["pointer"]); +Afl.jsApiSetPrefetchDisable = Afl.jsApiGetFunction("js_api_set_prefetch_disable", "void", []); +Afl.jsApiSetStalkerCallback = Afl.jsApiGetFunction("js_api_set_stalker_callback", "void", ["pointer"]); +Afl.jsApiSetStatsFile = Afl.jsApiGetFunction("js_api_set_stats_file", "void", ["pointer"]); +Afl.jsApiSetStatsInterval = Afl.jsApiGetFunction("js_api_set_stats_interval", "void", ["uint64"]); +Afl.jsApiSetStatsTransitions = Afl.jsApiGetFunction("js_api_set_stats_transitions", "void", []); +Afl.jsApiSetStdErr = Afl.jsApiGetFunction("js_api_set_stderr", "void", ["pointer"]); +Afl.jsApiSetStdOut = Afl.jsApiGetFunction("js_api_set_stdout", "void", ["pointer"]); +Afl.jsApiWrite = new NativeFunction( +/* tslint:disable-next-line:no-null-keyword */ +Module.getExportByName(null, "write"), "int", ["int", "pointer", "int"]); diff --git a/frida_mode/src/js/js.c b/frida_mode/src/js/js.c new file mode 100644 index 00000000..e3cd4933 --- /dev/null +++ b/frida_mode/src/js/js.c @@ -0,0 +1,142 @@ +#include "frida-gumjs.h" + +#include "debug.h" + +#include "js.h" +#include "util.h" + +static char * js_script = NULL; +gboolean js_done = FALSE; +js_api_stalker_callback_t js_user_callback = NULL; + +static gchar * filename = "afl.js"; +static gchar * contents; +static GumScriptBackend * backend; +static GCancellable * cancellable = NULL; +static GError * error = NULL; +static GumScript * script; +static GumScriptScheduler *scheduler; +static GMainContext * context; +static GMainLoop * main_loop; + +static void js_msg(GumScript *script, const gchar *message, GBytes *data, + gpointer user_data) { + + UNUSED_PARAMETER(script); + UNUSED_PARAMETER(data); + UNUSED_PARAMETER(user_data); + OKF("%s", message); + +} + +void js_config(void) { + + js_script = getenv("AFL_FRIDA_JS_SCRIPT"); + +} + +static gchar *js_get_script() { + + gsize length; + if (js_script != NULL) { filename = js_script; } + + filename = g_canonicalize_filename(filename, g_get_current_dir()); + + if (!g_file_get_contents(filename, &contents, &length, NULL)) { + + if (js_script == NULL) { + + return NULL; + + } else { + + FATAL("Could not load script file: %s", filename); + + } + + } else { + + OKF("Loaded AFL script: %s, %" G_GSIZE_MODIFIER "d bytes", filename, + length); + + gchar *source = g_malloc0(api_js_len + length + 1); + memcpy(source, api_js, api_js_len); + memcpy(&source[api_js_len], contents, length); + + return source; + + } + +} + +static void js_print_script(gchar *source) { + + gchar **split = g_strsplit(source, "\n", 0); + + for (size_t i = 0; split[i] != NULL; i++) { + + OKF("%3" G_GSIZE_MODIFIER "d. %s", i + 1, split[i]); + + } + + g_strfreev(split); + +} + +static void load_cb(GObject *source_object, GAsyncResult *result, + gpointer user_data) { + + UNUSED_PARAMETER(source_object); + UNUSED_PARAMETER(user_data); + gum_script_load_finish(script, result); + if (error != NULL) { FATAL("Failed to load script - %s", error->message); } + +} + +static void create_cb(GObject *source_object, GAsyncResult *result, + gpointer user_data) { + + UNUSED_PARAMETER(source_object); + UNUSED_PARAMETER(user_data); + script = gum_script_backend_create_finish(backend, result, &error); + if (error != NULL) { FATAL("Failed to create script: %s", error->message); } + + gum_script_set_message_handler(script, js_msg, NULL, NULL); + + gum_script_load(script, cancellable, load_cb, NULL); + +} + +void js_start(void) { + + gchar *source = js_get_script(); + if (source == NULL) { return; } + js_print_script(source); + + scheduler = gum_script_backend_get_scheduler(); + gum_script_scheduler_disable_background_thread(scheduler); + + backend = gum_script_backend_obtain_qjs(); + + context = gum_script_scheduler_get_js_context(scheduler); + main_loop = g_main_loop_new(context, true); + g_main_context_push_thread_default(context); + + gum_script_backend_create(backend, "example", source, cancellable, create_cb, + &error); + + while (g_main_context_pending(context)) + g_main_context_iteration(context, FALSE); + + if (!js_done) { FATAL("Script didn't call Afl.done()"); } + +} + +gboolean js_stalker_callback(const cs_insn *insn, gboolean begin, + gboolean excluded, GumStalkerOutput *output) { + + if (js_user_callback == NULL) { return TRUE; } + return js_user_callback(insn, begin, excluded, output); + +} + diff --git a/frida_mode/src/js/js_api.c b/frida_mode/src/js/js_api.c new file mode 100644 index 00000000..930a6dc0 --- /dev/null +++ b/frida_mode/src/js/js_api.c @@ -0,0 +1,201 @@ +#include "debug.h" + +#include "entry.h" +#include "instrument.h" +#include "js.h" +#include "output.h" +#include "persistent.h" +#include "prefetch.h" +#include "ranges.h" +#include "stats.h" +#include "util.h" +__attribute__((visibility("default"))) void js_api_done() { + + js_done = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_error(char *msg) { + + FATAL("%s", msg); + +} + +__attribute__((visibility("default"))) void js_api_set_entrypoint( + void *address) { + + if (address == NULL) { + + js_api_error("js_api_set_entrypoint called with NULL"); + + } + + entry_point = GPOINTER_TO_SIZE(address); + +} + +__attribute__((visibility("default"))) void js_api_set_persistent_address( + void *address) { + + if (address == NULL) { + + js_api_error("js_api_set_persistent_address called with NULL"); + + } + + persistent_start = GPOINTER_TO_SIZE(address); + +} + +__attribute__((visibility("default"))) void js_api_set_persistent_return( + void *address) { + + if (address == NULL) { + + js_api_error("js_api_set_persistent_return called with NULL"); + + } + + persistent_ret = GPOINTER_TO_SIZE(address); + +} + +__attribute__((visibility("default"))) void js_api_set_persistent_count( + uint64_t count) { + + persistent_count = count; + +} + +__attribute__((visibility("default"))) void js_api_set_persistent_debug() { + + persistent_debug = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_set_debug_maps() { + + ranges_debug_maps = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_add_include_range( + void *address, gsize size) { + + GumMemoryRange range = {.base_address = GUM_ADDRESS(address), .size = size}; + ranges_add_include(&range); + +} + +__attribute__((visibility("default"))) void js_api_add_exclude_range( + void *address, gsize size) { + + GumMemoryRange range = {.base_address = GUM_ADDRESS(address), .size = size}; + ranges_add_exclude(&range); + +} + +__attribute__((visibility("default"))) void js_api_set_instrument_jit() { + + ranges_inst_jit = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_set_instrument_libraries() { + + ranges_inst_libs = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_set_instrument_debug_file( + char *path) { + + instrument_debug_filename = g_strdup(path); + +} + +__attribute__((visibility("default"))) void js_api_set_prefetch_disable(void) { + + prefetch_enable = FALSE; + +} + +__attribute__((visibility("default"))) void js_api_set_instrument_no_optimize( + void) { + + instrument_optimize = FALSE; + +} + +__attribute__((visibility("default"))) void js_api_set_instrument_seed( + guint64 seed) { + + instrument_use_fixed_seed = TRUE; + instrument_fixed_seed = seed; + +} + +__attribute__((visibility("default"))) void js_api_set_instrument_trace(void) { + + instrument_tracing = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_set_instrument_trace_unique( + void) { + + instrument_unique = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_set_stdout(char *file) { + + output_stdout = g_strdup(file); + +} + +__attribute__((visibility("default"))) void js_api_set_stderr(char *file) { + + output_stderr = g_strdup(file); + +} + +__attribute__((visibility("default"))) void js_api_set_stats_file(char *file) { + + stats_filename = g_strdup(file); + +} + +__attribute__((visibility("default"))) void js_api_set_stats_interval( + uint64_t interval) { + + stats_interval = interval; + +} + +__attribute__((visibility("default"))) void js_api_set_stats_transitions() { + + stats_transitions = TRUE; + +} + +__attribute__((visibility("default"))) void js_api_set_persistent_hook( + void *address) { + + if (address == NULL) { + + js_api_error("js_api_set_persistent_hook called with NULL"); + + } + + persistent_hook = address; + +} + +__attribute__((visibility("default"))) void js_api_set_stalker_callback( + const js_api_stalker_callback_t callback) { + + js_user_callback = callback; + +} + diff --git a/frida_mode/src/stats/stats_arm32.c b/frida_mode/src/stats/stats_arm32.c new file mode 100644 index 00000000..71953af3 --- /dev/null +++ b/frida_mode/src/stats/stats_arm32.c @@ -0,0 +1,36 @@ +#include "frida-gumjs.h" + +#include "debug.h" + +#include "stats.h" +#include "util.h" + +#if defined(__arm__) + +gboolean stats_is_supported_arch(void) { + + return FALSE; + +} + +size_t stats_data_size_arch(void) { + + FATAL("Stats not supported on this architecture"); + +} + +void stats_write_arch(void) { + + FATAL("Stats not supported on this architecture"); + +} + +void stats_collect_arch(const cs_insn *instr) { + + UNUSED_PARAMETER(instr); + FATAL("Stats not supported on this architecture"); + +} + +#endif + diff --git a/frida_mode/test/jpeg/GNUmakefile b/frida_mode/test/jpeg/GNUmakefile new file mode 100644 index 00000000..1c124743 --- /dev/null +++ b/frida_mode/test/jpeg/GNUmakefile @@ -0,0 +1,166 @@ +PWD:=$(shell pwd)/ +ROOT:=$(shell realpath $(PWD)../../..)/ +BUILD_DIR:=$(PWD)build/ + +AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so + +LIBJPEG_BUILD_DIR:=$(BUILD_DIR)libjpeg/ +HARNESS_BUILD_DIR:=$(BUILD_DIR)harness/ +JPEGTEST_BUILD_DIR:=$(BUILD_DIR)jpegtest/ + +LIBJPEG_URL:=https://github.com/libjpeg-turbo/libjpeg-turbo.git +LIBJPEG_DIR:=$(LIBJPEG_BUILD_DIR)libjpeg/ +LIBJPEG_CONFIGURE:=$(LIBJPEG_DIR)configure.ac +LIBJPEG_MAKEFILE:=$(LIBJPEG_DIR)Makefile +LIBJPEG_LIB:=$(LIBJPEG_DIR).libs/libturbojpeg.a + +HARNESS_FILE:=$(HARNESS_BUILD_DIR)StandaloneFuzzTargetMain.c +HARNESS_OBJ:=$(HARNESS_BUILD_DIR)StandaloneFuzzTargetMain.o +HARNESS_URL:="https://raw.githubusercontent.com/AFLplusplus/AFLplusplus/stable/utils/aflpp_driver/aflpp_qemu_driver.c" + +JPEGTEST_FILE:=$(JPEGTEST_BUILD_DIR)target.cc +JPEGTEST_OBJ:=$(JPEGTEST_BUILD_DIR)target.o +JPEGTEST_URL:="https://raw.githubusercontent.com/google/fuzzbench/master/benchmarks/libjpeg-turbo-07-2017/libjpeg_turbo_fuzzer.cc" + +LDFLAGS += -lpthread + +TEST_BIN:=$(BUILD_DIR)test +ifeq "$(shell uname)" "Darwin" +TEST_BIN_LDFLAGS:=-undefined dynamic_lookup -Wl,-no_pie +endif + +TEST_DATA_DIR:=$(BUILD_DIR)in/ +TEST_DATA_FILE:=$(TEST_DATA_DIR)default_seed + +FRIDA_OUT:=$(BUILD_DIR)frida-out + +ifndef ARCH + +ARCH=$(shell uname -m) +ifeq "$(ARCH)" "aarch64" + ARCH:=arm64 +endif + +ifeq "$(ARCH)" "i686" + ARCH:=x86 +endif +endif + +GET_SYMBOL_ADDR:=$(ROOT)frida_mode/util/get_symbol_addr.sh + +ifeq "$(ARCH)" "aarch64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x0000aaaaaaaaa000) +endif + +ifeq "$(ARCH)" "x86_64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x0000555555554000) +endif + +ifeq "$(ARCH)" "x86" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x56555000) +endif + +.PHONY: all clean frida hook + +all: $(TEST_BIN) + make -C $(ROOT)frida_mode/ + +32: + CXXFLAGS="-m32" LDFLAGS="-m32" ARCH="x86" make all + +$(BUILD_DIR): + mkdir -p $@ + +######### HARNESS ######## +$(HARNESS_BUILD_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(HARNESS_FILE): | $(HARNESS_BUILD_DIR) + wget -O $@ $(HARNESS_URL) + +$(HARNESS_OBJ): $(HARNESS_FILE) + $(CC) $(CXXFLAGS) $(LDFLAGS) $(TEST_BIN_LDFLAGS) -o $@ -c $< + +######### JPEGTEST ######## + +$(JPEGTEST_BUILD_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(JPEGTEST_FILE): | $(JPEGTEST_BUILD_DIR) + wget -O $@ $(JPEGTEST_URL) + +$(JPEGTEST_OBJ): $(JPEGTEST_FILE) | $(LIBJPEG_MAKEFILE) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -std=c++11 -I $(LIBJPEG_DIR) -o $@ -c $< + +######### LIBJPEG ######## + +$(LIBJPEG_BUILD_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(LIBJPEG_CONFIGURE): $(LIBJPEG_BUILD_DIR) + git clone $(LIBJPEG_URL) $(LIBJPEG_DIR) + cd $(LIBJPEG_DIR) && git checkout b0971e47d76fdb81270e93bbf11ff5558073350d + +$(LIBJPEG_MAKEFILE): $(LIBJPEG_CONFIGURE) + cd $(LIBJPEG_DIR) && autoreconf -fiv + cd $(LIBJPEG_DIR) && ./configure + +$(LIBJPEG_LIB): $(LIBJPEG_MAKEFILE) + make -C $(LIBJPEG_DIR) -j $(shell nproc) + +######### TEST ######## + +$(TEST_BIN): $(HARNESS_OBJ) $(JPEGTEST_OBJ) $(LIBJPEG_LIB) + $(CXX) \ + $(CFLAGS) \ + -o $@ \ + $(HARNESS_OBJ) $(JPEGTEST_OBJ) $(LIBJPEG_LIB) \ + -lz \ + $(LDFLAGS) \ + $(TEST_BIN_LDFLAGS) \ + +########## DUMMY ####### + +$(TEST_DATA_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(TEST_DATA_FILE): | $(TEST_DATA_DIR) + echo "hi" > $(TEST_DATA_FILE) + +###### TEST DATA ####### + +clean: + rm -rf $(BUILD_DIR) + +frida: $(TEST_BIN) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) $(TEST_DATA_FILE) + AFL_DEBUG_CHILD=1 \ + AFL_DISABLE_TRIM=1 \ + AFL_FRIDA_PERSISTENT_CNT=1000000 \ + AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \ + AFL_NO_AFFINITY=1 \ + X__AFL_NO_UI=1 \ + AFL_PATH=/out \ + AFL_SHUFFLE_QUEUE=1 \ + AFL_SKIP_CPUFREQ=1 \ + AFL_SKIP_CRASHES=1 \ + AFL_TESTCACHE_SIZE=2 \ + 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 \ + -i $(TEST_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -m none \ + -t 1000+ \ + -d \ + -O \ + -c 0\ + -V 30 \ + -- \ + $(TEST_BIN) 2147483647 + +debug: + gdb \ + --ex 'set environment LD_PRELOAD=$(ROOT)afl-frida-trace.so' \ + --ex 'set disassembly-flavor intel' \ + --args $(TEST_BIN) $(TEST_DATA_DIR)basn0g01.jpeg diff --git a/frida_mode/test/jpeg/Makefile b/frida_mode/test/jpeg/Makefile new file mode 100644 index 00000000..7a237f99 --- /dev/null +++ b/frida_mode/test/jpeg/Makefile @@ -0,0 +1,16 @@ +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 + +frida: + @gmake frida + +debug: + @gmake debug diff --git a/frida_mode/test/jpeg/get_symbol_addr.py b/frida_mode/test/jpeg/get_symbol_addr.py new file mode 100755 index 00000000..1c46e010 --- /dev/null +++ b/frida_mode/test/jpeg/get_symbol_addr.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +import argparse +from elftools.elf.elffile import ELFFile + +def process_file(file, symbol, base): + with open(file, 'rb') as f: + elf = ELFFile(f) + symtab = elf.get_section_by_name('.symtab') + mains = symtab.get_symbol_by_name(symbol) + if len(mains) != 1: + print ("Failed to find main") + return 1 + + main_addr = mains[0]['st_value'] + main = base + main_addr + print ("0x%016x" % main) + return 0 + +def hex_value(x): + return int(x, 16) + +def main(): + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('-f', '--file', dest='file', type=str, + help='elf file name', required=True) + parser.add_argument('-s', '--symbol', dest='symbol', type=str, + help='symbol name', required=True) + parser.add_argument('-b', '--base', dest='base', type=hex_value, + help='elf base address', required=True) + + args = parser.parse_args() + return process_file (args.file, args.symbol, args.base) + +if __name__ == "__main__": + ret = main() + exit(ret) diff --git a/frida_mode/test/js/GNUmakefile b/frida_mode/test/js/GNUmakefile new file mode 100644 index 00000000..ee8d4ebc --- /dev/null +++ b/frida_mode/test/js/GNUmakefile @@ -0,0 +1,98 @@ +PWD:=$(shell pwd)/ +ROOT:=$(shell realpath $(PWD)../../..)/ +BUILD_DIR:=$(PWD)build/ +TEST_DATA_DIR:=$(BUILD_DIR)in/ +TEST_DATA_FILE:=$(TEST_DATA_DIR)in + +TESTINSTBIN:=$(BUILD_DIR)test +TESTINSTSRC:=$(PWD)test.c + +TESTINSTBIN2:=$(BUILD_DIR)test2 +TESTINSTSRC2:=$(PWD)test2.c + +QEMU_OUT:=$(BUILD_DIR)qemu-out +FRIDA_OUT:=$(BUILD_DIR)frida-out + +ifeq "$(shell uname)" "Darwin" +AFL_PRELOAD=/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation +endif + +.PHONY: all 32 clean qemu frida debug + +all: $(TESTINSTBIN) $(TESTINSTBIN2) + make -C $(ROOT)frida_mode/ + +32: + CFLAGS="-m32" LDFLAGS="-m32" ARCH="x86" make all + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_DATA_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(TEST_DATA_FILE): | $(TEST_DATA_DIR) + echo -n "000" > $@ + +$(TESTINSTBIN): $(TESTINSTSRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< + +$(TESTINSTBIN2): $(TESTINSTSRC2) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< + +clean: + rm -rf $(BUILD_DIR) + +frida_js_entry: $(TESTINSTBIN) $(TEST_DATA_FILE) + AFL_PRELOAD=$(AFL_PRELOAD) \ + AFL_FRIDA_JS_SCRIPT=entry.js \ + $(ROOT)afl-fuzz \ + -D \ + -O \ + -i $(TEST_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -t 10000+ \ + -- \ + $(TESTINSTBIN) @@ + +frida_js_replace: $(TESTINSTBIN) $(TEST_DATA_FILE) + AFL_FRIDA_JS_SCRIPT=replace.js \ + $(ROOT)afl-fuzz \ + -D \ + -O \ + -i $(TEST_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -- \ + $(TESTINSTBIN) @@ + +frida_js_patch: $(TESTINSTBIN2) $(TEST_DATA_FILE) + AFL_FRIDA_JS_SCRIPT=patch.js \ + $(ROOT)afl-fuzz \ + -D \ + -O \ + -i $(TEST_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -- \ + $(TESTINSTBIN2) @@ + +frida_js_stalker: $(TESTINSTBIN2) $(TEST_DATA_FILE) + AFL_FRIDA_JS_SCRIPT=stalker.js \ + $(ROOT)afl-fuzz \ + -D \ + -O \ + -i $(TEST_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -- \ + $(TESTINSTBIN2) @@ + +debug: $(TEST_DATA_FILE) + gdb \ + --ex 'set environment LD_PRELOAD=$(ROOT)afl-frida-trace.so' \ + --ex 'set environment AFL_FRIDA_JS_SCRIPT=entry.js' \ + --ex 'set disassembly-flavor intel' \ + --args $(TESTINSTBIN) $(TEST_DATA_FILE) + +strace: $(TEST_DATA_FILE) + LD_PRELOAD=$(ROOT)afl-frida-trace.so \ + AFL_FRIDA_JS_SCRIPT=entry.js \ + strace $(TESTINSTBIN) $(TEST_DATA_FILE) diff --git a/frida_mode/test/js/Makefile b/frida_mode/test/js/Makefile new file mode 100644 index 00000000..8a2b6fb0 --- /dev/null +++ b/frida_mode/test/js/Makefile @@ -0,0 +1,25 @@ +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 + +frida_js_entry: + @gmake frida_js_entry + +frida_js_replace: + @gmake frida_js_replace + +frida_js_patch: + @gmake frida_js_patch + +frida_js_stalker: + @gmake frida_js_stalker + +debug: + @gmake debug diff --git a/frida_mode/test/js/entry.js b/frida_mode/test/js/entry.js new file mode 100644 index 00000000..2bdd7d13 --- /dev/null +++ b/frida_mode/test/js/entry.js @@ -0,0 +1,26 @@ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +Afl.print(`PID: ${Process.id}`); + +new ModuleMap().values().forEach(m => { + Afl.print(`${m.base}-${m.base.add(m.size)} ${m.name}`); +}); + +const name = Process.enumerateModules()[0].name; +Afl.print(`Name: ${name}`); + +if (name === 'test') { + + Afl.print('Searching...\n'); + const entry_point = DebugSymbol.fromName('run'); + Afl.print(`entry_point: ${entry_point}`); + + Afl.setEntryPoint(entry_point.address); + +} + +Afl.done(); +Afl.print("done"); diff --git a/frida_mode/test/js/patch.js b/frida_mode/test/js/patch.js new file mode 100644 index 00000000..485a434f --- /dev/null +++ b/frida_mode/test/js/patch.js @@ -0,0 +1,34 @@ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +const main = DebugSymbol.fromName('main').address; +Afl.print(`main: ${main}`); +Afl.setEntryPoint(main); +Afl.setPersistentAddress(main); +Afl.setPersistentCount(10000000); + +const crc32_check = DebugSymbol.fromName('crc32_check').address; +const crc32_replacement = new NativeCallback( + (buf, len) => { + Afl.print(`len: ${len}`); + if (len < 4) { + return 0; + } + + return 1; + }, + 'int', + ['pointer', 'int']); +Interceptor.replace(crc32_check, crc32_replacement); + +const some_boring_bug = DebugSymbol.fromName('some_boring_bug').address +const boring_replacement = new NativeCallback( + (c) => { }, + 'void', + ['char']); +Interceptor.replace(some_boring_bug, boring_replacement); + +Afl.done(); +Afl.print("done"); diff --git a/frida_mode/test/js/replace.js b/frida_mode/test/js/replace.js new file mode 100644 index 00000000..4e1e7eb7 --- /dev/null +++ b/frida_mode/test/js/replace.js @@ -0,0 +1,43 @@ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +Afl.print(`PID: ${Process.id}`); + +const name = Process.enumerateModules()[0].name; +Afl.print(`Name: ${name}`); + +new ModuleMap().values().forEach(m => { + Afl.print(`${m.base}-${m.base.add(m.size)} ${m.name}`); +}); + +const slow = DebugSymbol.fromName('slow').address; +Afl.print(`slow: ${slow}`); + +const LLVMFuzzerTestOneInput = DebugSymbol.fromName('LLVMFuzzerTestOneInput').address; +Afl.print(`LLVMFuzzerTestOneInput: ${LLVMFuzzerTestOneInput}`); + +const cm = new CModule(` + + extern unsigned char * __afl_fuzz_ptr; + extern unsigned int * __afl_fuzz_len; + extern void LLVMFuzzerTestOneInput(char *buf, int len); + + void slow(void) { + + LLVMFuzzerTestOneInput(__afl_fuzz_ptr, *__afl_fuzz_len); + } + `, + { + LLVMFuzzerTestOneInput: LLVMFuzzerTestOneInput, + __afl_fuzz_ptr: Afl.getAflFuzzPtr(), + __afl_fuzz_len: Afl.getAflFuzzLen() + }); + +Afl.setEntryPoint(cm.slow); +Afl.setPersistentAddress(cm.slow); +Afl.setInMemoryFuzzing(); +Interceptor.replace(slow, cm.slow); +Afl.print("done"); +Afl.done(); diff --git a/frida_mode/test/js/stalker.js b/frida_mode/test/js/stalker.js new file mode 100644 index 00000000..33f024f5 --- /dev/null +++ b/frida_mode/test/js/stalker.js @@ -0,0 +1,109 @@ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +const main = DebugSymbol.fromName('main').address; +Afl.print(`main: ${main}`); +Afl.setEntryPoint(main); +Afl.setPersistentAddress(main); +Afl.setPersistentCount(10000000); + +/* Replace CRC-32 check */ +const crc32_check = DebugSymbol.fromName('crc32_check').address; +const crc32_replacement = new NativeCallback( + (buf, len) => { + if (len < 4) { + return 0; + } + + return 1; + }, + 'int', + ['pointer', 'int']); +Interceptor.replace(crc32_check, crc32_replacement); + +/* Patch out the first boring bug */ +const some_boring_bug = DebugSymbol.fromName('some_boring_bug').address +const boring_replacement = new NativeCallback( + (c) => { }, + 'void', + ['char']); +Interceptor.replace(some_boring_bug, boring_replacement); + +/* Modify the instructions */ +const some_boring_bug2 = DebugSymbol.fromName('some_boring_bug2').address +const pid = Memory.alloc(4); +pid.writeInt(Process.id); + +const cm = new CModule(` + #include <stdio.h> + #include <gum/gumstalker.h> + + typedef int pid_t; + + #define STDERR_FILENO 2 + #define BORING2_LEN 10 + + extern int dprintf(int fd, const char *format, ...); + extern void some_boring_bug2(char c); + extern pid_t getpid(void); + extern pid_t pid; + + gboolean js_stalker_callback(const cs_insn *insn, gboolean begin, + gboolean excluded, GumStalkerOutput *output) + { + pid_t my_pid = getpid(); + GumX86Writer *cw = output->writer.x86; + + if (GUM_ADDRESS(insn->address) < GUM_ADDRESS(some_boring_bug2)) { + + return TRUE; + + } + + if (GUM_ADDRESS(insn->address) >= + GUM_ADDRESS(some_boring_bug2) + BORING2_LEN) { + + return TRUE; + + } + + if (my_pid == pid) { + + if (begin) { + + dprintf(STDERR_FILENO, "\n> 0x%016lX: %s %s\n", insn->address, + insn->mnemonic, insn->op_str); + + } else { + + dprintf(STDERR_FILENO, " 0x%016lX: %s %s\n", insn->address, + insn->mnemonic, insn->op_str); + + } + + } + + if (insn->id == X86_INS_UD2) { + + gum_x86_writer_put_nop(cw); + return FALSE; + + } else { + + return TRUE; + + } + } + `, + { + dprintf: Module.getExportByName(null, 'dprintf'), + getpid: Module.getExportByName(null, 'getpid'), + some_boring_bug2: some_boring_bug2, + pid: pid + }); +Afl.setStalkerCallback(cm.js_stalker_callback) +Afl.setStdErr("/tmp/stderr.txt"); +Afl.done(); +Afl.print("done"); diff --git a/frida_mode/test/js/test.c b/frida_mode/test/js/test.c new file mode 100644 index 00000000..bbda5ccf --- /dev/null +++ b/frida_mode/test/js/test.c @@ -0,0 +1,115 @@ +/* + american fuzzy lop++ - a trivial program to test the build + -------------------------------------------------------- + Originally written by Michal Zalewski + Copyright 2014 Google Inc. All rights reserved. + Copyright 2019-2020 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 <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +void LLVMFuzzerTestOneInput(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"); + +} + +int run(char *file) { + + int fd = -1; + off_t len; + char * buf = NULL; + size_t n_read; + int result = -1; + + do { + + 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; + + } + + 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); + + LLVMFuzzerTestOneInput(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; + +} + +void slow() { + + usleep(100000); + +} + +int main(int argc, char **argv) { + + if (argc != 2) { return 1; } + slow(); + return run(argv[1]); + +} + diff --git a/frida_mode/test/js/test2.c b/frida_mode/test/js/test2.c new file mode 100644 index 00000000..d16f35fc --- /dev/null +++ b/frida_mode/test/js/test2.c @@ -0,0 +1,177 @@ +/* + american fuzzy lop++ - a trivial program to test the build + -------------------------------------------------------- + Originally written by Michal Zalewski + Copyright 2014 Google Inc. All rights reserved. + Copyright 2019-2020 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 <fcntl.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define IGNORED_RETURN(x) (void)!(x) + +const uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +uint32_t +crc32(const void *buf, size_t size) +{ + const uint8_t *p = buf; + uint32_t crc; + crc = ~0U; + while (size--) + crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + return crc ^ ~0U; +} + +/* + * Don't you hate those contrived examples which CRC their data. We can use + * FRIDA to patch this function out and always return success. Otherwise, we + * could change it to actually correct the checksum. + */ +int crc32_check (char * buf, int len) { + if (len < sizeof(uint32_t)) { return 0; } + uint32_t expected = *(uint32_t *)&buf[len - sizeof(uint32_t)]; + uint32_t calculated = crc32(buf, len - sizeof(uint32_t)); + return expected == calculated; +} + +/* + * So you've found a really boring bug in an earlier campaign which results in + * a NULL dereference or something like that. That bug can get in the way, + * causing the persistent loop to exit whenever it is triggered, and can also + * cloud your output unnecessarily. Again, we can use FRIDA to patch it out. + */ +void some_boring_bug(char c) { + switch (c) { + case 'A'...'Z': + case 'a'...'z': + __builtin_trap(); + break; + } +} + +extern void some_boring_bug2(char c); + +__asm__ ( + ".text \n" + "some_boring_bug2: \n" + ".global some_boring_bug2 \n" + ".type some_boring_bug2, @function \n" + "mov %edi, %eax \n" + "cmp $0xb4, %al \n" + "jne ok \n" + "ud2 \n" + "ok: \n" + "ret \n"); + +void LLVMFuzzerTestOneInput(char *buf, int len) { + + if (!crc32_check(buf, len)) return; + + some_boring_bug(buf[0]); + some_boring_bug2(buf[0]); + + 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 if (buf[0] == '2') { + printf("Oh we, weren't expecting that!"); + __builtin_trap(); + } + else + printf("Neither one or zero? How quaint!\n"); + +} + +int main(int argc, char **argv) { + + int fd = -1; + off_t len; + char * buf = NULL; + size_t n_read; + int result = -1; + + if (argc != 2) { return 1; } + + printf("Running: %s\n", argv[1]); + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { return 1; } + + len = lseek(fd, 0, SEEK_END); + if (len < 0) { return 1; } + + if (lseek(fd, 0, SEEK_SET) != 0) { return 1; } + + buf = malloc(len); + if (buf == NULL) { return 1; } + + n_read = read(fd, buf, len); + if (n_read != len) { return 1; } + + printf("Running: %s: (%zd bytes)\n", argv[1], n_read); + + LLVMFuzzerTestOneInput(buf, len); + printf("Done: %s: (%zd bytes)\n", argv[1], n_read); + + return 0; +} + diff --git a/frida_mode/test/persistent_ret/test.js b/frida_mode/test/persistent_ret/test.js new file mode 100644 index 00000000..8adb45b2 --- /dev/null +++ b/frida_mode/test/persistent_ret/test.js @@ -0,0 +1,48 @@ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +Afl.print(`PID: ${Process.id}`); + +const name = Process.enumerateModules()[0].name; +Afl.print(`Name: ${name}`); + +new ModuleMap().values().forEach(m => { + Afl.print(`${m.base}-${m.base.add(m.size)} ${m.name}`); +}); + +if (name === 'testinstr') { + const persistent_addr = DebugSymbol.fromName('LLVMFuzzerTestOneInput').address; + Afl.print(`persistent_addr: ${persistent_addr}`); + Afl.setEntryPoint(persistent_addr); + Afl.setPersistentAddress(persistent_addr); + Afl.setInstrumentDebugFile("/dev/stdout"); + Afl.setPersistentDebug(); + Afl.setInstrumentNoOptimize(); + Afl.setInstrumentEnableTracing(); + + const LLVMFuzzerTestOneInput = new NativeFunction( + persistent_addr, + 'void', + ['pointer', 'uint64'], + {traps: "all"}); + + const persistentHook = new NativeCallback( + (data, size) => { + const input = Afl.aflFuzzPtr.readPointer(); + const len = Afl.aflFuzzLen.readPointer().readU32(); + const hd = hexdump(input, {length: len, header: false, ansi: true}); + Afl.print(`input: ${hd}`); + LLVMFuzzerTestOneInput(input, len); + }, + 'void', + ['pointer', 'uint64']); + + Afl.aflSharedMemFuzzing.writeInt(1); + Interceptor.replace(persistent_addr, persistentHook); + Interceptor.flush(); +} + +Afl.print("done"); +Afl.done(); diff --git a/frida_mode/test/png/persistent/hook/cmodule.js b/frida_mode/test/png/persistent/hook/cmodule.js new file mode 100644 index 00000000..ab8bdc66 --- /dev/null +++ b/frida_mode/test/png/persistent/hook/cmodule.js @@ -0,0 +1,39 @@ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +Afl.print(`PID: ${Process.id}`); + +const name = Process.enumerateModules()[0].name; +Afl.print(`Name: ${name}`); + +new ModuleMap().values().forEach(m => { + Afl.print(`${m.base}-${m.base.add(m.size)} ${m.name}`); +}); + +const persistent_addr = DebugSymbol.fromName('LLVMFuzzerTestOneInput').address; +Afl.print(`persistent_addr: ${persistent_addr}`); +Afl.setEntryPoint(persistent_addr); +Afl.setPersistentAddress(persistent_addr); + +const cm = new CModule(` + + #include <string.h> + #include <gum/gumdefs.h> + + void afl_persistent_hook(GumCpuContext *regs, uint8_t *input_buf, + uint32_t input_buf_len) { + + memcpy((void *)regs->rdi, input_buf, input_buf_len); + regs->rsi = input_buf_len; + + } + `, + { + memcpy: Module.getExportByName(null, 'memcpy') + }); +Afl.setPersistentHook(cm.afl_persistent_hook); + +Afl.print("done"); +Afl.done(); diff --git a/frida_mode/test/png/persistent/hook/load.js b/frida_mode/test/png/persistent/hook/load.js new file mode 100644 index 00000000..ea4d28c3 --- /dev/null +++ b/frida_mode/test/png/persistent/hook/load.js @@ -0,0 +1,27 @@ +Afl.print('******************'); +Afl.print('* AFL FRIDA MODE *'); +Afl.print('******************'); +Afl.print(''); + +Afl.print(`PID: ${Process.id}`); + +const name = Process.enumerateModules()[0].name; +Afl.print(`Name: ${name}`); + +new ModuleMap().values().forEach(m => { + Afl.print(`${m.base}-${m.base.add(m.size)} ${m.name}`); +}); + +const persistent_addr = DebugSymbol.fromName('LLVMFuzzerTestOneInput').address; +Afl.print(`persistent_addr: ${persistent_addr}`); +Afl.setEntryPoint(persistent_addr); +Afl.setPersistentAddress(persistent_addr); + +const path = Afl.module.path; +const dir = path.substring(0, path.lastIndexOf("/")); +const mod = Module.load(`${dir}/frida_mode/build/frida_hook.so`); +const hook = mod.getExportByName('afl_persistent_hook'); +Afl.setPersistentHook(hook); + +Afl.print("done"); +Afl.done(); diff --git a/frida_mode/test/proj4/GNUmakefile b/frida_mode/test/proj4/GNUmakefile new file mode 100644 index 00000000..8555ebad --- /dev/null +++ b/frida_mode/test/proj4/GNUmakefile @@ -0,0 +1,166 @@ +PWD:=$(shell pwd)/ +ROOT:=$(shell realpath $(PWD)../../..)/ +BUILD_DIR:=$(PWD)build/ + +AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so + +LIBPROJ4_BUILD_DIR:=$(BUILD_DIR)libproj4/ +HARNESS_BUILD_DIR:=$(BUILD_DIR)harness/ +PROJ4TEST_BUILD_DIR:=$(BUILD_DIR)proj4test/ + +LIBPROJ4_URL:=https://github.com/OSGeo/PROJ +LIBPROJ4_DIR:=$(LIBPROJ4_BUILD_DIR)libproj4/ +LIBPROJ4_CONFIGURE:=$(LIBPROJ4_DIR)configure.ac +LIBPROJ4_MAKEFILE:=$(LIBPROJ4_DIR)Makefile +LIBPROJ4_LIB:=$(LIBPROJ4_DIR)src/.libs/libproj.a + +HARNESS_FILE:=$(HARNESS_BUILD_DIR)StandaloneFuzzTargetMain.c +HARNESS_OBJ:=$(HARNESS_BUILD_DIR)StandaloneFuzzTargetMain.o +HARNESS_URL:="https://raw.githubusercontent.com/AFLplusplus/AFLplusplus/stable/utils/aflpp_driver/aflpp_qemu_driver.c" + +PROJ4TEST_FILE:=$(PROJ4TEST_BUILD_DIR)target.cc +PROJ4TEST_OBJ:=$(PROJ4TEST_BUILD_DIR)target.o +PROJ4TEST_URL:="https://raw.githubusercontent.com/OSGeo/PROJ/d00501750b210a73f9fb107ac97a683d4e3d8e7a/test/fuzzers/standard_fuzzer.cpp" + +LDFLAGS += -lpthread + +TEST_BIN:=$(BUILD_DIR)test +ifeq "$(shell uname)" "Darwin" +TEST_BIN_LDFLAGS:=-undefined dynamic_lookup -Wl,-no_pie +endif + +TEST_DATA_DIR:=$(BUILD_DIR)in/ +TEST_DATA_FILE:=$(TEST_DATA_DIR)default_seed + +FRIDA_OUT:=$(BUILD_DIR)frida-out + +ifndef ARCH + +ARCH=$(shell uname -m) +ifeq "$(ARCH)" "aarch64" + ARCH:=arm64 +endif + +ifeq "$(ARCH)" "i686" + ARCH:=x86 +endif +endif + +GET_SYMBOL_ADDR:=$(ROOT)frida_mode/util/get_symbol_addr.sh + +ifeq "$(ARCH)" "aarch64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x0000aaaaaaaaa000) +endif + +ifeq "$(ARCH)" "x86_64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x0000555555554000) +endif + +ifeq "$(ARCH)" "x86" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x56555000) +endif + +.PHONY: all clean frida hook + +all: $(TEST_BIN) + make -C $(ROOT)frida_mode/ + +32: + CXXFLAGS="-m32" LDFLAGS="-m32" ARCH="x86" make all + +$(BUILD_DIR): + mkdir -p $@ + +######### HARNESS ######## +$(HARNESS_BUILD_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(HARNESS_FILE): | $(HARNESS_BUILD_DIR) + wget -O $@ $(HARNESS_URL) + +$(HARNESS_OBJ): $(HARNESS_FILE) + $(CC) $(CXXFLAGS) $(LDFLAGS) -o $@ -c $< + +######### PROJ4TEST ######## + +$(PROJ4TEST_BUILD_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(PROJ4TEST_FILE): | $(PROJ4TEST_BUILD_DIR) + wget -O $@ $(PROJ4TEST_URL) + +$(PROJ4TEST_OBJ): $(PROJ4TEST_FILE) | $(LIBPROJ4_MAKEFILE) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -std=c++11 -I $(LIBPROJ4_DIR)src/ -o $@ -c $< + +######### LIBPROJ4 ######## + +$(LIBPROJ4_BUILD_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(LIBPROJ4_CONFIGURE): $(LIBPROJ4_BUILD_DIR) + git clone $(LIBPROJ4_URL) $(LIBPROJ4_DIR) + cd $(LIBPROJ4_DIR) && git checkout d00501750b210a73f9fb107ac97a683d4e3d8e7a + +$(LIBPROJ4_MAKEFILE): $(LIBPROJ4_CONFIGURE) + cd $(LIBPROJ4_DIR) && ./autogen.sh + cd $(LIBPROJ4_DIR) && ./configure + +$(LIBPROJ4_LIB): $(LIBPROJ4_MAKEFILE) + make -C $(LIBPROJ4_DIR) -j $(shell nproc) + +######### TEST ######## + +$(TEST_BIN): $(HARNESS_OBJ) $(PROJ4TEST_OBJ) $(LIBPROJ4_LIB) + $(CXX) \ + $(CFLAGS) \ + -o $@ \ + $(HARNESS_OBJ) $(PROJ4TEST_OBJ) $(LIBPROJ4_LIB) \ + -lz \ + $(LDFLAGS) \ + $(TEST_BIN_LDFLAGS) \ + +########## DUMMY ####### + +$(TEST_DATA_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(TEST_DATA_FILE): | $(TEST_DATA_DIR) + echo "hi" > $(TEST_DATA_FILE) + +###### TEST DATA ####### + +clean: + rm -rf $(BUILD_DIR) + +frida: $(TEST_BIN) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) $(TEST_DATA_FILE) + AFL_DEBUG_CHILD=1 \ + AFL_DISABLE_TRIM=1 \ + AFL_FRIDA_PERSISTENT_CNT=1000000 \ + AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \ + AFL_NO_AFFINITY=1 \ + X__AFL_NO_UI=1 \ + AFL_PATH=/out \ + AFL_SHUFFLE_QUEUE=1 \ + AFL_SKIP_CPUFREQ=1 \ + AFL_SKIP_CRASHES=1 \ + AFL_TESTCACHE_SIZE=2 \ + 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 \ + -i $(TEST_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -m none \ + -t 1000+ \ + -d \ + -O \ + -c 0\ + -V 30 \ + -- \ + $(TEST_BIN) 2147483647 + +debug: + gdb \ + --ex 'set environment LD_PRELOAD=$(ROOT)afl-frida-trace.so' \ + --ex 'set disassembly-flavor intel' \ + --args $(TEST_BIN) $(TEST_DATA_DIR)basn0g01.proj4 diff --git a/frida_mode/test/proj4/Makefile b/frida_mode/test/proj4/Makefile new file mode 100644 index 00000000..f83e2992 --- /dev/null +++ b/frida_mode/test/proj4/Makefile @@ -0,0 +1,17 @@ +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 + +frida: + @gmake frida + +debug: + @gmake debug + diff --git a/frida_mode/test/proj4/get_symbol_addr.py b/frida_mode/test/proj4/get_symbol_addr.py new file mode 100755 index 00000000..1c46e010 --- /dev/null +++ b/frida_mode/test/proj4/get_symbol_addr.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +import argparse +from elftools.elf.elffile import ELFFile + +def process_file(file, symbol, base): + with open(file, 'rb') as f: + elf = ELFFile(f) + symtab = elf.get_section_by_name('.symtab') + mains = symtab.get_symbol_by_name(symbol) + if len(mains) != 1: + print ("Failed to find main") + return 1 + + main_addr = mains[0]['st_value'] + main = base + main_addr + print ("0x%016x" % main) + return 0 + +def hex_value(x): + return int(x, 16) + +def main(): + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('-f', '--file', dest='file', type=str, + help='elf file name', required=True) + parser.add_argument('-s', '--symbol', dest='symbol', type=str, + help='symbol name', required=True) + parser.add_argument('-b', '--base', dest='base', type=hex_value, + help='elf base address', required=True) + + args = parser.parse_args() + return process_file (args.file, args.symbol, args.base) + +if __name__ == "__main__": + ret = main() + exit(ret) diff --git a/frida_mode/test/sqlite/GNUmakefile b/frida_mode/test/sqlite/GNUmakefile new file mode 100644 index 00000000..80e0a939 --- /dev/null +++ b/frida_mode/test/sqlite/GNUmakefile @@ -0,0 +1,166 @@ +PWD:=$(shell pwd)/ +ROOT:=$(shell realpath $(PWD)../../..)/ +BUILD_DIR:=$(PWD)build/ + +SQLITE_BUILD_DIR:=$(BUILD_DIR)sqlite/ +SQLITE_BUILD_SRC_DIR:=$(SQLITE_BUILD_DIR)src/ + +AFLPP_DRIVER:=$(ROOT)utils/aflpp_driver/libAFLQemuDriver.a + +AFLPP_DRIVER:=$(ROOT)utils/aflpp_driver/libAFLQemuDriver.a +AFLPP_FRIDA_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/frida_hook.so +AFLPP_QEMU_DRIVER_HOOK_OBJ=$(ROOT)frida_mode/build/qemu_hook.so + + +CFLAGS += -fpermissive + +LDFLAGS += -lpthread + +TEST_BIN:=$(SQLITE_BUILD_DIR)ossfuzz +SQLITE_TEST_DIR:=$(BUILD_DIR)in/ +AFLPP_DRIVER_DUMMY_INPUT:=$(SQLITE_TEST_DIR)in + +SQLITE_CFLAGS:= -DSQLITE_MAX_LENGTH=128000000 \ + -DSQLITE_MAX_SQL_LENGTH=128000000 \ + -DSQLITE_MAX_MEMORY=25000000 \ + -DSQLITE_PRINTF_PRECISION_LIMIT=1048576 \ + -DSQLITE_DEBUG=1 \ + -DSQLITE_MAX_PAGE_COUNT=16384 + +QEMU_OUT:=$(BUILD_DIR)qemu-out +FRIDA_OUT:=$(BUILD_DIR)frida-out + +ifndef ARCH + +ARCH=$(shell uname -m) +ifeq "$(ARCH)" "aarch64" + ARCH:=arm64 +endif + +ifeq "$(ARCH)" "i686" + ARCH:=x86 +endif +endif + +GET_SYMBOL_ADDR:=$(ROOT)frida_mode/util/get_symbol_addr.sh + +AFL_QEMU_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x4000000000) + +ifeq "$(ARCH)" "aarch64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x0000aaaaaaaaa000) +endif + +ifeq "$(ARCH)" "x86_64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x0000555555554000) +endif + +ifeq "$(ARCH)" "x86" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(TEST_BIN) LLVMFuzzerTestOneInput 0x56555000) +endif + +.PHONY: all clean qemu frida hook sqlite + +all: $(TEST_BIN) + make -C $(ROOT)frida_mode/ + +32: + CXXFLAGS="-m32" LDFLAGS="-m32" ARCH="x86" make all + +$(BUILD_DIR): + mkdir -p $@ + +########## SQLITE ####### + +$(AFLPP_DRIVER): + make -C $(ROOT) + +$(SQLITE_BUILD_DIR): | $(BUILD_DIR) + mkdir $@ + +$(SQLITE_BUILD_DIR)sqlite3.tar.gz: | $(SQLITE_BUILD_DIR) + curl 'https://sqlite.org/src/tarball/sqlite.tar.gz?r=c78cbf2e86850cc6' -o $@ + +$(SQLITE_BUILD_SRC_DIR): $(SQLITE_BUILD_DIR)sqlite3.tar.gz + mkdir -p $@ + tar xzvf $< --strip-components 1 -C $@ + +$(SQLITE_TEST_DIR): | $(SQLITE_BUILD_SRC_DIR) + mkdir -p $@ + find $(SQLITE_BUILD_SRC_DIR) -name "*.test" | xargs -L1 -I%% cp -v %% $@ + +$(SQLITE_BUILD_SRC_DIR)Makefile: | $(SQLITE_BUILD_SRC_DIR) + cd $(SQLITE_BUILD_SRC_DIR) && \ + CFLAGS="$(SQLITE_CFLAGS)" \ + ASAN_OPTIONS=detect_leaks=0 \ + ./configure + +$(SQLITE_BUILD_SRC_DIR).libs/libsqlite3.so: $(SQLITE_BUILD_SRC_DIR)Makefile + CFLAGS="$(SQLITE_CFLAGS)" \ + ASAN_OPTIONS=detect_leaks=0 \ + make -C $(SQLITE_BUILD_SRC_DIR) -j $(shell nproc) + +$(SQLITE_BUILD_SRC_DIR)sqlite3.o: $(SQLITE_BUILD_SRC_DIR).libs/libsqlite3.so + CFLAGS="$(SQLITE_CFLAGS)" \ + ASAN_OPTIONS=detect_leaks=0 \ + make -C $(SQLITE_BUILD_SRC_DIR) -j $(shell nproc) sqlite3.c + +$(SQLITE_BUILD_DIR)ossfuzz.o: $(SQLITE_BUILD_SRC_DIR)sqlite3.o + $(CC) -I $(SQLITE_BUILD_SRC_DIR) -c $(SQLITE_BUILD_SRC_DIR)test/ossfuzz.c -o $@ + +$(TEST_BIN): $(SQLITE_BUILD_DIR)ossfuzz.o + $(CXX) -o $(TEST_BIN) \ + $(SQLITE_BUILD_DIR)ossfuzz.o \ + $(SQLITE_BUILD_SRC_DIR)sqlite3.o \ + $(AFLPP_DRIVER) \ + -l pthread \ + -l dl + +sqlite: $(SQLITE_TEST_DIR) $(TEST_BIN) + +########## DUMMY ####### + +$(AFLPP_DRIVER_DUMMY_INPUT): | $(SQLITE_TEST_DIR) + truncate -s 1M $@ + +###### TEST DATA ####### + +clean: + rm -rf $(BUILD_DIR) + +qemu: $(TEST_BIN) $(AFLPP_QEMU_DRIVER_HOOK_OBJ) $(AFLPP_DRIVER_DUMMY_INPUT) | $(SQLITE_TEST_DIR) + AFL_QEMU_PERSISTENT_CNT=1000000 \ + AFL_QEMU_PERSISTENT_HOOK=$(AFLPP_QEMU_DRIVER_HOOK_OBJ) \ + AFL_ENTRYPOINT=$(AFL_QEMU_PERSISTENT_ADDR) \ + AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \ + AFL_QEMU_PERSISTENT_GPR=1 \ + $(ROOT)afl-fuzz \ + -D \ + -V 30 \ + -Q \ + -i $(SQLITE_TEST_DIR) \ + -o $(QEMU_OUT) \ + -- \ + $(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT) + +frida: $(TEST_BIN) $(AFLPP_FRIDA_DRIVER_HOOK_OBJ) $(AFLPP_DRIVER_DUMMY_INPUT) | $(SQLITE_TEST_DIR) + AFL_FRIDA_PERSISTENT_CNT=1000000 \ + 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 \ + -V 30 \ + -O \ + -i $(SQLITE_TEST_DIR) \ + -o $(FRIDA_OUT) \ + -- \ + $(TEST_BIN) $(AFLPP_DRIVER_DUMMY_INPUT) + +debug: + gdb \ + --ex 'set environment LD_PRELOAD=$(ROOT)afl-frida-trace.so' \ + --ex 'set environment AFL_QEMU_DRIVER_NO_HOOK=1' \ + --ex 'set disassembly-flavor intel' \ + --ex 'b main' \ + --ex 'r < $(SQLITE_TEST_DIR)0034ecacd5427aafc6b97413da2053b36de5059f' \ + $(TEST_BIN) diff --git a/frida_mode/test/sqlite/Makefile b/frida_mode/test/sqlite/Makefile new file mode 100644 index 00000000..f83e2992 --- /dev/null +++ b/frida_mode/test/sqlite/Makefile @@ -0,0 +1,17 @@ +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 + +frida: + @gmake frida + +debug: + @gmake debug + diff --git a/frida_mode/test/unstable/GNUmakefile b/frida_mode/test/unstable/GNUmakefile new file mode 100644 index 00000000..938d7c17 --- /dev/null +++ b/frida_mode/test/unstable/GNUmakefile @@ -0,0 +1,92 @@ +PWD:=$(shell pwd)/ +ROOT:=$(shell realpath $(PWD)../../..)/ +BUILD_DIR:=$(PWD)build/ +UNSTABLE_DATA_DIR:=$(BUILD_DIR)in/ +UNSTABLE_DATA_FILE:=$(UNSTABLE_DATA_DIR)in + +UNSTABLE_BIN:=$(BUILD_DIR)unstable +UNSTABLE_SRC:=$(PWD)unstable.c + +QEMU_OUT:=$(BUILD_DIR)qemu-out +FRIDA_OUT:=$(BUILD_DIR)frida-out + +ifndef ARCH + +ARCH=$(shell uname -m) +ifeq "$(ARCH)" "aarch64" + ARCH:=arm64 +endif + +ifeq "$(ARCH)" "i686" + ARCH:=x86 +endif +endif + +GET_SYMBOL_ADDR:=$(ROOT)frida_mode/util/get_symbol_addr.sh + +AFL_QEMU_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(UNSTABLE_BIN) run_test 0x4000000000) + +ifeq "$(ARCH)" "aarch64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(UNSTABLE_BIN) run_test 0x0000aaaaaaaaa000) +endif + +ifeq "$(ARCH)" "x86_64" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(UNSTABLE_BIN) run_test 0x0000555555554000) +endif + +ifeq "$(ARCH)" "x86" + AFL_FRIDA_PERSISTENT_ADDR=$(shell $(GET_SYMBOL_ADDR) $(UNSTABLE_BIN) run_test 0x56555000) +endif + +.PHONY: all 32 clean qemu frida + +all: $(UNSTABLE_BIN) + make -C $(ROOT)frida_mode/ + +32: + CFLAGS="-m32" LDFLAGS="-m32" ARCH="x86" make all + +$(BUILD_DIR): + mkdir -p $@ + +$(UNSTABLE_DATA_DIR): | $(BUILD_DIR) + mkdir -p $@ + +$(UNSTABLE_DATA_FILE): | $(UNSTABLE_DATA_DIR) + echo -n "000" > $@ + +$(UNSTABLE_BIN): $(UNSTABLE_SRC) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< + +clean: + rm -rf $(BUILD_DIR) + + +qemu: $(UNSTABLE_BIN) $(UNSTABLE_DATA_FILE) + AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \ + $(ROOT)afl-fuzz \ + -D \ + -Q \ + -i $(UNSTABLE_DATA_DIR) \ + -o $(QEMU_OUT) \ + -- \ + $(UNSTABLE_BIN) @@ + +frida: $(UNSTABLE_BIN) $(UNSTABLE_DATA_FILE) + AFL_DEBUG=1 \ + AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \ + AFL_FRIDA_INST_TRACE_UNIQUE=1 \ + AFL_FRIDA_INST_NO_OPTIMIZE=1 \ + $(ROOT)afl-fuzz \ + -D \ + -O \ + -i $(UNSTABLE_DATA_DIR) \ + -o $(FRIDA_OUT) \ + -- \ + $(UNSTABLE_BIN) @@ + +debug: + gdb \ + --ex 'set environment LD_PRELOAD=$(ROOT)afl-frida-trace.so' \ + --ex 'set disassembly-flavor intel' \ + --args $(UNSTABLE_BIN) $(UNSTABLE_DATA_FILE) diff --git a/frida_mode/test/unstable/Makefile b/frida_mode/test/unstable/Makefile new file mode 100644 index 00000000..f843af19 --- /dev/null +++ b/frida_mode/test/unstable/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/unstable/unstable.c b/frida_mode/test/unstable/unstable.c new file mode 100644 index 00000000..67d56b73 --- /dev/null +++ b/frida_mode/test/unstable/unstable.c @@ -0,0 +1,67 @@ +/* + american fuzzy lop++ - a trivial program to test the build + -------------------------------------------------------- + Originally written by Michal Zalewski + Copyright 2014 Google Inc. All rights reserved. + Copyright 2019-2020 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 <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#ifdef __APPLE__ + #define TESTINSTR_SECTION +#else + #define TESTINSTR_SECTION __attribute__((section(".testinstr"))) +#endif + +void LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + + if (size < 1) return; + + int r = rand(); + if ((r % 2) == 0) { + printf ("Hooray all even\n"); + } else { + printf ("Hmm that's odd\n"); + } + + // we support three input cases + if (data[0] == '0') + printf("Looks like a zero to me!\n"); + else if (data[0] == '1') + printf("Pretty sure that is a one!\n"); + else + printf("Neither one or zero? How quaint!\n"); + +} + +void run_test(char * file) { + fprintf(stderr, "Running: %s\n", file); + FILE *f = fopen(file, "r"); + assert(f); + fseek(f, 0, SEEK_END); + size_t len = ftell(f); + fseek(f, 0, SEEK_SET); + unsigned char *buf = (unsigned char*)malloc(len); + size_t n_read = fread(buf, 1, len, f); + fclose(f); + assert(n_read == len); + LLVMFuzzerTestOneInput(buf, len); + free(buf); + fprintf(stderr, "Done: %s: (%zd bytes)\n", file, n_read); +} + +int main(int argc, char **argv) { + srand(1); + fprintf(stderr, "StandaloneFuzzTargetMain: running %d inputs\n", argc - 1); + for (int i = 1; i < argc; i++) { + run_test(argv[i]); + } +} diff --git a/frida_mode/ts/lib/afl.ts b/frida_mode/ts/lib/afl.ts new file mode 100644 index 00000000..6326c099 --- /dev/null +++ b/frida_mode/ts/lib/afl.ts @@ -0,0 +1,399 @@ +class Afl { + + /** + * Field containing the `Module` object for `afl-frida-trace.so` (the FRIDA mode + * implementation). + */ + public static module: Module = Process.getModuleByName("afl-frida-trace.so"); + + /** + * This is equivalent to setting a value in `AFL_FRIDA_EXCLUDE_RANGES`, + * it takes as arguments a `NativePointer` and a `number`. It can be + * called multiple times to exclude several ranges. + */ + public static addExcludedRange(addressess: NativePointer, size: number): void { + Afl.jsApiAddExcludeRange(addressess, size); + } + + /** + * This is equivalent to setting a value in `AFL_FRIDA_INST_RANGES`, + * it takes as arguments a `NativePointer` and a `number`. It can be + * called multiple times to include several ranges. + */ + public static addIncludedRange(addressess: NativePointer, size: number): void { + Afl.jsApiAddIncludeRange(addressess, size); + } + + /** + * This must always be called at the end of your script. This lets + * FRIDA mode know that your configuration is finished and that + * execution has reached the end of your script. Failure to call + * this will result in a fatal error. + */ + public static done(): void { + Afl.jsApiDone(); + } + + /** + * This function can be called within your script to cause FRIDA + * mode to trigger a fatal error. This is useful if for example you + * discover a problem you weren't expecting and want everything to + * stop. The user will need to enable `AFL_DEBUG_CHILD=1` to view + * this error message. + */ + public static error(msg: string): void { + const buf = Memory.allocUtf8String(msg); + Afl.jsApiError(buf); + } + + /** + * Function used to provide access to `__afl_fuzz_ptr`, which contains the length of + * fuzzing data when using in-memory test case fuzzing. + */ + public static getAflFuzzLen(): NativePointer { + + return Afl.jsApiGetSymbol("__afl_fuzz_len"); + } + + /** + * Function used to provide access to `__afl_fuzz_ptr`, which contains the fuzzing + * data when using in-memory test case fuzzing. + */ + public static getAflFuzzPtr(): NativePointer { + + return Afl.jsApiGetSymbol("__afl_fuzz_ptr"); + } + + /** + * Print a message to the STDOUT. This should be preferred to + * FRIDA's `console.log` since FRIDA will queue it's log messages. + * If `console.log` is used in a callback in particular, then there + * may no longer be a thread running to service this queue. + */ + public static print(msg: string): void { + const STDOUT_FILENO = 2; + const log = `${msg}\n`; + const buf = Memory.allocUtf8String(log); + Afl.jsApiWrite(STDOUT_FILENO, buf, log.length); + } + + /** + * See `AFL_FRIDA_DEBUG_MAPS`. + */ + public static setDebugMaps(): void { + Afl.jsApiSetDebugMaps(); + } + + /** + * This has the same effect as setting `AFL_ENTRYPOINT`, but has the + * convenience of allowing you to use FRIDAs APIs to determine the + * address you would like to configure, rather than having to grep + * the output of `readelf` or something similarly ugly. This + * function should be called with a `NativePointer` as its + * argument. + */ + public static setEntryPoint(address: NativePointer): void { + Afl.jsApiSetEntryPoint(address); + } + + /** + * Function used to enable in-memory test cases for fuzzing. + */ + public static setInMemoryFuzzing(): void { + Afl.jsApiAflSharedMemFuzzing.writeInt(1); + } + + /** + * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as + * an argument. + */ + public static setInstrumentDebugFile(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetInstrumentDebugFile(buf); + } + + /** + * See `AFL_FRIDA_INST_TRACE`. + */ + public static setInstrumentEnableTracing(): void { + Afl.jsApiSetInstrumentTrace(); + } + + /** + * See `AFL_FRIDA_INST_JIT`. + */ + public static setInstrumentJit(): void { + Afl.jsApiSetInstrumentJit(); + } + + /** + * See `AFL_INST_LIBS`. + */ + public static setInstrumentLibraries(): void { + Afl.jsApiSetInstrumentLibraries(); + } + + /** + * See `AFL_FRIDA_INST_NO_OPTIMIZE` + */ + public static setInstrumentNoOptimize(): void { + Afl.jsApiSetInstrumentNoOptimize(); + } + + /* + * See `AFL_FRIDA_INST_SEED` + */ + public static setInstrumentSeed(seed: NativePointer): void { + Afl.jsApiSetInstrumentSeed(seed); + } + + /** + * See `AFL_FRIDA_INST_TRACE_UNIQUE`. + */ + public static setInstrumentTracingUnique(): void { + Afl.jsApiSetInstrumentTraceUnique(); + } + + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_ADDR`, again a + * `NativePointer` should be provided as it's argument. + */ + public static setPersistentAddress(address: NativePointer): void { + Afl.jsApiSetPersistentAddress(address); + } + + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_CNT`, a + * `number` should be provided as it's argument. + */ + public static setPersistentCount(count: number): void { + Afl.jsApiSetPersistentCount(count); + } + + /** + * See `AFL_FRIDA_PERSISTENT_DEBUG`. + */ + public static setPersistentDebug(): void { + Afl.jsApiSetPersistentDebug(); + } + + /** + * See `AFL_FRIDA_PERSISTENT_ADDR`. This function takes a NativePointer as an + * argument. See above for examples of use. + */ + public static setPersistentHook(address: NativePointer): void { + Afl.jsApiSetPersistentHook(address); + } + + /** + * This is equivalent to setting `AFL_FRIDA_PERSISTENT_RET`, again a + * `NativePointer` should be provided as it's argument. + */ + public static setPersistentReturn(address: NativePointer): void { + Afl.jsApiSetPersistentReturn(address); + } + + /** + * See `AFL_FRIDA_INST_NO_PREFETCH`. + */ + public static setPrefetchDisable(): void { + Afl.jsApiSetPrefetchDisable(); + } + + /* + * Set a function to be called for each instruction which is instrumented + * by AFL FRIDA mode. + */ + public static setStalkerCallback(callback: NativePointer): void { + Afl.jsApiSetStalkerCallback(callback); + } + + /** + * See `AFL_FRIDA_STATS_FILE`. This function takes a single `string` as + * an argument. + */ + public static setStatsFile(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStatsFile(buf); + } + + /** + * See `AFL_FRIDA_STATS_INTERVAL`. This function takes a `number` as an + * argument + */ + public static setStatsInterval(interval: number): void { + Afl.jsApiSetStatsInterval(interval); + } + + /** + * See `AFL_FRIDA_STATS_TRANSITIONS` + */ + public static setStatsTransitions(): void { + Afl.jsApiSetStatsTransitions(); + } + + /** + * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as + * an argument. + */ + public static setStdErr(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStdErr(buf); + } + + /** + * See `AFL_FRIDA_OUTPUT_STDOUT`. This function takes a single `string` as + * an argument. + */ + public static setStdOut(file: string): void { + const buf = Memory.allocUtf8String(file); + Afl.jsApiSetStdOut(buf); + } + + private static readonly jsApiAddExcludeRange = Afl.jsApiGetFunction( + "js_api_add_exclude_range", + "void", + ["pointer", "size_t"]); + + private static readonly jsApiAddIncludeRange = Afl.jsApiGetFunction( + "js_api_add_include_range", + "void", + ["pointer", "size_t"]); + + private static readonly jsApiAflSharedMemFuzzing = Afl.jsApiGetSymbol("__afl_sharedmem_fuzzing"); + + private static readonly jsApiDone = Afl.jsApiGetFunction( + "js_api_done", + "void", + []); + + private static readonly jsApiError = Afl.jsApiGetFunction( + "js_api_error", + "void", + ["pointer"]); + + private static readonly jsApiSetDebugMaps = Afl.jsApiGetFunction( + "js_api_set_debug_maps", + "void", + []); + + private static readonly jsApiSetEntryPoint = Afl.jsApiGetFunction( + "js_api_set_entrypoint", + "void", + ["pointer"]); + + private static readonly jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction( + "js_api_set_instrument_debug_file", + "void", + ["pointer"]); + + private static readonly jsApiSetInstrumentJit = Afl.jsApiGetFunction( + "js_api_set_instrument_jit", + "void", + []); + + private static readonly jsApiSetInstrumentLibraries = Afl.jsApiGetFunction( + "js_api_set_instrument_libraries", + "void", + []); + + private static readonly jsApiSetInstrumentNoOptimize = Afl.jsApiGetFunction( + "js_api_set_instrument_no_optimize", + "void", + []); + + private static readonly jsApiSetInstrumentSeed = Afl.jsApiGetFunction( + "js_api_set_instrument_seed", + "void", + ["uint64"]); + + private static readonly jsApiSetInstrumentTrace = Afl.jsApiGetFunction( + "js_api_set_instrument_trace", + "void", + []); + + private static readonly jsApiSetInstrumentTraceUnique = Afl.jsApiGetFunction( + "js_api_set_instrument_trace_unique", + "void", + []); + + private static readonly jsApiSetPersistentAddress = Afl.jsApiGetFunction( + "js_api_set_persistent_address", + "void", + ["pointer"]); + + private static readonly jsApiSetPersistentCount = Afl.jsApiGetFunction( + "js_api_set_persistent_count", + "void", + ["uint64"]); + + private static readonly jsApiSetPersistentDebug = Afl.jsApiGetFunction( + "js_api_set_persistent_debug", + "void", + []); + + private static readonly jsApiSetPersistentHook = Afl.jsApiGetFunction( + "js_api_set_persistent_hook", + "void", + ["pointer"]); + + private static readonly jsApiSetPersistentReturn = Afl.jsApiGetFunction( + "js_api_set_persistent_return", + "void", + ["pointer"]); + + private static readonly jsApiSetPrefetchDisable = Afl.jsApiGetFunction( + "js_api_set_prefetch_disable", + "void", + []); + + private static readonly jsApiSetStalkerCallback = Afl.jsApiGetFunction( + "js_api_set_stalker_callback", + "void", + ["pointer"]); + + private static readonly jsApiSetStatsFile = Afl.jsApiGetFunction( + "js_api_set_stats_file", + "void", + ["pointer"]); + + private static readonly jsApiSetStatsInterval = Afl.jsApiGetFunction( + "js_api_set_stats_interval", + "void", + ["uint64"]); + + private static readonly jsApiSetStatsTransitions = Afl.jsApiGetFunction( + "js_api_set_stats_transitions", + "void", + []); + + private static readonly jsApiSetStdErr = Afl.jsApiGetFunction( + "js_api_set_stderr", + "void", + ["pointer"]); + + private static readonly jsApiSetStdOut = Afl.jsApiGetFunction( + "js_api_set_stdout", + "void", + ["pointer"]); + + private static readonly jsApiWrite = new NativeFunction( + /* tslint:disable-next-line:no-null-keyword */ + Module.getExportByName(null, "write"), + "int", + ["int", "pointer", "int"]); + + private static jsApiGetFunction(name: string, retType: NativeType, argTypes: NativeType[]): NativeFunction { + const addr: NativePointer = Afl.module.getExportByName(name); + + return new NativeFunction(addr, retType, argTypes); + } + + private static jsApiGetSymbol(name: string): NativePointer { + + return Afl.module.getExportByName(name); + } + +} + +export { Afl }; diff --git a/frida_mode/ts/package-lock.json b/frida_mode/ts/package-lock.json new file mode 100644 index 00000000..e766c2c2 --- /dev/null +++ b/frida_mode/ts/package-lock.json @@ -0,0 +1,12 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "tsc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tsc/-/tsc-2.0.3.tgz", + "integrity": "sha512-SN+9zBUtrpUcOpaUO7GjkEHgWtf22c7FKbKCA4e858eEM7Qz86rRDpgOU2lBIDf0fLCsEg65ms899UMUIB2+Ow==", + "dev": true + } + } +} diff --git a/frida_mode/ts/package.json b/frida_mode/ts/package.json new file mode 100644 index 00000000..191eb597 --- /dev/null +++ b/frida_mode/ts/package.json @@ -0,0 +1,32 @@ +{ + "name": "@worksbutnottested/aflplusplus-frida", + "version": "1.0.1", + "description": "AFLplusplus Frida Mode", + "main": "./dist/afl.js", + "types": "./dist/afl.d.ts", + "files": [ + "/dist/" + ], + "repository": { + "type": "git", + "url": "git@github.com:worksbutnottested/AFLplusplus.git" + }, + "publishConfig": { + "cache": "~/.npm", + "registry": "https://npm.pkg.github.com/@worksbutnottested" + }, + "scripts": { + "prepare": "npm run build", + "build": "tsc", + "lint": "tslint -p tslint.json" + }, + "devDependencies": { + "@types/node": "^14.14.2", + "typescript": "^4.0.3", + "typescript-tslint-plugin": "^0.5.5", + "tslint": "^6.1.3" + }, + "dependencies": { + "@types/frida-gum": "^16.2.0" + } +} diff --git a/frida_mode/ts/tsconfig.json b/frida_mode/ts/tsconfig.json new file mode 100644 index 00000000..624e4496 --- /dev/null +++ b/frida_mode/ts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "strict": true, + "module": "commonjs", + "esModuleInterop": true, + "declaration": true, + "outDir": "./dist" + }, + "include": [ + "lib/**/*" + ] + } diff --git a/frida_mode/ts/tslint.json b/frida_mode/ts/tslint.json new file mode 100644 index 00000000..0e7a77ed --- /dev/null +++ b/frida_mode/ts/tslint.json @@ -0,0 +1,256 @@ +{ + "rules": { + "adjacent-overload-signatures": true, + "ban-types": { + "options": [ + ["Object", "Avoid using the `Object` type. Did you mean `object`?"], + [ + "Function", + "Avoid using the `Function` type. Prefer a specific function type, like `() => void`." + ], + ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], + ["Number", "Avoid using the `Number` type. Did you mean `number`?"], + ["String", "Avoid using the `String` type. Did you mean `string`?"], + ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"] + ] + }, + "ban-ts-ignore": true, + "member-access": { + "options": ["check-accessor", "check-constructor", "check-parameter-property"] + }, + "member-ordering": { + "options": { + "order": "statics-first", + "alphabetize": true + } + }, + "no-any": true, + "no-empty-interface": true, + "no-for-in": true, + "no-import-side-effect": true, + "no-inferrable-types": { "options": ["ignore-params"] }, + "no-internal-module": true, + "no-magic-numbers": true, + "no-namespace": true, + "no-non-null-assertion": true, + "no-reference": true, + "no-restricted-globals": true, + "no-this-assignment": true, + "no-var-requires": true, + "only-arrow-functions": true, + "prefer-for-of": true, + "prefer-readonly": true, + "promise-function-async": true, + "typedef": { + "options": [ + "call-signature", + "parameter", + "property-declaration" + ] + }, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "unified-signatures": true, + "await-promise": true, + "ban-comma-operator": true, + "curly": true, + "forin": true, + "function-constructor": true, + "label-position": true, + "no-arg": true, + "no-async-without-await": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-console": true, + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-duplicate-switch-case": true, + "no-duplicate-variable": { "options": ["check-parameters"] }, + "no-dynamic-delete": true, + "no-empty": true, + "no-eval": true, + "no-floating-promises": true, + "no-for-in-array": true, + "no-implicit-dependencies": true, + "no-inferred-empty-object-type": true, + "no-invalid-template-strings": true, + "no-misused-new": true, + "no-null-keyword": true, + "no-null-undefined-union": true, + "no-object-literal-type-assertion": true, + "no-promise-as-boolean": true, + "no-return-await": true, + "no-shadowed-variable": true, + "no-string-literal": true, + "no-string-throw": true, + "no-sparse-arrays": true, + "no-submodule-imports": true, + "no-tautology-expression": true, + "no-unbound-method": true, + "no-unnecessary-class": { "options": ["allow-empty-class", "allow-static-only"] }, + "no-unsafe-any": false, + "no-unsafe-finally": true, + "no-unused-expression": true, + "no-var-keyword": true, + "no-void-expression": true, + "prefer-conditional-expression": true, + "radix": true, + "restrict-plus-operands": true, + "static-this": true, + "strict-boolean-expressions": true, + "strict-string-expressions": true, + "strict-comparisons": true, + "strict-type-predicates": true, + "switch-default": true, + "triple-equals": true, + "unnecessary-constructor": true, + "use-default-type-parameter": true, + "use-isnan": true, + "cyclomatic-complexity": true, + "eofline": true, + "indent": { "options": ["spaces"] }, + "invalid-void": true, + "linebreak-style": { "options": "LF" }, + "max-classes-per-file": { "options": 1 }, + "max-file-line-count": { "options": 1000 }, + "max-line-length": { + "options": { "limit": 120 } + }, + "no-default-export": true, + "no-default-import": true, + "no-duplicate-imports": true, + "no-irregular-whitespace": true, + "no-mergeable-namespace": true, + "no-parameter-reassignment": true, + "no-require-imports": true, + "no-trailing-whitespace": true, + "object-literal-sort-keys": true, + "prefer-const": true, + "trailing-comma": { + "options": { + "esSpecCompliant": true, + "multiline": "always", + "singleline": "never" + } + }, + "align": { + "options": ["parameters", "arguments", "statements", "elements", "members"] + }, + "array-type": { "options": "array-simple" }, + "arrow-parens": true, + "arrow-return-shorthand": { "options": "multiline" }, + "binary-expression-operand-order": true, + "callable-types": true, + "class-name": true, + "comment-format": { "options": ["check-space", "check-uppercase"] }, + "comment-type": { "options": ["singleline", "multiline", "doc", "directive"] }, + "completed-docs": [ + true, + { + "enums": true, + "methods": {"locations": "all", "privacies": ["public", "protected"]}, + "properties": {"locations": "all", "privacies": ["public", "protected"]} + } + ], + "deprecation": true, + "encoding": true, + "file-name-casing": { "options": "camel-case" }, + "import-spacing": true, + "increment-decrement": true, + "interface-name": true, + "interface-over-type-literal": true, + "jsdoc-format": { "options": "check-multiline-start" }, + "match-default-export-name": true, + "new-parens": true, + "newline-before-return": true, + "newline-per-chained-call": true, + "no-angle-bracket-type-assertion": true, + "no-boolean-literal-compare": true, + "no-consecutive-blank-lines": true, + "no-parameter-properties": true, + "no-redundant-jsdoc": true, + "no-reference-import": true, + "no-unnecessary-callback-wrapper": true, + "no-unnecessary-initializer": true, + "no-unnecessary-qualifier": true, + "no-unnecessary-type-assertion": true, + "number-literal-format": true, + "object-literal-key-quotes": { "options": "consistent-as-needed" }, + "object-literal-shorthand": true, + "one-line": { + "options": [ + "check-catch", + "check-else", + "check-finally", + "check-open-brace", + "check-whitespace" + ] + }, + "one-variable-per-declaration": true, + "ordered-imports": { + "options": { + "grouped-imports": true, + "import-sources-order": "case-insensitive", + "named-imports-order": "case-insensitive", + "module-source-path": "full" + } + }, + "prefer-function-over-method": true, + "prefer-method-signature": true, + "prefer-object-spread": true, + "prefer-switch": true, + "prefer-template": true, + "prefer-while": true, + "quotemark": { + "options": ["double", "avoid-escape", "avoid-template"] + }, + "return-undefined": true, + "semicolon": { "options": ["always"] }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" + } + }, + "space-within-parens": { "options": 0 }, + "switch-final-break": true, + "type-literal-delimiter": true, + "unnecessary-bind": true, + "unnecessary-else": true, + "variable-name": { "options": ["ban-keywords", "check-format", "require-const-for-all-caps"] }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-module", + "check-separator", + "check-type", + "check-typecast", + "check-preblock", + "check-type-operator", + "check-rest-spread" + ] + } + } +} diff --git a/frida_mode/util/bin2c.c b/frida_mode/util/bin2c.c new file mode 100644 index 00000000..899d0101 --- /dev/null +++ b/frida_mode/util/bin2c.c @@ -0,0 +1,117 @@ +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +void fatal(char *msg) { + + perror(msg); + exit(1); + +} + +void bin2c_write(char *name, char *output, unsigned char *buff, size_t size) { + + int fd = open(output, O_CREAT | O_WRONLY | O_TRUNC, 00660); + if (fd < 0) { fatal("open"); } + + /* Write the array definition */ + dprintf(fd, "unsigned char %s[] = {\n", name); + + /* 12 bytes per row, just like xxd means we fit an 80 character width */ + for (size_t i = 0; i < size; i += 12) { + + for (size_t j = 0; j < 12; j++) { + + size_t idx = i + j; + + /* If we get to the end of the input, then break */ + if (idx >= size) { break; } + + /* If we are writing the first column, then we need a leading indent */ + if (j == 0) { dprintf(fd, " "); } + + /* Write the hexadecimal byte value */ + dprintf(fd, "0x%02x", buff[idx]); + + /* If we have just written the last byte, then stop */ + if (idx == size - 1) { break; } + + /* + * If we have written the last byte in a row, then follow with a comma + * and a newline + */ + if (j == 11) { + + dprintf(fd, ",\n"); + + /* + * Otherwise, follow with a command and a space + */ + + } else { + + dprintf(fd, ", "); + + } + + } + + } + + /* Write the closing brace for the array */ + dprintf(fd, "\n};\n"); + + /* Write a parameter describing the length of the array */ + dprintf(fd, "unsigned int %s_len = %lu;\n", name, size); + + if (close(fd) < 0) { fatal("close"); } + +} + +void bin2c(char *name, char *input, char *output) { + + int fd = open(input, O_RDONLY); + if (fd < 0) { fatal("open(input)"); } + + size_t size = lseek(fd, 0, SEEK_END); + if (size < 0) { fatal("lseek(SEEK_END)"); } + + if (lseek(fd, 0, SEEK_SET) < 0) { fatal("lseek(SEEK_SET)"); } + + unsigned char *buff = malloc(size); + if (buff == NULL) { fatal("malloc(size)"); } + + if (read(fd, buff, size) != size) { fatal("read(size)"); } + + bin2c_write(name, output, buff, size); + + free(buff); + if (close(fd) < 0) { fatal("close(fd_in)"); } + +} + +int main(int argc, char **argv) { + + if (argc < 4) { + + dprintf(STDERR_FILENO, "%s <name> <input> <output>\n", argv[0]); + return 1; + + } + + char *name = argv[1]; + char *input = argv[2]; + char *output = argv[3]; + + dprintf(STDOUT_FILENO, "bin2c:\n"); + dprintf(STDOUT_FILENO, "\tname: %s\n", name); + dprintf(STDOUT_FILENO, "\tinput: %s\n", input); + dprintf(STDOUT_FILENO, "\toutput: %s\n", output); + + bin2c(name, input, output); + + return 0; + +} + diff --git a/frida_mode/util/get_symbol_addr.sh b/frida_mode/util/get_symbol_addr.sh new file mode 100755 index 00000000..f5d8df91 --- /dev/null +++ b/frida_mode/util/get_symbol_addr.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# set -x +target="$1" +symbol="$2" +base="$3" + +test -z "$target" -o -z "$symbol" -o '!' -e "$target" && exit 0 + +test $(uname -s) = "Darwin" && symbol=_"$symbol" + +file "$target" | grep -q executable && { + nm "$target" | grep -i "T $symbol" | awk '{print"0x"$1}' + exit 0 +} + +hex_base=$(echo "$3" | awk '{sub("^0x","");print $0}' | tr a-f A-F ) +nm "$target" | grep -i "T $symbol" | awk '{print$1}' | tr a-f A-F | \ + xargs echo "ibase=16;obase=10;$hex_base + " | bc | tr A-F a-f | awk '{print "0x"$0}' +exit 0 |