QuarkslaB Dynamic binary Instrumentation



Introduction


QuarkslaB Dynamic binary Instrumentation (QBDI) is a modular, cross-platform and cross-architecture DBI framework. It aims to support Linux, macOS, Android, iOS and Windows operating systems running on x86, x86-64, ARM and AArch64 architectures.

Why a DBI?

Debuggers are a popular approach to analyze the execution of a binary. While those tools are convenient, they are also quite slow. This performance problem is imperceptible to human users but really takes its toll on automated tools trying to step through a complete program. The only way to get rid of the problem is to place the tool inside the binary being analyzed and this is what DBI does: injecting instrumentation code inside the binary at runtime.

Why QBDI?

Existing DBI framework were designed more than 15 years ago, focusing on features and platforms that made sense at the time. Mobile platform support is often unstable or missing and instrumentation features are either simplistic or buried in low-level details. QBDI attempts to retain the interesting features of those frameworks while avoiding their pitfalls and bringing new designs and ideas to the table.


Usage


Instrumentation tools based on QBDI are compiled as dynamic libraries, that can be loaded in target process using any injection tools or techniques.
For this purpose, a generic library allowing loader based injections, QBDIPreload, is provided (currently supporting Linux and macOS).

Modularity stand for easy integration everywhere. pyQBDI brings together QBDIPreload and Python, permitting flexible and hassle-free instrumentation. QBDI is also fully integrated with Frida, a reference dynamic instrumentation toolkit, allowing anybody to use their combined powers in order to create custom reverse engineering tools.


QBDI Framework

Easy to use C/C++ APIs


QBDI::VMAction printInstruction(QBDI::VMInstanceRef vm,
                                QBDI::GPRState*     gprState,
                                QBDI::FPRState*     fprState,
                                void*               data) {
    const QBDI::InstAnalysis* instAnalysis = vm->getInstAnalysis();
    std::cout << std::setbase(16) << instAnalysis->address << " "
        << instAnalysis->disassembly << std::endl << std::setbase(10);
    return QBDI::VMAction::CONTINUE;
}

int main() {
    uint8_t *fakestack = nullptr;
    QBDI::VM *vm = new QBDI::VM();
    QBDI::GPRState *state = vm->getGPRState();
    QBDI::allocateVirtualStack(state, 0x1000000, &fakestack);
    vm->addInstrumentedModuleFromAddr(funcPtr);
    vm->addCodeCB(QBDI::PREINST, printInstruction, NULL);
    rword retVal;
    vm->call(&retVal, funcPtr, {42});
}

QBDIPreload

Simple yet powerful injector


VMAction printInstruction(VMInstanceRef vm,
                          GPRState     *gprState,
                          FPRState     *fprState,
                          void         *data) {
    int flags = QBDI_ANALYSIS_INSTRUCTION | QBDI_ANALYSIS_DISASSEMBLY;
    const InstAnalysis* inst = qbdi_getInstAnalysis(vm, flags);
    printf("0x%" PRIRWORD " %s\n", inst->address, inst->disassembly);
    return QBDI_CONTINUE;
}

int qbdipreload_on_run(VMInstanceRef vm, rword start, rword stop) {
    qbdi_addCodeCB(vm, QBDI_PREINST, printInstruction, NULL);
    qbdi_run(vm, start, stop);
    return QBDIPRELOAD_NO_ERROR;
}

Frida/QBDI

Full featured Frida bindings


var vm = new QBDI();
var state = vm.getGPRState();
vm.allocateVirtualStack(state, 0x1000000);
var funcPtr = Module.findExportByName(null, "aFunction");
vm.addInstrumentedModuleFromAddr(funcPtr);
var icbk = vm.newInstCallback(function(vm, gpr, fpr, data) {
    var inst = vm.getInstAnalysis();
    // Display instruction dissassembly
    fmt = "0x" + inst.address.toString(16) + " " + inst.disassembly;
    console.log(fmt);
    return VMAction.CONTINUE;
});
vm.addCodeCB(InstPosition.PREINST, icbk);
vm.call(funcPtr, [42]);

pyQBDI

Fun and flexible Python bindings


import pyqbdi;

def printInstruction(vm, gpr, fpr, data):
    inst = vm.getInstAnalysis()
    print "0x%x %s" % (inst.address, inst.disassembly)
    return pyqbdi.CONTINUE

def pyqbdipreload_on_run(vm, start, stop):
    state = vm.getGPRState()
    success, addr = pyqbdi.allocateVirtualStack(state, 0x100000)
    funcPtr = ctypes.cast(aLib.aFunction, ctypes.c_void_p).value
    vm.addInstrumentedModuleFromAddr(funcPtr)
    vm.addCodeCB(pyqbdi.PREINST, printInstruction, None)
    vm.call(funcPtr, [42])


Status

Current build status


x86-64 support is mature (even if SIMD memory access are not yet reported). ARM architecture is a work in progress but already sufficient to execute simple CLI program like ls or cat. x86 and AArch64 are planned, but currently unsupported.

A current limitation is that QBDI doesn’t handle signals, multithreading (it doesn't deal with new threads creation) and C++ exception mechanisms.
However, those system-dependent features will probably not be part of the core library (KISS), and should be integrated as a new layer (to be determined how).


CPU Operating Systems Execution Memory Access Information
x86-64 Linux, macOS, Windows Supported Partial (only non SIMD)
x86 Linux, macOS, Windows Unsupported Unsupported
ARM Linux, Android, iOS Partial Unsupported
AArch64 Linux, Android Unsupported Unsupported

Download

Get prebuilt packages

Linux version (64-bits)

0.5.0

Android

Android (developer preview)

Windows version

0.5.0

macOS version (64-bits)

0.5.0

Source

master


All releases are signed using QBDI developers GPG key: 2763 2215 DED8 D717 AD08 477D 874D 3F16 4D45 2193


About


QBDI is currently developed and sponsored by Quarkslab under the Apache 2.0 license.


This project is an ongoing experimentation, and it smells like fresh paint sometimes.
Feel free to open issues, and join us on freenode (#qbdi) to share ideas.