shadowhook 是一个 Android inline hook 库。 它的目标是:
如果你需要的是 Android PLT hook 库,建议试试 ByteHook。
Android 4.1 - 17 QPR1 Beta 4
我们会尽可能及时的测试和支持最新的 Android OS Beta 版本,并且在这里说明已经支持的 Android OS 版本范围。
4.1 - 17(API level 16 - 37)。.init + .init_array 和 .fini + .fini_array 前后的回调函数。.dynsym 和 .symtab 中的符号地址。[!CAUTION] 下面的「快速开始」能让你的 DEMO 运行起来。但是 shadowhook 并不仅仅是几个 hook API 这么简单,想要在 production app 中稳定的使用 shadowhook,并且充分发挥它的能力,请务必阅读「shadowhook 手册」。
shadowhook 发布在 Maven Central 上。为了使用 native 依赖项,shadowhook 使用了从 Android Gradle Plugin 4.0 开始支持的 Prefab 包格式。
allprojects {
repositories {
mavenCentral()
}
}
android {
buildFeatures {
prefab true
}
}
dependencies {
implementation 'com.bytedance.android:shadowhook:x.y.z'
}
x.y.z 请替换成版本号,建议使用最新的 release 版本。
注意:shadowhook 使用 prefab package schema v2,它是从 Android Gradle Plugin 7.1.0 开始作为默认配置的。如果你使用的是 Android Gradle Plugin 7.1.0 之前的版本,请在 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 包含两个 .so 文件:libshadowhook.so 和 libshadowhook_nothing.so。
如果你是在一个 SDK 工程里使用 shadowhook,你需要避免把 libshadowhook.so 和 libshadowhook_nothing.so 打包到你的 AAR 里,以免 app 工程打包时遇到重复的 .so 文件问题。
android {
packagingOptions {
exclude '**/libshadowhook.so'
exclude '**/libshadowhook_nothing.so'
}
}
另一方面, 如果你是在一个 APP 工程里使用 shadowhook,你可能需要增加一些选项,用来处理重复的 .so 文件引起的冲突。但是,这可能会导致 APP 使用错误版本的 shadowhook。
android {
packagingOptions {
pickFirst '**/libshadowhook.so'
pickFirst '**/libshadowhook_nothing.so'
}
}
shadowhook 支持三种模式(shared,multi,unique),你可以先试试 unique 模式。
import com.bytedance.shadowhook.ShadowHook;
public class MySdk {
public void init() {
ShadowHook.init(new ShadowHook.ConfigBuilder()
.setMode(ShadowHook.Mode.UNIQUE)
.build());
}
}
hook 作用于函数整体。你需要编写一个代理函数,这个代理函数需要以和原函数同样的方式接收参数和传递返回值,这通常意味着代理函数需要定义成和原函数同样的类型(包括:参数个数、参数顺序、参数类型、返回值类型)。当 hook 成功后,执行到被 hook 的函数时,会先执行代理函数,在代理函数中你可以自己决定是否调用原函数。
举例:hook libart.so 中的 art::ArtMethod::Invoke() 函数。
void *orig = NULL;
void *stub = NULL;
// 被 hook 函数的类型定义
typedef void (*artmethod_invoke_func_type_t)(void *, void *, uint32_t *, uint32_t, void *, const char *);
// 代理函数
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 是 art::ArtMethod::Invoke 在 libart.so 中经过 C++ Name Mangler 处理后的函数符号名,可以使用 readelf 查看。C 函数没有 Name Mangler 的概念。art::ArtMethod::Invoke 在 Android M 之前版本中的符号名有所不同,这个例子仅适用于 Android M 及之后的版本。如果要实现更好的 Android 版本兼容性,你需要自己处理函数符号名的差异。intercept 作用于指令。可以是函数的第一条指令,也可以是函数中间的某条指令。你需要编写一个拦截器函数。当 intercept 成功后,执行到被 intercept 的指令时,会先执行拦截器函数。在拦截器函数中,你可以读取和修改寄存器的值。当拦截器函数返回后,会继续执行被 intercept 的指令。intercept 类似于调试器的断点调试功能。
举例:intercept libart.so 中的 art::ArtMethod::Invoke() 函数中的某条指令。
void *stub;
#if defined(__aarch64__)
void artmethod_invoke_interceptor(shadowhook_cpu_context_t *ctx, void *data) {
// 当 x19 等于 0 时,修改 x20 和 x21 的值
if (ctx->regs[19] == 0) {
ctx->regs[20] = 1;
ctx->regs[21] = 1000;
LOG("interceptor: found x19 == 0");
}
// 当 q0 等于 0 时,修改 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) {
// 查询 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;
}
// 定位 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 固定为 sym_addr + 20。在真实的场景中,一般会结合内存扫描等手段来确定需要 intercept 的指令的地址。ShadowHook 使用 MIT 许可证 授权。
ShadowHook 使用了以下第三方源码或库:
$ claude mcp add android-inline-hook \
-- python -m otcore.mcp_server <graph>