- Getting Started
- API Reference
- Memory Patching
- Function Hooking
- IL2CPP Instrumentation
- JIT Compiler API
- Error Handling
- Best Practices
#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__)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"__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);
}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);| 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 |
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:
/proc/self/maps— fast, works for normal.soloadingxdl_iterate_phdr— linker internals, finds libs loaded from APKs- 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.
| 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 |
| 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 |
| 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 |
| 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 |
| 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) |
| 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 |
| 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) |
| 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 |
| 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* |
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)
| 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 |
| 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);| 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 |
| 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 |
| 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 |
| Function | Description | Returns |
|---|---|---|
memkit_xdl_open_from_phdr(info) |
Create handle from dl_phdr_info |
void* or NULL |
| Macro | Description |
|---|---|
XDL_RESOLVE(lib, sym) |
One-shot symbol resolve |
XDL_RESOLVE_SIZE(lib, sym, &size) |
Resolve with size output |
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"); // .symtabDifference from XDL wrapper:
memkit_dlopenuses ShadowHook's internal loader (better linker bypass)memkit_xdl_openuses XDL library (more portable, standard discovery)- Both work for most use cases; prefer
memkit_dlopenfor hooking targets
// 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);// 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");
}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"
);// 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);
}uintptr_t func_addr = base + 0xABCD;
hook_stub = memkit_hook(
func_addr,
(void*)my_function,
(void**)&orig_function
);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
);
}// When done, unhook to restore original behavior
memkit_unhook(hook_stub);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();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
);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
);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);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
);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
);// 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
);// 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");
}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);| 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) |
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-enableRegister 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);// 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);// 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);
}// 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();// Some symbols are only in .symtab
void* internal_func = memkit_il2cpp_resolve_symtab("_ZN6Player13InternalInitEv");
if (internal_func) {
LOGI("Found internal function: %p", internal_func);
}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
);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:
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).
| 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 | 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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.
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-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);// 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);// 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);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 automaticallyInclude #include "memkit.h" — it pulls in memkit_jit.h and sljitLir.h automatically.
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();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_DUPvoid* 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");
}
}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.
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;
}Always free patches when done:
MemPatch* patch = memkit_patch_create(...);
memkit_patch_apply(patch);
// ... later ...
memkit_patch_restore(patch);
memkit_patch_free(patch);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;
}// Always check for NULL before using
if (orig_function != NULL) {
orig_function(param);
}
// Check patch before applying
if (patch && memkit_patch_apply(patch)) {
// Success
}Unless you need multiple hooks on same function:
// Recommended for most cases
memkit_hook_init(SHADOWHOOK_MODE_UNIQUE, false);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__)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);
}// 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");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);
}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);
}// 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);
}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;
}- See RECIPES.md for common patterns
- See SECURITY_RESEARCH.md for legitimate use cases