Skip to content

Latest commit

 

History

History
967 lines (762 loc) · 25.1 KB

File metadata and controls

967 lines (762 loc) · 25.1 KB

Android-Mem-Kit - Common Recipes

A collection of common patterns and use cases for security research.


Table of Contents

  1. SSL/TLS Research
  2. Integrity Checks
  3. Function Tracing
  4. Root Detection Bypass
  5. Crypto Analysis
  6. Anti-Debugging Bypass
  7. Intercept API Patterns
  8. IL2CPP Runtime Instrumentation

SSL/TLS Research

1. SSL Pinning Bypass

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

#define LOG_TAG "SSL-Research"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

// Hook SSL_verify_cert_chain to always succeed
static int (*orig_SSL_verify_cert_chain)(void* ssl) = NULL;
static void* ssl_verify_stub = NULL;

static int my_SSL_verify_cert_chain(void* ssl) {
    LOGI("[SSL Research] Certificate verification intercepted");
    return 1; // Always succeed
}

// Hook SSL_get1_peer_certificate to log certificate info
static void* (*orig_SSL_get1_peer_certificate)(void* ssl) = NULL;
static void* ssl_peer_stub = NULL;

static void* my_SSL_get1_peer_certificate(void* ssl) {
    LOGI("[SSL Research] Peer certificate requested");
    return orig_SSL_get1_peer_certificate(ssl);
}

void init_ssl_bypass() {
    // Hook certificate verification
    ssl_verify_stub = memkit_hook_by_symbol(
        "libssl.so",
        "SSL_verify_cert_chain",
        (void*)my_SSL_verify_cert_chain,
        (void**)&orig_SSL_verify_cert_chain
    );

    // Hook peer certificate retrieval
    ssl_peer_stub = memkit_hook_by_symbol(
        "libssl.so",
        "SSL_get1_peer_certificate",
        (void*)my_SSL_get1_peer_certificate,
        (void**)&orig_SSL_get1_peer_certificate
    );
}

2. Log Decrypted SSL Data

static int (*orig_SSL_read)(void* ssl, void* buf, int num) = NULL;
static int (*orig_SSL_write)(void* ssl, const void* buf, int num) = NULL;

static int my_SSL_read(void* ssl, void* buf, int num) {
    int ret = orig_SSL_read(ssl, buf, num);

    if (ret > 0) {
        LOGI("[SSL Research] Received %d bytes:", ret);
        // Log first 256 bytes (for research only!)
        int log_len = (ret < 256) ? ret : 256;

        char hex[512];
        for (int i = 0; i < log_len; i++) {
            sprintf(hex + (i * 2), "%02x ", ((unsigned char*)buf)[i]);
        }
        LOGD("%s", hex);
    }

    return ret;
}

static int my_SSL_write(void* ssl, const void* buf, int num) {
    LOGI("[SSL Research] Sending %d bytes:", num);

    // Log outgoing data
    int log_len = (num < 256) ? num : 256;
    char hex[512];
    for (int i = 0; i < log_len; i++) {
        sprintf(hex + (i * 2), "%02x ", ((unsigned char*)buf)[i]);
    }
    LOGD("%s", hex);

    return orig_SSL_write(ssl, buf, num);
}

void init_ssl_logging() {
    memkit_hook_by_symbol("libssl.so", "SSL_read",
                          (void*)my_SSL_read, (void**)&orig_SSL_read);
    memkit_hook_by_symbol("libssl.so", "SSL_write",
                          (void*)my_SSL_write, (void**)&orig_SSL_write);
}

3. OKHttp Pinning Bypass (Java Layer)

// Hook OkHttp's CertificatePinter.check()
static bool (*orig_CertificatePinter_check)(void* instance, const char* hostname, void* chain) = NULL;

static bool my_CertificatePinter_check(void* instance, const char* hostname, void* chain) {
    LOGI("[OKHttp Research] Pinning check for: %s", hostname);
    return true; // Always pass
}

void init_okhttp_bypass() {
    memkit_hook_by_symbol(
        "libokhttp.so",
        "CertificatePinter_check",
        (void*)my_CertificatePinter_check,
        (void**)&orig_CertificatePinter_check
    );
}

Integrity Checks

1. Signature Verification Bypass

// Hook signature verification
static int (*orig_verifySignature)(const char* data, const char* sig) = NULL;
static void* verify_stub = NULL;

static int my_verifySignature(const char* data, const char* sig) {
    LOGI("[Integrity Research] Signature verification called");
    LOGI("  Data: %.50s...", data);
    return 1; // Always valid
}

void init_signature_bypass() {
    verify_stub = memkit_hook_by_symbol(
        "libtarget.so",
        "verifySignature",
        (void*)my_verifySignature,
        (void**)&orig_verifySignature
    );
}

2. Checksum/Hash Bypass

// Hook MD5/SHA comparison functions
static int (*orig_memcmp)(const void* s1, const void* s2, size_t n) = NULL;

static int my_memcmp(const void* s1, const void* s2, size_t n) {
    // Check if this is a hash comparison (16 or 32 bytes)
    if (n == 16 || n == 32) {
        LOGI("[Integrity Research] Hash comparison detected (%zu bytes)", n);
        return 0; // Always match
    }
    return orig_memcmp(s1, s2, n);
}

void init_hash_bypass() {
    memkit_hook_by_symbol("libc.so", "memcmp",
                          (void*)my_memcmp, (void**)&orig_memcmp);
}

3. Anti-Tamper Bypass

// Hook common anti-tamper functions
static int (*orig_ptrace)(int request, pid_t pid, void* addr, void* data) = NULL;

static int my_ptrace(int request, pid_t pid, void* addr, void* data) {
    // Block anti-debugging ptrace calls
    if (request == 0) {  // PTRACE_TRACEME
        LOGI("[Anti-Tamper Research] Blocked PTRACE_TRACEME");
        errno = EPERM;
        return -1;
    }
    return orig_ptrace(request, pid, addr, data);
}

void init_anti_tamper_bypass() {
    memkit_hook_by_symbol("libc.so", "ptrace",
                          (void*)my_ptrace, (void**)&orig_ptrace);
}

Function Tracing

1. Basic Function Tracing

// Trace all calls to a function
static void (*orig_targetFunc)(int param1, void* param2) = NULL;

static void my_targetFunc(int param1, void* param2) {
    LOGI("[Trace] targetFunc called:");
    LOGI("  param1 (int): %d", param1);
    LOGI("  param2 (ptr): %p", param2);

    // Call original
    orig_targetFunc(param1, param2);

    LOGI("[Trace] targetFunc returned");
}

void init_tracing() {
    memkit_hook_by_symbol("libtarget.so", "targetFunc",
                          (void*)my_targetFunc, (void**)&orig_targetFunc);
}

2. Return Value Tracing

static int (*orig_getValue)(void* instance) = NULL;

static int my_getValue(void* instance) {
    int ret = orig_getValue(instance);
    LOGI("[Trace] getValue() returned: %d (0x%x)", ret, ret);
    return ret;
}

3. String Parameter Logging

static void (*orig_logMessage)(const char* msg) = NULL;

static void my_logMessage(const char* msg) {
    LOGI("[Trace] logMessage: %s", msg ? msg : "(null)");
    orig_logMessage(msg);
}

Root Detection Bypass

1. File Existence Check Bypass

static int (*orig_access)(const char* pathname, int mode) = NULL;

static int my_access(const char* pathname, int mode) {
    // Common root detection files
    if (strstr(pathname, "su") ||
        strstr(pathname, "magisk") ||
        strstr(pathname, "root")) {
        LOGI("[Root Detection] Blocked access check: %s", pathname);
        errno = ENOENT;
        return -1;
    }
    return orig_access(pathname, mode);
}

void init_root_bypass() {
    memkit_hook_by_symbol("libc.so", "access",
                          (void*)my_access, (void**)&orig_access);
}

2. Process Check Bypass

static FILE* (*orig_fopen)(const char* filename, const char* mode) = NULL;

static FILE* my_fopen(const char* filename, const char* mode) {
    if (strstr(filename, "/proc/") && strstr(filename, "status")) {
        LOGI("[Root Detection] Blocked proc read: %s", filename);
        return NULL;
    }
    return orig_fopen(filename, mode);
}

3. System Property Check

static const char* (*orig_getprop)(const char* key, const char* default_value) = NULL;

static const char* my_getprop(const char* key, const char* default_value) {
    if (strstr(key, "ro.debuggable") ||
        strstr(key, "ro.secure") ||
        strstr(key, "ro.build.type")) {
        LOGI("[Root Detection] Blocked getprop: %s", key);
        return default_value;
    }
    return orig_getprop(key, default_value);
}

Crypto Analysis

1. AES Encryption Logging

static int (*orig_AES_encrypt)(const unsigned char* in, unsigned char* out, void* key) = NULL;

static int my_AES_encrypt(const unsigned char* in, unsigned char* out, void* key) {
    LOGI("[Crypto Research] AES encrypt called");

    // Log plaintext (for research only!)
    LOGI("  Plaintext: %.64s...", in);

    int ret = orig_AES_encrypt(in, out, key);

    // Log ciphertext
    char hex[256];
    for (int i = 0; i < 16 && i < 64; i++) {
        sprintf(hex + (i * 2), "%02x", out[i]);
    }
    LOGI("  Ciphertext: %s", hex);

    return ret;
}

2. Key Extraction

static int (*orig_AES_set_encrypt_key)(const unsigned char* userKey, int bits, void* key) = NULL;

static int my_AES_set_encrypt_key(const unsigned char* userKey, int bits, void* key) {
    LOGI("[Crypto Research] AES key setup (%d bits)", bits);

    // Log the key (for research only!)
    char hex[128];
    for (int i = 0; i < (bits / 8); i++) {
        sprintf(hex + (i * 2), "%02x", userKey[i]);
    }
    LOGI("  Key: %s", hex);

    return orig_AES_set_encrypt_key(userKey, bits, key);
}

Anti-Debugging Bypass

1. ptrace Anti-Attach

static int (*orig_ptrace)(int request, pid_t pid, void* addr, void* data) = NULL;

static int my_ptrace(int request, pid_t pid, void* addr, void* data) {
    // Block anti-debugging
    if (request == 0) {  // PTRACE_TRACEME
        LOGI("[Anti-Debug] Blocked PTRACE_TRACEME");
        errno = EPERM;
        return -1;
    }
    return orig_ptrace(request, pid, addr, data);
}

2. Debugger Detection

static int (*orig_syscall)(long number, ...) = NULL;

static int my_syscall(long number, ...) {
    // Block prctl(PR_SET_DUMPABLE, 0)
    if (number == 161) {  // SYS_prctl
        va_list args;
        va_start(args, number);
        int option = va_arg(args, int);
        va_end(args);

        if (option == 4) {  // PR_SET_DUMPABLE
            LOGI("[Anti-Debug] Blocked PR_SET_DUMPABLE");
            return 0;
        }
    }
    return orig_syscall(number);
}

3. Timing Check Bypass

static int (*orig_clock_gettime)(clockid_t clock_id, struct timespec* tp) = NULL;

static int my_clock_gettime(clockid_t clock_id, struct timespec* tp) {
    int ret = orig_clock_gettime(clock_id, tp);

    // Log timing checks (often used for anti-debug)
    if (clock_id == CLOCK_PROCESS_CPUTIME_ID) {
        LOGD("[Timing] CPU time check: %ld.%ld", tp->tv_sec, tp->tv_nsec);
    }

    return ret;
}

Intercept API Patterns

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

1. Argument Inspection and Modification

// Intercept a function and modify its first argument before execution
static void intercept_modify_arg(MemKitCpuContext* ctx, void* data) {
    // ARM64: x0 holds the first argument
    uint64_t original_arg0 = ctx->regs[0];
    LOGI("Intercepted! Original arg0: 0x%lx", original_arg0);

    // Modify the first argument before the target sees it
    ctx->regs[0] = 0xDEADBEEF;
}

void* stub = memkit_intercept_by_symbol(
    "libtarget.so",
    "target_function",
    intercept_modify_arg,
    NULL,
    MK_INTERCEPT_DEFAULT
);

2. String Argument Interception

// Intercept a function that takes a string argument and modify it
static void intercept_string_arg(MemKitCpuContext* ctx, void* data) {
    // x0 points to the string argument
    const char* original = (const char*)ctx->regs[0];
    LOGI("String arg: %s", original ? original : "(null)");

    // Replace with a different string (must be valid for the duration of the call)
    static const char* replacement = "/safe/path";
    ctx->regs[0] = (uint64_t)replacement;
}

void* stub = memkit_intercept_by_symbol(
    "libtarget.so",
    "open_file",
    intercept_string_arg,
    NULL,
    MK_INTERCEPT_DEFAULT
);

3. Intercept with Multiple Arguments

// Intercept a function with multiple arguments and log them all
static void intercept_multi_args(MemKitCpuContext* ctx, void* data) {
    // ARM64 calling convention: x0-x7 hold first 8 integer/pointer args
    uint64_t arg0 = ctx->regs[0];
    uint64_t arg1 = ctx->regs[1];
    uint64_t arg2 = ctx->regs[2];
    uint64_t arg3 = ctx->regs[3];

    LOGI("Intercepted: arg0=0x%lx, arg1=0x%lx, arg2=0x%lx, arg3=0x%lx",
         arg0, arg1, arg2, arg3);

    // Selectively modify arguments
    if (arg1 == 0x1234) {
        ctx->regs[1] = 0x5678;  // Change arg1 conditionally
    }
}

void* stub = memkit_intercept_by_symbol(
    "libtarget.so",
    "complex_function",
    intercept_multi_args,
    NULL,
    MK_INTERCEPT_DEFAULT
);

4. Intercept with FP/SIMD Context (NEON)

// Intercept a function that uses NEON/FP registers
static void intercept_simd(MemKitCpuContext* ctx, void* data) {
    // Access VFP/NEON registers when using MK_INTERCEPT_WITH_FPSIMD_*
    LOGI("V0: %llu", ctx->vregs[0].d[0]);
    LOGI("V1: %llu", ctx->vregs[1].d[0]);

    // Modify a SIMD register if needed
    ctx->vregs[0].d[0] = 0x0000000000000000ULL;
}

void* stub = memkit_intercept_by_symbol(
    "libtarget.so",
    "simd_heavy_function",
    intercept_simd,
    NULL,
    MK_INTERCEPT_WITH_FPSIMD_READ_WRITE  // Required for NEON register access
);

5. Intercept with Recording Enabled

// Enable recording for this specific intercept operation
static void intercept_logged(MemKitCpuContext* ctx, void* data) {
    LOGI("Intercepted with recording enabled");
}

void* stub = memkit_intercept_by_symbol(
    "libtarget.so",
    "sensitive_function",
    intercept_logged,
    NULL,
    MK_INTERCEPT_DEFAULT | MK_INTERCEPT_RECORD  // Log this intercept
);

6. Intercept at Specific Instruction Offset

// Intercept at a specific instruction address (not function entry)
static void intercept_at_offset(MemKitCpuContext* ctx, void* data) {
    LOGI("Hit specific instruction offset!");
}

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

void* stub = memkit_intercept_at_instr(
    target_instr,
    intercept_at_offset,
    NULL,
    MK_INTERCEPT_DEFAULT
);

7. Intercept with Completion Callback

// Get notified when intercept is installed
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);
    } else {
        LOGE("Intercept failed: %d", error_number);
    }
}

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

8. Remove Interceptor

// When done, remove the interceptor to restore normal behavior
if (stub) {
    int ret = memkit_unintercept(stub);
    if (ret == 0) {
        LOGI("Interceptor removed successfully");
    } else {
        LOGE("Interceptor removal failed: %d", memkit_errno());
    }
}

Intercept vs Hook: When to Use Which

Scenario Use Why
Replace function entirely Hook (memkit_hook_*) Full control, can call original
Inspect/modify arguments before call Intercept (memkit_intercept_*) Pre-call CPU context access
Log function calls without changing behavior Intercept Lightweight, no trampoline needed
Return value modification Hook Post-call access via return value
NEON/FP register inspection Intercept (with MK_INTERCEPT_WITH_FPSIMD_*) Direct register access
Specific instruction targeting Intercept (memkit_intercept_at_instr) Not limited to function entry

Complete Example: All-in-One

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

#define LOG_TAG "Research-Kit"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

// Storage for stubs
static void* ssl_stub = NULL;
static void* ptrace_stub = NULL;
static void* access_stub = NULL;

// Initialize all bypasses
void init_research_tools() {
    // Initialize hooking
    if (memkit_hook_init(SHADOWHOOK_MODE_UNIQUE, false) != 0) {
        LOGE("Failed to init ShadowHook");
        return;
    }

    LOGI("=== Research Tools Initialized ===");

    // SSL pinning bypass
    ssl_stub = memkit_hook_by_symbol(
        "libssl.so", "SSL_verify_cert_chain",
        (void*)my_SSL_verify_cert_chain, (void**)&orig_SSL_verify_cert_chain
    );

    // Anti-debug bypass
    ptrace_stub = memkit_hook_by_symbol(
        "libc.so", "ptrace",
        (void*)my_ptrace, (void**)&orig_ptrace
    );

    // Root detection bypass
    access_stub = memkit_hook_by_symbol(
        "libc.so", "access",
        (void*)my_access, (void**)&orig_access
    );

    LOGI("Active hooks: SSL=%p, ptrace=%p, access=%p",
         ssl_stub, ptrace_stub, access_stub);
}

// Cleanup
void cleanup_research_tools() {
    if (ssl_stub) memkit_unhook(ssl_stub);
    if (ptrace_stub) memkit_unhook(ptrace_stub);
    if (access_stub) memkit_unhook(access_stub);
}

Tips

  1. Always test on emulators first before real devices
  2. Log everything during research - you never know what's important
  3. Use UNIQUE mode unless you need multiple hooks on same function
  4. Free resources when done to prevent memory leaks
  5. Document your findings for future reference

IL2CPP Runtime Instrumentation

IL2CPP Safe Instrumentation Pattern

When instrumenting Unity/IL2CPP applications, you need to wait for the runtime to initialize, attach your thread, and use safe calls to avoid crashes:

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

#define LOG_TAG "IL2CPP-Research"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

void il2cpp_instrumentation() {
    // Step 1: Wait for IL2CPP runtime to be ready (5 second timeout)
    void* domain = memkit_il2cpp_wait_ready(5000);
    if (!domain) {
        LOGE("IL2CPP runtime not ready — timeout");
        return;
    }
    LOGI("IL2CPP domain acquired: %p", domain);

    // Step 2: Attach current thread to IL2CPP domain
    void* thread = memkit_il2cpp_attach_thread(domain);
    if (!thread) {
        LOGE("Failed to attach thread to IL2CPP domain");
        return;
    }
    LOGI("Thread attached: %p", thread);

    // Step 3: Get image for target assembly
    void* image = memkit_il2cpp_get_image("Assembly-CSharp");
    if (!image) {
        LOGE("Assembly-CSharp image not found");
        goto cleanup;
    }
    LOGI("Found Assembly-CSharp image: %p", image);

    // Step 4: Use safe calls for IL2CPP APIs (crash-protected)
    // Example: Get all classes from a namespace
    void* (*il2cpp_class_get_classes)(void*, void**) =
        IL2CPP_CALL(void*, "il2cpp_class_get_classes", void*, void**);

    if (il2cpp_class_get_classes) {
        void* result;
        bool ok = memkit_il2cpp_safe_call(
            (void* (*)(void*))il2cpp_class_get_classes,
            image,
            &result
        );
        if (ok) {
            LOGI("Safe call succeeded — result: %p", result);
        } else {
            LOGE("Safe call failed or crashed");
        }
    }

cleanup:
    // Step 5: Detach thread when done
    memkit_il2cpp_detach_thread(thread);
    LOGI("Thread detached");
}

// Call from constructor or initialization
__attribute__((constructor))
void init() {
    memkit_hook_init(SHADOWHOOK_MODE_UNIQUE, false);
    il2cpp_instrumentation();
}

Hook IL2CPP Functions with Safe Call Wrapper

For functions that may crash the runtime, wrap them in safe calls:

// Resolve a function from libil2cpp.so
void* (*il2cpp_class_get_name)(void*) =
    IL2CPP_CALL(void*, "il2cpp_class_get_name", void*);

// Safe wrapper
const char* safe_class_name(void* klass) {
    if (!klass || !il2cpp_class_get_name) return NULL;

    void* result;
    bool ok = memkit_il2cpp_safe_call(
        (void* (*)(void*))il2cpp_class_get_name,
        klass,
        &result
    );
    return ok ? (const char*)result : NULL;
}

// Usage
void* some_class = /* ... */;
const char* name = safe_class_name(some_class);
if (name) {
    LOGI("Class name: %s", name);
}

XDL Wrapper Recipes

1. List All Loaded Libraries

#include "memkit.h"

static bool list_all_callback(const MemKitLibInfo* info, void* user_data) {
    (void)user_data;
    LOGI("Library: %s", info->name);
    LOGI("  Base: 0x%016lx  Size: %zu bytes", info->base, info->size);
    if (info->path) {
        LOGI("  Path: %s", info->path);
    }
    return true;  // Continue iteration
}

void list_loaded_libraries() {
    LOGI("=== Loaded Libraries ===");
    int count = memkit_xdl_iterate(list_all_callback, NULL, XDL_DEFAULT);
    LOGI("Total: %d libraries", count);
}

2. Find Library Base Without Hardcoding

typedef struct {
    const char* name;
    uintptr_t base;
} find_ctx_t;

static bool find_lib_callback(const MemKitLibInfo* info, void* user_data) {
    find_ctx_t* ctx = (find_ctx_t*)user_data;
    
    if (strstr(info->name, ctx->name) != NULL) {
        ctx->base = info->base;
        LOGI("Found %s at 0x%lx", info->name, ctx->base);
        return false;  // Stop
    }
    return true;
}

uintptr_t find_library_base(const char* partial_name) {
    find_ctx_t ctx = {.name = partial_name, .base = 0};
    memkit_xdl_iterate(find_lib_callback, &ctx, XDL_DEFAULT);
    return ctx.base;
}

// Usage
uintptr_t il2cpp_base = find_library_base("il2cpp");
uintptr_t libc_base = find_library_base("libc.so");

3. Symbolicate Stack Addresses

void symbolicate_stack_trace(void** addresses, int count) {
    memkit_addr_ctx_t* ctx = memkit_xdl_addr_ctx_create();
    
    LOGI("=== Stack Trace ===");
    for (int i = 0; i < count; i++) {
        MemKitSymInfo info;
        if (memkit_xdl_addr_to_symbol(addresses[i], &info, ctx)) {
            LOGI("#%d  %p  in  %s!%s+0x%lx",
                 i, addresses[i],
                 info.lib_name,
                 info.sym_name ? info.sym_name : "<unknown>",
                 info.sym_offset);
        } else {
            LOGI("#%d  %p  <unresolved>", i, addresses[i]);
        }
    }
    
    memkit_xdl_addr_ctx_destroy(ctx);
}

4. Resolve Internal Functions (Debug Symbols)

// Some functions are only in .symtab (stripped from .dynsym)
void* resolve_internal_function() {
    void* handle = memkit_xdl_open("libil2cpp.so", XDL_DEFAULT);
    if (!handle) return NULL;

    // Try .dynsym first (faster)
    void* sym = memkit_xdl_sym(handle, "il2cpp_init", NULL);
    
    // Fall back to .symtab if not found
    if (!sym) {
        LOGI("Symbol not in .dynsym, trying .symtab...");
        sym = memkit_xdl_dsym(handle, "il2cpp_init", NULL);
    }

    memkit_xdl_close(handle);
    return sym;
}

5. Hook Any Library Function (Not Just IL2CPP)

static int (*orig_libc_open)(const char* path, int flags) = NULL;
static void* open_hook_stub = NULL;

static int my_libc_open(const char* path, int flags) {
    LOGI("libc::open called: %s", path ? path : "(null)");
    return orig_libc_open(path, flags);
}

void hook_libc_open() {
    // Use XDL wrapper to resolve from libc
    void* handle = memkit_xdl_open("libc.so", XDL_DEFAULT);
    if (!handle) return;

    void* open_sym = memkit_xdl_sym(handle, "open", NULL);
    if (open_sym) {
        open_hook_stub = memkit_hook(
            (uintptr_t)open_sym,
            (void*)my_libc_open,
            (void**)&orig_libc_open
        );
    }

    memkit_xdl_close(handle);
}

6. Fast Batch Address Resolution

// Reuse context for multiple lookups (better performance)
void resolve_batch(void** addresses, 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(addresses[i], &info, ctx)) {
            // Process result
        }
    }
    
    memkit_xdl_addr_ctx_destroy(ctx);  // Important: clean up cache
}

7. Get Detailed Library Information

void print_library_details(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 Info: %s ===", info.name);
        LOGI("Base Address: 0x%016lx", info.base);
        LOGI("Size: %zu bytes (%.2f MB)", info.size, info.size / (1024.0 * 1024.0));
        if (info.path) {
            LOGI("Path: %s", info.path);
        }
    }

    memkit_xdl_close(handle);
}

8. Quick One-Shot Symbol Resolution

// Use macros for quick one-off lookups
void quick_resolve() {
    // Resolve without size
    void* open_sym = XDL_RESOLVE("libc.so", "open");
    LOGI("libc::open = %p", open_sym);

    // Resolve with size
    size_t size;
    void* malloc_sym = XDL_RESOLVE_SIZE("libc.so", "malloc", &size);
    LOGI("libc::malloc = %p (size: %zu)", malloc_sym, size);
}

These recipes are for security research and educational purposes only.