[!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”.
Quick Start
1. Add dependencies in build.gradle
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.
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
2. Add dependencies in CMakeLists.txt or Android.mk
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.
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.
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());
}
}
6. hook and unhook
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.
_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.
The symbol name of 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.
7. intercept and unintercept
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
To simplify the example code, 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.
Since aarch32 and aarch64 have different registers and the instructions of the same function are different, the intercept logic generally needs to be written separately. Here only includes example code for aarch64.
shadowhook
简体中文
shadowhook is an Android inline hook library. Its goals are:
Features
4.1-16(API level16-36)..init+.init_arrayand.fini+.fini_arrayof newly loaded ELFs..dynsymand.symtabof all ELFs in the process.Documentation
shadowhook Manual
Quick Start
1. Add dependencies in build.gradle
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.
Replace
x.y.zwith 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:2. Add dependencies in CMakeLists.txt or Android.mk
3. Specify one or more ABIs you need
4. Add packaging options
shadowhook includes two
.sofiles: 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
.sofile issues when packaging the app.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
.sofiles. However, this may cause the APP to use an incorrect version of shadowhook.5. Initialize
shadowhook supports three modes (shared, multi, unique). You can try the unique mode first.
6. hook and unhook
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._ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKcis the function symbol name ofart::ArtMethod::Invokein 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::Invokeis 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.7. intercept and unintercept
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.instr_addris fixed assym_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.Feedback
Contributing
License
ShadowHook is licensed under the MIT License.
ShadowHook uses the following third-party source code or libraries:
BSD 3-Clause License
Copyright (c) 1991, 1993 The Regents of the University of California.
BSD 2-Clause License
Copyright (c) 2002 Niels Provos provos@citi.umich.edu
BSD 3-Clause License
Copyright (c) 2005-2011 Google Inc.
MIT License
Copyright (c) 2020-2025 HexHacking Team