QBDIPreload

The examples from the API sections demonstrated a use case where the instrumented code, QBDI and the instrumentation are compiled in the same binary. This, however, only works for a very limited amount of real-world scenarios. Other scenarios require some injection tool that can load QBDI and your instrumentation code in another binary.

QBDIPreload is a small utility library that provides code injection capabilities using dynamic library injection. It currently only works under linux using the LD_PRELOAD mechanism and macOS using the DYLD_INSERT_LIBRARIES mechanism. For other platforms please look into using Frida/QBDI instead.

QBDIPreload exploits these library injection mechanisms to hijack the normal program startup. During the hijacking process QBDIPreload will callback your code allowing you to setup and start your instrumentation. The compilation should produce a dynamic library (.so under linux, .dylib under macOS) which should then be added to the matching environment variable (LD_PRELOAD under linux and DYLD_INSERT_LIBRARIES under macOS) when running the target binary.

You can look at the QBDIPreload Template for a working example with build and usage instructions.

Note

QBDIPreload automaticaly takes care of blacklisting instrumentation of the C standard library and the OS loader as described in Limitations.

Note

Please note that QBDIPreload does not allow to instrument a binary before the main function (inside the loader and the library constructors / init) as explained in Limitations.

QBDIPreload API

Initialization

Initialization is performed using a constructor:

#include <QBDIPreload.h>

QBDIPRELOAD_INIT;
QBDIPRELOAD_INIT

A C pre-processor macro declaring a constructor.

Warning
QBDIPRELOAD_INIT must be used once in any project using QBDIPreload. It declares a constructor, so it must be placed like a function declaration on a single line.

Return Codes

QBDIPRELOAD_NO_ERROR

No error.

QBDIPRELOAD_NOT_HANDLED

Startup step not handled by callback.

QBDIPRELOAD_ERR_STARTUP_FAILED

Error in the startup (preload) process.

User callbacks

Each of these functions must be declared and will be called (in the same order than in this documentation) by QBDIPreload during the hijacking process.

int QBDI::qbdipreload_on_start(void *main)

Function called when preload is on a program entry point (interposed start or an early constructor). It provides the main function address, that can be used to place a hook using the qbdipreload_hook_main API.

Return
int QBDIPreload state
Parameters
  • main: Address of the main function

int QBDI::qbdipreload_on_premain(void *gprCtx, void *fpuCtx)

Function called when preload hook on main function is triggered. It provides original (and platforms dependent) GPR and FPR contexts. They can be converted to QBDI states, using qbdipreload_threadCtxToGPRState and qbdipreload_floatCtxToFPRState APIs.

Return
int QBDIPreload state
Parameters
  • gprCtx: Original GPR context
  • fpuCtx: Original FPU context

int QBDI::qbdipreload_on_main(int argc, char **argv)

Function called when preload has successfully hijacked the main thread and we are in place of the original main function (with the same thread state).

Return
int QBDIPreload state
Parameters
  • argc: Original argc
  • argv: Original argv

int QBDI::qbdipreload_on_run(VMInstanceRef vm, rword start, rword stop)

Function called when preload is done and we have a valid QBDI VM object on which we can call run (after some last initializations).

Return
int QBDIPreload state
Parameters
  • vm: VM instance.
  • start: Start address of the range (included).
  • stop: End address of the range (excluded).

int QBDI::qbdipreload_on_exit(int status)

Function called when process is exiting (using _exit or exit).

Return
int QBDIPreload state
Parameters
  • status: exit status

Helpers

qbdipreload_hook_main() can be used to hook any address as main during the hijacking process.

int QBDI::qbdipreload_hook_main(void *main)

Enable QBDIPreload hook on the main function (using its address)

Warning
It MUST be used in qbdipreload_on_start if you want to handle this step. The assumed main address is provided as a callback argument.
Parameters
  • main: Pointer to the main function

Contexts related helpers allow to convert a platform dependent GPR or FPR state structure to a QBDI structure. Under linux both functions should receive a ucontext_t* and under macOS they should receive a x86_thread_state64_t* or a x86_float_state64_t*. Please look into QBDIPreload source code for more information.

void QBDI::qbdipreload_threadCtxToGPRState(const void *gprCtx, GPRState *gprState)

Convert a QBDIPreload GPR context (platform dependent) to a QBDI GPR state.

Parameters
  • gprCtx: Platform GPRState pointer
  • gprState: QBDI GPRState pointer

void QBDI::qbdipreload_floatCtxToFPRState(const void *fprCtx, FPRState *fprState)

Convert a QBDIPreload FPR context (platform dependent) to a QBDI FPR state.

Parameters
  • fprCtx: Platform FPRState pointer
  • fprState: QBDI FPRState pointer

QBDIPreload Template

To get started with QBDIPreload you can follow those few simple steps:

$ mkdir QBDIPreload && cd QBDIPreload
$ qbdi-preload-template
$ mkdir build && cd build
$ cmake ..
$ make

This will simply build the default QBDIPreload template (which prints instruction address and disassembly) and can be executed doing the following under linux:

$ LD_PRELOAD=./libqbdi_tracer.so /bin/ls

Or the following under macOS:

$ cp /bin/ls ./ls
$ sudo DYLD_INSERT_LIBRARIES=./libqbdi_tracer.so ./ls

Note

Please note that, under macOS, the System Integrity Protection (SIP) will prevent you from instrumenting system binaries. You must either use a local copy of the binary or disable SIP.