PyQBDIPreload
PyQBDIPreload is an implementation of QBDIPreload for PyQBDI. It allows users to inject the Python runtime into a target process and execute their own script in it. The limitations are pretty much the same as those we face with QBDIPreload and PyQBDI:
For Linux and macOS the executable should be injectable with
LD_PRELOADorDYLD_INSERT_LIBRARIESPyQBDIPreload cannot be injected into a Python process
The Python runtime and the target must share the same architecture
An extra
VMmust not be created. An already preparedVMis provided topyqbdipreload_on_run().
Note
For Linux and macOS the Python library libpython3.x.so must be installed.
Main hook process
Unlike QBDIPreload, the hook of the main function cannot be customised so you are unable to alter the hook process. The Python interpreter is only initialised once the main function is hooked and the script is loaded. Furthermore, some modifications are made to the environment of the interpreter before the user script loading:
sys.argvare the arguments of the executableLD_PRELOADorDYLD_INSERT_LIBRARIESare removed fromos.environpyqbdi.__preload__is set toTrue
Instrumentation
Once the script is loaded, the pyqbdipreload_on_run() function is called with a ready-to-run VM.
Any user callbacks should be registered to the VM, then the VM can be run with pyqbdi.VM.run().
import pyqbdi
def instCallback(vm, gpr, fpr, data):
# User code ...
return pyqbdi.CONTINUE
def pyqbdipreload_on_run(vm, start, stop):
vm.addCodeCB(pyqbdi.PREINST, instCallback, None)
vm.run(start, stop)
Exit hook
The atexit module is triggered when the execution is finished, or when exit or _exit are called.
Note
Any VM object is invalidated when the atexit module is triggered and should never be used.
Execution
A script called pyqbdipreload.py is provided to set up the environment and run the executable.
The first parameter is the PyQBDIPreload script. Next comes the binary, followed by its respective arguments if any.
python3 -m pyqbdipreload <script> <executable> [<arguments> ...]
Full example
Merging everything we have learnt throughout this tutorial, we are now able to write our Python script which prints the instructions that are being executed by the target executable.
#!/usr/bin/env python3
import pyqbdi
def showInstruction(vm, gpr, fpr, data):
instAnalysis = vm.getInstAnalysis()
print("0x{:x}: {}".format(instAnalysis.address, instAnalysis.disassembly))
return pyqbdi.CONTINUE
def pyqbdipreload_on_run(vm, start, stop):
vm.addCodeCB(pyqbdi.PREINST, showInstruction, None)
vm.run(start, stop)
On macOS
Apple silicon architecture
In addition to the caveats discussed for QBDIPreload (see Apple silicon architecture), using PyQBDIPreload on arm64e binaries requires a Python interpreter built for the same ABI.
Example commands for compiling OpenSSL and Python from sources for arm64e are provided in the workflow file .github/workflows/python_macos.yml.