Skip to content

Latest commit

 

History

History
398 lines (332 loc) · 20.3 KB

README.md

File metadata and controls

398 lines (332 loc) · 20.3 KB

FRIDA mode

The purpose of FRIDA mode is to provide an alternative binary only fuzzer for AFL++ just like that provided by QEMU mode. The intention is to provide a very similar user experience, right down to the options provided through environment variables.

In FRIDA mode, binary programs are instrumented, similarly to QEMU mode.

Current progress

As FRIDA mode is new, it is missing a lot of features. The design is such that it should be possible to add these features in a similar manner to QEMU mode and perhaps leverage some of its design and implementation.

Feature/Instrumentation FRIDA mode Notes
NeverZero x
Persistent Mode x (x86/x64/aarch64 only)
LAF-Intel / CompCov - (CMPLOG is better 90% of the time)
CMPLOG x (x86/x64/aarch64 only)
Selective Instrumentation x
Non-Colliding Coverage - (not possible in binary-only instrumentation)
Ngram prev_loc Coverage -
Context Coverage -
Auto Dictionary -
Snapshot LKM Support -
In-Memory Test Cases x (x86/x64/aarch64 only)

Compatibility

Currently FRIDA mode supports Linux and macOS targets on both x86/x64 architecture and aarch64. Later releases may add support for aarch32 and Windows targets as well as embedded linux environments.

FRIDA has been used on various embedded targets using both uClibc and musl C runtime libraries, so porting should be possible. However, the current build system does not support cross compilation.

Getting started

To build everything, run make. To build for x86, run make 32. Note that in x86 bit mode, it is not necessary for afl-fuzz to be built for 32-bit. However, the shared library for FRIDA mode must be since it is injected into the target process.

Various tests can be found in subfolders within the test/ directory. To use these, first run make to build any dependencies. Then run make qemu or make frida to run on either QEMU of FRIDA mode respectively. To run frida tests in 32-bit mode, run make ARCH=x86 frida. When switching between architectures, it may be necessary to run make clean first for a given build target to remove previously generated binaries for a different architecture.

Android

In order to build, you need to download the Android SDK:

https://developer.android.com/ndk/downloads

Then creating locally a standalone chain as follows:

https://developer.android.com/ndk/guides/standalone_toolchain

Usage

FRIDA mode added some small modifications to afl-fuzz and similar tools in AFL++. The intention was that it behaves identically to QEMU, but it uses the 'O' switch rather than 'Q'. Whilst the options 'f', 'F', 's' or 'S' may have made more sense for a mode powered by FRIDA Stalker, they were all taken, so instead we use 'O' in homage to the author of FRIDA.

Similarly, the intention is to mimic the use of environment variables used by QEMU where possible (by replacing s/QEMU/FRIDA/g). Accordingly, the following options are currently supported:

  • AFL_FRIDA_DEBUG_MAPS - See AFL_QEMU_DEBUG_MAPS.
  • AFL_FRIDA_EXCLUDE_RANGES - See AFL_QEMU_EXCLUDE_RANGES.
  • AFL_FRIDA_INST_RANGES - See AFL_QEMU_INST_RANGES.
  • AFL_FRIDA_PERSISTENT_ADDR - See AFL_QEMU_PERSISTENT_ADDR.
  • AFL_FRIDA_PERSISTENT_CNT - See AFL_QEMU_PERSISTENT_CNT.
  • AFL_FRIDA_PERSISTENT_HOOK - See AFL_QEMU_PERSISTENT_HOOK.
  • AFL_FRIDA_PERSISTENT_RET - See AFL_QEMU_PERSISTENT_RET.

To enable the powerful CMPLOG mechanism, set -c 0 for afl-fuzz.

Scripting

One of the more powerful features of FRIDA mode is its support for configuration by JavaScript, rather than using environment variables. For details of how this works, see Scripting.md.

Performance

Additionally, the intention is to be able to make a direct performance comparison between the two approaches. Accordingly, FRIDA mode includes various test targets based on the libpng benchmark used by fuzzbench and integrated with the StandaloneFuzzTargetMain from the llvm project. These tests include basic fork-server support, persistent mode and persistent mode with in-memory test-cases. These are built and linked without any special modifications to suit FRIDA or QEMU. The test data provided with libpng is used as the corpus.

The intention is to add support for FRIDA mode to the FuzzBench project and perform a like-for-like comparison with QEMU mode to get an accurate appreciation of its performance.

Design

FRIDA mode is supported by using LD_PRELOAD (DYLD_INSERT_LIBRARIES on macOS) to inject a shared library (afl-frida-trace.so) into the target. This shared library is built using the frida-gum devkit from the FRIDA project. One of the components of frida-gum is Stalker, this allows the dynamic instrumentation of running code for AARCH32, AARCH64, x86 and x64 architectures. Implementation details can be found here.

Dynamic instrumentation is used to augment the target application with similar coverage information to that inserted by afl-gcc or afl-clang. The shared library is also linked to the compiler-rt component of AFL++ to feedback this coverage information to AFL++ and also provide a fork server. It also makes use of the FRIDA prefetch support to feedback instrumented blocks from the child to the parent using a shared memory region to avoid the need to regenerate instrumented blocks on each fork.

Whilst FRIDA allows for a normal C function to be used to augment instrumented code, FRIDA mode instead makes use of optimized assembly instead on AARCH64 and x86/64 targets. By injecting these small snippets of assembly, we avoid having to push and pop the full register context. Note that since this instrumentation is used on every basic block to generate coverage, it has a large impact on performance.

CMPLOG support also adds code to the assembly, however, at present this code makes use of a basic C function and is yet to be optimized. Since not all instances run CMPLOG mode and instrumentation of the binary is less frequent (only on CMP, SUB and CALL instructions) performance is not quite so critical.

Advanced configuration options

  • AFL_FRIDA_DRIVER_NO_HOOK - See AFL_QEMU_DRIVER_NO_HOOK. When using the QEMU driver to provide a main loop for a user provided LLVMFuzzerTestOneInput, this option configures the driver to read input from stdin rather than using in-memory test cases.
  • AFL_FRIDA_INST_COVERAGE_ABSOLUTE - Generate coverage files using absolute virtual addresses rather than relative virtual addresses.
  • AFL_FRIDA_INST_COVERAGE_FILE - File to write DynamoRIO format coverage information (e.g., to be loaded within IDA lighthouse).
  • AFL_FRIDA_INST_DEBUG_FILE - File to write raw assembly of original blocks and their instrumented counterparts during block compilation.
Creating block for 0x7ffff7953313:
        0x7ffff7953313  mov qword ptr [rax], 0
        0x7ffff795331a  add rsp, 8
        0x7ffff795331e  ret

Generated block 0x7ffff75e98e2
        0x7ffff75e98e2  mov qword ptr [rax], 0
        0x7ffff75e98e9  add rsp, 8
        0x7ffff75e98ed  lea rsp, [rsp - 0x80]
        0x7ffff75e98f5  push rcx
        0x7ffff75e98f6  movabs rcx, 0x7ffff795331e
        0x7ffff75e9900  jmp 0x7ffff75e9384


  ***
  • AFL_FRIDA_INST_CACHE_SIZE - Set the size of the instrumentation cache used as a look-up table to cache real to instrumented address block translations. Default is 256Mb.

  • AFL_FRIDA_INST_INSN - Generate instrumentation for conditional instructions (e.g. CMOV instructions on x64).

  • AFL_FRIDA_INST_JIT - Enable the instrumentation of Just-In-Time compiled code. Code is considered to be JIT if the executable segment is not backed by a file.

  • AFL_FRIDA_INST_NO_DYNAMIC_LOAD - Don't instrument the code loaded late at runtime. Strictly limits instrumentation to what has been included.

  • AFL_FRIDA_INST_NO_OPTIMIZE - Don't use optimized inline assembly coverage instrumentation (the default where available). Required to use AFL_FRIDA_INST_TRACE.

  • AFL_FRIDA_INST_REGS_FILE - File to write raw register contents at the start of each block.

  • AFL_FRIDA_INST_NO_CACHE - Don't use a look-up table to cache real to instrumented address block translations.

  • AFL_FRIDA_INST_NO_PREFETCH - Disable prefetching. By default, the child will report instrumented blocks back to the parent so that it can also instrument them and they be inherited by the next child on fork, implies AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH.

  • AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH - Disable prefetching of stalker backpatching information. By default, the child will report applied backpatches to the parent so that they can be applied and then be inherited by the next child on fork.

  • AFL_FRIDA_INST_NO_SUPPRESS - Disable deterministic branch suppression. Deterministic branch suppression skips the preamble which generates coverage information at the start of each block, if the block is reached by a deterministic branch. This reduces map polution, and may improve performance when all the executing blocks have been prefetched and backpatching applied. However, in the event that backpatching is incomplete, this may incur a performance penatly as branch instructions are disassembled on each branch.

  • AFL_FRIDA_INST_SEED - Sets the initial seed for the hash function used to generate block (and hence edge) IDs. Setting this to a constant value may be useful for debugging purposes, e.g., investigating unstable edges.

  • AFL_FRIDA_INST_TRACE - Log to stdout the address of executed blocks, implies AFL_FRIDA_INST_NO_OPTIMIZE.

  • AFL_FRIDA_INST_TRACE_UNIQUE - As per AFL_FRIDA_INST_TRACE, but each edge is logged only once, requires AFL_FRIDA_INST_NO_OPTIMIZE.

  • AFL_FRIDA_INST_UNSTABLE_COVERAGE_FILE - File to write DynamoRIO format coverage information for unstable edges (e.g., to be loaded within IDA lighthouse).

  • AFL_FRIDA_JS_SCRIPT - Set the script to be loaded by the FRIDA scripting engine. See Scipting.md for details.

  • AFL_FRIDA_OUTPUT_STDOUT - Redirect the standard output of the target application to the named file (supersedes the setting of AFL_DEBUG_CHILD).

  • AFL_FRIDA_OUTPUT_STDERR - Redirect the standard error of the target application to the named file (supersedes the setting of AFL_DEBUG_CHILD).

  • AFL_FRIDA_PERSISTENT_DEBUG - Insert a Breakpoint into the instrumented code at AFL_FRIDA_PERSISTENT_HOOK and AFL_FRIDA_PERSISTENT_RET to allow the user to detect issues in the persistent loop using a debugger.

    gdb \
        --ex 'set environment AFL_FRIDA_PERSISTENT_ADDR=XXXXXXXXXX' \
        --ex 'set environment AFL_FRIDA_PERSISTENT_RET=XXXXXXXXXX' \
        --ex 'set environment AFL_FRIDA_PERSISTENT_DEBUG=1' \
        --ex 'set environment AFL_DEBUG_CHILD=1' \
        --ex 'set environment LD_PRELOAD=afl-frida-trace.so' \
        --args <my-executable> [my arguments]
    
  • AFL_FRIDA_SECCOMP_FILE - Write a log of any syscalls made by the target to the specified file.

  • AFL_FRIDA_STALKER_ADJACENT_BLOCKS - Configure the number of adjacent blocks to fetch when generating instrumented code. By fetching blocks in the same order they appear in the original program, rather than the order of execution should help reduce locality and adjacency. This includes allowing us to vector between adjacent blocks using a NOP slide rather than an immediate branch.

  • AFL_FRIDA_STALKER_IC_ENTRIES - Configure the number of inline cache entries stored along-side branch instructions which provide a cache to avoid having to call back into FRIDA to find the next block. Default is 32.

  • AFL_FRIDA_STALKER_NO_BACKPATCH - Disable backpatching. At the end of executing each block, control will return to FRIDA to identify the next block to execute.

  • AFL_FRIDA_STATS_FILE - Write statistics information about the code being instrumented to the given file name. The statistics are written only for the child process when new block is instrumented (when the AFL_FRIDA_STATS_INTERVAL has expired). Note that just because a new path is found does not mean a new block needs to be compiled. It could be that the existing blocks instrumented have been executed in a different order.

    stats
    -----
    Time                  2021-07-21 11:45:49
    Elapsed                                 1 seconds
    
    
    Transitions                    cumulative               delta
    -----------                    ----------               -----
    total                              753619               17645
    call_imm                             9193 ( 1.22%)        344 ( 1.95%) [       344/s]
    call_reg                                0 ( 0.00%)          0 ( 0.00%) [         0/s]
    call_mem                                0 ( 0.00%)          0 ( 0.00%) [         0/s]
    ret_slow_path                       67974 ( 9.02%)       2988 (16.93%) [      2988/s]
    post_call_invoke                     7996 ( 1.06%)        299 ( 1.69%) [       299/s]
    excluded_call_imm                    3804 ( 0.50%)        200 ( 1.13%) [       200/s]
    jmp_imm                              5445 ( 0.72%)        255 ( 1.45%) [       255/s]
    jmp_reg                             42081 ( 5.58%)       1021 ( 5.79%) [      1021/s]
    jmp_mem                            578092 (76.71%)      10956 (62.09%) [     10956/s]
    jmp_cond_imm                        38951 ( 5.17%)       1579 ( 8.95%) [      1579/s]
    jmp_cond_mem                            0 ( 0.00%)          0 ( 0.00%) [         0/s]
    jmp_cond_reg                            0 ( 0.00%)          0 ( 0.00%) [         0/s]
    jmp_cond_jcxz                           0 ( 0.00%)          0 ( 0.00%) [         0/s]
    jmp_continuation                       84 ( 0.01%)          3 ( 0.02%) [         3/s]
    
    
    Instrumentation
    ---------------
    Instructions                         7907
    Blocks                               1764
    Avg Instructions / Block                4
    
    
    EOB Instructions
    ----------------
    Total                                1763 (22.30%)
    Call Immediates                       358 ( 4.53%)
    Call Immediates Excluded               74 ( 0.94%)
    Call Register                           0 ( 0.00%)
    Call Memory                             0 ( 0.00%)
    Jump Immediates                       176 ( 2.23%)
    Jump Register                           8 ( 0.10%)
    Jump Memory                            10 ( 0.13%)
    Conditional Jump Immediates          1051 (13.29%)
    Conditional Jump CX Immediate           0 ( 0.00%)
    Conditional Jump Register               0 ( 0.00%)
    Conditional Jump Memory                 0 ( 0.00%)
    Returns                               160 ( 2.02%)
    
    
    Relocated Instructions
    ----------------------
    Total                                 232 ( 2.93%)
    addsd                                   2 ( 0.86%)
    cmp                                    46 (19.83%)
    comisd                                  2 ( 0.86%)
    divsd                                   2 ( 0.86%)
    divss                                   2 ( 0.86%)
    lea                                   142 (61.21%)
    mov                                    32 (13.79%)
    movsd                                   2 ( 0.86%)
    ucomisd                                 2 ( 0.86%)
    
  • AFL_FRIDA_STATS_INTERVAL - The maximum frequency to output statistics information. Stats will be written whenever they are updated if the given interval has elapsed since last time they were written.

  • AFL_FRIDA_TRACEABLE - Set the child process to be traceable by any process to aid debugging and overcome the restrictions imposed by YAMA. Supported on Linux only. Permits a non-root user to use gcore or similar to collect a core dump of the instrumented target. Note that in order to capture the core dump you must set a sufficient timeout (using -t) to avoid afl-fuzz killing the process whilst it is being dumped.

  • AFL_FRIDA_VERBOSE - Enable verbose output from FRIDA mode.

FASAN - FRIDA Address Sanitizer mode

FRIDA mode also supports FASAN. The design of this is actually quite simple and very similar to that used when instrumenting applications compiled from source.

Address Sanitizer basics

When Address Sanitizer is used to instrument programs built from source, the compiler first adds a dependency (DT_NEEDED entry) for the Address Sanitizer dynamic shared object (DSO). This shared object contains the main logic for Address Sanitizer, including setting and managing up the shadow memory. It also provides replacement implementations for a number of functions in standard libraries.

These replacements include things like malloc and free which allows for those allocations to be marked in the shadow memory, but also a number of other functions. Consider memcpy, for example. This is instrumented to validate the parameters (test the source and destination buffers against the shadow memory). This is much easier than instrumenting those standard libraries, since first, it would require you to re-compile them and secondly it would mean that the instrumentation would be applied at a more expensive granular level. Lastly, load-widening (typically found in highly optimized code) can also make this instrumentation more difficult.

Since the DSO is loaded before all of the standard libraries (in fact it insists on being first), the dynamic loader will use it to resolve imports from other modules which depend on it.

FASAN implementation

FASAN takes a similar approach. It requires the user to add the Address Sanitizer DSO to the AFL_PRELOAD environment variable such that it is loaded into the target. Again, it must be first in the list. This means that it is not necessary to instrument the standard libraries to detect when an application has provided an incorrect argument to memcpy, for example. This avoids issues with load-widening and should also mean a huge improvement in performance.

FASAN then adds instrumentation for any instructions which use memory operands and then calls into the __asan_loadN and __asan_storeN functions provided by the DSO to validate memory accesses against the shadow memory.

Collisions

FRIDA mode has also introduced some improvements to reduce collisions in the map. For details, see MapDensity.md.

OSX library fuzzing

An example of how to fuzz a dynamic library on OSX is included, see test/osx-lib. This requires the use of a simple test harness executable which will load the library and call a target function within it. The dependent library can either be loaded in using dlopen and dlsym in a function marked __attribute__((constructor())) or the test harness can be linked against it. It is important that the target library is loaded before execution of main, since this is the point where FRIDA mode is initialized. Otherwise, it will not be possible to configure coverage for the test library using AFL_FRIDA_INST_RANGES or similar.

Debugging

Should you encounter problems with FRIDA mode, refer to DEBUGGING.md for assistance.

To do

The next features to be added are Aarch32 support as well as looking at potential performance improvements. The intention is to achieve feature parity with QEMU mode in due course. Contributions are welcome, but please get in touch to ensure that efforts are deconflicted.