通用的Android inline hook库,支持thumb,arm32,arm64
2,388
stars
112
commits
C
primary language
Aug 26, 2026
updated
shadowhook is an Android inline hook library. Its goals are:
If you need an Android PLT hook library, try ByteHook.
Android 4.1 - 17 QPR1 Beta 4
We will test and support the latest Android OS Beta versions as promptly as possible, and list the supported Android OS versions here.
4.1 - 17 (API level 16 - 37)..init + .init_array and .fini + .fini_array of newly loaded ELFs..dynsym and .symtab of all ELFs in the process.[!CAUTION] The "Quick Start" below will get your DEMO running. However, shadowhook is not just a few hook APIs. To use shadowhook stably in production apps and fully leverage its capabilities, please be sure to read the "shadowhook Manual".
shadowhook is published on Maven Central. To use native dependencies, shadowhook uses the Prefab package format, which is supported from Android Gradle Plugin 4.0.
allprojects {
repositories {
mavenCentral()
}
}
android {
buildFeatures {
prefab true
}
}
dependencies {
implementation 'com.bytedance.android:shadowhook:x.y.z'
}
Replace x.y.z with the version number. It's recommended to use the latest release version.
Note: shadowhook uses prefab package schema v2, which is the default configuration from Android Gradle Plugin 7.1.0. If you're using a version of Android Gradle Plugin prior to 7.1.0, add the following configuration to gradle.properties:
android.prefabVersion=2.0.0
CMakeLists.txt
find_package(shadowhook REQUIRED CONFIG)
add_library(mylib SHARED mylib.c)
target_link_libraries(mylib shadowhook::shadowhook)
Android.mk
include $(CLEAR_VARS)
LOCAL_MODULE := mylib
LOCAL_SRC_FILES := mylib.c
LOCAL_SHARED_LIBRARIES += shadowhook
include $(BUILD_SHARED_LIBRARY)
$(call import-module,prefab/shadowhook)
android {
defaultConfig {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
shadowhook includes two .so files: libshadowhook.so and libshadowhook_nothing.so.
If you're using shadowhook in an SDK project, you need to avoid packaging libshadowhook.so and libshadowhook_nothing.so into your AAR to prevent duplicate .so file issues when packaging the app.
android {
packagingOptions {
exclude '**/libshadowhook.so'
exclude '**/libshadowhook_nothing.so'
}
}
On the other hand, if you're using shadowhook in an APP project, you may need to add some options to handle conflicts caused by duplicate .so files. However, this may cause the APP to use an incorrect version of shadowhook.
android {
packagingOptions {
pickFirst '**/libshadowhook.so'
pickFirst '**/libshadowhook_nothing.so'
}
}
shadowhook supports three modes (shared, multi, unique). You can try the unique mode first.
import com.bytedance.shadowhook.ShadowHook;
public class MySdk {
public void init() {
ShadowHook.init(new ShadowHook.ConfigBuilder()
.setMode(ShadowHook.Mode.UNIQUE)
.build());
}
}
Hook applies to the entire function. You need to write a proxy function that receives arguments and passes return values in the same way as the original function, which typically means the proxy function needs to be defined with the same type as the original function (including: number of parameters, parameter order, parameter types, return value type). When the hook is successful, when the hooked function is executed, the proxy function will be executed first, and in the proxy function, you can decide whether to call the original function.
Example: hook the art::ArtMethod::Invoke() function in libart.so.
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);
}
}
_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc is the function symbol name of art::ArtMethod::Invoke in libart.so after C++ Name Mangler processing, which can be viewed using readelf. C functions do not have the concept of Name Mangler.art::ArtMethod::Invoke is different in versions before Android M. This example only applies to Android M and later versions. If you want to achieve better Android version compatibility, you need to handle the differences in function symbol names yourself.Intercept applies to instructions. It can be the first instruction of a function or a certain instruction in the middle of a function. You need to write an interceptor function. When the intercept is successful, when the intercepted instruction is executed, the interceptor function will be executed first. In the interceptor function, you can read and modify the values of registers. After the interceptor function returns, the intercepted instruction will continue to be executed. Intercept is similar to the breakpoint debugging function of a debugger.
Example: intercept a certain instruction in the art::ArtMethod::Invoke() function in libart.so.
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
instr_addr is fixed as sym_addr + 20. In real scenarios, memory scanning and other methods are generally used to determine the address of the instruction that needs to be intercepted.ShadowHook is licensed under the MIT License.
ShadowHook uses the following third-party source code or libraries:
C
83.7%
Assembly
6.7%
Java
4.1%
Python
2.7%
C++
1.9%
通用的Android inline hook库,支持thumb,arm32,arm64
2,388
stars
112
commits
C
primary language
Aug 26, 2026
updated
shadowhook is an Android inline hook library. Its goals are:
If you need an Android PLT hook library, try ByteHook.
Android 4.1 - 17 QPR1 Beta 4
We will test and support the latest Android OS Beta versions as promptly as possible, and list the supported Android OS versions here.
4.1 - 17 (API level 16 - 37)..init + .init_array and .fini + .fini_array of newly loaded ELFs..dynsym and .symtab of all ELFs in the process.[!CAUTION] The "Quick Start" below will get your DEMO running. However, shadowhook is not just a few hook APIs. To use shadowhook stably in production apps and fully leverage its capabilities, please be sure to read the "shadowhook Manual".
shadowhook is published on Maven Central. To use native dependencies, shadowhook uses the Prefab package format, which is supported from Android Gradle Plugin 4.0.
allprojects {
repositories {
mavenCentral()
}
}
android {
buildFeatures {
prefab true
}
}
dependencies {
implementation 'com.bytedance.android:shadowhook:x.y.z'
}
Replace x.y.z with the version number. It's recommended to use the latest release version.
Note: shadowhook uses prefab package schema v2, which is the default configuration from Android Gradle Plugin 7.1.0. If you're using a version of Android Gradle Plugin prior to 7.1.0, add the following configuration to gradle.properties:
android.prefabVersion=2.0.0
CMakeLists.txt
find_package(shadowhook REQUIRED CONFIG)
add_library(mylib SHARED mylib.c)
target_link_libraries(mylib shadowhook::shadowhook)
Android.mk
include $(CLEAR_VARS)
LOCAL_MODULE := mylib
LOCAL_SRC_FILES := mylib.c
LOCAL_SHARED_LIBRARIES += shadowhook
include $(BUILD_SHARED_LIBRARY)
$(call import-module,prefab/shadowhook)
android {
defaultConfig {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
shadowhook includes two .so files: libshadowhook.so and libshadowhook_nothing.so.
If you're using shadowhook in an SDK project, you need to avoid packaging libshadowhook.so and libshadowhook_nothing.so into your AAR to prevent duplicate .so file issues when packaging the app.
android {
packagingOptions {
exclude '**/libshadowhook.so'
exclude '**/libshadowhook_nothing.so'
}
}
On the other hand, if you're using shadowhook in an APP project, you may need to add some options to handle conflicts caused by duplicate .so files. However, this may cause the APP to use an incorrect version of shadowhook.
android {
packagingOptions {
pickFirst '**/libshadowhook.so'
pickFirst '**/libshadowhook_nothing.so'
}
}
shadowhook supports three modes (shared, multi, unique). You can try the unique mode first.
import com.bytedance.shadowhook.ShadowHook;
public class MySdk {
public void init() {
ShadowHook.init(new ShadowHook.ConfigBuilder()
.setMode(ShadowHook.Mode.UNIQUE)
.build());
}
}
Hook applies to the entire function. You need to write a proxy function that receives arguments and passes return values in the same way as the original function, which typically means the proxy function needs to be defined with the same type as the original function (including: number of parameters, parameter order, parameter types, return value type). When the hook is successful, when the hooked function is executed, the proxy function will be executed first, and in the proxy function, you can decide whether to call the original function.
Example: hook the art::ArtMethod::Invoke() function in libart.so.
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);
}
}
_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc is the function symbol name of art::ArtMethod::Invoke in libart.so after C++ Name Mangler processing, which can be viewed using readelf. C functions do not have the concept of Name Mangler.art::ArtMethod::Invoke is different in versions before Android M. This example only applies to Android M and later versions. If you want to achieve better Android version compatibility, you need to handle the differences in function symbol names yourself.Intercept applies to instructions. It can be the first instruction of a function or a certain instruction in the middle of a function. You need to write an interceptor function. When the intercept is successful, when the intercepted instruction is executed, the interceptor function will be executed first. In the interceptor function, you can read and modify the values of registers. After the interceptor function returns, the intercepted instruction will continue to be executed. Intercept is similar to the breakpoint debugging function of a debugger.
Example: intercept a certain instruction in the art::ArtMethod::Invoke() function in libart.so.
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
instr_addr is fixed as sym_addr + 20. In real scenarios, memory scanning and other methods are generally used to determine the address of the instruction that needs to be intercepted.ShadowHook is licensed under the MIT License.
ShadowHook uses the following third-party source code or libraries:
C
83.7%
Assembly
6.7%
Java
4.1%
Python
2.7%
C++
1.9%