Binary code-coverage fuzzer for macOS, based on libFuzzer and LLVM
ManuFuzzer is an LLVM-based binary, coverage-guided fuzzing framework for macOS. It leverages LLVM's powerful disassembly capabilities and libFuzzer's mutation engine to provide sophisticated binary-only fuzzing. It is simple to integrate coverage-guided fuzzing with ManuFuzzer: just define a special function, update some build flags, and you have instant binary-only, coverage-guided fuzzing with basic-block coverage. Using ManuFuzzer, you can instrument one or more selected frameworks for coverage and fuzz the target functions/library without requiring source code.
ManuFuzzer makes use of custom breakpoint handlers and LLVM's disassembly capabilities:
-
Branch Identification: ManuFuzzer uses LLVM MC to accurately identify branch instructions in the binary.
-
Instrumentation: When you select a module to instrument, ManuFuzzer replaces branch instructions with breakpoint instructions. If the module resides in the dyld shared cache, ManuFuzzer also identifies and instruments related modules that share memory pages to ensure comprehensive coverage.
-
Shadow Memory: For each instrumented module (or group of related modules from the dyld cache), ManuFuzzer allocates a dedicated shadow memory region. The original instructions from the instrumented locations are copied to this shadow memory. A dynamic mapping between the original code addresses and their corresponding shadow memory locations is maintained.
-
Runtime Tracking: Every time a breakpoint (instrumented location) is hit, ManuFuzzer's custom SIGTRAP handler:
- Looks up the original instruction in the shadow memory using the dynamic mapping.
- Updates coverage information for that basic-block.
- Restores the original instruction at the breakpoint location.
- Resumes execution of the original instruction.
-
libFuzzer Integration: Coverage information is shared with libFuzzer to guide the mutation engine towards exploring new code paths.
ManuFuzzer depends on LLVM MC for disassembly and LLVM libFuzzer for fuzzing. It applies a custom patch (llvm_ManuFuzzer.patch
) to enhance LLVM's disassembler for better branch detection and integrate with libFuzzer.
➜ git clone https://github.com/ant4g0nist/ManuFuzzer
➜ cd ManuFuzzer
➜ ./build_llvm.sh
➜ make -f Makefile
➜ FUZZ=1 make -f Makefile
ManuFuzzer provides a simple API that makes it easy to integrate coverage-guided fuzzing with any macOS binary target.
The library exports these key functions:
// Set up signal handlers for instrumentation
void installHandlers(void);
// Clean up resources when fuzzing is complete
void libFuzzerCleanUp(void);
// Instrument a target module/framework
int instrumentMe(const char * module);
// Start libFuzzer's mutation engine with a custom test function
int libFuzzerStart(int argc, char **argv, UserCallback LLVMFuzzerTestOneInput);
Where UserCallback
is defined as:
typedef int (*UserCallback)(const uint8_t *Data, size_t Size);
To create a fuzzer, follow this pattern:
- Initialize handlers - Call
installHandlers()
to set up breakpoint handlers - Instrument targets - Call
instrumentMe()
for each framework you want to instrument - Define test function - Create a function with the
LLVMFuzzerTestOneInput
signature - Start fuzzing - Call
libFuzzerStart()
with your test function
Here's an example fuzzer targeting the CGFontCreateWithDataProvider
function:
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#include "libManuFuzzer.h"
extern uint16_t previousLoc;
// Called before fuzzing starts
void LLVMFuzzerInitialize(int *argc, char ***argv) {
// Set up breakpoint handlers
installHandlers();
// Instrument target frameworks
instrumentMe("/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO");
instrumentMe("/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics");
instrumentMe("/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText");
instrumentMe("/System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib");
}
// Main fuzzer function - called for each input
int LLVMFuzzerTestOneInput(const uint8_t *fuzz_buff, size_t size)
{
// Reset coverage tracking for this input
previousLoc = 0;
// Skip very small inputs
if (size < 10) return 0;
// Create input data from fuzz buffer
NSData *inData = [[NSData alloc] initWithBytes:fuzz_buff length:size];
// Call the target function
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
// Clean up resources
if (font)
CFRelease(font);
CFRelease(provider);
[inData release];
return 0;
}
// Main entry point
int main(int argc, char* argv[])
{
// Initialize the fuzzer
LLVMFuzzerInitialize(&argc, &argv);
// Start libFuzzer's main loop
libFuzzerStart(argc, argv, LLVMFuzzerTestOneInput);
// Clean up resources
libFuzzerCleanUp();
return 0;
}
You can compile the example with:
# Assuming your source is in examples/main.mm
clang++ -ObjC++ -std=c++17 -I./src examples/main.mm -o bin/fuzzer -L./build -lManuFuzzer -framework Foundation -framework CoreGraphics
# Fix dylib path
install_name_tool -change libManuFuzzer.dylib @executable_path/../build/libManuFuzzer.dylib bin/fuzzer
# Set library path and run the fuzzer
DYLD_LIBRARY_PATH=./build ./bin/fuzzer -max_len=65536 corpus_dir/
This will start fuzzing with libFuzzer, automatically generating and mutating inputs to maximize code coverage.
ManuFuzzer includes a sophisticated fuzzer for Apple's CoreAudio framework:
- Advanced Integration: Uses LLVM's disassembler for accurate branch detection
- Full libFuzzer Support: Leverages mutation-based fuzzing with coverage guidance
- Multi-Framework Testing: Instruments CoreAudio, AudioToolbox, and more
- Easy to Use: Simple command-line interface with detailed options
To run CoreAudioFuzz:
➜ cd CoreAudioFuzz
➜ ./run_libfuzzer.sh
See CoreAudioFuzz/README.md
for detailed instructions.
An example showing how to fuzz CoreGraphics APIs is included in the examples
directory.
- Binary-Only Instrumentation: No source code required
- LLVM-Powered Disassembly: Accurate branch detection across architectures
- Coverage-Guided Fuzzing: Integration with libFuzzer's mutation engine
- Multi-Framework Support: Instrument multiple frameworks simultaneously
- Intelligent Dyld Shared Cache Handling: When instrumenting a framework from the dyld shared cache, ManuFuzzer automatically identifies and instruments other related frameworks that share memory pages, providing more thorough fuzzing.
- macOS Native: Built for fuzzing Apple frameworks on macOS
- Replace Capstone with LLVM MC
- Add support for macOS on Apple Silicon (M1/M2)
- Integrate with libFuzzer's mutation engine
- Add support for macOS on Intel
- Expand documentation and examples
- Add more fuzzing targets
Let me know if you have found any vulnerabilities using this and will add it here :)