VM C++

Initialization

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

QBDI::VM *vm = new QBDI::VM();
QBDI::VM::VM(const std::string &cpu = "", const std::vector<std::string> &mattrs = {})

Construct a new VM for a given CPU with specific attributes

Parameters
  • cpu: The name of the CPU
  • mattrs: A list of additional attributes

The QBDI::VM::VM() constructor can also take 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:

QBDI::VM *vm = new QBDI::VM("cortex-a9", {"vfp2"});

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::VM::getGPRState() and QBDI::VM::getFPRState() allow to query the current state of the guest processor while the QBDI::VM::setGPRState() and QBDI::VM::setFPRState() allow to modify it:

QBDI::GPRState gprState; // local GPRState structure
gprState = *(vm->getGPRState()); // Copy the vm structure into our local structure
gprState.rax = (QBDI::rword) 42; // Set the value of the RAX register
vm->setGPRState(&gprState); // Copy our local structure to the vm structure

As QBDI::VM::getGPRState() and QBDI::VM::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:

vm->getGPRState()->rax = (QBDI::rword) 42;
GPRState *QBDI::VM::getGPRState() const

Obtain the current general purpose register state.

Return
A structure containing the GPR state.

FPRState *QBDI::VM::getFPRState() const

Obtain the current floating point register state.

Return
A structure containing the FPR state.

void QBDI::VM::setGPRState(GPRState *gprState)

Set the GPR state

Parameters
  • gprState: A structure containing the GPR state.

void QBDI::VM::setFPRState(FPRState *fprState)

Set the GPR state

Parameters
  • fprState: A structure containing the FPR state.

X86

The X86 QBDI::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 QBDI::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 QBDI::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 QBDI::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, const std::vector<rword> &args = {})

Simulate a call by modifying the stack and registers accordingly (std::vector version).

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.
  • args: A list of arguments.

Execution

Starting a Run

The QBDI::VM::run() method 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::VM::run() method.

bool QBDI::VM::run(rword start, rword stop)

Start the execution by the DBI.

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

QBDI::VM::run() is a low level method which implies that the QBDI::GPRState has been properly initialized before.

But a very common usage of QBDI is to start instrumentation on a function call. The QBDI::VM::call() is a helper method 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::VM::call(rword *retval, rword function, const std::vector<rword> &args = {})

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

Example:

// perform (with QBDI) a call similar to (*funcPtr)(42);
uint8_t *fakestack = nullptr;
QBDI::VM *vm = new QBDI::VM();
QBDI::GPRState *state = vm->getGPRState();
QBDI::allocateVirtualStack(state, 0x1000000, &fakestack);
vm->addInstrumentedModuleFromAddr(funcPtr);
rword retVal;
vm->call(&retVal, funcPtr, {42});
Return
True if at least one block has been executed.
Parameters
  • [retval]: Pointer to the returned value (optional).
  • function: Address of the function start instruction.
  • args: A 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 QBDI::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::VM::run() will thus very quickly escape from the instrumentation process through an execution transfer made by the QBDI::ExecBroker.

The basic methods to manipulate this set of instrumented ranges are QBDI::VM::addInstrumentedRange() and QBDI::VM::removeInstrumentedRange().

void QBDI::VM::addInstrumentedRange(rword start, rword end)

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

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

void QBDI::VM::removeInstrumentedRange(rword start, rword end)

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

Parameters
  • 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::VM::addInstrumentedModule() and QBDI::VM::removeInstrumentedModule() allow to use executable module names instead of address ranges. The currently loaded module names can be queried using QBDI::getModuleNames(). The QBDI::VM::addInstrumentedModuleFromAddr() and QBDI::VM::removeInstrumentedModuleFromAddr() methods allow to use any known address from a module to instrument it.

The QBDI::VM::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::VM::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 print the module names using the API.

#include <iostream>

#include <QBDI.h>

int main(int argc, char** argv) {
    for(const QBDI::MemoryMap& m :  QBDI::getCurrentProcessMaps()) {
        std::cout << m.name << " (" << m.permission << ") ";
        m.range.display(std::cout);
        std::cout << std::endl;
    }
}
struct QBDI::MemoryMap

Map of a memory area (region).

Public Functions

MemoryMap(rword start, rword end, Permission permission, std::string name)

Construct a new MemoryMap (given some properties).

Parameters
  • start: Range start value.
  • end: Range end value (always excluded).
  • permission: Region access rights (PF_READ, PF_WRITE, PF_EXEC).
  • name: Region name (useful when a region is mapping a module).

MemoryMap(Range<rword> range, Permission permission, std::string name)

Construct a new MemoryMap (given some properties).

Parameters
  • range: A range of memory (region), delimited between a start and an (excluded) end address.
  • permission: Region access rights (PF_READ, PF_WRITE, PF_EXEC).
  • name: Region name (useful when a region is mapping a module).

Public Members

Range<rword> range

A range of memory (region), delimited between a start and an (excluded) end address.

Permission permission

Region access rights (PF_READ, PF_WRITE, PF_EXEC).

std::string name

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

enum QBDI::Permission

Memory access rights.

Values:

PF_NONE = 0

No access

PF_READ = 1

Read access

PF_WRITE = 2

Write access

PF_EXEC = 4

Execution access

template <typename T>
class QBDI::Range

Public Functions

Range(T start, T end)

Construct a new range.

Parameters
  • start: Range start value.
  • end: Range end value (excluded).

T size() const

Return the total length of a range.

bool operator==(const Range &r) const

Return True if two ranges are equal (same boundaries).

Return
True if equal.
Parameters

bool contains(T t) const

Return True if an value is inside current range boundaries.

Return
True if contained.
Parameters
  • t: Value to check.

bool contains(Range<T> r) const

Return True if a range is inside current range boundaries.

Return
True if contained.
Parameters

bool overlaps(Range<T> r) const

Return True if a range is overlapping current range lower or/and upper boundary.

Return
True if overlapping.
Parameters

void display(std::ostream &os) const

Pretty print a range

Parameters
  • os: An output stream.

Range<T> intersect(Range<T> r) const

Return the intersection of two ranges.

Return
A new range.
Parameters
  • r: Range to intersect with current range.

Public Members

T start

Range start value.

T end

Range end value (always excluded).

std::vector<MemoryMap> QBDI::getCurrentProcessMaps(bool full_path = false)

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

Return
A vector of MemoryMap object.
Parameters
  • full_path: Return the full path of the module in name field

std::vector<std::string> QBDI::getModuleNames()

Get a list of all the module names loaded in the process memory.

Return
A vector of string of module names.

bool QBDI::VM::addInstrumentedModule(const std::string &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
  • name: The module’s name.

bool QBDI::VM::removeInstrumentedModule(const std::string &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
  • name: The module’s name.

bool QBDI::VM::addInstrumentedModuleFromAddr(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
  • addr: An address contained by module’s range.

bool QBDI::VM::removeInstrumentedModuleFromAddr(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
  • addr: An address contained by module’s range.

bool QBDI::VM::instrumentAllExecutableMaps()

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

Return
True if at least one range was added to the instrumented ranges.

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::VM::removeAllInstrumentedRanges()

Remove all instrumented ranges.

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 QBDI::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:

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

The return value of this method is very important to determine how the VM should handle the resuming of execution. 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 BREAK_TO_VM. STOP is not yet implemented.

The QBDI::VM::addCodeCB() method 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;
vm->addCodeCB(QBDI::PREINST, increment, &counter);
vm->run(...); // Run the VM of some piece of code
// counter contains the number of instructions executed
std::cout << "Instruction count: " << counter << std::endl;
enum QBDI::InstPosition

Position relative to an instruction.

Values:

PREINST = 0

Positioned before the instruction.

POSTINST

Positioned after the instruction.

typedef VMAction (*QBDI::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 QBDI::VMAction

The callback results.

Values:

CONTINUE = 0

The execution of the basic block continues.

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.

STOP = 2

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

uint32_t QBDI::VM::addCodeCB(InstPosition pos, InstCallback cbk, void *data)

Register a callback event for every instruction executed.

Return
The id of the registered instrumentation (or VMError::INVALID_EVENTID in case of failure).
Parameters
  • pos: Relative position of the event callback (PREINST / POSTINST).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

It is also possible to register an QBDI::InstCallback for a specific instruction address or address range with the QBDI::VM::addCodeAddrCB() and QBDI::VM::addCodeRangeCB() methods. These allow to fine-tune the instrumentation to specific codes or portions of them.

uint32_t QBDI::VM::addCodeAddrCB(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 VMError::INVALID_EVENTID in case of failure).
Parameters
  • address: Code address which will trigger the callback.
  • pos: Relative position of the callback (PREINST / POSTINST).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

uint32_t QBDI::VM::addCodeRangeCB(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 VMError::INVALID_EVENTID in case of failure).
Parameters
  • 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 (PREINST / POSTINST).
  • cbk: A function pointer to the callback.
  • data: User defined data passed to the callback.

uint32_t QBDI::VM::addMnemonicCB(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 VMError::INVALID_EVENTID in case of failure).
Parameters
  • mnemonic: Mnemonic to match.
  • pos: Relative position of the event callback (PREINST / 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 and x86) allow to trigger callbacks on specific memory events. They use the same callback prototype, QBDI::InstCallback, than instruction callback. They can be registered using the QBDI::VM::addMemAccessCB() API. The API takes care of enabling the corresponding inline memory access logging instrumentation using QBDI::VM::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::VM::addMemAccessCB(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 VMError::INVALID_EVENTID in case of failure).
Parameters

To enable more flexibility on the callback filtering, QBDI::VM::addMemAddrCB() and QBDI::VM::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::VM::addMemAccessCB() and is only meant as an API helper.

uint32_t QBDI::VM::addMemAddrCB(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 VMError::INVALID_EVENTID in case of failure).
Parameters

uint32_t QBDI::VM::addMemRangeCB(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 VMError::INVALID_EVENTID in case of failure).
Parameters
  • 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 QBDI::VMCallback is different as it is triggered by the VM and not by an instruction. The callback receives a QBDI::VMState structure. QBDI::VMCallback can be registered for several events at the same time by combining several values of QBDI::VMEvent in a mask using the | binary operator.

typedef VMAction (*QBDI::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::VM::addVMEventCB(VMEvent mask, VMCallback cbk, void *data)

Register a callback event for a specific VM event.

Return
The id of the registered instrumentation (or VMError::INVALID_EVENTID in case of failure).
Parameters
  • 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 QBDI::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 QBDI::VMEvent

Values:

SEQUENCE_ENTRY = 1

Triggered when the execution enters a sequence.

SEQUENCE_EXIT = 1<<1

Triggered when the execution exits from the current sequence.

BASIC_BLOCK_ENTRY = 1<<2

Triggered when the execution enters a basic block.

BASIC_BLOCK_EXIT = 1<<3

Triggered when the execution exits from the current basic block.

BASIC_BLOCK_NEW = 1<<4

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

EXEC_TRANSFER_CALL = 1<<5

Triggered when the ExecBroker executes an execution transfer.

EXEC_TRANSFER_RETURN = 1<<6

Triggered when the ExecBroker returns from an execution transfer.

SYSCALL_ENTRY = 1<<7

Not implemented.

SYSCALL_EXIT = 1<<8

Not implemented.

SIGNAL = 1<<9

Not implemented.

Custom Instrumentation

Custom instrumentation can be pushed by inserting your own QBDI::InstrRule in the VM. This requires using private headers to define your own rules but allows a very high level of flexibility.

uint32_t QBDI::VM::addInstrRule(InstrRule rule)

Add a custom instrumentation rule to the VM. Requires internal headers

Return
The id of the registered instrumentation (or VMError::INVALID_EVENTID in case of failure).
Parameters
  • rule: A custom instrumentation rule.

Removing Instrumentations

Several callbacks can be registered for the same event and will run in the order they were added. The id returned by this method can be used to modify the callback afterward. The QBDI::VM::deleteInstrumentation() method allows to remove an instrumentation by id while the QBDI::VM::deleteAllInstrumentations() method will remove all instrumentations. This code would do two different executions with different callbacks:

uint32_t cb1 = vm->addCodeCB(QBDI::InstPosition::PREINST, Callback1, &some_data);
uint32_t cb2 = vm->addVMEventCB(QBDI::VMEvent::EXEC_TRANSFER_CALL | QBDI::VMEvent::BASIC_BLOCK_ENTRY, Callback2, &some_data);
vm->run(...); // Run the VM of some piece of code with all instrumentations
vm->deleteInstrumentation(cb1);
vm->run(...); // Run the VM of some piece of code with some instrumentation
vm->deleteAllInstrumentations();
vm->run(...); // Run the VM of some piece of code without instrumentation
bool QBDI::VM::deleteInstrumentation(uint32_t id)

Remove an instrumentation.

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

void QBDI::VM::deleteAllInstrumentations()

Remove all the registered instrumentations.

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 QBDI::InstAnalysis structure:

QBDI::CallbackResult increment(QBDI::VMInstanceRef vm, QBDI::GPRState *gprState, QBDI::FPRState *fprState, void *data) {
    const QBDI::InstAnalysis *instAnalysis = vm->getInstAnalysis();

    std::cout << instAnalysis->disassembly << std::endl;

    return QBDI::CallbackResult::CONTINUE;
}
const InstAnalysis *QBDI::VM::getInstAnalysis(AnalysisType type = ANALYSIS_INSTRUCTION | ANALYSIS_DISASSEMBLY)

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

Analysis can gather all kinds of properties linked with the instruction. By default, instructions basic properties (address, mnemonic, …), as well as disassembly, are returned (as this is a very common case). It’s also possible to select which properties will be retrieved using type argument.

enum QBDI::AnalysisType

Instruction analysis type

Values:

ANALYSIS_INSTRUCTION = 1

Instruction analysis (address, mnemonic, …)

ANALYSIS_DISASSEMBLY = 1<<1

Instruction disassembly

ANALYSIS_OPERANDS = 1<<2

Instruction operands analysis

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 QBDI::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 QBDI::AnalysisType is equal to QBDI::ANALYSIS_OPERANDS, then QBDI::getInstAnalysis() will also analyze all instruction operands, and fill an array of QBDI::OperandAnalysis (of length numOperands).

struct QBDI::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 QBDI::OperandType

Operand type

Values:

OPERAND_INVALID = 0

Invalid operand

OPERAND_IMM

Immediate operand

OPERAND_GPR

Register operand

OPERAND_PRED

Predicate operand

enum QBDI::OperandFlag

Values:

OPERANDFLAG_NONE = 0

No flag

OPERANDFLAG_ADDR = 1<<0

The operand is used to compute an address

OPERANDFLAG_PCREL = 1<<1

The value of the operand is PC relative

OPERANDFLAG_UNDEFINED_EFFECT = 1<<2

The operand role isn’t fully defined

enum QBDI::RegisterAccessType

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

Values:

REGISTER_UNUSED = 0

Unused register

REGISTER_READ = 1

Register read access

REGISTER_WRITE = 1<<1

Register write access

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::VM::recordMemoryAccess() API can be used. This memory logging is also automatically enabled when calling one of the memory callbacks.

bool QBDI::VM::recordMemoryAccess(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

The memory access type always refers to either QBDI::MEMORY_READ, QBDI::MEMORY_WRITE, QBDI::MEMORY_READ_WRITE (which is a bit field combination of the two previous ones).

Once the logging has been enabled, QBDI::VM::getInstMemoryAccess() and QBDI::VM::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::InstPosition::POSTINST). The Cryptolock example shows how to use those APIs to log the memory writes.

struct QBDI::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 QBDI::MemoryAccessType

Memory access type (read / write / …)

Values:

MEMORY_READ = 1

Memory read access

MEMORY_WRITE = 1<<1

Memory write access

MEMORY_READ_WRITE = 3

Memory read/write access

std::vector<MemoryAccess> QBDI::VM::getInstMemoryAccess() const

Obtain the memory accesses made by the last executed instruction.

Return
List of memory access made by the instruction.

std::vector<MemoryAccess> QBDI::VM::getBBMemoryAccess() const

Obtain the memory accesses made by the last executed basic block.

Return
List of memory access made by the instruction.

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::VM::precacheBasicBlock(rword pc)

Pre-cache a known basic block

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

void QBDI::VM::clearCache(rword start, rword end)

Clear a specific address range from the translation cache.

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

void QBDI::VM::clearAllCache()

Clear the entire translation cache.

Free resources

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

void QBDI::alignedFree(void *ptr)

Free a block of aligned memory allocated with alignedAlloc.

Parameters
  • ptr: Pointer to the allocated memory.

Examples

Fibonacci

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

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cstdint>
#include <cstring>

#include "QBDI.h"

#define FAKE_RET_ADDR 42

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


QBDI::VMAction countInstruction(QBDI::VMInstanceRef vm, QBDI::GPRState *gprState,
                                QBDI::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 QBDI::InstAnalysis* instAnalysis = vm->getInstAnalysis();
    // Printing disassembly
    std::cout << std::setbase(16) << instAnalysis->address << ": "
        << instAnalysis->disassembly << std::endl << std::setbase(10);
    // Incrementing the instruction counter
    (*counter)++;
    // Signaling the VM to continue execution
    return QBDI::VMAction::CONTINUE;
}

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

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

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

    std::cout << "Initializing VM ..." << std::endl;
    // Constructing a new QBDI vm
    QBDI::VM *vm = new QBDI::VM();
    // Registering countInstruction() callback to be called after every instruction
    vm->addCodeCB(QBDI::POSTINST, countInstruction, &counter);
    // Registering countRecursion() callback to be called before the first instruction of fibonacci
    vm->addCodeAddrCB((QBDI::rword) &fibonacci, QBDI::PREINST, countRecursion, &recursions);

    // Get a pointer to the GPR state of the vm
    state = vm->getGPRState();
    // 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, {(QBDI::rword) n});

    std::cout << "Running fibonacci(" << n << ") ..." << std::endl;
    // Instrument everything
    vm->instrumentAllExecutableMaps();
    // Run the DBI execution
    vm->run((QBDI::rword) fibonacci, (QBDI::rword) FAKE_RET_ADDR);
    std::cout << "fibonnaci ran in " << counter << " instructions, recursed " << recursions - 1
        << " times and returned " << QBDI_GPR_GET(state, QBDI::REG_RETURN) << std::endl;

    delete 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 <iostream>
#include <iomanip>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <ctime>
#include <cinttypes>

#include "QBDI.h"


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(const 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() {
    time_t t = 0;
    std::string name;
    uint64_t hash = 0;
    char *secret = nullptr;

    std::cout << "Hi I'm the dude." << std::endl;
    std::cout << "Give me your name and I'll give you a hash." << std::endl;
    std::cout << "So what's your name ? ";
    std::cin  >> name;
    time(&t);
    secret = new char[name.length() + 16 + 2];
    sprintf(secret, "%" PRIu64 ":%s", (uint64_t) t, name.c_str());
    std::cout << "Ok I'll give you the hash of " << secret << "." << std::endl;
    hash = magicHash(secret);
    std::cout << "Your hash is " << hash << "." << std::endl;
    std::cout << "No need to thank me." << std::endl;

    delete[] secret;
    return 0;
}

QBDI::VMAction count(QBDI::VMInstanceRef vm, QBDI::GPRState *gprState, QBDI::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 QBDI::InstAnalysis* instAnalysis = vm->getInstAnalysis();
    // Printing disassembly
    std::cout << std::setbase(16) << instAnalysis->address << ": " << instAnalysis->disassembly << std::endl << std::setbase(10);
    // Incrementing the instruction counter
    (*counter)++;
    // Signaling the VM to continue execution
    return QBDI::VMAction::CONTINUE;
}

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

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

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

    std::cout << "Initializing VM ..." << std::endl;
    // Constructing a new QBDI vm
    QBDI::VM *vm = new QBDI::VM();
    // Registering count() callback to be called after every instruction
    vm->addCodeCB(QBDI::POSTINST, count, &counter);

    // Get a pointer to the GPR state of the vm
    state = vm->getGPRState();
    // 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);

    std::cout << "Running thedude() with trace level " << traceLevel << "..." << std::endl;
    // Select which part to instrument
    instrumented = vm->addInstrumentedModuleFromAddr((QBDI::rword) &main);
    if (instrumented) {
        if(traceLevel < 1) {
            vm->removeInstrumentedRange((QBDI::rword) magicHash, (QBDI::rword) magicHash + 32);
        }
        if(traceLevel < 2) {
            vm->removeInstrumentedRange((QBDI::rword) magicPow, (QBDI::rword) magicPow + 32);
        }
        // Run the DBI execution
        vm->run((QBDI::rword) thedude, (QBDI::rword) FAKE_RET_ADDR);
        std::cout << "thedude ran in " << counter << " instructions" << std::endl;
    } else {
        std::cout << "failed to find main module..." << std::endl;
    }
    delete 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 <iostream>
#include <iomanip>
#include <vector>
#include <cstring>

#include "QBDI.h"

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

    for(size_t 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 password_len = strlen(password);
    for(size_t 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";

    hashPassword(hash, password);

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

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

QBDI::VMAction onwrite(QBDI::VMInstanceRef vm, QBDI::GPRState *gprState,
                       QBDI::FPRState *fprState, void *data) {
    // Obtain an analysis of the instruction from the vm
    const QBDI::InstAnalysis* instAnalysis = vm->getInstAnalysis();
    // Obtain the instruction memory accesses
    std::vector<QBDI::MemoryAccess> memAccesses = vm->getInstMemoryAccess();

    // Printing disassembly
    std::cout << std::setbase(16) << instAnalysis->address << ": "
              << instAnalysis->disassembly << std::endl << std::setbase(10);
    // Printing write memory accesses
    for(const QBDI::MemoryAccess& memAccess : memAccesses) {
        // Checking the access mode
        if(memAccess.type == QBDI::MEMORY_WRITE) {
            // Writing the written value, the size and the address
            std::cout << "\t" << "Wrote 0x" << std::setbase(16) << memAccess.value << " on "
                      << std::setbase(10) << (int) memAccess.size << " bytes at 0x"
                      << std::setbase(16) << memAccess.accessAddress << std::endl;
        }
    }
    std::cout << std::endl;

    return QBDI::CONTINUE;
}

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

int main(int argc, char **argv) {
    uint8_t *fakestack = nullptr;
    QBDI::GPRState *state;

    if(argc < 2) {
        std::cout << "Please give a password as first argument" << std::endl;
        return 1;
    }

    std::cout << "Initializing VM ..." << std::endl;
    // Constructing a new QBDI vm
    QBDI::VM *vm = new QBDI::VM();
    // Registering a callback on every memory write to our onwrite() function
    vm->addMemAccessCB(QBDI::MEMORY_WRITE, onwrite, nullptr);
    // Instrument this module
    vm->addInstrumentedModuleFromAddr((QBDI::rword) &cryptolock);
    // Get a pointer to the GPR state of the vm
    state = vm->getGPRState();
    // 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, {(QBDI::rword) argv[1]});

    std::cout << "Running cryptolock(\"" << argv[1] <<"\")" << std::endl;
    vm->run((QBDI::rword) cryptolock, (QBDI::rword) FAKE_RET_ADDR);
    // Getting the return value from the call
    const char* ret = (const char*)QBDI_GPR_GET(state, QBDI::REG_RETURN);
    // If it is not null, display it
    if(ret != nullptr) {
        std::cout << "Returned \"" << ret << "\"" << std::endl;
    }
    else {
        std::cout << "Returned null" << std::endl;
    }

    delete vm;
    QBDI::alignedFree(fakestack);

    return 0;
}

Reference

class QBDI::VM

Public Functions

VM(const std::string &cpu = "", const std::vector<std::string> &mattrs = {})
GPRState *getGPRState() const
FPRState *getFPRState() const
void setGPRState(GPRState *gprState)
void setFPRState(FPRState *fprState)
void addInstrumentedRange(rword start, rword end)
bool addInstrumentedModule(const std::string &name)
bool addInstrumentedModuleFromAddr(rword addr)
bool instrumentAllExecutableMaps()
void removeInstrumentedRange(rword start, rword end)
bool removeInstrumentedModule(const std::string &name)
bool removeInstrumentedModuleFromAddr(rword addr)
void removeAllInstrumentedRanges()
bool run(rword start, rword stop)
bool call(rword *retval, rword function, const std::vector<rword> &args = {})
bool callA(rword *retval, rword function, uint32_t argNum, const rword *args)
bool callV(rword *retval, rword function, uint32_t argNum, va_list ap)
uint32_t addInstrRule(InstrRule rule)
uint32_t addMnemonicCB(const char *mnemonic, InstPosition pos, InstCallback cbk, void *data)
uint32_t addCodeCB(InstPosition pos, InstCallback cbk, void *data)
uint32_t addCodeAddrCB(rword address, InstPosition pos, InstCallback cbk, void *data)
uint32_t addCodeRangeCB(rword start, rword end, InstPosition pos, InstCallback cbk, void *data)
uint32_t addMemAccessCB(MemoryAccessType type, InstCallback cbk, void *data)
uint32_t addMemAddrCB(rword address, MemoryAccessType type, InstCallback cbk, void *data)
uint32_t addMemRangeCB(rword start, rword end, MemoryAccessType type, InstCallback cbk, void *data)
uint32_t addVMEventCB(VMEvent mask, VMCallback cbk, void *data)
bool deleteInstrumentation(uint32_t id)
void deleteAllInstrumentations()
const InstAnalysis *getInstAnalysis(AnalysisType type = ANALYSIS_INSTRUCTION | ANALYSIS_DISASSEMBLY)
bool recordMemoryAccess(MemoryAccessType type)
std::vector<MemoryAccess> getInstMemoryAccess() const
std::vector<MemoryAccess> getBBMemoryAccess() const
bool precacheBasicBlock(rword pc)
void clearCache(rword start, rword end)
void clearAllCache()