Skip to content

Advanced Usage

xwings edited this page Jul 6, 2025 · 4 revisions

Advanced Usage

This section delves into the more powerful and nuanced features of the Qiling Framework, enabling fine-grained control and sophisticated analysis.

Advanced Hooking

Beyond simple address hooks, Qiling offers a variety of hooking mechanisms.

Code Hooks (hook_code)

You can hook every instruction that gets executed. This is useful for tracing, but can have a significant performance impact.

def instruction_tracer(ql, address, size):
    print(f"Executing instruction at {address:#x}")

# Hook all instructions within a specific range
ql.hook_code(instruction_tracer, begin=0x400000, end=0x401000)

Syscall Hooks (set_syscall)

You can intercept and even replace system calls.

# Define a custom handler for the 'uname' syscall
def on_uname(ql, *args, **kwargs):
    # Get the buffer address from the arguments
    buf_addr = args[0]
    print(f"'uname' syscall called, buffer at {buf_addr:#x}")

    # Write a custom OS name to the buffer
    ql.mem.write(buf_addr, b"QilingOS 1.0\0")

    # Set the return value to 0 (success)
    ql.reg.rax = 0

# Replace the default uname syscall handler
ql.os.set_syscall("uname", on_uname)

API Hooks (hook_api)

For Windows PE files, you can hook API calls from specific DLLs.

def on_messagebox(ql, address, params):
    title = params['lpCaption']
    text = params['lpText']
    print(f"MessageBoxA called with title: '{title}' and text: '{text}'")

# Hook MessageBoxA in user32.dll
ql.os.set_api("MessageBoxA", on_messagebox, 'user32.dll')

Interacting with the OS

The ql.os object provides a rich API for interacting with the emulated operating system.

Filesystem Manipulation (ql.fs)

Qiling's virtual filesystem (VFS) can be manipulated at runtime.

# Map a host directory to a path in the emulated fs
ql.add_fs_mapper('/host/path', '/emulated/path')

# Create a virtual file in memory
ql.fs.write('/var/log/virtual.log', b"Log entry from Qiling script.")

# Check if a file exists
if ql.fs.exists('/etc/passwd'):
    print("/etc/passwd exists in the virtual environment.")

Environment Variables

You can set environment variables for the emulated process.

ql.os.set_env("MY_CUSTOM_VAR", "hello_from_qiling")

Running Shellcode

Qiling can directly emulate raw binary code (shellcode) without needing a full executable file.

from qiling import Qiling
from qiling.const import QL_ARCH, QL_OS

# x86_64 shellcode to exit with code 42
shellcode = b"\x48\x31\xc0\xb0\x3c\x48\x31\xff\x40\xb7\x2a\x0f\x05"

# Initialize Qiling for shellcode
# No rootfs is needed for simple shellcode
ql = Qiling(shellcode=shellcode, archtype=QL_ARCH.X8664, ostype=QL_OS.LINUX)

# Run the shellcode
ql.run()

# Check the exit code
print(f"Shellcode exited with code: {ql.exit_code}")

Multi-threading

Qiling has experimental support for multi-threading. You need to enable it during initialization.

ql = Qiling(argv, rootfs, multithread=True)

# You can then use thread-related syscalls like clone or CreateThread
# and Qiling will attempt to manage multiple execution contexts.

Saving and Restoring State (Snapshots)

For tasks like fuzzing, it's efficient to save the machine state and restore it repeatedly.

# Run to a specific point in the program
ql.run(end=0x401200)

# Save a snapshot of the current state
ql.save() 

# Now you can perform some operations...
ql.mem.write(0x500000, b"some input")
ql.run(begin=0x401200, end=0x401500)

# And then restore to the saved state to try something else
ql.restore()

ql.mem.write(0x500000, b"another input")
ql.run(begin=0x401200, end=0x401500)

These advanced features provide the flexibility to build complex and customized binary analysis tools tailored to your specific needs.

Clone this wiki locally