An Introduction to ShadowHook
ShadowHook is an Android inline hook library designed to support thumb, arm32, and arm64 architectures. Notably, it has been successfully integrated and used in popular applications like TikTok, Douyin, Toutiao, Xigua Video, and Lark. For developers interested in Android PLT hook libraries, ByteHook is recommended instead.
Key Features
- Android Version Compatibility: ShadowHook is compatible with Android versions 4.1 to 15, covering API levels 16 to 35.
- Architecture Support: It supports armeabi-v7a and arm64-v8a architectures.
- Function Hooking: Allows hooking of entire functions, though it does not support hooking at arbitrary positions within a function.
- Hook Specification: Enables specifying hook locations by using a "function address" or a combination of "library name and function name."
- Dynamic Libraries: Can automatically complete hooks on newly loaded dynamic libraries by library and function names, with an option to call a callback function upon hook completion.
- Concurrency: Supports concurrent execution of multiple hooks and unhooks on the same point without interference, but only in shared mode.
- Recursive Call Management: Automatically avoids possible recursive or circular calls between proxy functions in shared mode.
- Backtrace Support: The proxy function supports unwinding backtrace using conventional methods like CFI, EH, and FP.
- Symbol Address Search Functionality: Integrated for convenience.
- Open Source: Licensed under the MIT License, allowing for wide use and adaptation.
Getting Started
To begin using ShadowHook, developers can examine the sample app in the app
module or review system function hooking examples in the systest
module.
Setup in build.gradle
ShadowHook is available on Maven Central and uses the Prefab package format for native dependencies, requiring Android Gradle Plugin 4.0 or higher.
android {
buildFeatures {
prefab true
}
}
dependencies {
implementation 'com.bytedance.android:shadowhook:1.1.1'
}
Note: For users of Android Gradle Plugin versions below 7.1.0, ensure to configure gradle.properties
for prefab version 2.0.0.
Configuration in CMakeLists.txt or Android.mk
For CMakeLists.txt
:
find_package(shadowhook REQUIRED CONFIG)
add_library(mylib SHARED mylib.c)
target_link_libraries(mylib shadowhook::shadowhook)
For 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)
Specifying ABIs
Developers should specify the required ABIs in their project:
android {
defaultConfig {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
Packaging Options
To avoid packaging conflicts of libshadowhook.so
:
-
In SDK projects, exclude these files:
android { packagingOptions { exclude '**/libshadowhook.so' exclude '**/libshadowhook_nothing.so' } }
-
In APP projects, use
pickFirst
to resolve conflicts:android { packagingOptions { pickFirst '**/libshadowhook.so' pickFirst '**/libshadowhook_nothing.so' } }
Initialization
ShadowHook provides two modes: shared and unique. Here’s an example of initializing it in unique mode:
import com.bytedance.shadowhook.ShadowHook;
public class MySdk {
public static void init() {
ShadowHook.init(new ShadowHook.ConfigBuilder()
.setMode(ShadowHook.Mode.UNIQUE)
.build());
}
}
Hooking and Unhooking Functions
#include "shadowhook.h"
// Function prototypes for hooking and unhooking
void *shadowhook_hook_func_addr(void *func_addr, void *new_addr, void **orig_addr);
void *shadowhook_hook_sym_addr(void *sym_addr, void *new_addr, void **orig_addr);
void *shadowhook_hook_sym_name(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr);
int shadowhook_unhook(void *stub);
For example, hooking the art::ArtMethod::Invoke
function involves naming conventions and managing the address:
void *orig = NULL;
void *stub = NULL;
typedef void (*type_t)(void *, void *, uint32_t *, uint32_t, void *, const char *);
void proxy(void *thiz, void *thread, uint32_t *args, uint32_t args_size, void *result, const char *shorty) {
// Additional operations before the original function call
((type_t)orig)(thiz, thread, args, args_size, result, shorty);
// Additional operations after the original function call
}
void do_hook() {
stub = shadowhook_hook_sym_name("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", (void *)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() {
shadowhook_unhook(stub);
stub = NULL;
}
Contributions and Licensing
ShadowHook encourages contributions from the community and follows a Code of Conduct and a Contributing Guide. It is open and available under the MIT License, fostering freedom of use and distribution.
ShadowHook also incorporates third-party libraries such as queue.h
, tree.h
, and linux-syscall-support
, which are licensed under BSD licenses, ensuring community collaboration and security standards.
For security concerns or vulnerabilities, the process for Reporting Security vulnerabilities is detailed in the project documentation.