### Example Usage of Callbacks Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Demonstrates how to register and handle callbacks for ELF initialization and finalization using ShadowHook. ```APIDOC ## Example Usage ### Description This example shows how to register a `pre` callback for ELF initialization and a `post` callback for ELF finalization, including error handling. ### Code ```C #include "shadowhook.h" static void dl_init_pre(struct dl_phdr_info *info, size_t size, void *data) { (void)size, (void)data; LOG("dl_init_pre. load_bias %" PRIxPTR ", %s\n", (uintptr_t)info->dlpi_addr, info->dlpi_name); } static void dl_fini_post(struct dl_phdr_info *info, size_t size, void *data) { (void)size, (void)data; LOG("dl_fini_post. load_bias %" PRIxPTR ", %s\n", (uintptr_t)info->dlpi_addr, info->dlpi_name); } void register_callback(void) { if (0 != shadowhook_register_dl_init_callback(dl_init_pre, NULL, NULL)) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("register dl_init callback. failed: %d - %s\n", error_num, error_msg); } if (0 != shadowhook_register_dl_fini_callback(NULL, dl_fini_post, NULL)) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("register dl_fini callback. failed: %d - %s\n", error_num, error_msg); } } ``` ``` -------------------------------- ### Gradle Setup for ShadowHook Source: https://context7.com/bytedance/android-inline-hook/llms.txt Add the ShadowHook dependency and enable Prefab in your project's build files. ```gradle // build.gradle (project level) allprojects { repositories { mavenCentral() } } // build.gradle (app/library module) android { buildFeatures { prefab true } defaultConfig { ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' } } } dependencies { implementation 'com.bytedance.android:shadowhook:2.0.0' } ``` -------------------------------- ### Unhooking Example Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.zh-CN.md Demonstrates how to unhook a previously established hook using the `shadowhook_unhook` function. It checks the return value for success or failure and logs any errors encountered. ```C void *stub; void do_unhook() { int result = shadowhook_unhook(stub); if (result != 0) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("unhook failed: %d - %s", error_num, error_msg); } } ``` -------------------------------- ### Hooking Example with Callback Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.zh-CN.md Shows how to hook a function using `shadowhook_hook_sym_name_callback`, providing a custom callback function (`my_hooked_callback`) that is executed after the hook is invoked. It also includes error handling for hook failures or pending states. ```C void *orig; void *stub; typedef void my_hooked_callback(int error_number, const char *lib_name, const char *sym_name, void *sym_addr, void *new_addr, void *orig_addr, void *arg) { const char *error_msg = shadowhook_to_errmsg(error_number); LOG("hook finished: %s, %s, %d - %s", lib_name, sym_name, error_number, error_msg); } void do_hook(void) { stub = shadowhook_hook_sym_name_callback("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", my_proxy, &orig, my_hooked_callback, NULL); int error_num = shadowhook_get_errno(); if(stub == NULL) { const char *error_msg = shadowhook_to_errmsg(error_num); LOG("hook failed: %p, %d - %s", stub, error_num, error_msg); } else if (error_num == 1) { LOG("hook pending"); } } ``` -------------------------------- ### Hooking Example with Specific Mode Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.zh-CN.md Demonstrates how to hook a function using `shadowhook_hook_sym_name_2` with a specified mode (SHADOWHOOK_HOOK_WITH_MULTI_MODE). It checks for hook success, pending status, or failure, logging relevant error messages. ```C void *orig; void *stub; void do_hook() { stub = shadowhook_hook_sym_name_2("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", my_proxy, &orig, SHADOWHOOK_HOOK_WITH_MULTI_MODE); int error_num = shadowhook_get_errno(); if(stub == NULL) { const char *error_msg = shadowhook_to_errmsg(error_num); LOG("hook failed: %p, %d - %s", stub, error_num, error_msg); } else if (error_num == 1) { LOG("hook pending"); } } ``` -------------------------------- ### Intercepting Instructions via Address Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md This example demonstrates how to intercept a specific instruction address within a function, with platform-specific logic for ARM and ARM64. ```APIDOC ## Intercepting Instructions via Address This example demonstrates how to intercept a specific instruction address within a function, with platform-specific logic for ARM and ARM64. ### ARM Example ```c void *stub; void my_interceptor_arm(shadowhook_cpu_context_t *cpu_context, void *data) { if (cpu_context->regs[5] == 0) { // When r5 equals 0, modify the values of r6 and r7 cpu_context->regs[6] = 1; cpu_context->regs[7] = 10; LOG("arm: found r5 == 0, data = %p", data); } } void do_intercept(void) { // Target address is at offset+8 in the read function, which is a thumb instruction, ensure the address value is odd void *instr_addr = (read + 8) & 0x1; stub = shadowhook_intercept_instr_addr(instr_addr, my_interceptor_arm, 0x123, SHADOWHOOK_INTERCEPT_RECORD, "libc.so", "read+8"); if(stub == NULL) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("intercept failed: %p, %d - %s", stub, error_num, error_msg); } } ``` ### ARM64 Example ```c void *stub; void my_interceptor_arm64(shadowhook_cpu_context_t *cpu_context, void *data) { if (cpu_context->regs[19] == 0) { // When x19 equals 0, modify the values of x20 and x21 cpu_context->regs[20] = 1; cpu_context->regs[21] = 1000; LOG("arm64: found x19 == 0, data = %p", data); } } void do_intercept(void) { // Target address is at offset 20 in the read function, since arm64 has fixed 4-byte instructions, this is equivalent to adding the interceptor between the "5th instruction" and "6th instruction" of the read function. After the interceptor executes, the 6th instruction will be executed void *instr_addr = read + 20; stub = shadowhook_intercept_instr_addr(instr_addr, my_interceptor_arm64, 0x123, SHADOWHOOK_INTERCEPT_RECORD, "libc.so", "read+20"); if(stub == NULL) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("intercept failed: %p, %d - %s", stub, error_num, error_msg); } } ``` ``` -------------------------------- ### Basic Inline Hooking Example Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Demonstrates how to hook the `malloc` function to intercept calls. Use `shadowhook_hook_sym_addr_2` to set up the hook and `shadowhook_unhook` to remove it. The `orig` global variable stores the original function pointer. ```C void *orig; void *stub; typedef void *(*malloc_t)(size_t); void *my_malloc(size_t sz) { if(sz > 1024) return nullptr; // Call the original function return ((malloc_t)orig)(sz); } void do_hook(void) { // orig_addr cannot be NULL stub = shadowhook_hook_sym_addr_2(malloc, my_malloc, &orig, SHADOWHOOK_HOOK_WITH_MULTI_MODE); } void do_unhook(void) { shadowhook_unhook(stub); stub = NULL; } ``` -------------------------------- ### Intercept at Instruction Address (AArch64) Source: https://context7.com/bytedance/android-inline-hook/llms.txt Installs a callback at a specific instruction address, allowing modification of CPU registers. Requires SHADOWHOOK_INTERCEPT_WITH_FPSIMD_READ_WRITE for SIMD register access. The callback receives a shadowhook_cpu_context_t. ```c #include "shadowhook.h" static void *stub_intercept = NULL; #if defined(__aarch64__) void my_interceptor(shadowhook_cpu_context_t *ctx, void *data) { // Read x19; if zero, patch x20 and x21 if (ctx->regs[19] == 0) { ctx->regs[20] = 1; ctx->regs[21] = 1000; __android_log_print(ANDROID_LOG_DEBUG, "TAG", "interceptor: x19==0, patching x20/x21"); } // Read/write a SIMD register (requires FPSIMD_READ_WRITE flag) if (ctx->vregs[0].q == 0) { ctx->vregs[0].q = 1; } } void do_intercept(void) { void *handle = shadowhook_dlopen("libart.so"); void *sym_addr = shadowhook_dlsym(handle, "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc"); shadowhook_dlclose(handle); if (sym_addr == NULL) { __android_log_print(ANDROID_LOG_ERROR, "TAG", "symbol not found"); return; } // Intercept at offset +20 (5th arm64 instruction), with SIMD read+write enabled void *instr_addr = (void *)((uintptr_t)sym_addr + 20); stub_intercept = shadowhook_intercept_instr_addr( instr_addr, my_interceptor, NULL, // data passed to interceptor SHADOWHOOK_INTERCEPT_WITH_FPSIMD_READ_WRITE | SHADOWHOOK_INTERCEPT_RECORD, "libart.so", "ArtMethod::Invoke+20"); if (stub_intercept == NULL) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "intercept failed: %d - %s", err, shadowhook_to_errmsg(err)); } } void do_unintercept(void) { int ret = shadowhook_unintercept(stub_intercept); if (ret != 0) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "unintercept failed: %d - %s", err, shadowhook_to_errmsg(err)); } stub_intercept = NULL; } #endif ``` -------------------------------- ### Control Proxy Reentrance with ShadowHook Macros Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md This C++ example demonstrates how to control reentrancy in proxy functions using `SHADOWHOOK_ALLOW_REENTRANT()` and `SHADOWHOOK_DISALLOW_REENTRANT()`. This is useful when the original function might call back into the proxy function, preventing deadlocks. ```C++ void *stub; int test_func_1_proxy(int a, int b) { // Execute stack cleanup (cannot be omitted), only need to call once SHADOWHOOK_STACK_SCOPE(); // Add some business logic if(a > 1024 || b > 1024) return -1; // About to call the original function, we want each call to test_func_1 to go through our proxy function SHADOWHOOK_ALLOW_REENTRANT(); // Call the original function int result = SHADOWHOOK_CALL_PREV(test_func_1_proxy, sz); // Restore shadowhook's "prevent proxy function reentrance" protection feature SHADOWHOOK_DISALLOW_REENTRANT(); // Continue adding business logic write_log(global_log_fd, "test_func_1 called with a=%d, b=%d", a, b); return result; } void do_hook(void) { stub = shadowhook_hook_sym_addr_2(test_func_1, test_func_1_proxy, NULL, SHADOWHOOK_HOOK_WITH_SHARED_MODE); } void do_unhook(void) { shadowhook_unhook(stub); stub = NULL; } ``` -------------------------------- ### Get Java Error Message Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Use `toErrmsg()` to retrieve the error message string for a given errno after `ShadowHook.init()` fails. ```Java package com.bytedance.shadowhook; public class ShadowHook public static String toErrmsg(int errno) ``` -------------------------------- ### Hook Function by Symbol Address (with Flags and Recording) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Hooks a function using its symbol address with specified flags, including multi-mode and recording. This example demonstrates dynamic symbol lookup and error handling. The `record_lib_name` and `record_sym_name` are used when `SHADOWHOOK_HOOK_RECORD` is set. ```c void *orig; void *stub; void do_hook() { void *handle = shadowhook_dlopen("libart.so"); void *sym_addr = shadowhook_dlsym(handle, "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc") shadowhook_dlclose(handle); if (sym_addr == NULL) { LOG("symbol not found"); return; } stub = shadowhook_hook_sym_addr_2(sym_addr, my_proxy, &orig, SHADOWHOOK_HOOK_WITH_MULTI_MODE | SHADOWHOOK_HOOK_RECORD, "libart.so", "ArtMethod::Invoke"); if(stub == NULL) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("hook failed: %d - %s", error_num, error_msg); } } ``` -------------------------------- ### Intercepting Function Address with Shadowhook Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md This example demonstrates how to intercept a function using its absolute address. It defines a custom interceptor function and uses `shadowhook_intercept_func_addr` to hook the target function, providing library and symbol names for recording purposes. ```c #include "shadowhook.h" void *shadowhook_intercept_func_addr(void *func_addr, shadowhook_interceptor_t pre, void *data, uint32_t flags, ... /* char *record_lib_name, char *record_sym_name */); void *stub; #if defined(__aarch64__) void my_interceptor_arm64(shadowhook_cpu_context_t *cpu_context, void *data) { if (cpu_context->regs[19] == 0) { // When x19 equals 0, modify the values of x20 and x21 cpu_context->regs[20] = 1; cpu_context->regs[21] = 1000; LOG("arm64: found x19 == 0, data = %p", data); } } void do_intercept(void) { void *func_addr = get_hidden_func_addr(); stub = shadowhook_intercept_func_addr(func_addr, my_interceptor_arm64, 0x123, SHADOWHOOK_INTERCEPT_RECORD, "libmy.so", "my_hidden_func"); if(stub == NULL) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("intercept failed: %p, %d - %s", stub, error_num, error_msg); } } #endif ``` -------------------------------- ### Proxy Function in Unique Mode with ShadowHook Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Example of a proxy function for `malloc` in unique mode. It checks the size and calls the original `malloc` via the `orig` pointer. Ensure `orig` is a global or static variable. ```C void *orig; //global variable void *stub; typedef void *(*malloc_t)(size_t); void *my_malloc(size_t sz) { if(sz > 1024) return nullptr; // Call the original function return ((malloc_t)orig)(sz); } void do_hook(void) { stub = shadowhook_hook_sym_addr_2(malloc, my_malloc, &orig, SHADOWHOOK_HOOK_WITH_UNIQUE_MODE); } void do_unhook(void) { shadowhook_unhook(stub); } ``` -------------------------------- ### Intercept Symbol by Name with Callback (ARM64) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Intercepts a function by its symbol name and registers a callback function to be executed after the original function. This example also targets ARM64 and demonstrates handling interception errors. The callback receives detailed information about the hook's completion. ```C void *stub; typedef void my_intercepted_callback(int error_number, const char *lib_name, const char *sym_name, void *sym_addr, shadowhook_interceptor_t pre, void *data, void *arg) { const char *error_msg = shadowhook_to_errmsg(error_number); LOG("hook finished: %s, %s, %d - %s", lib_name, sym_name, error_number, error_msg); } #if defined(__aarch64__) void my_interceptor_arm64(shadowhook_cpu_context_t *cpu_context, void *data) { if (cpu_context->regs[19] == 0) { // When x19 equals 0, modify the values of x20 and x21 cpu_context->regs[20] = 1; cpu_context->regs[21] = 1000; LOG("arm64: found x19 == 0, data = %p", data); } } void do_intercept(void) { stub = shadowhook_intercept_sym_name_callback("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", my_interceptor_arm64, 0x123, SHADOWHOOK_INTERCEPT_DEFAULT, my_intercepted_callback, NULL); int error_num = shadowhook_get_errno(); if(stub == NULL) { const char *error_msg = shadowhook_to_errmsg(error_num); LOG("intercept failed: %p, %d - %s", stub, error_num, error_msg); } else if (error_num == 1) { LOG("intercept pending"); } } #endif ``` -------------------------------- ### Get Native Error Message Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Use `shadowhook_to_errmsg()` to get the error message for an error number returned by native APIs. ```C #include "shadowhook.h" int shadowhook_get_errno(void); const char *shadowhook_to_errmsg(int error_number); ``` -------------------------------- ### Configure Packaging Options to Pick First Shadowhook SO Files Source: https://github.com/bytedance/android-inline-hook/blob/main/README.md When using Shadowhook in an APP project, use 'pickFirst' to resolve conflicts if duplicate .so files are present. Be aware this might lead to using an incorrect version. ```gradle android { packagingOptions { pickFirst '**/libshadowhook.so' pickFirst '**/libshadowhook_nothing.so' } } ``` -------------------------------- ### ShadowHook Mode and Debugging Source: https://github.com/bytedance/android-inline-hook/blob/main/shadowhook/src/main/cpp/shadowhook.map.txt Functions to get and set the debuggable status of ShadowHook. ```APIDOC ## shadowhook_get_debuggable ### Description Checks if ShadowHook is currently in debuggable mode. ### Method `bool shadowhook_get_debuggable()` ## shadowhook_set_debuggable ### Description Sets the debuggable mode for ShadowHook. ### Method `void shadowhook_set_debuggable(bool debuggable)` ``` -------------------------------- ### Get Shadowhook Disable Status (Java API) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Retrieves the current disable status of Shadowhook. ```Java public static boolean getDisable() ``` -------------------------------- ### Initialize Shadowhook with Default Parameters (Java) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Use this method to initialize Shadowhook with default configuration settings. No explicit parameters are required. ```Java import com.bytedance.shadowhook.ShadowHook; public class MySdk { public static void init() { ShadowHook.init(); } } ``` -------------------------------- ### Registering ELF Initialization and Finalization Callbacks Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md This C code demonstrates how to register pre-initialization and post-finalization callbacks for ELF loading and unloading. It includes error handling by checking return values and logging errors using shadowhook's error reporting functions. ```c #include "shadowhook.h" static void dl_init_pre(struct dl_phdr_info *info, size_t size, void *data) { (void)size, (void)data; LOG("dl_init_pre. load_bias %" PRIxPTR ", %s", (uintptr_t)info->dlpi_addr, info->dlpi_name); } static void dl_fini_post(struct dl_phdr_info *info, size_t size, void *data) { (void)size, (void)data; LOG("dl_fini_post. load_bias %" PRIxPTR ", %s", (uintptr_t)info->dlpi_addr, info->dlpi_name); } void register_callback(void) { if (0 != shadowhook_register_dl_init_callback(dl_init_pre, NULL, NULL)) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("register dl_init callback. failed: %d - %s", error_num, error_msg); } if (0 != shadowhook_register_dl_fini_callback(NULL, dl_fini_post, NULL)) { int error_num = shadowhook_get_errno(); const char *error_msg = shadowhook_to_errmsg(error_num); LOG("register dl_fini callback. failed: %d - %s", error_num, error_msg); } } ``` -------------------------------- ### Get Shadowhook Disable Status (Native API) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Retrieves the current disable status of Shadowhook using the native interface. ```C bool shadowhook_get_disable(void); ``` -------------------------------- ### Intercept at Instruction Address Source: https://context7.com/bytedance/android-inline-hook/llms.txt Details on using `shadowhook_intercept_instr_addr` to install a callback at a specific instruction address within a function, allowing modification of CPU context registers. ```APIDOC ## `shadowhook_intercept_instr_addr` — Intercept at an Instruction Address Installs a callback at a specific instruction address (can be mid-function). The callback receives a `shadowhook_cpu_context_t` with all register values, which can be read and modified (except SP and PC). Unlike hook, intercept always executes all registered interceptors—there is no chain-call concept. ```c #include "shadowhook.h" static void *stub_intercept = NULL; #if defined(__aarch64__) void my_interceptor(shadowhook_cpu_context_t *ctx, void *data) { // Read x19; if zero, patch x20 and x21 if (ctx->regs[19] == 0) { ctx->regs[20] = 1; ctx->regs[21] = 1000; __android_log_print(ANDROID_LOG_DEBUG, "TAG", "interceptor: x19==0, patching x20/x21"); } // Read/write a SIMD register (requires FPSIMD_READ_WRITE flag) if (ctx->vregs[0].q == 0) { ctx->vregs[0].q = 1; } } void do_intercept(void) { void *handle = shadowhook_dlopen("libart.so"); void *sym_addr = shadowhook_dlsym(handle, "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc"); shadowhook_dlclose(handle); if (sym_addr == NULL) { __android_log_print(ANDROID_LOG_ERROR, "TAG", "symbol not found"); return; } // Intercept at offset +20 (5th arm64 instruction), with SIMD read+write enabled void *instr_addr = (void *)((uintptr_t)sym_addr + 20); stub_intercept = shadowhook_intercept_instr_addr( instr_addr, my_interceptor, NULL, // data passed to interceptor SHADOWHOOK_INTERCEPT_WITH_FPSIMD_READ_WRITE | SHADOWHOOK_INTERCEPT_RECORD, "libart.so", "ArtMethod::Invoke+20"); if (stub_intercept == NULL) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "intercept failed: %d - %s", err, shadowhook_to_errmsg(err)); } } void do_unintercept(void) { int ret = shadowhook_unintercept(stub_intercept); if (ret != 0) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "unintercept failed: %d - %s", err, shadowhook_to_errmsg(err)); } stub_intercept = NULL; } #endif ``` ``` -------------------------------- ### Hook and Unhook Functions with ShadowHook Source: https://github.com/bytedance/android-inline-hook/blob/main/README.md Demonstrates how to hook a function by its symbol name and how to unhook it. The proxy function must match the original function's signature. Error handling for hook and unhook operations is included. ```C void *orig = NULL; void *stub = NULL; // Type definition of the hooked function typedef void (*artmethod_invoke_func_type_t)(void *, void *, uint32_t *, uint32_t, void *, const char *); // Proxy function void artmethod_invoke_proxy(void *thiz, void *thread, uint32_t *args, uint32_t args_size, void *result, const char *shorty) { // do something ((artmethod_invoke_func_type_t)orig)(thiz, thread, args, args_size, result, shorty); // do something } void do_hook() { stub = shadowhook_hook_sym_name( "libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", (void *)artmethod_invoke_proxy, (void **)&orig); if(stub == NULL) { int err_num = shadowhook_get_errno(); const char *err_msg = shadowhook_to_errmsg(err_num); LOG("hook error %d - %s", err_num, err_msg); } } void do_unhook() { int result = shadowhook_unhook(stub); if(result != 0) { int err_num = shadowhook_get_errno(); const char *err_msg = shadowhook_to_errmsg(err_num); LOG("unhook error %d - %s", err_num, err_msg); } } ``` -------------------------------- ### Register Linker Load/Unload Callbacks Source: https://context7.com/bytedance/android-inline-hook/llms.txt Use shadowhook_register_dl_init_callback and shadowhook_register_dl_fini_callback to execute code before or after an ELF's .init/.fini sections. Available on Android 5.0+. ```c #include "shadowhook.h" #include static void on_lib_loading(struct dl_phdr_info *info, size_t size, void *data) { // Called BEFORE .init_array runs — ideal time for pre-init hooks __android_log_print(ANDROID_LOG_DEBUG, "TAG", "loading: %s (load_bias=0x%" PRIxPTR ")", info->dlpi_name, (uintptr_t)info->dlpi_addr); // Example: hook a symbol in this ELF before its constructors run if (strstr(info->dlpi_name, "libmy_target.so")) { // perform hooks on the just-mapped ELF } } static void on_lib_unloaded(struct dl_phdr_info *info, size_t size, void *data) { __android_log_print(ANDROID_LOG_DEBUG, "TAG", "unloaded: %s", info->dlpi_name); } void register_linker_callbacks(void) { // pre=on_lib_loading fires BEFORE .init_array; post=NULL if (0 != shadowhook_register_dl_init_callback(on_lib_loading, NULL, NULL)) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "register init callback failed: %d - %s", err, shadowhook_to_errmsg(err)); } // pre=NULL; post=on_lib_unloaded fires AFTER .fini_array if (0 != shadowhook_register_dl_fini_callback(NULL, on_lib_unloaded, NULL)) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "register fini callback failed: %d - %s", err, shadowhook_to_errmsg(err)); } } void unregister_linker_callbacks(void) { shadowhook_unregister_dl_init_callback(on_lib_loading, NULL, NULL); shadowhook_unregister_dl_fini_callback(NULL, on_lib_unloaded, NULL); } ``` -------------------------------- ### Remove Hook using shadowhook_unhook Source: https://context7.com/bytedance/android-inline-hook/llms.txt Removes a previously installed hook using the stub returned by the hook API. This function is thread-safe. Ensure `stub_malloc` is not NULL before calling. ```c #include "shadowhook.h" extern void *stub_malloc; // saved stub from hook call void do_unhook(void) { if (stub_malloc == NULL) return; int ret = shadowhook_unhook(stub_malloc); if (ret != 0) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "unhook failed: %d - %s", err, shadowhook_to_errmsg(err)); } else { stub_malloc = NULL; // safe to NULL only in non-multi mode } } ``` -------------------------------- ### ShadowHook Initialization and Version Source: https://github.com/bytedance/android-inline-hook/blob/main/shadowhook/src/main/cpp/shadowhook.map.txt Functions for initializing ShadowHook and retrieving its version information. ```APIDOC ## shadowhook_init ### Description Initializes the ShadowHook library. ### Method `void shadowhook_init()` ## shadowhook_get_version ### Description Retrieves the current version of the ShadowHook library. ### Method `int shadowhook_get_version()` ``` -------------------------------- ### Hook malloc in C++ with ShadowHook Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md This C++ code provides a similar `malloc` hook to the C example, using `SHADOWHOOK_STACK_SCOPE()` for stack cleanup. It returns `nullptr` for sizes greater than 1024. ```C++ void *orig; void *stub; typedef void *(*malloc_t)(size_t); void * malloc_proxy(size_t sz) { // Execute stack cleanup (cannot be omitted), only need to call once SHADOWHOOK_STACK_SCOPE(); if(sz > 1024) return nullptr; // Call the original function return SHADOWHOOK_CALL_PREV(malloc_proxy, sz); } void do_hook(void) { stub = shadowhook_hook_sym_addr_2(malloc, malloc_proxy, &orig, SHADOWHOOK_HOOK_WITH_SHARED_MODE); } void do_unhook(void) { shadowhook_unhook(stub); stub = NULL; } void *my_malloc_4k(void) { // In some scenarios, you might need to directly call the original function return ((malloc_t)orig)(4096); } ``` -------------------------------- ### Initialize Shadowhook with Custom Config (Java) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Initialize Shadowhook by providing a custom configuration object. This allows fine-grained control over mode, debugging, and recording. ```Java import com.bytedance.shadowhook.ShadowHook; public class MySdk { public static void init() { ShadowHook.init(new ShadowHook.ConfigBuilder() .setMode(ShadowHook.Mode.SHARED) .setDebuggable(false) .setRecordable(false) .setDisable(false) .build()); } } ``` -------------------------------- ### Initialize Shadowhook (Native API) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Initialize Shadowhook using the native C API. This function accepts the desired hook mode and a flag to enable debug logs. ```C #include "shadowhook.h" typedef enum { SHADOWHOOK_MODE_SHARED = 0, SHADOWHOOK_MODE_UNIQUE = 1 } shadowhook_mode_t; int shadowhook_init(shadowhook_mode_t mode, bool debuggable); ``` -------------------------------- ### CMake Integration for ShadowHook Source: https://context7.com/bytedance/android-inline-hook/llms.txt Link the ShadowHook library in your CMakeLists.txt file. ```cmake # CMakeLists.txt find_package(shadowhook REQUIRED CONFIG) add_library(mylib SHARED mylib.c) target_link_libraries(mylib shadowhook::shadowhook) ``` -------------------------------- ### Proxy Function Requirements Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.zh-CN.md Guidelines for creating proxy functions that correctly interact with the original functions at the CPU instruction level. ```APIDOC ## Proxy Function Requirements Guidelines for creating proxy functions that correctly interact with the original functions at the CPU instruction level. > [!IMPORTANT] > - The compiled product of a proxy function directly replaces the original function during runtime. Therefore, the 'proxy function' and the 'original function' must have the same way of 'receiving parameters' and 'passing return values' at the CPU instruction level during runtime. > - The simplest way to achieve this is to define the proxy function with the same type (number of parameters, parameter types, return value types) as the original function. However, this is not absolute. Sometimes, the parameter type or return value type of the original function is a complex C++ Class. If the proxy function does not need to read or write the data members within this Class instance, the handling can be simplified. Specific guidance can be found by referring to AAPCS64 and AAPCS32. If you are unsure or encounter problems, you can also decompile the original function and the proxy function to observe the differences in parameter and return value handling at the instruction level. > - Regardless of the hook mode, the hook API will return the address of the original function through the `void **orig_addr` parameter. ### Proxy Functions in Unique Mode > [!IMPORTANT] > - When you need to call the original function, always use `orig_addr` to do so. > - If you do not need to call the original function, you can pass `NULL` for the `void **orig_addr` parameter during hooking. > [!NOTE] > **Why can't the original function be called directly by its name?** > > Considering the call relationship diagram between hook and intercept from before: because inline hook modifies the header instructions of the original function (instruction 1) to jump to the proxy function address (branch to). If the proxy function continues to call the original function by its name, it will lead to recursive execution of the proxy function. After the hook is completed, `orig_addr` actually points to a trampoline. ``` -------------------------------- ### `shadowhook_unhook` — Remove a Hook Source: https://context7.com/bytedance/android-inline-hook/llms.txt Removes a previously installed hook using the stub returned by the hook API. Thread-safe. In multi mode, do not set `orig_addr` to NULL after unhooking due to potential concurrent access. ```APIDOC ## `shadowhook_unhook` — Remove a Hook Removes a previously installed hook using the stub returned by the hook API. Thread-safe. In multi mode, do not set `orig_addr` to NULL after unhooking due to potential concurrent access. ```c #include "shadowhook.h" extern void *stub_malloc; // saved stub from hook call void do_unhook(void) { if (stub_malloc == NULL) return; int ret = shadowhook_unhook(stub_malloc); if (ret != 0) { int err = shadowhook_get_errno(); __android_log_print(ANDROID_LOG_ERROR, "TAG", "unhook failed: %d - %s", err, shadowhook_to_errmsg(err)); } else { stub_malloc = NULL; // safe to NULL only in non-multi mode } } ``` ``` -------------------------------- ### Native API Initialization Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Initialize ShadowHook using the Native API. This function takes the hook mode and debuggable flag as parameters. ```APIDOC ## Native API Initialization ### Description Initializes the ShadowHook library using the native C API. ### Function Signature ```C int shadowhook_init(shadowhook_mode_t mode, bool debuggable); ``` ### Parameters - `mode` (shadowhook_mode_t): Default hook mode. Possible values are `SHADOWHOOK_MODE_SHARED` (0) and `SHADOWHOOK_MODE_UNIQUE` (1). - `debuggable` (bool): Whether to enable debug logs. ### Return Value - `0`: Success - Non-`0`: Failure (non-`0` value is an error code) ``` -------------------------------- ### Initialize ShadowHook in Unique Mode Source: https://github.com/bytedance/android-inline-hook/blob/main/README.md Initializes ShadowHook with the configuration builder. The unique mode is recommended for initial testing. ```Java import com.bytedance.shadowhook.ShadowHook; public class MySdk { public void init() { ShadowHook.init(new ShadowHook.ConfigBuilder() .setMode(ShadowHook.Mode.UNIQUE) .build()); } } ``` -------------------------------- ### Intercept Symbol by Name (ARM64) Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Intercepts a function by its symbol name in the specified library. This example targets ARM64 architecture and modifies register values when a specific condition is met. Ensure the library and symbol name are correct for your target. ```C void *stub; #if defined(__aarch64__) void my_interceptor_arm64(shadowhook_cpu_context_t *cpu_context, void *data) { if (cpu_context->regs[19] == 0) { // When x19 equals 0, modify the values of x20 and x21 cpu_context->regs[20] = 1; cpu_context->regs[21] = 1000; LOG("arm64: found x19 == 0, data = %p", data); } } void do_intercept(void) { stub = shadowhook_intercept_sym_name("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", my_interceptor_arm64, 0x123, SHADOWHOOK_INTERCEPT_DEFAULT); int error_num = shadowhook_get_errno(); if(stub == NULL) { const char *error_msg = shadowhook_to_errmsg(error_num); LOG("intercept failed: %p, %d - %s", stub, error_num, error_msg); } else if (error_num == 1) { LOG("intercept pending"); } } #endif ``` -------------------------------- ### Java API Initialization Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Initialize ShadowHook using the Java API. You can use the default configuration or provide a custom Config object. ```APIDOC ## Java API Initialization ### Description Initializes the ShadowHook library with default or custom configuration parameters. ### Methods - `public static int init()`: Initializes with default parameters. - `public static int init(Config config)`: Initializes with a custom configuration. ### Parameters - `config` (Config): Configuration parameter set for initialization. ### Return Value - `0`: Success - Non-`0`: Failure (non-`0` value is an error code) ### Examples **Using default initialization parameters:** ```Java import com.bytedance.shadowhook.ShadowHook; public class MySdk { public static void init() { ShadowHook.init(); } } ``` **Specifying more options via `Config` parameter:** ```Java import com.bytedance.shadowhook.ShadowHook; public class MySdk { public static void init() { ShadowHook.init(new ShadowHook.ConfigBuilder() .setMode(ShadowHook.Mode.SHARED) .setDebuggable(false) .setRecordable(false) .setDisable(false) .build()); } } ``` ``` -------------------------------- ### Symbol Lookup Overview Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md Explains the process and considerations for using symbol names to locate inline hook positions and directly call functions. ```APIDOC ## Symbols ### Overview Using symbol names to locate inline hook positions is a common method with good compatibility benefits. After obtaining a function's address through its symbol name, you can also directly call this function. When looking up symbol addresses by symbol name, please note: - C++ function symbol names refer to strings after C++ name mangling (which includes parameter type information), while C function symbol names are just the function names themselves. - The same C++ system function name might have different function types (parameters, return values) in different AOSP versions, resulting in different symbol names. When hooking, be careful to define symbol names separately for different AOSP versions. - C++ functions with the same function type may have different symbol names in 32-bit and 64-bit ELFs because, for example, `size_t` is 4 bytes in 32-bit and 8 bytes in 64-bit, and parameter types are part of C++ function symbol names. When hooking, be careful to define symbol names separately for 32-bit and 64-bit. - Symbol names displayed by tools may have suffixes. For example, `readelf -s libc.so | grep memcpy` might show `memcpy@@LIBC`, where the symbol name is `memcpy`. - LLVM may add one or more random hash value suffixes to Local symbols (debug symbols) in `.symtab` during operations like LTO. These suffixes start with `.` or `$`. From the ELF perspective, these suffixes are part of the symbol name, such as: `_ZN3artL12IsStringInitERKNS_11InstructionEPNS_9ArtMethodE.__uniq.113072803961313432437294106468273961305` in libart.so, but from the C++ function name perspective, its symbol is `_ZN3artL12IsStringInitERKNS_11InstructionEPNS_9ArtMethodE`, which demangled is `art::IsStringInit(art::Instruction const&, art::ArtMethod*)`. When using shadowhook to find symbol addresses or hook, you only need to pass `_ZN3artL12IsStringInitERKNS_11InstructionEPNS_9ArtMethodE`; shadowhook supports ignoring random hash value suffixes added by LLVM when finding symbol addresses. - Some ELFs have special symbol names, such as linker, which adds the `__dl_` prefix to all symbols. For example, the symbol name `__dl___loader_cfi_fail` in the linker ELF corresponds to the `__loader_cfi_fail` function in the source code. When using shadowhook to look up symbols, you need to pass `__dl___loader_cfi_fail`. - In different vendor devices with the same AOSP version, due to vendor modifications of compilation parameters or functions, functions that were not originally inlined might now be inlined in some system libraries, causing their symbols to disappear from `.symtab` in the ELF product. In such cases, compatibility handling is needed. ``` -------------------------------- ### Shadowhook ELF Load/Unload API Declarations Source: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md These C functions allow registering and unregistering callbacks for ELF initialization (.init + .init_array) and finalization (.fini + .fini_array) phases. Callbacks receive ELF information similar to dl_iterate_phdr. Ensure pre and post callbacks are not both NULL. ```c #include "shadowhook.h" // Callback function definition typedef void (*shadowhook_dl_info_t)(struct dl_phdr_info *info, size_t size, void *data); // Register and unregister .init + .init_array callbacks int shadowhook_register_dl_init_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data); int shadowhook_unregister_dl_init_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data); // Register and unregister .fini + .fini_array callbacks int shadowhook_register_dl_fini_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data); int shadowhook_unregister_dl_fini_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data); ``` -------------------------------- ### Intercept and Unintercept Instructions (aarch64) Source: https://github.com/bytedance/android-inline-hook/blob/main/README.md Use this code to intercept a specific instruction address and later unintercept it. The interceptor function can read and modify register values. Note that `instr_addr` is a simplified example; real-world usage may require more sophisticated address determination. ```c void *stub; #if defined(__aarch64__) void artmethod_invoke_interceptor(shadowhook_cpu_context_t *ctx, void *data) { // When x19 equals 0, modify the values of x20 and x21 if (ctx->regs[19] == 0) { ctx->regs[20] = 1; ctx->regs[21] = 1000; LOG("interceptor: found x19 == 0"); } // When q0 equals 0, modify the values of q0, q1, q2, q3 if (ctx->vregs[0].q == 0) { ctx->vregs[0].q = 1; ctx->vregs[1].q = 0; ctx->vregs[2].q = 0; ctx->vregs[3].q = 0; LOG("interceptor: found q0 == 0"); } } void do_intercept(void) { // Query the address of art::ArtMethod::Invoke void *handle = shadowhook_dlopen("libart.so"); if (handle == NULL) { LOG("handle not found"); return; } void *sym_addr = shadowhook_dlsym(handle, "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc"); shadowhook_dlclose(handle); if (sym_addr == NULL) { LOG("symbol not found"); return; } // Locate the address of a certain instruction in art::ArtMethod::Invoke void *instr_addr = (void *)((uintptr_t)sym_addr + 20); stub = shadowhook_intercept_instr_addr( instr_addr, artmethod_invoke_interceptor, NULL, SHADOWHOOK_INTERCEPT_WITH_FPSIMD_READ_WRITE); if(stub == NULL) { int err_num = shadowhook_get_errno(); const char *err_msg = shadowhook_to_errmsg(err_num); LOG("intercept failed: %d - %s", err_num, err_msg); } } void do_unintercept() { int result = shadowhook_unintercept(stub); if (result != 0) { int err_num = shadowhook_get_errno(); const char *err_msg = shadowhook_to_errmsg(err_num); LOG("unintercept failed: %d - %s", err_num, err_msg); } } #endif ```