VM C

Initialization

The qbdi_initVM() function is used to create a new VM instance, using host CPU as a reference:

VMInstanceRef vm;
qbdi_initVM(&vm, NULL, NULL);
void qbdi_initVM(VMInstanceRef *instance, const char *cpu, const char **mattrs)

Create and initialize a VM instance.

Parameters
  • instance: VM instance created.
  • cpu: A C string naming the CPU model to use. If NULL, the default architecture CPU model is used (see LLVM documentation for more details).
  • mattrs: A NULL terminated array of C strings specifying the attributes of the cpu model. If NULL, no additional features are specified.

The qbdi_initVM() function takes two optional parameters used to specify the CPU type or architecture and additional attributes of the CPU. Those are the same used by LLVM. Here’s how you would initialize a VM for an ARM Cortex-A9 supporting the VFP2 floating point instructions:

VMInstanceRef vm;
const char* mattrs[] = {"vfp2"};
qbdi_initVM(&vm, "cortex-a9", mattrs);

State Management

The state of the guest processor can be queried and modified using two architectures dependent structures GPRState and FPRState which are detailed below for each architecture (X86, X86_64 and ARM).

The qbdi_getGPRState() and qbdi_getFPRState() allow to query the current state of the guest processor while the qbdi_setGPRState() and qbdi_setFPRState() allow to modify it:

GPRState gprState; // local GPRState structure
gprState = *qbdi_getGPRState(vm); // Copy the VM structure into our local structure
gprState.rax = (rword) 42; // Set the value of the RAX register
qbdi_setGPRState(vm, &gprState); // Copy our local structure to the VM structure

As qbdi_getGPRState() and qbdi_getFPRState() return pointers to the VM context, it is possible to use them to modify the GPR and FPR states directly. The code below is equivalent to the example from above but avoid unnecessary copies:

qbdi_getGPRState(vm)->rax = (rword) 42;
GPRState *qbdi_getGPRState(VMInstanceRef instance)

Obtain the current general purpose register state.

Return
A structure containing the General Purpose Registers state.
Parameters
  • instance: VM instance.

FPRState *qbdi_getFPRState(VMInstanceRef instance)

Obtain the current floating point register state.

Return
A structure containing the Floating Point Registers state.
Parameters
  • instance: VM instance.

void qbdi_setGPRState(VMInstanceRef instance, GPRState *gprState)

Set the GPR state

Parameters
  • instance: VM instance.
  • gprState: A structure containing the General Purpose Registers state.

void qbdi_setFPRState(VMInstanceRef instance, FPRState *fprState)

Set the GPR state

Parameters
  • instance: VM instance.
  • fprState: A structure containing the Floating Point Registers state.

X86

The X86 GPRState structure is the following:


/*! X86 General Purpose Register context.
 */
typedef struct {
    rword eax;
    rword ebx;
    rword ecx;
    rword edx;
    rword esi;
    rword edi;
    rword ebp;
    rword esp;
    rword eip;
    rword eflags;
} GPRState;
//

The X86 FPRState structure is the following:


/*! X86 Floating Point Register context.
 */
typedef struct {
    union {
        FPControl     fcw;      /* x87 FPU control word */
        uint16_t      rfcw;
    };
    union {
        FPStatus      fsw;      /* x87 FPU status word */
        uint16_t      rfsw;
    };
    uint8_t           ftw;          /* x87 FPU tag word */
    uint8_t           rsrv1;        /* reserved */
    uint16_t          fop;          /* x87 FPU Opcode */
    uint32_t          ip;           /* x87 FPU Instruction Pointer offset */
    uint16_t          cs;           /* x87 FPU Instruction Pointer Selector */
    uint16_t          rsrv2;        /* reserved */
    uint32_t          dp;           /* x87 FPU Instruction Operand(Data) Pointer offset */
    uint16_t          ds;           /* x87 FPU Instruction Operand(Data) Pointer Selector */
    uint16_t          rsrv3;        /* reserved */
    uint32_t          mxcsr;        /* MXCSR Register state */
    uint32_t          mxcsrmask;    /* MXCSR mask */
    MMSTReg           stmm0;        /* ST0/MM0   */
    MMSTReg           stmm1;        /* ST1/MM1  */
    MMSTReg           stmm2;        /* ST2/MM2  */
    MMSTReg           stmm3;        /* ST3/MM3  */
    MMSTReg           stmm4;        /* ST4/MM4  */
    MMSTReg           stmm5;        /* ST5/MM5  */
    MMSTReg           stmm6;        /* ST6/MM6  */
    MMSTReg           stmm7;        /* ST7/MM7  */
    char              xmm0[16];     /* XMM 0  */
    char              xmm1[16];     /* XMM 1  */
    char              xmm2[16];     /* XMM 2  */
    char              xmm3[16];     /* XMM 3  */
    char              xmm4[16];     /* XMM 4  */
    char              xmm5[16];     /* XMM 5  */
    char              xmm6[16];     /* XMM 6  */
    char              xmm7[16];     /* XMM 7  */
    char              reserved[14*16];
    char              ymm0[16];     /* YMM0[255:128] */
    char              ymm1[16];     /* YMM1[255:128] */
    char              ymm2[16];     /* YMM2[255:128] */
    char              ymm3[16];     /* YMM3[255:128] */
    char              ymm4[16];     /* YMM4[255:128] */
    char              ymm5[16];     /* YMM5[255:128] */
    char              ymm6[16];     /* YMM6[255:128] */
    char              ymm7[16];     /* YMM7[255:128] */
} FPRState;
//

X86_64

The X86_64 GPRState structure is the following:


/*! X86_64 General Purpose Register context.
 */
typedef struct {
    rword rax;
    rword rbx;
    rword rcx;
    rword rdx;
    rword rsi;
    rword rdi;
    rword r8;
    rword r9;
    rword r10;
    rword r11;
    rword r12;
    rword r13;
    rword r14;
    rword r15;
    rword rbp;
    rword rsp;
    rword rip;
    rword eflags;

} GPRState;
//

The X86_64 FPRState structure is the following:


/*! X86_64 Floating Point Register context.
 */
typedef struct {
    union {
        FPControl     fcw;      /* x87 FPU control word */
        uint16_t      rfcw;
    };
    union {
        FPStatus      fsw;      /* x87 FPU status word */
        uint16_t      rfsw;
    };
    uint8_t           ftw;          /* x87 FPU tag word */
    uint8_t           rsrv1;        /* reserved */
    uint16_t          fop;          /* x87 FPU Opcode */
    uint32_t          ip;           /* x87 FPU Instruction Pointer offset */
    uint16_t          cs;           /* x87 FPU Instruction Pointer Selector */
    uint16_t          rsrv2;        /* reserved */
    uint32_t          dp;           /* x87 FPU Instruction Operand(Data) Pointer offset */
    uint16_t          ds;           /* x87 FPU Instruction Operand(Data) Pointer Selector */
    uint16_t          rsrv3;        /* reserved */
    uint32_t          mxcsr;        /* MXCSR Register state */
    uint32_t          mxcsrmask;    /* MXCSR mask */
    MMSTReg           stmm0;        /* ST0/MM0   */
    MMSTReg           stmm1;        /* ST1/MM1  */
    MMSTReg           stmm2;        /* ST2/MM2  */
    MMSTReg           stmm3;        /* ST3/MM3  */
    MMSTReg           stmm4;        /* ST4/MM4  */
    MMSTReg           stmm5;        /* ST5/MM5  */
    MMSTReg           stmm6;        /* ST6/MM6  */
    MMSTReg           stmm7;        /* ST7/MM7  */
    char              xmm0[16];     /* XMM 0  */
    char              xmm1[16];     /* XMM 1  */
    char              xmm2[16];     /* XMM 2  */
    char              xmm3[16];     /* XMM 3  */
    char              xmm4[16];     /* XMM 4  */
    char              xmm5[16];     /* XMM 5  */
    char              xmm6[16];     /* XMM 6  */
    char              xmm7[16];     /* XMM 7  */
    char              xmm8[16];     /* XMM 8  */
    char              xmm9[16];     /* XMM 9  */
    char              xmm10[16];    /* XMM 10  */
    char              xmm11[16];    /* XMM 11  */
    char              xmm12[16];    /* XMM 12  */
    char              xmm13[16];    /* XMM 13  */
    char              xmm14[16];    /* XMM 14  */
    char              xmm15[16];    /* XMM 15  */
    char              reserved[6*16];
    char              ymm0[16];     /* YMM0[255:128] */
    char              ymm1[16];     /* YMM1[255:128] */
    char              ymm2[16];     /* YMM2[255:128] */
    char              ymm3[16];     /* YMM3[255:128] */
    char              ymm4[16];     /* YMM4[255:128] */
    char              ymm5[16];     /* YMM5[255:128] */
    char              ymm6[16];     /* YMM6[255:128] */
    char              ymm7[16];     /* YMM7[255:128] */
    char              ymm8[16];     /* YMM8[255:128] */
    char              ymm9[16];     /* YMM9[255:128] */
    char              ymm10[16];    /* YMM10[255:128] */
    char              ymm11[16];    /* YMM11[255:128] */
    char              ymm12[16];    /* YMM12[255:128] */
    char              ymm13[16];    /* YMM13[255:128] */
    char              ymm14[16];    /* YMM14[255:128] */
    char              ymm15[16];    /* YMM15[255:128] */
} FPRState;
//

ARM

The ARM GPRState structure is the following:


/*! ARM General Purpose Register context.
 */
typedef struct {
    rword r0;
    rword r1;
    rword r2;
    rword r3;
    rword r4;
    rword r5;
    rword r6;
    rword r7;
    rword r8;
    rword r9;
    rword r10;
    rword r12;
    rword fp;
    rword sp;
    rword lr;
    rword pc;
    rword cpsr;

} GPRState;
//

The ARM FPRState structure is the following:


/*! ARM Floating Point Register context.
 */
typedef struct {
    float s[QBDI_NUM_FPR];
} FPRState;
//

State Initialization

Since the instrumented software and the VM need to run on different stacks, helper initialization functions exist for this purpose. The qbdi_alignedAlloc() function offers a cross-platform interface for aligned allocation which is required for allocating a stack. The qbdi_allocateVirtualStack() allows to allocate this stack and also setup the GPRState accordingly such that the required registers point to this stack. The qbdi_simulateCall() allows to simulate the call to a function by modifying the GPRState and stack to setup the return address and the arguments. The Fibonacci example is using those functions to call a function through the QBDI.

void *qbdi_alignedAlloc(size_t size, size_t align)

Allocate a block of memory of a specified sized with an aligned base address.

Return
Pointer to the allocated memory or NULL in case an error was encountered.
Parameters
  • size: Allocation size in bytes.
  • align: Base address alignement in bytes.

bool qbdi_allocateVirtualStack(GPRState *ctx, uint32_t stackSize, uint8_t **stack)

Allocate a new stack and setup the GPRState accordingly. The allocated stack needs to be freed with alignedFree().

Return
True if stack allocation was successfull.
Parameters
  • ctx: GPRState which will be setup to use the new stack.
  • stackSize: Size of the stack to be allocated.
  • stack: The newly allocated stack pointer will be returned in the variable pointed by stack.

void qbdi_simulateCall(GPRState *ctx, rword returnAddress, uint32_t argNum, ...)

Simulate a call by modifying the stack and registers accordingly.

Parameters
  • ctx: GPRState where the simulated call will be setup. The state needs to point to a valid stack for example setup with allocateVirtualStack().
  • returnAddress: Return address of the call to simulate.
  • argNum: The number of arguments in the variadic list.
  • ...: A variadic list of arguments.

Execution

Starting a Run

The run function is used to start the execution of a piece of code by the DBI. It first sets the Program Counter of the VM CPU state to the start parameter then runs the code through the DBI until a control flow instruction branches on the stop parameter. See the Fibonacci example for a basic usage of QBDI and the qbdi_run() function.

bool qbdi_run(VMInstanceRef instance, rword start, rword stop)

Start the execution by the DBI from a given address (and stop when another is reached).

Return
True if at least one block has been executed.
Parameters
  • instance: VM instance.
  • start: Address of the first instruction to execute.
  • stop: Stop the execution when this instruction is reached.

qbdi_run() is a low level function which implies that the GPRState has been properly initialized before.

But a very common usage of QBDI is to start instrumentation on a function call. qbdi_call() is a helper which behaves like a function call. It takes a function pointer, a list of arguments, and return the call’s result. The only requirement is a valid (and properly aligned) stack pointer. In most cases, using qbdi_allocateVirtualStack() is enough, as a call rarely needs to work on the original thread stack.

bool qbdi_call(VMInstanceRef instance, rword *retval, rword function, uint32_t argNum, ...)

Call a function using the DBI (and its current state).

Example:

// perform (with QBDI) a call similar to (*funcPtr)(42);
uint8_t *fakestack = NULL;
VMInstanceRef vm;
qbdi_initVM(&vm, NULL, NULL);
GPRState* gprState = qbdi_getGPRState(vm);
qbdi_allocateVirtualStack(gprState, 0x1000000, &fakestack);
qbdi_addInstrumentedModuleFromAddr(vm, funcPtr);
rword retVal;
qbdi_call(vm, &retVal, funcPtr, 1, 42);
Return
True if at least one block has been executed.
Parameters
  • instance: VM instance.
  • [retval]: Pointer to the returned value (optional).
  • function: Address of the function start instruction.
  • argNum: The number of arguments in the variadic list.
  • ...: A variadic list of arguments.

Execution Filtering

For performance reasons but also integrity reasons it is rarely preferable to instrument the entirety of the guest code. The VM stores a set of instrumented ranges, when the execution gets out of those ranges the VM attempts to transfer the execution to a real execution via the ExecBroker which implements a return address hooking mechanism. As this hooking process is only a heuristic, it is neither guaranteed to be triggered as soon as the execution gets out of the set of instrumented ranges nor guaranteed to actually catch the return to the set of instrumented ranges.

Warning

By default the set of instrumented ranges is empty, a call to qbdi_run() will thus very quickly escape from the instrumentation process through an execution transfer made by the ExecBroker.

The basic functions to manipulate this set of instrumented ranges are qbdi_addInstrumentedRange() and qbdi_removeInstrumentedRange().

void qbdi_addInstrumentedRange(VMInstanceRef instance, rword start, rword end)

Add an address range to the set of instrumented address ranges.

Parameters
  • instance: VM instance.
  • start: Start address of the range (included).
  • end: End address of the range (excluded).

void qbdi_removeInstrumentedRange(VMInstanceRef instance, rword start, rword end)

Remove an address range from the set of instrumented address ranges.

Parameters
  • instance: VM instance.
  • start: Start address of the range (included).
  • end: End address of the range (excluded).

As manipulating address ranges can be problematic, helpers API allow to use process memory maps information. The qbdi_addInstrumentedModule() and qbdi_removeInstrumentedModule() allow to use executable module names instead of address ranges. The currently loaded module names can be queried using qbdi_getModuleNames(). The qbdi_addInstrumentedModuleFromAddr() and qbdi_removeInstrumentedModuleFromAddr() functions allow to use any known address from a module to instrument it.

The qbdi_instrumentAllExecutableMaps() allows to instrument every memory map which is executable, the undesired address range can later be removed using the previous APIs in a blacklist approach. This approach is demonstrated in the The Dude example where sub-functions are removed in function of a trace level supplied as argument.

The qbdi_removeAllInstrumentedRanges() can be used to remove all ranges recorded through previous APIs (after this call, the VM will have no range left to instrument).

Below is an example of how to obtain, iterate then destroy the module names using the API.

#include <stdlib.h>
#include <stdio.h>

#include <QBDI.h>

int main(int argc, char** argv) {
    size_t size = 0, i = 0;
    char **modules = qbdi_getModuleNames(&size);

    for(i = 0; i < size; i++) {
        printf("%s\n", modules[i]);
    }

    for(i = 0; i < size; i++) {
        free(modules[i]);
    }
    free(modules);

    qbdi_MemoryMap *maps = qbdi_getCurrentProcessMaps(false, &size);
    for(i = 0; i < size; i++) {
        printf("%s (%c%c%c) ", maps[i].name,
                maps[i].permission & QBDI_PF_READ ? 'r' : '-',
                maps[i].permission & QBDI_PF_WRITE ? 'w' : '-',
                maps[i].permission & QBDI_PF_EXEC ? 'x' : '-');
        printf("(%#" PRIRWORD ", %#" PRIRWORD ")\n", maps[i].start, maps[i].end);
    }
    qbdi_freeMemoryMapArray(maps, size);

    return 0;
}
struct qbdi_MemoryMap

Map of a memory area (region).

Public Members

rword start

Range start value.

rword end

Range end value (always excluded).

qbdi_Permission permission

Region access rights (PF_READ, PF_WRITE, PF_EXEC).

char *name

Region name or path (useful when a region is mapping a module).

enum qbdi_Permission

Memory access rights.

Values:

QBDI_PF_NONE = 0

No access

QBDI_PF_READ = 1

Read access

QBDI_PF_WRITE = 2

Write access

QBDI_PF_EXEC = 4

Execution access

qbdi_MemoryMap *qbdi_getCurrentProcessMaps(bool full_path, size_t *size)

Get a list of all the memory maps (regions) of the current process.

Return
An array of MemoryMap object.
Parameters
  • full_path: Return the full path of the module in name field
  • size: Will be set to the number of strings in the returned array.

void qbdi_freeMemoryMapArray(qbdi_MemoryMap *arr, size_t size)

Free an array of memory maps objects.

Parameters
  • arr: An array of MemoryMap object.
  • size: Number of elements in the array.

char **qbdi_getModuleNames(size_t *size)

Get a list of all the module names loaded in the process memory. If no modules are found, size is set to 0 and this function returns NULL.

Return
An array of C strings, each one containing the name of a loaded module. This array needs to be free’d by the caller by repetively calling free() on each of its element then finally on the array itself.
Parameters
  • size: Will be set to the number of strings in the returned array.

bool qbdi_addInstrumentedModule(VMInstanceRef instance, const char *name)

Add the executable address ranges of a module to the set of instrumented address ranges.

Return
True if at least one range was added to the instrumented ranges.
Parameters
  • instance: VM instance.
  • name: The module’s name.

bool qbdi_removeInstrumentedModule(VMInstanceRef instance, const char *name)

Remove the executable address ranges of a module from the set of instrumented address ranges.

Return
True if at least one range was removed from the instrumented ranges.
Parameters
  • instance: VM instance.
  • name: The module’s name.

bool qbdi_addInstrumentedModuleFromAddr(VMInstanceRef instance, rword addr)

Add the executable address ranges of a module to the set of instrumented address ranges using an address belonging to the module.

Return
True if at least one range was added to the instrumented ranges.
Parameters
  • instance: VM instance.
  • addr: An address contained by module’s range.

bool qbdi_removeInstrumentedModuleFromAddr(VMInstanceRef instance, rword addr)

Remove the executable address ranges of a module from the set of instrumented address ranges using an address belonging to the module.

Return
True if at least one range was removed from the instrumented ranges.
Parameters
  • instance: VM instance.
  • addr: An address contained by module’s range.

bool qbdi_instrumentAllExecutableMaps(VMInstanceRef instance)

Adds all the executable memory maps to the instrumented range set.

Return
True if at least one range was added to the instrumented ranges.
Parameters
  • instance: VM instance.

Warning

Instrumenting the libc very often results in deadlock problems as it is also used by QBDI. It is thus recommended to always exclude it from the instrumentation.

void qbdi_removeAllInstrumentedRanges(VMInstanceRef instance)

Remove all instrumented ranges.

Parameters
  • instance: VM instance.

Instrumentation

Instrumentation allows to run additional code alongside the original code of the software. There are three types of instrumentations implemented by the VM. The first one are instruction event callbacks where the execution of an instruction, under certain conditions, triggers a callback from the guest to the host. The second one are VM event callbacks where certain actions taken by the VM itself trigger a callback. The last one are custom instrumentations which allow to use the internal instrumentation mechanism of the VM.

Instruction Callback

The instruction callbacks follow the prototype of InstCallback. A user supplied data pointer parameter can be passed to the callback. Here’s a simple callback which would increment an integer passed as a pointer through the data parameter:

VMAction increment(VMInstanceRef vm, GPRState *gprState, FPRState *fprState, void *data) {
    int *counter = (int*) data;
    *counter += 1;
    return QBDI_CONTINUE;
}

The return value of this function is very important to determine how the VM should handle the resuming of execution. QBDI_CONTINUE will directly switch back to the guest code in the current ExecBlock without further processing by the VM. This means that modification of the Program Counter will not be taken into account and modifications of the program code will only be effective after the end of the current basic block (provided the code cache is cleared, which is not currently exported via the API). One can force those changes to be taken into account by breaking from the ExecBlock execution and returning early inside the VM using the QBDI_BREAK_TO_VM. QBDI_STOP causes the VM to stop the execution and the qbdi_run() to return.

The qbdi_addCodeCB() function allows to add a callback on every instruction (inside the instrumented range). If we register the previous callback to be called before every instruction, we effectively get an instruction counting instrumentation:

int counter = 0;
qbdi_addCodeCB(vm, QBDI_PREINST, increment, &counter);
qbdi_run(vm, ...); // Run the VM of some piece of code
printf("Instruction count: %d\n", counter); // counter contains the number of instructions executed
enum InstPosition

Position relative to an instruction.

Values:

QBDI_PREINST = 0

Positioned before the instruction.

QBDI_POSTINST

Positioned after the instruction.

typedef VMAction (*InstCallback)(VMInstanceRef vm, GPRState *gprState, FPRState *fprState, void *data)

Instruction callback function type.

Return
The callback result used to signal subsequent actions the VM needs to take.
Parameters
  • vm: VM instance of the callback.
  • gprState: A structure containing the state of the General Purpose Registers. Modifying it affects the VM execution accordingly.
  • fprState: A structure containing the state of the Floating Point Registers. Modifying it affects the VM execution accordingly.
  • data: User defined data which can be defined when registering the callback.

enum VMAction

The callback results.

Values:

QBDI_CONTINUE = 0

The execution of the basic block continues.

QBDI_BREAK_TO_VM = 1

The execution breaks and returns to the VM causing a complete reevaluation of the execution state. A BREAK_TO_VM is needed to ensure that modifications of the Program Counter or the program code are taken into account.

QBDI_STOP = 2

Stops the execution of the program. This causes the run function to return early.

uint32_t qbdi_addCodeCB(VMInstanceRef instance, InstPosition pos, InstCallback cbk, void *data)

Register a callback event for a specific instruction event.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • pos: Relative position of the event callback (QBDI_PREINST / QBDI_POSTINST).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

It is also possible to register an InstCallback for a specific instruction address or address range with the qbdi_addCodeAddrCB() and qbdi_addCodeRangeCB() functions. These allow to fine-tune the instrumentation to specific codes or even portions of them.

uint32_t qbdi_addCodeAddrCB(VMInstanceRef instance, rword address, InstPosition pos, InstCallback cbk, void *data)

Register a callback for when a specific address is executed.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • address: Code address which will trigger the callback.
  • pos: Relative position of the callback (QBDI_PREINST / QBDI_POSTINST).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

uint32_t qbdi_addCodeRangeCB(VMInstanceRef instance, rword start, rword end, InstPosition pos, InstCallback cbk, void *data)

Register a callback for when a specific address range is executed.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • start: Start of the address range which will trigger the callback.
  • end: End of the address range which will trigger the callback.
  • pos: Relative position of the callback (QBDI_PREINST / QBDI_POSTINST).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

uint32_t qbdi_addMnemonicCB(VMInstanceRef instance, const char *mnemonic, InstPosition pos, InstCallback cbk, void *data)

Register a callback event if the instruction matches the mnemonic.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • mnemonic: Mnemonic to match.
  • pos: Relative position of the event callback (QBDI_PREINST / QBDI_POSTINST).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

Note

Mnemonics can be instrumented using LLVM convention (You can register a callback on ADD64rm or ADD64rr for instance).

Note

You can also use “*” as a wildcard. (eg : ADD*rr)

If the execution of an instruction triggers more than one callback, those will be called in the order they were added to the VM.

Memory Callback

The memory callbacks (currently only supported under X86_64) allow to trigger callbacks on specific memory events. They use the same callback prototype, InstCallback, than instruction callback. They can be registered using the qbdi_addMemAccessCB() API. The API takes care of enabling the corresponding inline memory access logging instrumentation using qbdi_recordMemoryAccess(). The memory accesses themselves are not directly provided as a callback parameter and need to be retrieved using the memory access APIs (see Memory Access Analysis). The Cryptolock example shows how to use those APIs to log the memory writes.

The type parameter allows to filter the callback on specific memory access type:

  • QBDI_MEMORY_READ triggers the callback before every instruction performing a memory read.
  • QBDI_MEMORY_WRITE triggers the callback after every instruction performing a memory write.
  • QBDI_MEMORY_READ_WRITE triggers the callback after every instruction performing a memory read and/or write.
uint32_t qbdi_addMemAccessCB(VMInstanceRef instance, MemoryAccessType type, InstCallback cbk, void *data)

Register a callback event for every memory access matching the type bitfield made by the instructions.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • type: A mode bitfield: either QBDI_MEMORY_READ, QBDI_MEMORY_WRITE or both (QBDI_MEMORY_READ_WRITE).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

To enable more flexibility on the callback filtering qbdi_addMemAddrCB() and qbdi_addMemRangeCB() allow to filter for memory accesses targeting a specific memory address or range. However those callbacks are virtual callbacks: they cannot be directly triggered by the instrumentation because the access address needs to be checked dynamically but are instead triggered by a gate callback which takes care of the dynamic access address check and forwards or not the event to the virtual callback. This system thus has the same overhead as qbdi_addMemAccessCB() and is only meant as an API helper.

uint32_t qbdi_addMemAddrCB(VMInstanceRef instance, rword address, MemoryAccessType type, InstCallback cbk, void *data)

Add a virtual callback which is triggered for any memory access at a specific address matching the access type. Virtual callbacks are called via callback forwarding by a gate callback triggered on every memory access. This incurs a high performance cost.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • address: Code address which will trigger the callback.
  • type: A mode bitfield: either QBDI_MEMORY_READ, QBDI_MEMORY_WRITE or both (QBDI_MEMORY_READ_WRITE).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

uint32_t qbdi_addMemRangeCB(VMInstanceRef instance, rword start, rword end, MemoryAccessType type, InstCallback cbk, void *data)

Add a virtual callback which is triggered for any memory access in a specific address range matching the access type. Virtual callbacks are called via callback forwarding by a gate callback triggered on every memory access. This incurs a high performance cost.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • start: Start of the address range which will trigger the callback.
  • end: End of the address range which will trigger the callback.
  • type: A mode bitfield: either QBDI_MEMORY_READ, QBDI_MEMORY_WRITE or both (QBDI_MEMORY_READ_WRITE).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

VM Events

VM events are triggered by the VM itself when it takes specific actions related to the execution of the instrumented program. The callback prototype VMCallback is different as it is triggered by the VM and not by an instruction. The callback receives a VMState structure. VMCallback can be registered for several events at the same time by combining several values of VMEvent in a mask using the | binary operator.

typedef VMAction (*VMCallback)(VMInstanceRef vm, const VMState *vmState, GPRState *gprState, FPRState *fprState, void *data)

VM callback function type.

Return
The callback result used to signal subsequent actions the VM needs to take.
Parameters
  • vm: VM instance of the callback.
  • vmState: A structure containing the current state of the VM.
  • gprState: A structure containing the state of the General Purpose Registers. Modifying it affects the VM execution accordingly.
  • fprState: A structure containing the state of the Floating Point Registers. Modifying it affects the VM execution accordingly.
  • data: User defined data which can be defined when registering the callback.

uint32_t qbdi_addVMEventCB(VMInstanceRef instance, VMEvent mask, VMCallback cbk, void *data)

Register a callback event for a specific VM event.

Return
The id of the registered instrumentation (or QBDI_INVALID_EVENTID in case of failure).
Parameters
  • instance: VM instance.
  • mask: A mask of VM event type which will trigger the callback.
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

struct VMState

Structure describing the current VM state

Public Members

VMEvent event

The event(s) which triggered the callback (must be checked using a mask: event & BASIC_BLOCK_ENTRY).

rword basicBlockStart

The current basic block start address which can also be the execution transfer destination.

rword basicBlockEnd

The current basic block end address which can also be the execution transfer destination.

rword sequenceStart

The current sequence start address which can also be the execution transfer destination.

rword sequenceEnd

The current sequence end address which can also be the execution transfer destination.

rword lastSignal

Not implemented.

enum VMEvent

Values:

QBDI_SEQUENCE_ENTRY = 1

Triggered when the execution enters a sequence.

QBDI_SEQUENCE_EXIT = 1<<1

Triggered when the execution exits from the current sequence.

QBDI_BASIC_BLOCK_ENTRY = 1<<2

Triggered when the execution enters a basic block.

QBDI_BASIC_BLOCK_EXIT = 1<<3

Triggered when the execution exits from the current basic block.

QBDI_BASIC_BLOCK_NEW = 1<<4

Triggered when the execution enters a new (~unknown) basic block.

QBDI_EXEC_TRANSFER_CALL = 1<<5

Triggered when the ExecBroker executes an execution transfer.

QBDI_EXEC_TRANSFER_RETURN = 1<<6

Triggered when the ExecBroker returns from an execution transfer.

QBDI_SYSCALL_ENTRY = 1<<7

Not implemented.

QBDI_SYSCALL_EXIT = 1<<8

Not implemented.

QBDI_SIGNAL = 1<<9

Not implemented.

Custom Instrumentation

Custom instrumentation is not available via the C API as it requires manipulating internal C++ classes.

Removing Instrumentations

The id returned by the previous instrumentation functions can be used to modify the instrumentation afterward. The qbdi_deleteInstrumentation() function allows to remove an instrumentation by id while the qbdi_deleteAllInstrumentations() function will remove all instrumentations. This code would do two different executions with different callbacks:

uint32_t cb1 = qbdi_addCodeCB(vm, QBDI_PREINST, Callback1, &some_data);
uint32_t cb2 = qbdi_addVMEventCB(vm, QBDI_EXEC_TRANSFER_CALL | QBDI_BASIC_BLOCK_ENTRY, Callback2, &some_data);
qbdi_run(vm, ...); // Run the VM of some piece of code with all instrumentations
qbdi_deleteInstrumentation(vm, cb1);
qbdi_run(vm, ...); // Run the VM of some piece of code with some instrumentation
qbdi_deleteAllInstrumentations(vm);
qbdi_run(vm, ...); // Run the VM of some piece of code without instrumentation
bool qbdi_deleteInstrumentation(VMInstanceRef instance, uint32_t id)

Remove an instrumentation.

Return
True if instrumentation has been removed.
Parameters
  • instance: VM instance.
  • id: The id of the instrumentation to remove.

void qbdi_deleteAllInstrumentations(VMInstanceRef instance)

Remove all the registered instrumentations.

Parameters
  • instance: VM instance.

Instruction Analysis

Callback based instrumentation has only limited utility if the current context of execution is not available. To obtain more information about an instruction, the VM can parse its internal structures for us and provide analysis results in a InstAnalysis structure:

CallbackResult increment(VMInstanceRef vm, GPRState *gprState, FPRState *fprState, void *data) {
    const InstAnalysis *instAnalysis = qbdi_getInstAnalysis(vm, QBDI_ANALYSIS_INSTRUCTION | QBDI_ANALYSIS_DISASSEMBLY);

    printf("%s\n", instAnalysis->disassembly);

    return QBDI_CONTINUE;
}
const InstAnalysis *qbdi_getInstAnalysis(VMInstanceRef instance, AnalysisType type)

Obtain the analysis of an instruction metadata. Analysis results are cached in the VM. The validity of the returned pointer is only guaranteed until the end of the callback, else a deepcopy of the structure is required.

Return
A InstAnalysis structure containing the analysis result.
Parameters
  • instance: VM instance.
  • type: Properties to retrieve during analysis.

Analysis can gather all kinds of properties linked with the instruction. It’s possible to select which one will be retrieved using type argument.

enum AnalysisType

Instruction analysis type

Values:

QBDI_ANALYSIS_INSTRUCTION = 1

Instruction analysis (address, mnemonic, …)

QBDI_ANALYSIS_DISASSEMBLY = 1<<1

Instruction disassembly

QBDI_ANALYSIS_OPERANDS = 1<<2

Instruction operands analysis

QBDI_ANALYSIS_SYMBOL = 1<<3

Instruction symbol

Every analysis has a performance cost, which can be reduced by selecting types carefully, and is amortized using a cache inside the VM.

struct InstAnalysis

Structure containing analysis results of an instruction provided by the VM.

Public Members

const char *mnemonic

LLVM mnemonic (warning: NULL if !ANALYSIS_INSTRUCTION)

rword address

Instruction address

uint32_t instSize

Instruction size (in bytes)

bool affectControlFlow

true if instruction affects control flow

bool isBranch

true if instruction acts like a ‘jump’

bool isCall

true if instruction acts like a ‘call’

bool isReturn

true if instruction acts like a ‘return’

bool isCompare

true if instruction is a comparison

bool isPredicable

true if instruction contains a predicate (~is conditional)

bool mayLoad

true if instruction ‘may’ load data from memory

bool mayStore

true if instruction ‘may’ store data to memory

char *disassembly

Instruction disassembly (warning: NULL if !ANALYSIS_DISASSEMBLY)

uint8_t numOperands

Number of operands used by the instruction

OperandAnalysis *operands

Structure containing analysis results of an operand provided by the VM. (warning: NULL if !ANALYSIS_OPERANDS)

const char *symbol

Instruction symbol (warning: NULL if !ANALYSIS_SYMBOL or not found)

uint32_t symbolOffset

Instruction symbol offset

const char *module

Instruction module name (warning: NULL if !ANALYSIS_SYMBOL or not found)

uint32_t analysisType

INTERNAL: Instruction analysis type (this should NOT be used)

If provided AnalysisType is equal to QBDI_ANALYSIS_OPERANDS, then qbdi_getInstAnalysis() will also analyze all instruction operands, and fill an array of OperandAnalysis (of length numOperands).

struct OperandAnalysis

Structure containing analysis results of an operand provided by the VM.

Public Members

OperandType type

Operand type

OperandFlag flag

Operand flag

rword value

Operand value (if immediate), or register Id

uint8_t size

Operand size (in bytes)

uint8_t regOff

Sub-register offset in register (in bits)

uint16_t regCtxIdx

Register index in VM state

const char *regName

Register name

RegisterAccessType regAccess

Register access type (r, w, rw)

enum OperandType

Operand type

Values:

QBDI_OPERAND_INVALID = 0

Invalid operand

QBDI_OPERAND_IMM

Immediate operand

QBDI_OPERAND_GPR

Register operand

QBDI_OPERAND_PRED

Predicate operand

enum OperandFlag

Values:

QBDI_OPERANDFLAG_NONE = 0

No flag

QBDI_OPERANDFLAG_ADDR = 1<<0

The operand is used to compute an address

QBDI_OPERANDFLAG_PCREL = 1<<1

The value of the operand is PC relative

QBDI_OPERANDFLAG_UNDEFINED_EFFECT = 1<<2

The operand role isn’t fully defined

enum RegisterAccessType

Access type (R/W/RW) of a register operand

Values:

QBDI_REGISTER_UNUSED = 0

Unused register

QBDI_REGISTER_READ = 1

Register read access

QBDI_REGISTER_WRITE = 1<<1

Register write access

QBDI_REGISTER_READ_WRITE = 3

Register read/write access

Memory Access Analysis

QBDI VM can log memory access using inline instrumentation. This adds instrumentation rules which store the accessed addresses and values into specific memory variables called instruction shadows. To enable this memory logging, the qbdi_recordMemoryAccess() API can be used. This memory logging is also automatically enabled when calling one of the memory callbacks.

bool qbdi_recordMemoryAccess(VMInstanceRef instance, MemoryAccessType type)

Add instrumentation rules to log memory access using inline instrumentation and instruction shadows.

Return
True if inline memory logging is supported, False if not or in case of error.
Parameters
  • instance: VM instance.
  • type: Memory mode bitfield to activate the logging for: either QBDI_MEMORY_READ, QBDI_MEMORY_WRITE or both (QBDI_MEMORY_READ_WRITE).

The memory access type always refers to either QBDI_MEMORY_READ, QBDI_MEMORY_WRITE, QBDI_MEMORY_READ_WRITE (which is a bitfield combination of the two previous ones).

Once the logging has been enabled, qbdi_getInstMemoryAccess() and qbdi_getBBMemoryAccess() can be used to retrieve the memory accesses made by the last instruction or by the last basic block. These two APIs return qbdi_MemoryAccess() structures. Write memory accesses are only returned if the instruction has already been executed (i.e. in the case of a QBDI_POSTINST). The Cryptolock example shows how to use those APIs to log the memory writes.

struct MemoryAccess

Describe a memory access

Public Members

rword instAddress

Address of instruction making the access

rword accessAddress

Address of accessed memory

rword value

Value read from / written to memory

uint8_t size

Size of memory access (in bytes)

MemoryAccessType type

Memory access type (READ / WRITE)

enum MemoryAccessType

Memory access type (read / write / …)

Values:

QBDI_MEMORY_READ = 1

Memory read access

QBDI_MEMORY_WRITE = 1<<1

Memory write access

QBDI_MEMORY_READ_WRITE = 3

Memory read/write access

MemoryAccess *qbdi_getInstMemoryAccess(VMInstanceRef instance, size_t *size)

Obtain the memory accesses made by the last executed instruction. Return NULL and a size of 0 if the instruction made no memory access.

Return
An array of memory accesses made by the instruction.
Parameters
  • instance: VM instance.
  • size: Will be set to the number of elements in the returned array.

MemoryAccess *qbdi_getBBMemoryAccess(VMInstanceRef instance, size_t *size)

Obtain the memory accesses made by the last executed basic block. Return NULL and a size of 0 if the basic block made no memory access.

Return
An array of memory accesses made by the basic block.
Parameters
  • instance: VM instance.
  • size: Will be set to the number of elements in the returned array.

Free resources

Some resources returned by a VM instance must be manually freed using specialized functions. A VM instance itself must be destroyed using qbdi_terminateVM().

void qbdi_terminateVM(VMInstanceRef instance)

Destroy an instance of VM.

Parameters
  • instance: VM instance.

void qbdi_alignedFree(void *ptr)

Free a block of aligned memory allocated with alignedAlloc.

Parameters
  • ptr: Pointer to the allocated memory.

Cache management

QBDI provides a cache system for basic blocks that you might want to deal directly with it. There are a few functions that can help you with that.

bool qbdi_precacheBasicBlock(VMInstanceRef instance, rword pc)

Pre-cache a known basic block

Return
True if basic block has been inserted in cache.
Parameters
  • instance: VM instance.
  • pc: Start address of a basic block

void qbdi_clearCache(VMInstanceRef instance, rword start, rword end)

Clear a specific address range from the translation cache.

Parameters
  • instance: VM instance.
  • start: Start of the address range to clear from the cache.
  • end: End of the address range to clear from the cache.

void qbdi_clearAllCache(VMInstanceRef instance)

Clear the entire translation cache.

Parameters
  • instance: VM instance.

Examples

Fibonacci

This example instruments its own code to count the executed instructions and displays the disassembly of every instruction executed.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>

#include "QBDI.h"

#define FAKE_RET_ADDR 42


int fibonacci(int n) {
    if(n <= 2)
        return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}

VMAction countRecursion(VMInstanceRef vm, GPRState *gprState, FPRState *fprState, void *data) {
    *((unsigned*) data) += 1;
    return QBDI_CONTINUE;
}

VMAction countInstruction(VMInstanceRef vm, GPRState *gprState, FPRState *fprState, void *data) {
    // Cast data to our counter
    uint32_t* counter = (uint32_t *) data;
    // Obtain an analysis of the instruction from the VM
    const InstAnalysis* instAnalysis = qbdi_getInstAnalysis(vm, QBDI_ANALYSIS_INSTRUCTION | QBDI_ANALYSIS_DISASSEMBLY);
    // Printing disassembly
    printf("%" PRIRWORD ": %s\n", instAnalysis->address, instAnalysis->disassembly);
    // Incrementing the instruction counter
    (*counter)++;
    // Signaling the VM to continue execution
    return QBDI_CONTINUE;
}

static const size_t STACK_SIZE = 0x100000; // 1MB

int main(int argc, char **argv) {
    int n = 0;
    unsigned recursions = 0;
    uint32_t counter = 0;
    uint8_t *fakestack;
    VMInstanceRef vm;
    GPRState *state;

    printf("Initializing VM ...\n");
    // Constructing a new QBDI VM
    qbdi_initVM(&vm, NULL, NULL);
    // Registering countInstruction() callback to be called after every instruction
    qbdi_addCodeCB(vm, QBDI_POSTINST, countInstruction, &counter);
    qbdi_addCodeAddrCB(vm, (rword) &fibonacci, QBDI_PREINST, countRecursion, &recursions);

    // Get a pointer to the GPR state of the VM
    state = qbdi_getGPRState(vm);
    // Setup initial GPR state, this fakestack will produce a ret NULL at the end of the execution
    qbdi_allocateVirtualStack(state, STACK_SIZE, &fakestack);
    // Argument to the fibonnaci call
    if(argc >= 2) {
        n = atoi(argv[1]);
    }
    if(n < 1) {
        n = 1;
    }
    qbdi_simulateCall(state, FAKE_RET_ADDR, 1, (rword) n);

    printf("Running fibonacci(%d) ...\n", n);
    // Running DBI execution
    qbdi_instrumentAllExecutableMaps(vm);
    qbdi_run(vm, (rword) fibonacci, (rword) FAKE_RET_ADDR);
    printf("fibonnaci ran in %u instructions, recursed %u times and returned %d\n",
            counter, recursions - 1, (int) QBDI_GPR_GET(state, REG_RETURN));
    qbdi_terminateVM(vm);
    qbdi_alignedFree(fakestack);

    return 0;
}

The Dude

This example instruments its own code to count the executed instructions and displays the disassembly of every instruction executed. A supplied trace level argument allows to disable the tracing in specific sub-functions using the APIs presented in Execution Filtering.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <inttypes.h>

#include "QBDI.h"


void stripLF(char *s) {
    char* pos = strrchr(s, '\n');
    if(pos != NULL) {
        *pos = '\0';
    }
}

QBDI_NOINLINE uint64_t magicPow(uint64_t n, uint64_t e) {
    uint64_t r = 1;
    uint64_t i = 0;

    for(i = 0; i < e; i++) {
        r = (r*n);
    }
    return r;
}

QBDI_NOINLINE uint64_t magicHash(char* secret) {
    uint64_t hash = 0;
    uint64_t acc = 1;
    int len = strlen(secret);
    int i = 0;

    for(i = 0; i < len; i++) {
        uint64_t magic = magicPow(secret[i], acc);
        hash ^= magic;
        acc = (acc + magic) % 256;
    }

    return hash;
}

QBDI_NOINLINE int thedude() {
    static int BUFFER_SIZE = 64;
    time_t t = 0;
    char *name = (char*) malloc(BUFFER_SIZE);
    char *secret = (char*) malloc(2*BUFFER_SIZE);
    uint64_t hash = 0;

    printf("Hi I'm the dude.\n");
    printf("Give me your name and I'll give you a hash.\n");
    printf("So what's your name ? ");
    if (!fgets(name, BUFFER_SIZE, stdin)) {
        return 1;
    }
    stripLF(name);
    time(&t);
    sprintf(secret, "%" PRIu64 ":%s", (uint64_t) t, name);
    printf("Ok I'll give you the hash of %s.\n", secret);
    hash = magicHash(secret);
    printf("Your hash is %" PRIx64 ".\n", hash);
    printf("No need to thank me.\n");

    free(name);
    free(secret);

    return 0;
}

VMAction count(VMInstanceRef vm, GPRState *gprState, FPRState *fprState, void *data) {
    // Cast data to our CallbackInfo struct
    uint32_t* counter = (uint32_t *) data;
    // Obtain an analysis of the instruction from the VM
    const InstAnalysis* instAnalysis = qbdi_getInstAnalysis(vm, QBDI_ANALYSIS_INSTRUCTION | QBDI_ANALYSIS_DISASSEMBLY);
    // Printing disassembly
    printf("%" PRIRWORD ": %s\n", instAnalysis->address, instAnalysis->disassembly);
    // Incrementing the instruction counter
    (*counter)++;
    // Signaling the VM to continue execution
    return QBDI_CONTINUE;
}

static const size_t STACK_SIZE = 0x100000; // 1MB
static const rword FAKE_RET_ADDR = 0x40000;

int main(int argc, char **argv) {
    uint8_t *fakestack;
    VMInstanceRef vm;
    GPRState *state;
    int traceLevel = 0;
    uint32_t counter = 0;
    bool instrumented;

    if(argc > 1) {
        traceLevel = atoi(argv[1]);
        if(traceLevel > 2) {
            traceLevel = 2;
        }
    }

    printf("Initializing VM ...\n");
    // Constructing a new QBDI VM
    qbdi_initVM(&vm, NULL, NULL);
    // Registering count() callback to be called after every instruction
    qbdi_addCodeCB(vm, QBDI_POSTINST, count, &counter);

    // Get a pointer to the GPR state of the VM
    state = qbdi_getGPRState(vm);
    // Setup initial GPR state, this fakestack will produce a ret NULL at the end of the execution
    qbdi_allocateVirtualStack(state, STACK_SIZE, &fakestack);
    qbdi_simulateCall(state, FAKE_RET_ADDR, 0);


    printf("Running thedude() with trace level %d...\n", traceLevel);
    // Running DBI execution
    instrumented = qbdi_addInstrumentedModuleFromAddr(vm, (rword) &main);
    if (instrumented) {
        if(traceLevel < 1) {
            qbdi_removeInstrumentedRange(vm, (rword) magicHash, (rword) magicHash + 32);
        }
        if(traceLevel < 2) {
            qbdi_removeInstrumentedRange(vm, (rword) magicPow, (rword) magicPow + 32);
        }
        qbdi_run(vm, (rword) thedude, (rword) FAKE_RET_ADDR);
        printf("thedude ran in %u instructions\n", counter);
    } else {
        printf("failed to find main module...\n");
    }
    qbdi_terminateVM(vm);
    qbdi_alignedFree(fakestack);

    return 0;
}

Cryptolock

This example instruments its own code to display instruction performing memory writes and the written values. This can be used to discover the password of the cryptolock and reveal the message (hint: if the password is correct then the hash buffer is all zero).

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>

#include "QBDI.h"


QBDI_NOINLINE void hashPassword(char* hash, const char* password) {
    char acc = 42;
    size_t i = 0;
    size_t hash_len = strlen(hash);
    size_t password_len = strlen(password);

    for(i = 0; i < hash_len && i < password_len; i++) {
        hash[i] = (hash[i] ^ acc) - password[i];
        acc = password[i];
    }

}

char SECRET[] = "\x29\x0d\x20\x00\x00\x00\x00\x0a\x65\x1f\x32\x00\x19\x0c\x4e\x1b\x2d\x09\x66\x0c\x1a\x06\x05\x06\x20\x1f\x46";

QBDI_NOINLINE const char* getSecret(const char* password) {
    size_t i = 0;
    size_t password_len = strlen(password);
    for(i = 0; i < sizeof(SECRET); i++) {
        SECRET[i] ^= password[i%password_len];
    }
    return SECRET;
}

QBDI_NOINLINE const char* cryptolock(const char* password) {
    char hash[] = "\x6f\x29\x2a\x29\x1a\x1c\x07\x01";
    size_t i = 0;

    hashPassword(hash, password);

    bool good = true;
    for(i = 0; i < sizeof(hash); i++) {
        if(hash[i] != 0) {
            good = false;
        }
    }

    if(good) {
        return getSecret(password);
    }
    else {
        return NULL;
    }
}

VMAction onwrite(VMInstanceRef vm, GPRState *gprState, FPRState *fprState, void *data) {
    size_t i = 0;
    size_t num_access = 0;
    // Obtain an analysis of the instruction from the vm
    const InstAnalysis* instAnalysis = qbdi_getInstAnalysis(vm, QBDI_ANALYSIS_INSTRUCTION | QBDI_ANALYSIS_DISASSEMBLY);
    // Obtain the instruction memory accesses
    MemoryAccess* memAccesses = qbdi_getInstMemoryAccess(vm, &num_access);

    // Printing disassembly
    printf("%" PRIRWORD ": %s\n", instAnalysis->address, instAnalysis->disassembly);
    // Printing write memory accesses
    for(i = 0; i < num_access; i++) {
        // Checking the access mode
        if(memAccesses[i].type == QBDI_MEMORY_WRITE) {
            // Writing the written value, the size and the address
            printf("\tWrote 0x%" PRIRWORD " on %u bytes at 0x%" PRIRWORD "\n", memAccesses[i].value, (unsigned) memAccesses[i].size, memAccesses[i].accessAddress);
        }
    }
    printf("\n");
    free(memAccesses);

    return QBDI_CONTINUE;
}

static const size_t STACK_SIZE = 0x100000; // 1MB
static const rword FAKE_RET_ADDR = 0x40000;

int main(int argc, char **argv) {
    uint8_t *fakestack = NULL;
    VMInstanceRef vm;
    GPRState *state;

    if(argc < 2) {
        printf("Please give a password as first argument\n");
        return 1;
    }

    printf("Initializing VM ...\n");
    // Constructing a new QBDI vm
    qbdi_initVM(&vm, NULL, NULL);
    // Registering a callback on every memory write to our onwrite() function
    qbdi_addMemAccessCB(vm, QBDI_MEMORY_WRITE, onwrite, NULL);
    // Instrument this module
    qbdi_addInstrumentedModuleFromAddr(vm, (rword) &cryptolock);
    // Get a pointer to the GPR state of the vm
    state = qbdi_getGPRState(vm);
    // Setup initial GPR state, this fakestack will produce a ret FAKE_RET_ADDR at the end of the execution
    // Also setup one argument on the stack which is the password string
    qbdi_allocateVirtualStack(state, STACK_SIZE, &fakestack);
    qbdi_simulateCall(state, FAKE_RET_ADDR, 1, argv[1]);

    printf("Running cryptolock(\"%s\")\n", argv[1]);
    qbdi_run(vm, (rword) cryptolock, (rword) FAKE_RET_ADDR);
    // Getting the return value from the call
    const char* ret = (const char*)QBDI_GPR_GET(state, REG_RETURN);
    // If it is not null, display it
    if(ret != NULL) {
        printf("Returned \"%s\"\n", ret);
    }
    else {
        printf("Returned null\n");
    }

    qbdi_terminateVM(vm);
    qbdi_alignedFree(fakestack);

    return 0;
}