Hook an Android Native Function to Always Return True with ShadowHook
This guide demonstrates how to locate a target .so library’s base address on Android, compute the offset of a native method, and use the ShadowHook framework to hook the function, replacing its implementation with a stub that always returns true, complete with CMake setup and Java loading steps.
Preface
Third‑party SDK contains a .so file that is protected by VMP shell and OLLVM obfuscation.
Using Frida, the target native function information is obtained as follows:
------------ [ #1 Native Method ] ------------
Method Name : public static final native boolean ****************************************.ca(android.content.Context)
ArtMethod Ptr : 0x7cc748a128
Native Addr : 0x7cb77c5834
Module Name : libhexymsb.so
Module Offset : 0x09C8
Module Base : 0x7cb7788000
Module Size : 335872 bytes
Module Path : /data/app/com.demotestapp.test-B17GBv-5lpTxH5GABufXfw==/lib/arm64/libhexymsb.so
------------------------------------------------The ca function resides at offset 0x09C8 in libhexymsb.so.
Requirement: modify the return value of ca to always be true.
Get so module base address
Because ca is dynamically registered and lacks symbol information, we must obtain the base address of the target .so before hooking.
The dl_iterate_phdr function can iterate over all loaded ELF modules and return their load addresses.
int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *info, size_t size, void *data), void *data);Parameters:
callback – called for each loaded module with its information.
struct dl_phdr_info *info – contains path, base address, program‑header table, etc.
size – size of the dl_phdr_info structure.
data – user‑provided pointer passed to the callback.
Common fields of struct dl_phdr_info:
struct dl_phdr_info {
const char *dlpi_name; // module path (may be NULL)
ElfW(Addr) dlpi_addr; // load address (base address)
const ElfW(Phdr) *dlpi_phdr;// start of program header table
ElfW(Half) dlpi_phnum; // number of program headers
// ... other fields
};Source links for dl_iterate_phdr definition:
https://cs.android.com/android/platform/superproject/+/android-latest-release:external/musl/include/link.h;l=21
https://cs.android.com/android/platform/superproject/+/android-latest-release:external/musl/include/link.h;l=47
Implementation of a helper that returns the base address of a library matching a given name substring:
/**
* @brief Get the load base address of a specified shared library.
* @param libname Substring of the library name, e.g., "libtarget.so".
* Supports fuzzy matching via strstr.
* @return void* Base address (dlpi_addr) or nullptr if not found.
*/
void *get_library_base(const char *libname) {
struct callback_data { const char *libname; void *base; } data = {libname, nullptr};
dl_iterate_phdr([](struct dl_phdr_info *info, size_t, void *data) {
auto *cb = (callback_data *)data;
if (info->dlpi_name && strstr(info->dlpi_name, cb->libname)) {
cb->base = (void *)info->dlpi_addr;
return 1; // stop iteration
}
return 0;
}, &data);
return data.base;
}Write Hook Code
1. CMakeLists.txt
Edit CMakeLists.txt to add a custom hook library:
find_package(shadowhook REQUIRED CONFIG)
add_library(
sohooker
SHARED
sohooker.cpp)
target_link_libraries(
sohooker
${log-lib}
shadowhook::shadowhook)The project uses the ShadowHook framework (see the referenced article “Understanding Android Hook: PLT Hook and Inline Hook”).
2. Implement function hooking and return‑value replacement
First obtain the .so base address with get_library_base, then add the function offset to compute the target address. Use shadowhook_hook_func_addr to replace the original function with a stub that always returns true. The hook runs automatically at library load via a __attribute__((constructor)) function and adapts to different architectures (arm64 and armeabi‑v7a).
#include <jni.h>
#include <android/log.h>
#include "shadowhook.h"
#include <link.h>
#include <string.h>
#define TAG "sohooker"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
typedef bool (*orig_ca_func_t)(JNIEnv *, jclass, jobject);
static orig_ca_func_t orig_ca_func = nullptr;
static bool fake_ca(JNIEnv *env, jclass clazz, jobject context) {
LOGI("🚀 fake_ca called, always return true");
return true;
}
void print_arch_info() {
#if defined(__aarch64__)
LOGI("Current architecture: arm64-v8a");
#elif defined(__arm__)
LOGI("Current architecture: armeabi-v7a");
#elif defined(__i386__)
LOGI("Current architecture: x86");
#elif defined(__x86_64__)
LOGI("Current architecture: x86_64");
#else
LOGI("Unknown architecture");
#endif
}
__attribute__((constructor)) static void init_hook() {
shadowhook_init(SHADOWHOOK_MODE_UNIQUE, true);
void *base = get_library_base("libhexymsb.so");
if (!base) { LOGI("❌ libhexymsb.so not loaded yet"); return; }
uintptr_t target_addr = 0;
#ifdef __aarch64__
target_addr = (uintptr_t)base + 0x09C8; // arm64 offset
#else
target_addr = (uintptr_t)base + 0x0610; // armeabi-v7a offset
#endif
print_arch_info();
LOGI("🎯 hooking address: %p", (void *)target_addr);
shadowhook_hook_func_addr((void *)target_addr, (void *)fake_ca, (void **)&orig_ca_func);
}Inject Custom Hook Library
Load the hook library from Java:
System.loadLibrary("sohooker")Run Test and Verification
Without loading sohooker, the native method returns false:
After loading sohooker, the method returns true:
Log output shows that ca has been successfully replaced by fake_ca:
Full Source Code
Example repository: https://github.com/CYRUS-STUDIO/AndroidExample
Reference Links
Understanding Android Hook: PLT Hook and Inline Hook – Full Analysis : https://cyrus-studio.github.io/blog/posts/%E6%90%9E%E6%87%82-android-hook-%E7%9A%84%E4%B8%A4%E5%A4%A7%E6%A0%B8%E5%BF%83plt-hook-%E4%B8%8E-inline-hook-%E5%85%A8%E8%A7%A3%E6%9E%90/
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
