ShadowHook is an Android inline hook library which supports thumb, arm32 and arm64.
ShadowHook is now used in TikTok, Douyin, Toutiao, Xigua Video, Lark.
If you need an Android PLT hook library, please move to ByteHook.
- Support Android 4.1 - 15 (API level 16 - 35).
- Support armeabi-v7a and arm64-v8a.
- Support hook for the whole function, but does not support hook for the middle position of the function.
- Support to specify the hook location by "function address" or "library name + function name".
- Automatically complete the hook of "newly loaded dynamic library" (only "library name + function name"), and call the optional callback function after the hook is completed.
- Multiple hooks and unhooks can be executed concurrently on the same hook point without interfering with each other (only in shared mode).
- Automatically avoid possible recursive calls and circular calls between proxy functions (only in shared mode).
- The proxy function supports unwinding backtrace in a normal way (CFI, EH, FP).
- Integrated symbol address search function.
- MIT licensed.
ShadowHook is published on Maven Central, and uses Prefab package format for native dependencies, which is supported by Android Gradle Plugin 4.0+.
allprojects {
repositories {
mavenCentral()
}
}
android {
buildFeatures {
prefab true
}
}
dependencies {
implementation 'com.bytedance.android:shadowhook:x.y.z'
}
Please replace x.y.z
with the version number. It is recommended to use the latest release version.
Note: ShadowHook uses the prefab package schema v2, which is configured by default since Android Gradle Plugin 7.1.0. If you are using Android Gradle Plugin earlier than 7.1.0, please 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'
}
}
}
If you are using ShadowHook in an SDK project, you may need to avoid packaging libshadowhook.so into your AAR, so as not to encounter duplicate libshadowhook.so file when packaging the app project.
android {
packagingOptions {
exclude '**/libshadowhook.so'
exclude '**/libshadowhook_nothing.so'
}
}
On the other hand, if you are using ShadowHook in an APP project, you may need to add some options to deal with conflicts caused by duplicate libshadowhook.so file.
android {
packagingOptions {
pickFirst '**/libshadowhook.so'
pickFirst '**/libshadowhook_nothing.so'
}
}
- ShadowHook can be initialized at the java layer or the native layer, and you can choose one of the two.
- The java layer initialization logic actually only does two things:
System.loadLibrary
; calling theinit
function of the native layer. - The initialization can be executed multiple times concurrently, but only the first time actually takes effect, and subsequent initialization calls will directly return the return value of the first initialization.
shared
mode (default): Multiple hooks and unhooks can be executed concurrently on the same hook point without interfering with each other. Automatically avoid recursive and circular calls that may form between proxy functions. The shared mode is recommended for complex institutions or organizations.unique
mode: The same hook point can only be hooked once (and can be hooked again after unhook). You need to deal with recursive calls and circular calls that may be formed between proxy functions. This mode can be used for personal or small apps, or in some debugging scenarios (for example, if you want to skip the proxy management mechanism of ShadowHook, and debug and analyze the simple inlinehook process).
true
: On. Debug information will be written to logcat. tag:shadowhook_tag
.false
(default): Off.
true
:On。Write hook and unhook operation records to internal buffer. These operation record information can be obtained through other APIs.false
(default): Off.
package com.bytedance.shadowhook;
public class ShadowHook
public static int init()
public static int init(Config config)
Returns 0
for success, non-0
for failure (non-0
is an error code).
import com.bytedance.shadowhook.ShadowHook;
public class MySdk {
public static void init() {
ShadowHook.init();
}
}
import com.bytedance.shadowhook.ShadowHook;
public class MySdk {
public static void init() {
ShadowHook.init(new ShadowHook.ConfigBuilder()
.setMode(ShadowHook.Mode.SHARED)
.setDebuggable(true)
.setRecordable(true)
.build());
}
}
#include "shadowhook.h"
typedef enum
{
SHADOWHOOK_MODE_SHARED = 0,
SHADOWHOOK_MODE_UNIQUE = 1
} shadowhook_mode_t;
int shadowhook_init(shadowhook_mode_t mode, bool debuggable);
Returns 0
for success, non-0
for failure (non-0
is an error code).
The Mode
is specified at initialization time and cannot be modified afterwards. Debug Log
and Operation Record
can be turned on and off at any time during runtime.
Java API:
package com.bytedance.shadowhook;
public class ShadowHook
public static Mode getMode()
public static boolean getDebuggable()
public static void setDebuggable(boolean debuggable)
public static boolean getRecordable()
public static void setRecordable(boolean recordable)
Native API:
#include "shadowhook.h"
shadowhook_mode_t shadowhook_get_mode(void);
bool shadowhook_get_debuggable(void);
void shadowhook_set_debuggable(bool debuggable);
bool shadowhook_get_recordable(void);
void shadowhook_set_recordable(bool recordable);
- The hook API of ShadowHook supports specifying the hook location by "function address" or "library name + function name".
- The function addresses in
.dynsym
exposed by NDK can be obtained through linker, but the function addresses in.symtab
and.symtab in .gnu_debugdata
need to be obtained by other means.
#include "shadowhook.h"
void *shadowhook_dlopen(const char *lib_name);
void shadowhook_dlclose(void *handle);
void *shadowhook_dlsym(void *handle, const char *sym_name);
void *shadowhook_dlsym_dynsym(void *handle, const char *sym_name);
void *shadowhook_dlsym_symtab(void *handle, const char *sym_name);
- The usage of this set of APIs is similar to the system-provided
dlopen
,dlclose
,dlsym
. shadowhook_dlsym_dynsym
can only find symbols in.dynsym
, which is faster.shadowhook_dlsym_symtab
can find symbols in.symtab
and.symtab in .gnu_debugdata
, but is slower.shadowhook_dlsym
will first try to find the symbol in.dynsym
, and if not found, it will continue to try to find it in.symtab
and.symtab in .gnu_debugdata
.
- Supports specifying the hook location by "function address" or "library name + function name".
- Automatically completes the hook of "newly loaded so library" (only "library name + function name" method), and calls an optional callback function after the hook is completed.
- Only hooks for the whole function are supported, and hooks for the middle of the function are not supported.
- If the function to be hooked is not in the ELF's symbol table (
.dynsym
or.symtab
or.symtab in .gnu_debugdata
), It can only be hooked throughshadowhook_hook_func_addr
, and other hook APIs will definitely not be able to hook successfully.
#include "shadowhook.h"
void *shadowhook_hook_sym_addr(void *sym_addr, void *new_addr, void **orig_addr);
This method can only hook "dynamic libraries that are currently loaded into the process".
A function (sym_addr
) hooked with shadowhook_hook_sym_addr
must exist in the ELF symbol table (.dynsym
/ .symtab
). This can be confirmed with readelf -sW
.
Since the ELF symbol table contains the length information of the function, ShadowHook can use this information to confirm that "the length of the modified function header during hooking will not exceed the total length of the function", which can improve the stability of the hook.
sym_addr
(must be specified): The absolute address of the function that needs to be hooked.new_addr
(must be specified): The absolute address of the new function (proxy function).orig_addr
(you can passNULL
if not needed): Return the address of the original function.
- Not
NULL
: the hook succeeded. The return value is a stub, you can save the return value for subsequent use in unhook. NULL
: The hook failed. You can callshadowhook_get_errno
to get the errno, and you can continue to callshadowhook_to_errmsg
to get the error message.
void *orig;
void *stub = shadowhook_hook_sym_addr(malloc, my_malloc, &orig);
if(stub == NULL)
{
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
__android_log_print(ANDROID_LOG_WARN, "test", "hook failed: %d - %s", error_num, error_msg);
}
In this example, sym_addr
is specified by the linker when loading the current dynamic library.
**ShadowHook provides shadowhook_hook_func_addr
since version 1.0.4. **
#include "shadowhook.h"
void *shadowhook_hook_func_addr(void *func_addr, void *new_addr, void **orig_addr);
This method can only hook "dynamic libraries that are currently loaded into the process".
A function (func_addr
) hooked with shadowhook_hook_func_addr
may not exist in the ELF symbol table (.dynsym
/ .symtab
).
At this time, the user needs to ensure that the function corresponding to func_addr
is long enough. The length of the function required by different architectures and instruction types is as follows:
Architecture | Instruction type | Minimum function length (bytes) | Ideal function length (bytes) |
---|---|---|---|
arm32 | thumb | 4 | 10 |
arm32 | arm32 | 4 | 8 |
arm64 | arm64 | 4 | 16 |
Note: The function length here refers to the length of the binary CPU instruction sequence generated after the function is compiled, which can be confirmed with tools such as objdump
.
- actual function length < minimum function length: arm32 and arm64 will definitely have problems, thumb may have problems.
- minimum function length <= actual function length < ideal function length: maybe something goes wrong, maybe not. Or sometimes it goes wrong, sometimes it doesn't.
- ideal function length <= actual function length: No problem for sure. (only refers to problems caused by the length of instruction coverage exceeding the total length of the function)
It can be seen that shadowhook_hook_func_addr
is less reliable than shadowhook_hook_sym_addr
. Therefore, it is recommended to use it only in the case of "the length of the instruction of the hooked function is controllable", for example:
- Fix system library bugs of specific models and specific OS versions by hooking a function without symbol info.
- The function that needs to be hooked is returned by a specific version of the API. For example, to hook a specific version of Unity, obtain the address of the function that needs to be hooked through
vkGetInstanceProcAddr
provided by vulkan.
In these cases where "the version of the hooked ELF file is known and controllable", even using shadowhook_hook_func_addr
to execute the hook, we can verify the stability of the hook in advance during the development phase.
func_addr
(must be specified): The absolute address of the function that needs to be hooked.new_addr
(must be specified): The absolute address of the new function (proxy function).orig_addr
(you can passNULL
if not needed): Return the address of the original function.
- Not
NULL
: the hook succeeded. The return value is a stub, you can save the return value for subsequent use in unhook. NULL
: The hook failed. You can callshadowhook_get_errno
to get the errno, and you can continue to callshadowhook_to_errmsg
to get the error message.
void *orig;
void *func = get_hidden_func_addr();
void *stub = shadowhook_hook_func_addr(func, my_func, &orig);
if(stub == NULL)
{
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
__android_log_print(ANDROID_LOG_WARN, "test", "hook failed: %d - %s", error_num, error_msg);
}
In this way, func_addr
is returned by an external function, which has no symbol info in ELF.
#include "shadowhook.h"
void *shadowhook_hook_sym_name(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr);
- In this way, you can hook "the dynamic library that is currently loaded into the process", or you can hook "the dynamic library that has not been loaded into the process" (if the dynamic library has not been loaded at the time of hooking, ShadowHook will internally record the current hook "demand", and the hook operation will be executed immediately once the target dynamic library is loaded into memory).
- ShadowHook can hook symbols in
.dynsym
/.symtab
/.symtab in .gnu_debugdata
in ELF, andshadowhook_hook_sym_name
will complete the work of symbol address lookup. - Symbols in
.dynsym
/.symtab
can be viewed withreadelf
. - Symbols in
.symtab in .gnu_debugdata
can be viewed withdd
+xz
+readelf
. - If you need to hook "multiple symbols from the same dynamic library", it is recommended to use the
shadowhook_dl*
API to complete the symbol address lookup in batches, which can speed up the symbol lookup in the hook process.
lib_name
(must be specified): The basename or pathname of the ELF where the symbol is located. For the only dynamic library identified in the process, you can only pass the basename, for example:libart.so
. For non-unique dynamic libraries, you need to handle compatibility according to Android version and arch, for example:/system/lib64/libbinderthreadstate.so
and/system/lib64/vndk-sp-29/libbinderthreadstate.so
. Otherwise, ShadowHook will only hook the first dynamic library that matches basename in the process.sym_name
(must be specified): Symbol name.new_addr
(must be specified): The absolute address of the new function (proxy function).orig_addr
(you can passNULL
if not needed): Return the address of the original function.
- Not
NULL
(errno == 0): the hook succeeded. The return value is a stub, you can save the return value for subsequent use in unhook. - Not
NULL
(errno == 1): The hook cannot be executed because the target dynamic library has not been loaded. ShadowHook will record the current hook "demand" internally, and once the target dynamic library is loaded into the memory, the hook operation will be executed immediately. The return value is a stub, you can save the return value for subsequent use in unhook. NULL
: The hook failed. You can callshadowhook_get_errno
to get the errno, and you can continue to callshadowhook_to_errmsg
to get the error message.
void *orig;
void *stub = shadowhook_hook_sym_name("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", my_invoke, &orig);
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
__android_log_print(ANDROID_LOG_WARN, "test", "hook return: %p, %d - %s", stub, error_num, error_msg);
#include "shadowhook.h"
typedef void (*shadowhook_hooked_t)(int error_number, const char *lib_name, const char *sym_name, void *sym_addr, void *new_addr, void *orig_addr, void *arg);
void *shadowhook_hook_sym_name_callback(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg);
For the case of hook "dynamic library that has not been loaded into the process", sometimes we need to know "the execution result and execution time of the current hook request in the future". At this time, you can use shadowhook_hook_sym_name_callback
to specify an additional callback, which will be called when the hook operation is executed in the future (or now).
hooked
(can passNULL
if not needed): When the hook is executed, the callback function is called. The definition of the callback function isshadowhook_hooked_t
, where the first parameter is the errno executed by the hook,0
means success, non-0
failure (you can callshadowhook_to_errmsg
to get the error message). Subsequent parameters inshadowhook_hooked_t
correspond to the parameters inshadowhook_hook_sym_name_callback
.hooked_arg
(can passNULL
if not needed): The last parameter (arg
) in the hooked callback function.- Other parameters are the same as
shadowhook_hook_sym_name
.
- The meaning is the same as
shadowhook_hook_sym_name
.
typedef void my_hooked_callback(int error_number, const char *lib_name, const char *sym_name, void *sym_addr, void *new_addr, void *orig_addr, void *arg);
{
const char *error_msg = shadowhook_to_errmsg(error_number);
__android_log_print(ANDROID_LOG_WARN, "test", "hooked: %s, %s, %d - %s", lib_name, sym_name, error_number, error_msg);
}
void do_hook(void)
{
void *orig;
void *stub = shadowhook_hook_sym_name_callback("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc", my_invoke, &orig, my_hooked_callback, NULL);
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
__android_log_print(ANDROID_LOG_WARN, "test", "hook return: %p, %d - %s", stub, error_num, error_msg);
}
#include "shadowhook.h"
int shadowhook_unhook(void *stub);
stub
(must be specified): The stub value returned by the hook function.
0
: Unhook succeeded.-1
: Unhook failed. You can callshadowhook_get_errno
to get the errno, and you can continue to callshadowhook_to_errmsg
to get the error message.
int result = shadowhook_unhook(stub);
if(result != 0)
{
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
__android_log_print(ANDROID_LOG_WARN, "test", "unhook failed: %d - %s", error_num, error_msg);
}
- The proxy function needs to be defined as the same type as the original function (parameter type + return value type).
- Regardless of shared mode or unique mode, the hook function will return the address of the original function through the
void **orig_addr
parameter.
In shared mode. Inside the proxy function, use the SHADOWHOOK_CALL_PREV
macro to call the original function. Outside the proxy function, call the original function via orig_addr
.
#include "shadowhook.h"
#ifdef __cplusplus
#define SHADOWHOOK_CALL_PREV(func, ...) ...
#else
#define SHADOWHOOK_CALL_PREV(func, func_sig, ...) ...
#endif
- Used to call the original function in the proxy function. It is also possible not to call the original function in the proxy function, but please do not directly call the original function through the function name or
orig_addr
. - The usage in the C++ source file is: the first parameter passes the address of the current proxy function, followed by each parameter of the function in sequence.
- The usage in the C source file is: the first parameter passes the address of the current proxy function, the second parameter passes the type definition of the function of the current hook, and then passes the parameters of the function in order.
#include "shadowhook.h"
// pop stack in proxy-function (for C/C++)
#define SHADOWHOOK_POP_STACK() ...
// pop stack in proxy-function (for C++ only)
#define SHADOWHOOK_STACK_SCOPE() ...
ShadowHook's proxy function management mechanism accomplishes a few things:
- You can hook and unhook the same hook point multiple times without interfering with each other.
- Automatically avoid recursive calls and circular calls that may be formed between proxy functions (for example:
read
is called inopen_proxy
of SDK1, andopen
is called inread_proxy
of SDK2). - The call stack can be unwind in the normal way in the proxy function.
In order to do all of the above at the same time, you need to do something extra in the proxy function, namely "perform the stack cleanup inside the ShadowHook", which requires you to call the SHADOWHOOK_POP_STACK
macro or SHADOWHOOK_STACK_SCOPE
macro in the proxy function to complete (two options one). Note: Even if you do nothing in the proxy function, you need to "perform stack cleanup inside shadowhook".
SHADOWHOOK_POP_STACK
macro: for C and C++ source files. Need to make sure to call before the proxy function returns.SHADOWHOOK_STACK_SCOPE
macro: for C++ source files. Call it once at the beginning of the proxy function.
#include "shadowhook.h"
// get return address in proxy-function
#define SHADOWHOOK_RETURN_ADDRESS() ...
Occasionally, you may need to get the value of LR through __builtin_return_address(0)
in the proxy function. Since the trampoline in shared mode changes LR, calling __builtin_return_address(0)
directly will return the address of the trampoline.
In the proxy function, the original LR needs to be obtained through the SHADOWHOOK_RETURN_ADDRESS
macro.
#include "shadowhook.h"
// allow reentrant of the current proxy-function
#define SHADOWHOOK_ALLOW_REENTRANT() ...
// disallow reentrant of the current proxy-function
#define SHADOWHOOK_DISALLOW_REENTRANT() ...
In shared mode, proxy functions are not allowed to be reentrant by default, because reentrancy may occur between multiple SDKs using ShadowHook, eventually forming an infinite loop of calls (for example, open_proxy
in SDK1 call read
, and read_proxy
in SDK2 call open
).
However, in some special usage scenarios, reentrancy controlled by business logic may be required, they will not form an "infinite" call loop, but will terminate when certain business conditions are met. If you confirm that this is the case for your use case, please call SHADOWHOOK_ALLOW_REENTRANT
in the proxy function to allow reentrancy, and when the logic of the proxy function runs to "no longer need to allow reentrancy", you can call SHADOWHOOK_DISALLOW_REENTRANT
.
void *orig;
void *stub;
typedef void *(*malloc_t)(size_t);
void *malloc_proxy(size_t sz)
{
if(sz > 1024)
{
// Perform stack cleanup (cannot be omitted).
SHADOWHOOK_POP_STACK();
return NULL;
}
// Call the original function.
void *result = SHADOWHOOK_CALL_PREV(malloc_proxy, malloc_t, sz);
// Perform stack cleanup (cannot be omitted).
SHADOWHOOK_POP_STACK();
return result;
}
void do_hook(void)
{
stub = shadowhook_hook_sym_addr(malloc, malloc_proxy, &orig);
}
void do_unhook(void)
{
shadowhook_unhook(stub);
stub = NULL;
}
void *my_malloc_4k(void)
{
// In some scenarios, maybe you need to call the original function directly.
return ((malloc_t)orig)(4096);
}
void *orig;
void *stub;
typedef void *(*malloc_t)(size_t);
void * malloc_proxy(size_t sz)
{
// Perform stack cleanup (cannot be omitted), just call it once.
SHADOWHOOK_STACK_SCOPE();
if(sz > 1024)
return nullptr;
// Call the original function.
return SHADOWHOOK_CALL_PREV(malloc_proxy, sz);
}
void do_hook(void)
{
stub = shadowhook_hook_sym_addr(malloc, malloc_proxy, &orig);
}
void do_unhook(void)
{
shadowhook_unhook(stub);
stub = NULL;
}
void *my_malloc_4k(void)
{
// In some scenarios, maybe you need to call the original function directly.
return ((malloc_t)orig)(4096);
}
int test_func_2(int a, int b)
{
a--; // a is decremented by 1 each time
return test_func_1(a, b);
}
int test_func_1(int a, int b)
{
if(a < b)
return 0;
else
return test_func_2(a, b);
}
void test(void)
{
test_func_1(10, 5);
}
test_func_1
and test_func_2
seem to form an infinite loop, but when a < b
, the loop will terminate, so it is not really an infinite loop. (The arguments to call test_func_1
in the test
function are a = 10
and b = 5
, decrement a
by 1
each time test_func_2
)
By default ShadowHook prevents the reentrancy of proxy functions, because reentrancy can easily lead to an infinite loop between proxy functions. But if this kind of reentrancy of proxy functions is what you need, please refer to the following example to use the SHADOWHOOK_ALLOW_REENTRANT
macro and SHADOWHOOK_DISALLOW_REENTRANT
macro to control the "reentrancy" of a code area in the proxy function:
The following code hooks test_func_1
, which uses the SHADOWHOOK_ALLOW_REENTRANT
macro to allow reentrancy.
void *stub;
int test_func_1_proxy(int a, int b)
{
// Perform stack cleanup (cannot be omitted), just call it once.
SHADOWHOOK_STACK_SCOPE();
// Add your own business logic.
if(a > 1024 || b > 1024)
return -1;
// Now to call the original function,
// we want every call to test_func_1 to go into our proxy function.
SHADOWHOOK_ALLOW_REENTRANT();
// Call the original function.
int result = SHADOWHOOK_CALL_PREV(test_func_1_proxy, sz);
// Next, we will continue to add some business logic,
// and want to restore ShadowHook's "preventing proxy function from being reentrant" protection function.
SHADOWHOOK_DISALLOW_REENTRANT();
// Continue to add some business logic.
write_log(global_log_fd, "test_func_1 called with a=%d, b=%d", a, b);
return result;
}
void do_hook(void)
{
stub = shadowhook_hook_sym_addr(test_func_1, test_func_1_proxy, NULL);
}
void do_unhook(void)
{
shadowhook_unhook(stub);
stub = NULL;
}
In unique mode. Please always call the original function through the original function address orig_addr
returned by the hook function.
void *orig;
void *stub;
typedef void *(*malloc_t)(size_t);
void *my_malloc(size_t sz)
{
if(sz > 1024)
return nullptr;
// Call the original function.
return ((malloc_t)orig)(sz);
}
void do_hook(void)
{
stub = shadowhook_hook_sym_addr(malloc, my_malloc, &orig);
}
void do_unhook(void)
{
shadowhook_unhook(stub);
stub = NULL;
}
#include "shadowhook.h"
// register and unregister callbacks for executing dynamic library's .init .init_array / .fini .fini_array
typedef void (*shadowhook_dl_info_t)(struct dl_phdr_info *info, size_t size, void *data);
int shadowhook_register_dl_init_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data);
int shadowhook_unregister_dl_init_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data);
int shadowhook_register_dl_fini_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data);
int shadowhook_unregister_dl_fini_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data);
Register/Unregister soinfo::call_constructors
and soinfo::call_destructors
callback functions
pre
(can passNULL
if not needed): the proxy function before callingcall_constructors/call_destructors
.post
(can passNULL
if not needed): the proxy function after callingcall_constructors/call_destructors
.data
(can passNULL
if not needed): the data passed in thepre
/post
proxy function.
Note: pre
/post
proxy functions cannot be empty at the same time.
0
: Succeeded.-1
: Failed. You can callshadowhook_get_errno
to get the errno, and you can continue to callshadowhook_to_errmsg
to get the error message.
#include "shadowhook.h"
static void dl_init_pre(struct dl_phdr_info *info, size_t size, void *data) {
(void)size, (void)data;
__android_log_print(ANDROID_LOG_WARN, "test", "dl_init, load_bias %" PRIxPTR ", %s", (uintptr_t)info->dlpi_addr, info->dlpi_name);
}
static void dl_fini_post(struct dl_phdr_info *info, size_t size, void *data) {
(void)size, (void)data;
__android_log_print(ANDROID_LOG_WARN, "test", "dl_fini, load_bias %" PRIxPTR ", %s", (uintptr_t)info->dlpi_addr, info->dlpi_name);
}
void register_callback(void) {
if (0 != shadowhook_register_dl_init_callback(dl_init_pre, NULL, NULL)) {
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
__android_log_print(ANDROID_LOG_WARN, "test", "dl_init failed: %d - %s", error_num, error_msg);
}
if (0 != shadowhook_register_dl_fini_callback(NULL, dl_fini_post, NULL)) {
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
__android_log_print(ANDROID_LOG_WARN, "test", "dl_fini failed: %d - %s", error_num, error_msg);
}
}
0
: Success.1
: "Hook executed by library name + function name" cannot be completed because the dynamic library is not loaded, and the current hook request is in the pending state.- Other: Various other errors.
package com.bytedance.shadowhook;
public class ShadowHook
public static String toErrmsg(int errno)
init
returns "non-0
", indicating that initialization failed, and the return value ofinit
is errno.- The error message corresponding to errno can be obtained through
toErrmsg
.
#include "shadowhook.h"
int shadowhook_get_errno(void);
const char *shadowhook_to_errmsg(int error_number);
shadowhook_init
returns "non-0
", which means initialization failed. At this point, the return value ofshadowhook_init
is errno.shadowhook_hook_*
returnsNULL
for failure;shadowhook_unhook
returns-1
for failure. At this point, errno can be obtained throughshadowhook_get_errno
.- The error message corresponding to errno can be obtained through
shadowhook_to_errmsg
.
- ShadowHook will record the operation information of hook / unhook in memory.
- These operation records can be obtained at the java layer or native layer, and they are returned as strings, in row units, and comma-separated information items.
- The specific information items and order that can be returned are as follows:
Order | Name | Description | Remark |
---|---|---|---|
1 | TIMESTAMP | Timestamp | Format:YYYY-MM-DDThh:mm:ss.sss+hh:mm |
2 | CALLER_LIB_NAME | Caller dynamic library name | basename |
3 | OP | Operation type | hook_sym_addr / hook_sym_name / unhook / error |
4 | LIB_NAME | The name of the dynamic library where the target function is located | only valid for hooks |
5 | SYM_NAME | Target function name | only valid for hooks |
6 | SYM_ADDR | Target function address | only valid for hooks |
7 | NEW_ADDR | Proxy function address | only valid for hooks |
8 | BACKUP_LEN | The instruction length of the overridden function header | only valid for hooks。arm32:4 / 8 / 10;arm64:4 / 16 |
9 | ERRNO | Error number | |
10 | STUB | The stub returned by the hook | pointer type value, hook and unhook can be paired by this value |
package com.bytedance.shadowhook;
public class ShadowHook
public static String getRecords(RecordItem... recordItems)
public enum RecordItem {
TIMESTAMP,
CALLER_LIB_NAME,
OP,
LIB_NAME,
SYM_NAME,
SYM_ADDR,
NEW_ADDR,
BACKUP_LEN,
ERRNO,
STUB
}
- If you call
getRecords
without passing any parameters, it means to get all the information items. - If you only want to collect statistics about hook/unhook operation information and errno of some app, it is more convenient to use java API.
#include "shadowhook.h"
#define SHADOWHOOK_RECORD_ITEM_ALL 0x3FF // 0b1111111111
#define SHADOWHOOK_RECORD_ITEM_TIMESTAMP (1 << 0)
#define SHADOWHOOK_RECORD_ITEM_CALLER_LIB_NAME (1 << 1)
#define SHADOWHOOK_RECORD_ITEM_OP (1 << 2)
#define SHADOWHOOK_RECORD_ITEM_LIB_NAME (1 << 3)
#define SHADOWHOOK_RECORD_ITEM_SYM_NAME (1 << 4)
#define SHADOWHOOK_RECORD_ITEM_SYM_ADDR (1 << 5)
#define SHADOWHOOK_RECORD_ITEM_NEW_ADDR (1 << 6)
#define SHADOWHOOK_RECORD_ITEM_BACKUP_LEN (1 << 7)
#define SHADOWHOOK_RECORD_ITEM_ERRNO (1 << 8)
#define SHADOWHOOK_RECORD_ITEM_STUB (1 << 9)
char *shadowhook_get_records(uint32_t item_flags);
void shadowhook_dump_records(int fd, uint32_t item_flags);
- Through the
item_flags
parameter, you can specify which information items need to be returned. When you need to return all information items, you can useSHADOWHOOK_RECORD_ITEM_ALL
. - The buffer returned by
shadowhook_get_records
is allocated withmalloc
, you need to usefree
to free it. shadowhook_dump_records
will write the content tofd
, this function is async-signal safety, you can use it in the signal handler. For example, after a native crash, callshadowhook_dump_records
in the signal handler to write hook/unhook operation information to your own tombstone file.