Skip to content

Latest commit

 

History

History
1475 lines (1142 loc) · 47.1 KB

File metadata and controls

1475 lines (1142 loc) · 47.1 KB

Android-Mem-Kit Usage Guide

Table of Contents

  1. Getting Started
  2. API Reference
  3. Memory Patching
  4. Function Hooking
  5. IL2CPP Instrumentation
  6. JIT Compiler API
  7. Error Handling
  8. Best Practices

Getting Started

Quick Setup

#include "memkit.h"
#include <android/log.h>

#define LOG_TAG "MyResearch"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

Header Layout

memkit.h is an umbrella header. Including it exposes the entire public API. For smaller compile footprints, include only the sub-headers you need:

Header Content
memkit_common.h Shared types, MK_ERRNO_* error codes, callback types
memkit_memory.h Memory patching & library base discovery
memkit_hook.h Function hooking, V2 hook flags, proxy/stack management
memkit_intercept.h Intercept API (CPU context inspection/modification)
memkit_records.h Hook/intercept operation records (CSV)
memkit_il2cpp.h IL2CPP symbol resolution & runtime helpers, IL2CPP_CALL
memkit_xdl.h XDL wrapper (library discovery, symbol resolution)
memkit_dl.h DL helpers & dlopen/dlclose callbacks
memkit_runtime.h Runtime configuration (mode, debug, record, disable)
memkit_nothing.h libshadowhook_nothing.so path management
memkit_jit.h JIT compiler API (SLJIT wrappers)
// Umbrella — everything (recommended for most cases)
#include "memkit.h"

// Or selectively — e.g., JIT only
#include "memkit_jit.h"

// Or memory patching only
#include "memkit_memory.h"

Initialization Sequence

__attribute__((constructor))
void init() {
    // Step 1: Initialize ShadowHook (required before any hooking)
    int ret = memkit_hook_init(SHADOWHOOK_MODE_UNIQUE, false);
    if (ret != 0) {
        LOGE("ShadowHook init failed: %d", ret);
        return;
    }

    // Step 2: Wait for target library to load
    // Use memkit_get_lib_base_v2() for Android 12+ APK-loaded libs
    uintptr_t base = 0;
    for (int i = 0; i < 30 && base == 0; i++) {
        base = memkit_get_lib_base_v2("libtarget.so");
        if (base == 0) sleep(1);
    }

    if (base == 0) {
        LOGE("Target library not found");
        return;
    }

    LOGI("Library base: 0x%lx", base);

    // Step 3: Start your instrumentation
    start_instrumentation(base);
}

Nothing Library Path

The libshadowhook_nothing.so companion library is required by ShadowHook for Android 15+ compatibility. MemKit manages it automatically by default, but you can provide a custom path.

Function Description Returns
memkit_set_nothing_path(path) Set custom path for libshadowhook_nothing.so void
memkit_get_nothing_path() Get current nothing library path char* (caller frees) or NULL

Call memkit_set_nothing_path() before memkit_hook_init() if you manage the nothing library yourself. When not called, AMK automatically extracts the embedded library to a temp directory.

// Option A: auto-extract (default — no call needed)
memkit_hook_init(SHADOWHOOK_MODE_UNIQUE, false);

// Option B: custom path
memkit_set_nothing_path("/data/local/tmp/libshadowhook_nothing.so");
memkit_hook_init(SHADOWHOOK_MODE_UNIQUE, false);

API Reference

Memory Functions

Function Description Returns
memkit_get_lib_base(const char* lib_name) Get lowest base address via /proc/self/maps uintptr_t or 0
memkit_get_lib_base_v2(const char* lib_name) Enhanced discovery — tries maps, xdl, and APK ZIP parsing uintptr_t or 0
memkit_get_lib_base_in_apk(lib_entry, &out_base) Find library base by parsing APK ZIP files bool
memkit_patch_create(addr, hex_string) Create memory patch from hex string MemPatch* or NULL
memkit_patch_apply(patch) Apply memory patch bool
memkit_patch_restore(patch) Restore original bytes bool
memkit_patch_free(patch) Free patch resources void

Enhanced Library Discovery (V2)

On Android 12+, native libraries from split APKs may be mapped directly in-place from the APK without extraction. They don't appear as separate .so entries in /proc/self/maps, so memkit_get_lib_base() won't find them. Use memkit_get_lib_base_v2() which tries all methods:

  1. /proc/self/maps — fast, works for normal .so loading
  2. xdl_iterate_phdr — linker internals, finds libs loaded from APKs
  3. APK ZIP parsing — last resort, parses ZIP central directory of split APKs
// Recommended: try all methods automatically
uintptr_t base = memkit_get_lib_base_v2("libMyGame.so");
if (base) {
    LOGI("Found libMyGame.so at 0x%lx", base);
} else {
    LOGE("Library not found via any method");
}

// Or use the individual methods directly:
// Method 1: /proc/self/maps (existing)
uintptr_t base1 = memkit_get_lib_base("libMyGame.so");

// Method 2: xdl_iterate_phdr (linker internals)
uintptr_t base2 = memkit_get_lib_base_from_xdl("libMyGame.so");

// Method 3: APK ZIP parsing (explicit ABI + entry path)
uintptr_t base3 = 0;
memkit_get_lib_base_in_apk("lib/arm64-v8a/libMyGame.so", &base3);

When using memkit_get_lib_base_v2(), the APK fallback automatically tries each ABI prefix (arm64-v8a, armeabi-v7a, x86_64, x86, riscv64) in priority order.

Hooking Functions

Basic Hook API

Function Description Returns
memkit_hook_init(mode, debuggable) Initialize ShadowHook int (0 = success)
memkit_hook(addr, replace, &orig) Hook function by address stub or NULL
memkit_hook_sym_addr(sym_addr, new_addr, &orig) Hook by already-resolved symbol address stub or NULL
memkit_hook_by_symbol(lib, sym, func, &orig) Hook by symbol name stub or NULL
memkit_unhook(stub) Unhook function void

V2 Hook API

Function Description Returns
memkit_hook_v2(lib, sym, new, &orig, flags) Hook with mode flags stub or NULL
memkit_hook_by_symbol_v2(lib, sym, new, &orig, flags) Hook by symbol with flags stub or NULL
memkit_hook_func_addr_2(addr, new, &orig, flags, ...) Hook by function address with flags (variadic for RECORD mode) stub or NULL
memkit_hook_sym_addr_2(sym_addr, new, &orig, flags, ...) Hook by symbol address with flags (variadic for RECORD mode) stub or NULL
memkit_hook_sym_name_callback_2(lib, sym, new, &orig, cb, arg) Hook by symbol name with completion callback stub or NULL

V2 Hook Flags:

Flag Description
MK_HOOK_DEFAULT Default behavior (respects init mode)
MK_HOOK_WITH_SHARED_MODE Force SHARED mode for this hook
MK_HOOK_WITH_UNIQUE_MODE Force UNIQUE mode for this hook
MK_HOOK_WITH_MULTI_MODE Force MULTI mode for this hook
MK_HOOK_RECORD Enable recording for this hook operation

Hook with Callback

Function Description Returns
memkit_hook_with_callback(lib, sym, new, &orig, cb, arg) Hook with completion callback stub or NULL
memkit_hook_by_symbol_callback(lib, sym, new, &orig, cb, arg) Alias for above stub or NULL

Callback Types:

Type Description
MemKitHooked Called when hook operation completes (success or failure)
MemKitIntercepted Called when intercept operation completes
MemKitInterceptor Receives CPU context on each call to target

Intercept API

Function Description Returns
memkit_intercept(addr, pre, data, flags, ...) Intercept by address stub or NULL
memkit_intercept_by_symbol(lib, sym, pre, data, flags) Intercept by symbol stub or NULL
memkit_intercept_by_sym_addr(addr, pre, data, flags, ...) Intercept by symbol address stub or NULL
memkit_intercept_at_instr(addr, pre, data, flags, ...) Intercept at specific instruction stub or NULL
memkit_intercept_with_callback(lib, sym, pre, data, flags, cb, arg) Intercept with callback stub or NULL
memkit_unintercept(stub) Remove interceptor int

Intercept Flags:

Flag Description
MK_INTERCEPT_DEFAULT Standard intercept (no FP/SIMD context)
MK_INTERCEPT_WITH_FPSIMD_READ_ONLY Include FP/SIMD registers (read-only)
MK_INTERCEPT_WITH_FPSIMD_WRITE_ONLY Include FP/SIMD registers (write-only)
MK_INTERCEPT_WITH_FPSIMD_READ_WRITE Include FP/SIMD registers (read-write)
MK_INTERCEPT_RECORD Enable recording for this intercept

Context Types:

Type Description
MemKitCpuContext CPU context passed to interceptor
MemKitVReg NEON/VFP vector register

Records API

Function Description Returns
memkit_get_records(item_flags) Get records as CSV string char* (caller frees)
memkit_dump_records_fd(fd, item_flags) Dump records to file descriptor void

Record Item Flags:

Flag Description
MK_RECORD_ITEM_TIMESTAMP Include timestamp
MK_RECORD_ITEM_CALLER_LIB_NAME Include caller library name
MK_RECORD_ITEM_OP Include operation type
MK_RECORD_ITEM_LIB_NAME Include target library name
MK_RECORD_ITEM_SYM_NAME Include symbol name
MK_RECORD_ITEM_SYM_ADDR Include symbol address
MK_RECORD_ITEM_NEW_ADDR Include new function address
MK_RECORD_ITEM_BACKUP_LEN Include backup length
MK_RECORD_ITEM_ERRNO Include error code
MK_RECORD_ITEM_STUB Include stub pointer
MK_RECORD_ITEM_FLAGS Include flags
MK_RECORD_ITEM_ALL Include all fields (0x7FF)

Runtime Configuration

Function Description Returns
memkit_get_mode() Get current hooking mode int
memkit_set_debuggable(val) Enable/disable debug logging void
memkit_get_debuggable() Check debug mode status bool
memkit_set_recordable(val) Enable/disable recording void
memkit_get_recordable() Check recording status bool
memkit_set_disable(val) Global enable/disable switch void
memkit_get_disable() Check disabled status bool

Mode Check Macros:

Macro Description
MEMKIT_IS_SHARED_MODE Evaluates to true if in SHARED mode
MEMKIT_IS_UNIQUE_MODE Evaluates to true if in UNIQUE mode
MEMKIT_IS_MULTI_MODE Evaluates to true if in MULTI mode

DL Callbacks

Function Description Returns
memkit_register_dl_init_callback(pre, post, data) Register dlopen callback int
memkit_unregister_dl_init_callback(pre, post, data) Unregister dlopen callback int
memkit_register_dl_fini_callback(pre, post, data) Register dlclose callback int
memkit_unregister_dl_fini_callback(pre, post, data) Unregister dlclose callback int

DL Callback Types:

Type Description
MemKitDlInfo Library info for DL callbacks
MemKitDlInitCallback Called when library is loaded (dlopen)
MemKitDlFiniCallback Called when library is unloaded (dlclose)

Proxy/Stack Macros

Macro Description
MEMKIT_CALL_PREV(func, func_sig, ...) Call previous function in proxy chain (MULTI mode)
MEMKIT_POP_STACK() Pop current stack frame after proxy call
MEMKIT_ALLOW_REENTRANT() Allow reentrant calls from same thread
MEMKIT_DISALLOW_REENTRANT() Disallow reentrant calls from same thread
MEMKIT_RETURN_ADDRESS() Get return address of current proxy caller

Proxy/Stack Functions

Function Description Returns
memkit_get_prev_func(func) Get previous function pointer void*
memkit_pop_stack(return_addr) Pop stack frame void
memkit_allow_reentrant(return_addr) Allow reentrancy void
memkit_disallow_reentrant(return_addr) Disallow reentrancy void
memkit_get_return_address() Get caller return address void*

C++ RAII Stack Scope (C++ only)

For C++ code, MEMKIT_STACK_SCOPE() provides automatic stack cleanup via RAII. This is safer than manual MEMKIT_POP_STACK() when your proxy has multiple return paths or may throw exceptions:

void* my_proxy(int arg1, const char* arg2) {
    MEMKIT_STACK_SCOPE();  // Automatically calls memkit_pop_stack() on scope exit

    if (!arg1) {
        return NULL;  // Early return — stack still popped automatically
    }

    void* result = original_fn(arg1, arg2);
    return result;  // Normal return — stack popped by destructor
}

When to use:

  • ✅ C++ proxy functions with multiple return paths
  • ✅ Code that may throw exceptions
  • ✅ Complex logic where manual MEMKIT_POP_STACK() is error-prone
  • ❌ C code — use manual MEMKIT_POP_STACK() instead (RAII is C++ only)

IL2CPP Functions

Function Description Returns
memkit_il2cpp_init() Initialize IL2CPP handle bool
memkit_il2cpp_resolve(symbol) Resolve from .dynsym void* or NULL
memkit_il2cpp_resolve_symtab(symbol) Resolve from .symtab void* or NULL
memkit_il2cpp_get_handle() Get cached handle void* or NULL
IL2CPP_CALL(ret, name, ...) Macro for auto-cached calls Function pointer

IL2CPP Helper Functions

Function Description Returns
memkit_il2cpp_get_image(assembly_name) Get the Il2CppImage* for a named assembly void* or NULL
memkit_il2cpp_safe_call(fn, arg, &out_result) Safely call an IL2CPP runtime API with crash protection (sigsetjmp/siglongjmp) bool
memkit_il2cpp_wait_ready(timeout_ms) Wait until the IL2CPP runtime is ready (polls il2cpp_domain_get with timeout) void* (domain) or NULL
memkit_il2cpp_attach_thread(domain) Attach current thread to IL2CPP domain void* (thread) or NULL
memkit_il2cpp_detach_thread(thread) Detach current thread from IL2CPP domain void

Usage Example — Full IL2CPP Instrumentation Pattern:

// 1. Wait for runtime
void* domain = memkit_il2cpp_wait_ready(5000);
if (!domain) {
    LOGE("IL2CPP runtime not ready (timeout)");
    return;
}

// 2. Attach thread
void* thread = memkit_il2cpp_attach_thread(domain);
if (!thread) {
    LOGE("Failed to attach thread");
    return;
}

// 3. Get image for assembly
void* image = memkit_il2cpp_get_image("Assembly-CSharp");
if (!image) {
    LOGE("Assembly-CSharp image not found");
}

// 4. Safe call to IL2CPP API (with crash protection)
void* result;
bool ok = memkit_il2cpp_safe_call(some_il2cpp_fn, arg, &result);
if (!ok) {
    LOGE("IL2CPP call crashed or failed");
}

// 5. Detach when done
memkit_il2cpp_detach_thread(thread);

XDL Wrapper Functions

Library Discovery

Function Description Returns
memkit_xdl_iterate(cb, data, flags) Iterate all loaded libraries int (count or -1)
memkit_xdl_open(name, flags) Open library handle void* or NULL
memkit_xdl_close(handle) Close library handle bool
memkit_xdl_get_lib_info(handle, &info) Get library details bool
memkit_get_lib_base_from_xdl(lib_name) Find library base via xdl_iterate_phdr (linker internals) uintptr_t or 0

Symbol Resolution

Function Description Returns
memkit_xdl_sym(handle, symbol, &size) Resolve from .dynsym void* or NULL
memkit_xdl_dsym(handle, symbol, &size) Resolve from .symtab (debug) void* or NULL

Address-to-Symbol (Debug Introspection)

Function Description Returns
memkit_xdl_addr_ctx_create() Create resolution context ctx* or NULL
memkit_xdl_addr_ctx_destroy(ctx) Destroy context void
memkit_xdl_addr_to_symbol(addr, &info, ctx) Resolve address to symbol bool
memkit_xdl_addr_to_symbol4(addr, &info, ctx, flags) With flags (e.g., XDL_NON_SYM) bool

Advanced

Function Description Returns
memkit_xdl_open_from_phdr(info) Create handle from dl_phdr_info void* or NULL

Convenience Macros

Macro Description
XDL_RESOLVE(lib, sym) One-shot symbol resolve
XDL_RESOLVE_SIZE(lib, sym, &size) Resolve with size output

DL Helpers (ShadowHook's Internal Loader)

These functions wrap ShadowHook's internal dlopen/dlsym and work even when standard dlopen is restricted by Android's linker namespace.

Function Description Returns
memkit_dlopen(lib_name) Open library handle (ShadowHook's loader) void* or NULL
memkit_dlclose(handle) Close library handle void
memkit_dlsym(handle, sym) Resolve symbol (tries .dynsym then .symtab) void* or NULL
memkit_dlsym_dynsym(handle, sym) Resolve from .dynsym only (faster) void* or NULL
memkit_dlsym_symtab(handle, sym) Resolve from .symtab only (debug/stripped, slower) void* or NULL

When to use: Prefer these over standard dlopen/dlsym when working with Android's restricted linker namespace, especially on Android 7+.

// Basic usage
void* handle = memkit_dlopen("libtarget.so");
if (handle) {
    void* sym = memkit_dlsym(handle, "target_function");
    if (sym) {
        LOGI("Found target_function at %p", sym);
    }
    memkit_dlclose(handle);
}

// Selective resolution (faster if you know where the symbol is)
void* dynsym_sym = memkit_dlsym_dynsym(handle, "public_api");    // .dynsym
void* symtab_sym = memkit_dlsym_symtab(handle, "internal_func"); // .symtab

Difference from XDL wrapper:

  • memkit_dlopen uses ShadowHook's internal loader (better linker bypass)
  • memkit_xdl_open uses XDL library (more portable, standard discovery)
  • Both work for most use cases; prefer memkit_dlopen for hooking targets

Memory Patching

Basic Patch

// ARM64: MOV X0, #0 (returns 0)
uintptr_t base = memkit_get_lib_base("libtarget.so");
MemPatch* patch = memkit_patch_create(base + 0x1234, "00 00 80 D2");

if (patch && memkit_patch_apply(patch)) {
    LOGI("Patch applied successfully");
} else {
    LOGE("Patch failed: %d", errno);
}

// Restore later if needed
// memkit_patch_restore(patch);

// Free when done
// memkit_patch_free(patch);

Multi-Byte Patch

// ARM64: Multiple instructions
MemPatch* multi_patch = memkit_patch_create(
    base + 0x5678,
    "00 00 80 D2 20 00 80 D2 1F 20 03 D5"  // MOV X0,#0; MOV X0,#1; NOP
);

if (multi_patch && memkit_patch_apply(multi_patch)) {
    LOGI("Multi-byte patch applied");
}

Cross-Page Boundary (Safe)

The library automatically handles patches that span memory pages:

// This 20-byte patch might span two pages
// memkit handles this automatically
MemPatch* cross_page = memkit_patch_create(
    base + 0xFFF0,  // Near page boundary
    "00 00 80 D2 20 00 80 D2 40 00 80 D2 60 00 80 D2 80 00 80 D2"
);

Function Hooking

Hook by Symbol Name (Recommended)

// Original function pointer
static int (*orig_target_function)(int param) = NULL;
static void* hook_stub = NULL;

// Replacement function
static int my_target_function(int param) {
    LOGI("target_function called with: %d", param);

    // Call original if needed
    return orig_target_function(param);
}

// Hook it
hook_stub = memkit_hook_by_symbol(
    "libtarget.so",
    "target_function",
    (void*)my_target_function,
    (void**)&orig_target_function
);

if (hook_stub) {
    LOGI("Hook successful");
} else {
    LOGE("Hook failed: %d", errno);
}

Hook by Address

uintptr_t func_addr = base + 0xABCD;

hook_stub = memkit_hook(
    func_addr,
    (void*)my_function,
    (void**)&orig_function
);

Hook by Symbol Address

Use memkit_hook_sym_addr() when you already have the function pointer resolved from XDL or other methods:

// Resolve symbol first (e.g., via XDL wrapper)
void* sym_addr = memkit_xdl_sym(handle, "target_function", NULL);

if (sym_addr) {
    hook_stub = memkit_hook_sym_addr(
        sym_addr,
        (void*)my_function,
        (void**)&orig_function
    );
}

Unhook

// When done, unhook to restore original behavior
memkit_unhook(hook_stub);

ShadowHook Macros

MemKit provides convenient macros for proxy/stack management:

// In MULTI mode: call the previous function in the proxy chain
int my_proxy(int a, const char* b) {
    int ret = MEMKIT_CALL_PREV(my_proxy, int(*)(int, const char*), a, b);
    MEMKIT_POP_STACK();  // Must call at end of every proxy
    return ret;
}

// Control reentrancy within a proxy
MEMKIT_ALLOW_REENTRANT();    // Allow recursive calls from same thread
MEMKIT_DISALLOW_REENTRANT(); // Block recursive calls

// Get the return address of the current proxy caller
void* ret_addr = MEMKIT_RETURN_ADDRESS();

V2 Hook API

The V2 API allows per-hook mode control with flags:

// Hook with specific mode (overrides global init mode)
void* stub = memkit_hook_v2(
    "libtarget.so",
    "target_function",
    (void*)my_function,
    (void**)&orig_function,
    MK_HOOK_WITH_UNIQUE_MODE  // Force UNIQUE for this hook only
);

// Hook with recording enabled
void* recorded_stub = memkit_hook_by_symbol_v2(
    "libssl.so",
    "SSL_read",
    (void*)my_SSL_read,
    (void**)&orig_SSL_read,
    MK_HOOK_RECORD  // Log this hook operation
);

// Combine flags
void* stub = memkit_hook_v2(
    "libtarget.so",
    "check_integrity",
    (void*)my_check,
    (void**)&orig_check,
    MK_HOOK_WITH_SHARED_MODE | MK_HOOK_RECORD
);

Hook with Callback

Get notified when a hook operation completes:

// Completion callback — called after hook is installed (or fails)
void on_hook_complete(int error_number, const char* lib_name,
                      const char* sym_name, void* sym_addr,
                      void* new_addr, void* orig_addr, void* arg) {
    if (error_number == 0) {
        LOGI("Hook installed: %s!%s at %p", lib_name, sym_name, sym_addr);
    } else {
        LOGE("Hook failed: %d — %s", error_number, memkit_strerror(error_number));
    }
}

// Hook with callback
void* stub = memkit_hook_with_callback(
    "libtarget.so",
    "target_function",
    (void*)my_function,
    (void**)&orig_function,
    on_hook_complete,
    NULL  // user arg passed to callback
);

// Same as above (alias)
void* stub2 = memkit_hook_by_symbol_callback(
    "libssl.so", "SSL_read",
    (void*)my_SSL_read, (void**)&orig_SSL_read,
    on_hook_complete, NULL
);

Intercept API

The Intercept API allows you to inspect and modify CPU registers before the target function executes. Unlike hooks, interceptors receive the full CPU context and can modify arguments in-place.

// Interceptor function — receives CPU context on each call
static void my_interceptor(MemKitCpuContext* cpu_context, void* data) {
    // Read arguments from registers (ARM64: x0-x7 hold first 8 args)
    uint64_t arg0 = cpu_context->regs[0];
    uint64_t arg1 = cpu_context->regs[1];

    LOGI("Intercepted! arg0=0x%lx, arg1=0x%lx", arg0, arg1);

    // Modify arguments before the target function sees them
    cpu_context->regs[0] = 0;  // Zero out first argument

    // Optionally skip calling the original function entirely
    // by setting the PC to the return address
}

// Basic intercept by symbol
void* stub = memkit_intercept_by_symbol(
    "libtarget.so",
    "target_function",
    my_interceptor,
    NULL,  // user data
    MK_INTERCEPT_DEFAULT
);

// Intercept with FP/SIMD context (for functions using NEON)
void* stub = memkit_intercept_by_symbol(
    "libtarget.so",
    "simd_function",
    my_interceptor,
    NULL,
    MK_INTERCEPT_WITH_FPSIMD_READ_WRITE  // Include vfp/regs
);

// Remove interceptor
memkit_unintercept(stub);

Intercept with Completion Callback

void on_intercept_complete(int error_number, const char* lib_name,
                           const char* sym_name, void* sym_addr,
                           void* pre, void* data, void* arg) {
    if (error_number == 0) {
        LOGI("Intercept installed: %s!%s", lib_name, sym_name);
    }
}

void* stub = memkit_intercept_with_callback(
    "libtarget.so",
    "check_signature",
    my_interceptor,
    NULL,
    MK_INTERCEPT_DEFAULT,
    on_intercept_complete,
    NULL
);

Intercept at Specific Instruction

For advanced use cases where you need to intercept at a specific instruction offset:

uintptr_t base = memkit_get_lib_base("libtarget.so");
void* instr_addr = (void*)(base + 0x1234);  // Specific instruction

void* stub = memkit_intercept_at_instr(
    instr_addr,
    my_interceptor,
    NULL,
    MK_INTERCEPT_DEFAULT
);

Real-World Example: SSL Pinning Bypass via Intercept

// Intercept SSL_CTX_set_verify to force VERIFY_NONE
static void intercept_SSL_CTX_set_verify(MemKitCpuContext* ctx, void* data) {
    // ARM64 calling convention: x0=ctx, x1=mode, x2=callback
    uint64_t mode = ctx->regs[1];
    LOGI("SSL_CTX_set_verify called with mode=0x%lx", mode);

    // Force mode to VERIFY_NONE (0)
    ctx->regs[1] = 0;
}

void* stub = memkit_intercept_by_symbol(
    "libssl.so",
    "SSL_CTX_set_verify",
    intercept_SSL_CTX_set_verify,
    NULL,
    MK_INTERCEPT_DEFAULT
);

Real-World Example: Integrity Check Bypass via Intercept

// Intercept a signature verification function
static void intercept_verify_signature(MemKitCpuContext* ctx, void* data) {
    // Force return value to 1 (valid) by modifying x0 before return
    // We can't modify return value directly in interceptor,
    // but we can use a hook instead for post-call modification.
    // Interceptors are best for argument inspection/modification.
    LOGI("verify_signature called — arguments logged");
}

Records API

MemKit can log all hook/intercept operations to a CSV format for analysis:

// Enable recording globally
memkit_set_recordable(true);

// Get records as CSV string (caller must free)
char* csv = memkit_get_records(MK_RECORD_ITEM_ALL);
if (csv) {
    LOGI("Operation records:\n%s", csv);
    free(csv);
}

// Dump records directly to a file descriptor
int fd = open("/data/local/tmp/memkit_records.csv", O_WRONLY | O_CREAT, 0644);
if (fd >= 0) {
    memkit_dump_records_fd(fd, MK_RECORD_ITEM_ALL);
    close(fd);
}

// Selective recording — only include specific fields
uint32_t flags = MK_RECORD_ITEM_TIMESTAMP
               | MK_RECORD_ITEM_LIB_NAME
               | MK_RECORD_ITEM_SYM_NAME
               | MK_RECORD_ITEM_ERRNO;
char* csv = memkit_get_records(flags);

Record Item Flags

Flag Field
MK_RECORD_ITEM_TIMESTAMP Timestamp of operation
MK_RECORD_ITEM_CALLER_LIB_NAME Library that initiated the operation
MK_RECORD_ITEM_OP Operation type (hook, intercept, unhook)
MK_RECORD_ITEM_LIB_NAME Target library name
MK_RECORD_ITEM_SYM_NAME Target symbol name
MK_RECORD_ITEM_SYM_ADDR Symbol address
MK_RECORD_ITEM_NEW_ADDR Replacement function address
MK_RECORD_ITEM_BACKUP_LEN Backup length of trampoline
MK_RECORD_ITEM_ERRNO Error code (0 = success)
MK_RECORD_ITEM_STUB Stub pointer
MK_RECORD_ITEM_FLAGS Flags used
MK_RECORD_ITEM_ALL All fields (0x7FF)

Runtime Configuration

Control MemKit behavior at runtime:

// Check current mode
int mode = memkit_get_mode();
if (MEMKIT_IS_UNIQUE_MODE) {
    LOGI("Running in UNIQUE mode");
} else if (MEMKIT_IS_SHARED_MODE) {
    LOGI("Running in SHARED mode");
}

// Toggle debug logging
memkit_set_debuggable(true);
bool is_debug = memkit_get_debuggable();

// Toggle recording at runtime
memkit_set_recordable(true);
bool is_recordable = memkit_get_recordable();

// Global disable — suspends all hooking/intercepting
memkit_set_disable(true);   // Disable all operations
bool is_disabled = memkit_get_disable();
memkit_set_disable(false);  // Re-enable

DL Callbacks

Register callbacks to be notified when libraries are loaded or unloaded:

// Called before and after a library is loaded (dlopen)
void dl_init_pre(struct dl_phdr_info* info, size_t size, void* data) {
    LOGI("Library loading: %s", info->dlpi_name);
}

void dl_init_post(struct dl_phdr_info* info, size_t size, void* data) {
    LOGI("Library loaded: %s at base 0x%lx", info->dlpi_name, info->dlpi_addr);
}

// Called before and after a library is unloaded (dlclose)
void dl_fini_pre(struct dl_phdr_info* info, size_t size, void* data) {
    LOGI("Library unloading: %s", info->dlpi_name);
}

void dl_fini_post(struct dl_phdr_info* info, size_t size, void* data) {
    LOGI("Library unloaded: %s", info->dlpi_name);
}

// Register callbacks
memkit_register_dl_init_callback(dl_init_pre, dl_init_post, NULL);
memkit_register_dl_fini_callback(dl_fini_pre, dl_fini_post, NULL);

// Unregister when done
memkit_unregister_dl_init_callback(dl_init_pre, dl_init_post, NULL);
memkit_unregister_dl_fini_callback(dl_fini_pre, dl_fini_post, NULL);

Real-World Example: Auto-Hook When Target Library Loads

// Auto-hook SSL functions when libssl.so is loaded
static void on_ssl_loaded(struct dl_phdr_info* info, size_t size, void* data) {
    if (strstr(info->dlpi_name, "libssl.so")) {
        LOGI("libssl.so detected — installing hooks...");
        // Install hooks here
    }
}

memkit_register_dl_init_callback(NULL, on_ssl_loaded, NULL);

IL2CPP Instrumentation

Basic IL2CPP Call

// Auto-cached function call
void* (*il2cpp_domain_get)(void) = IL2CPP_CALL(void*, "il2cpp_domain_get");

if (il2cpp_domain_get) {
    void* domain = il2cpp_domain_get();
    LOGI("IL2CPP Domain: %p", domain);
}

Multiple Calls

// Get domain
void* (*il2cpp_domain_get)(void) = IL2CPP_CALL(void*, "il2cpp_domain_get");
void* domain = il2cpp_domain_get();

// Attach thread
void* (*il2cpp_thread_attach)(void*) = IL2CPP_CALL(void*, "il2cpp_thread_attach", void*);
il2cpp_thread_attach(domain);

// Get root namespace
void* (*il2cpp_get_root_namespace)(void) = IL2CPP_CALL(void*, "il2cpp_get_root_namespace");
void* root_ns = il2cpp_get_root_namespace();

Resolve Internal Symbols

// Some symbols are only in .symtab
void* internal_func = memkit_il2cpp_resolve_symtab("_ZN6Player13InternalInitEv");

if (internal_func) {
    LOGI("Found internal function: %p", internal_func);
}

Hook IL2CPP Functions

static void* (*orig_il2cpp_thread_attach)(void*) = NULL;
static void* thread_hook_stub = NULL;

static void* my_il2cpp_thread_attach(void* domain) {
    LOGI("il2cpp_thread_attach called");
    return orig_il2cpp_thread_attach(domain);
}

// Hook it
thread_hook_stub = memkit_hook_by_symbol(
    "libil2cpp.so",
    "il2cpp_thread_attach",
    (void*)my_il2cpp_thread_attach,
    (void**)&orig_il2cpp_thread_attach
);

JIT Compiler API

MemKit integrates SLJIT, a platform-independent JIT compiler. You can dynamically generate native code at runtime for creating optimized stubs, hook trampolines, or runtime code specialization.

The API is split into two tiers:

Tier 1: Thin Wrappers (1:1 SLJIT Mapping)

Thin wrappers follow SLJIT semantics with a memkit_jit_ prefix. See sljitLir.h for full documentation. All emit functions return sljit_s32 (SLJIT_SUCCESS = 0 on success).

Lifecycle

Function Description Returns
memkit_jit_create_compiler() Create a new compiler instance struct sljit_compiler* or NULL
memkit_jit_destroy_compiler(C) Destroy compiler void
memkit_jit_get_error(C) Get last compiler error sljit_s32
memkit_jit_generate_code(C) Compile to executable code void* or NULL
memkit_jit_free_code(code) Free generated code void
memkit_jit_has_cpu_feature(type) Check CPU feature support sljit_s32
memkit_jit_get_platform_name() Get platform name string const char*

Function Entry/Exit

Function Description
memkit_jit_emit_enter(C, options, arg_types, scratches, saveds, local_size) Emit function prologue
memkit_jit_set_context(C, options, arg_types, scratches, saveds, local_size) Set function context
memkit_jit_emit_return_void(C) Emit void return
memkit_jit_emit_return(C, op, src, srcw) Emit return with value
memkit_jit_emit_return_to(C, src, srcw) Emit return to address

Instructions

Function Description
memkit_jit_emit_op0(C, op) Zero-operand instruction (e.g., NOP)
memkit_jit_emit_op1(C, op, dst, dstw, src, srcw) One-operand instruction
memkit_jit_emit_op2(C, op, dst, dstw, src1, src1w, src2, src2w) Two-operand instruction
memkit_jit_emit_op2u(C, op, src1, src1w, src2, src2w) Two-operand (no dst)
memkit_jit_emit_op2r(C, op, dst_reg, src1, src1w, src2, src2w) Two-operand (reg dst)
memkit_jit_emit_shift_into(C, op, dst_reg, src1_reg, src2_reg, src3, src3w) Shift into register
memkit_jit_emit_op2_shift(C, op, dst, dstw, src1, src1w, src2, src2w, shift) Two-operand with shifted src
memkit_jit_emit_op_src(C, op, src, srcw) Source-only operation
memkit_jit_emit_op_dst(C, op, dst, dstw) Destination-only operation

Floating Point & SIMD

Function Description
memkit_jit_emit_fop1(C, op, dst, dstw, src, srcw) Unary FP operation
memkit_jit_emit_fop2(C, op, dst, dstw, src1, src1w, src2, src2w) Binary FP operation
memkit_jit_emit_fcmp(C, type, src1, src1w, src2, src2w) FP compare (returns jump)
memkit_jit_emit_fset32(C, freg, value) Set 32-bit float constant
memkit_jit_emit_fset64(C, freg, value) Set 64-bit double constant
memkit_jit_emit_simd_mov(C, type, vreg, srcdst, srcdstw) SIMD register move
memkit_jit_emit_simd_op2(C, type, dst_vreg, src1_vreg, src2, src2w) SIMD binary operation
memkit_jit_emit_atomic_load(C, op, dst_reg, mem_reg) Atomic load
memkit_jit_emit_atomic_store(C, op, src_reg, mem_reg, temp_reg) Atomic store

Labels, Jumps & Calls

Function Description
memkit_jit_emit_label(C) Emit label at current position
memkit_jit_emit_jump(C, type) Emit conditional/unconditional jump
memkit_jit_emit_cmp(C, type, src1, src1w, src2, src2w) Compare and conditional jump
memkit_jit_emit_ijump(C, type, src, srcw) Indirect jump
memkit_jit_emit_call(C, type, arg_types) Direct function call (returns jump)
memkit_jit_emit_icall(C, type, arg_types, src, srcw) Indirect function call
memkit_jit_emit_const(C, op, dst, dstw, init_value) Emit constant pool entry
memkit_jit_emit_op_addr(C, op, dst, dstw) Emit address of current position

Code Introspection

Function Description Returns
memkit_jit_get_executable_offset(C) Get offset to executable area sljit_sw
memkit_jit_get_generated_code_size(C) Get generated code size sljit_uw
memkit_jit_get_label_addr(label) Get label address in code sljit_uw
memkit_jit_get_jump_addr(jump) Get jump address in code sljit_uw

Serialization (AOT) & Stack

Function Description Returns
memkit_jit_serialize_compiler(C, options, &size) Serialize compiler for AOT sljit_uw*
memkit_jit_deserialize_compiler(buf, size, options) Deserialize compiler struct sljit_compiler*
memkit_jit_allocate_stack(start_size, max_size) Allocate JIT stack struct sljit_stack*
memkit_jit_free_stack(stack) Free JIT stack void

Full list of all ~80 thin wrappers is available in include/memkit_jit.h.

Tier 2: High-Level Wrappers

Convenience functions built on top of the thin wrappers for common use cases:

Function Description Returns
memkit_jit_forwarder_create(target, num_args) Create forwarding trampoline (0-4 args) void* or NULL
memkit_jit_forwarder_create_explicit(target, arg_types, num_scratches) Forwarder with explicit arg types void* or NULL
memkit_jit_emit_nops(C, nop_count) Emit NOP sled sljit_s32
memkit_jit_alloc_exec(size) Allocate RWX executable memory void* or NULL
memkit_jit_free_exec(ptr, size) Free executable memory void
memkit_jit_write_exec(ptr, data, size) Write data to executable memory (handles cache sync) bool

JIT Examples

Basic JIT: add_one(int x)

// JIT-compile: int add_one(int x) { return x + 1; }
struct sljit_compiler *C = memkit_jit_create_compiler();

memkit_jit_emit_enter(C, 0, SLJIT_ARGS1(W, W_R), 4, 0, 0);
memkit_jit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_R0, 0, SLJIT_IMM, 1);
memkit_jit_emit_return(C, SLJIT_MOV, SLJIT_R0, 0);

int (*add_one)(int) = (int (*)(int))memkit_jit_generate_code(C);
memkit_jit_destroy_compiler(C);

int result = add_one(41);  // returns 42
memkit_jit_free_code((void*)add_one);

Forwarding Trampoline

// Forward all args to an existing function
int real_add(int a, int b) { return a + b; }

int (*forwarder)(int, int) = (int (*)(int, int))
    memkit_jit_forwarder_create((void*)real_add, 2);

int result = forwarder(3, 4);  // returns 7
memkit_jit_free_code((void*)forwarder);

Executable Memory Helpers

// Allocate + write + execute custom shellcode
size_t code_size = 128;
void *exec = memkit_jit_alloc_exec(code_size);

uint8_t code[] = { /* ... native instructions ... */ };
memkit_jit_write_exec(exec, code, sizeof(code));

// Execute:
void (*func)(void) = (void (*)(void))exec;
func();

memkit_jit_free_exec(exec, code_size);

Build Requirements

To use the JIT API, link against SLJIT. The build system handles this automatically when you include memkit.h:

# CMake: add_subdirectory already handles SLJIT
target_link_libraries(your_target PRIVATE memkit)

# Or Makefile: included automatically

Include #include "memkit.h" — it pulls in memkit_jit.h and sljitLir.h automatically.


Error Handling

MemKit Error Functions

MemKit provides its own error handling layer wrapping ShadowHook:

#include <errno.h>
#include <string.h>

// Get last error code from ShadowHook
int err = memkit_errno();

// Get human-readable error message
const char* msg = memkit_strerror(err);
LOGE("Operation failed: %d - %s", err, msg);

// Get version string
const char* version = memkit_version();
LOGI("ShadowHook version: %s", version);

// Get error from last shadowhook_init() call
int init_err = memkit_init_errno();

MK_ERRNO_* Constants

MemKit exposes all 46 ShadowHook error codes:

// Common error codes
MK_ERRNO_OK                     // 0: Success
MK_ERRNO_UNINIT                 // 2: Not initialized
MK_ERRNO_INVALID_ARG            // 3: Invalid argument
MK_ERRNO_OOM                    // 4: Out of memory
MK_ERRNO_MPROT                  // 5: mprotect failed
MK_ERRNO_HOOK_DLSYM             // 18: Symbol not found
MK_ERRNO_HOOK_ENTER             // 24: Failed to enter hook
MK_ERRNO_HOOK_DUP               // 20: Duplicate hook
MK_ERRNO_UNHOOK_NOTFOUND        // 27: Unhook target not found
MK_ERRNO_DISABLED               // 45: Operations disabled

// Full list available in memkit.h:
// MK_ERRNO_PENDING, MK_ERRNO_WRITE_CRASH, MK_ERRNO_INIT_ERRNO,
// MK_ERRNO_INIT_SIGSEGV, MK_ERRNO_INIT_SIGBUS, MK_ERRNO_INTERCEPT_DUP,
// MK_ERRNO_INIT_SAFE, MK_ERRNO_INIT_LINKER, MK_ERRNO_INIT_HUB,
// MK_ERRNO_HUB_CREAT, MK_ERRNO_MONITOR_DLOPEN, MK_ERRNO_HOOK_UNIQUE_DUP,
// MK_ERRNO_HOOK_DLOPEN_CRASH, MK_ERRNO_HOOK_DLSYM_CRASH,
// MK_ERRNO_HOOK_DLADDR_CRASH, MK_ERRNO_HOOK_DLINFO, MK_ERRNO_HOOK_SYMSZ,
// MK_ERRNO_HOOK_REWRITE_CRASH, MK_ERRNO_HOOK_REWRITE_FAILED,
// MK_ERRNO_UNHOOK_CMP_CRASH, MK_ERRNO_UNHOOK_TRAMPO_MISMATCH,
// MK_ERRNO_UNHOOK_EXIT_MISMATCH, MK_ERRNO_UNHOOK_EXIT_CRASH,
// MK_ERRNO_UNHOOK_ON_ERROR, MK_ERRNO_UNHOOK_ON_UNFINISHED,
// MK_ERRNO_ELF_ARCH_MISMATCH, MK_ERRNO_LINKER_ARCH_MISMATCH,
// MK_ERRNO_DUP, MK_ERRNO_NOT_FOUND, MK_ERRNO_NOT_SUPPORT,
// MK_ERRNO_INIT_TASK, MK_ERRNO_HOOK_ISLAND_EXIT,
// MK_ERRNO_HOOK_ISLAND_ENTER, MK_ERRNO_HOOK_ISLAND_REWRITE,
// MK_ERRNO_MODE_CONFLICT, MK_ERRNO_HOOK_MULTI_DUP

Using memkit_strerror()

void* stub = memkit_hook_by_symbol("lib.so", "func", my_func, (void**)&orig);
if (stub == NULL) {
    int err = memkit_errno();
    const char* msg = memkit_strerror(err);
    LOGE("Hook failed: %d - %s", err, msg);

    // Common troubleshooting:
    if (err == MK_ERRNO_HOOK_DLSYM) {
        LOGE("Symbol not found — check library name and symbol spelling");
    } else if (err == MK_ERRNO_UNINIT) {
        LOGE("ShadowHook not initialized — call memkit_hook_init() first");
    } else if (err == MK_ERRNO_INVALID_ARG) {
        LOGE("Invalid argument — check function pointers and addresses");
    }
}

Direct ShadowHook Access (Edge Cases)

For edge cases where you need direct access to ShadowHook's native error functions:

// Direct ShadowHook access (rarely needed — prefer memkit_errno/memkit_strerror)
if (stub == NULL) {
    int err = shadowhook_get_errno();
    const char* msg = shadowhook_to_errmsg(err);
    LOGE("ShadowHook error: %d - %s", err, msg);
}

Note: In almost all cases, prefer memkit_errno() and memkit_strerror() — they wrap the same underlying ShadowHook error state but keep your code decoupled from the hooking library.


Best Practices

1. Thread Safety

The library is thread-safe. You can call APIs from multiple threads:

// Safe to call from any thread
void* thread_func(void* arg) {
    void* func = IL2CPP_CALL(void*, "some_function");
    if (func) func();
    return NULL;
}

2. Memory Management

Always free patches when done:

MemPatch* patch = memkit_patch_create(...);
memkit_patch_apply(patch);

// ... later ...
memkit_patch_restore(patch);
memkit_patch_free(patch);

3. Wait for Library Load

uintptr_t wait_for_lib(const char* name, int timeout_sec) {
    uintptr_t base = 0;
    for (int i = 0; i < timeout_sec && base == 0; i++) {
        base = memkit_get_lib_base(name);
        if (base == 0) sleep(1);
    }
    return base;
}

4. Validate Pointers

// Always check for NULL before using
if (orig_function != NULL) {
    orig_function(param);
}

// Check patch before applying
if (patch && memkit_patch_apply(patch)) {
    // Success
}

5. Use UNIQUE Mode

Unless you need multiple hooks on same function:

// Recommended for most cases
memkit_hook_init(SHADOWHOOK_MODE_UNIQUE, false);

6. Logging

Use Android logging for debugging:

#include <android/log.h>

#define LOG_TAG "MyResearch"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

XDL Wrapper Examples

Discover All Loaded Libraries

typedef struct {
    const char* target;
    uintptr_t base;
} find_lib_ctx_t;

static bool find_library_callback(const MemKitLibInfo* info, void* user_data) {
    find_lib_ctx_t* ctx = (find_lib_ctx_t*)user_data;

    if (strcmp(info->name, ctx->target) == 0) {
        ctx->base = info->base;
        LOGI("Found %s at 0x%lx (size: %zu bytes)",
             info->name, info->base, info->size);
        return false;  // Stop iteration
    }

    LOGD("Library: %s @ 0x%lx", info->name, info->base);
    return true;  // Continue
}

void discover_libraries() {
    find_lib_ctx_t ctx = {.target = "libil2cpp.so"};

    int count = memkit_xdl_iterate(find_library_callback, &ctx, XDL_DEFAULT);
    LOGI("Iterated %d libraries, found target at 0x%lx", count, ctx.base);
}

Resolve Symbol from Any Library

// Generic symbol resolution (not just IL2CPP)
void* resolve_from_libc() {
    void* handle = memkit_xdl_open("libc.so", XDL_DEFAULT);
    if (!handle) return NULL;

    void* open_sym = memkit_xdl_sym(handle, "open", NULL);
    LOGI("libc.so::open = %p", open_sym);

    memkit_xdl_close(handle);
    return open_sym;
}

// One-shot with macro
void* sym = XDL_RESOLVE("libc.so", "open");

Address-to-Symbol (Stack Trace / Debugging)

void resolve_address(void* addr) {
    memkit_addr_ctx_t* ctx = memkit_xdl_addr_ctx_create();
    MemKitSymInfo info;

    if (memkit_xdl_addr_to_symbol(addr, &info, ctx)) {
        LOGI("Address %p:", addr);
        LOGI("  Library: %s (base: 0x%lx)", info.lib_name, info.lib_base);
        LOGI("  Symbol: %s (offset: 0x%lx, size: %zu)",
             info.sym_name ? info.sym_name : "<unknown>",
             info.sym_offset, info.sym_size);
    } else {
        LOGI("Could not resolve address %p", addr);
    }

    memkit_xdl_addr_ctx_destroy(ctx);
}

// Resolve multiple addresses (reuse context for performance)
void resolve_multiple_addresses(void** addrs, int count) {
    memkit_addr_ctx_t* ctx = memkit_xdl_addr_ctx_create();

    for (int i = 0; i < count; i++) {
        MemKitSymInfo info;
        if (memkit_xdl_addr_to_symbol(addrs[i], &info, ctx)) {
            LOGI("[%d] %p -> %s!%s+0x%lx",
                 i, addrs[i], info.lib_name,
                 info.sym_name ? info.sym_name : "?", info.sym_offset);
        }
    }

    memkit_xdl_addr_ctx_destroy(ctx);
}

Get Library Information

void print_lib_info(const char* lib_name) {
    void* handle = memkit_xdl_open(lib_name, XDL_DEFAULT);
    if (!handle) {
        LOGE("Could not open %s", lib_name);
        return;
    }

    MemKitLibInfo info;
    if (memkit_xdl_get_lib_info(handle, &info)) {
        LOGI("Library: %s", info.name);
        LOGI("  Base: 0x%lx", info.base);
        LOGI("  Path: %s", info.path ? info.path : "N/A");
    }

    memkit_xdl_close(handle);
}

Fast Address-to-Library (Skip Symbol Lookup)

// Use XDL_NON_SYM for faster lookup when you only need library info
void quick_lib_lookup(void* addr) {
    memkit_addr_ctx_t* ctx = memkit_xdl_addr_ctx_create();
    MemKitSymInfo info;

    // Skip symbol resolution for faster results
    if (memkit_xdl_addr_to_symbol4(addr, &info, ctx, XDL_NON_SYM)) {
        LOGI("Address %p is in %s (base: 0x%lx)",
             addr, info.lib_name, info.lib_base);
        // info.sym_name will be NULL (skipped)
    }

    memkit_xdl_addr_ctx_destroy(ctx);
}

Thread Safety

The XDL wrapper is thread-safe:

// Multiple threads can safely call memkit_xdl_iterate()
void* thread_func(void* arg) {
    // Each thread gets its own TLS buffer
    memkit_xdl_iterate(my_callback, NULL, XDL_DEFAULT);
    return NULL;
}

// Address resolution context is per-thread (NOT shared)
void* worker(void* arg) {
    // Create per-thread context
    memkit_addr_ctx_t* ctx = memkit_xdl_addr_ctx_create();
    // ... use ctx ...
    memkit_xdl_addr_ctx_destroy(ctx);
    return NULL;
}

Next Steps