fake-linker is a framework that provides features such as Linker modification, PLT Hook, and Java Native Hook for android applications. Its implementation principle involves dynamically searching for and modifying Linker data within the process. It offers PLT hook based on a LD_PRELOAD-like mode and various interfaces for operating on soinfo and namespace. For a detailed analysis of the principle, please refer to Android Dynamic Modification of Linker to Implement LD_PRELOAD Global Library PLT Hook.
Supports Android 5.0 ~ Android 14+ devices with x86, x86_64, arm, and arm64 instruction sets. It also supports HarmonyOS 2.x and 3.x versions, with versions beyond 3.x untested.
We now offer the new v3.0.0+ version with the following major updates:
- Removed the need for different
libfakelinker.sofiles for differentAndroid Api Levels. The internal function table now automatically adapts to differentAndroidversions. - Introduced a stable C/C++ FakeLinker function table. Future versions guarantee backward compatibility and will not change the function pointer offsets of
FakeLinker. The current version number can be obtained viaFakeLinker.get_fakelinker_version. - Added support for the
fake-linkerstatic library, allowing customization offake-linkerand usage of interfaces such as elf_reader.h, jni_helper.h, maps_util.h individually. This enablesfake-linkerto act as a hook module and reduces the number ofsofiles. - Optional
Java FakeLinker API. The new version no longer requires theJava APImandatorily; it can be deleted as needed. You can also customize the class name for dynamic registration of theJava API.
Below is the description for the sub-projects:
- The
libraryproject is the core implementation offake-linker, providing both static and dynamic libraries forfakelinker. Note that when linking thefakelinkerdynamic library, only thefake_linker.hheader file can be used. Other header files require linking to thefakelinker_staticstatic library for support. - The
emulator-testappproject is used to test the functionality offake-linkerin an emulator. It supports thehoudiniarchitecture, meaning that despite using dynamic translation to support thearmarchitecture, it can actually loadx86/x64libraries. Therefore, it effectively uses thex86/x64architecture offake-linkerto provide functionality. - The
fakelinker-testproject is for normalapktesting onx86,x86_64,arm,arm64architectures. It includesandroidTestand executable test programs.androidTestcan be run via theUIinAndroid Studioby importing the project and runningFakelinkerGTest, or by executing the command./gradlew :fakelinker-test:connectedDebugAndroidTestto connect to anAndroiddevice for testing. The executable test programs can be pushed to a device viaadb shelland executed after adding executable permissions, using the pathfake-linker\fakelinker-test\src\main\cpp\build\Debug\xxxx\fakelinker_static_test. - The
Stubproject only provides privateapiinterfaces and is not packaged into theapk.
-
Source Code Build
Refer to the local.properties.sample for configuration parameters, then rename it to
local.properties. After that, you can import the project intoAndroid Studioor compile it directly using thegradlecommand line. For reference, see the workflow.Note: The new version adds the private APIs
BaseDexClassLoader.getLdLibraryPathandBaseDexClassLoader.addNativePath. Incremental builds might cause errors....fake-linker\library\src\main\java\com\sanfengandroid\fakelinker\FakeLinker.java:153: error: cannot find symbol ((BaseDexClassLoader) loader).addNativePath(paths);
You can clean the project or use the
gradlewcommand with the--rerun-tasksparameter to clear the build cache and then compile again. Once thelibraryproject code is compiled successfully, subsequent incremental builds should not be affected. -
Using the Built Library
Download the latest Release version
aaror the latest test version Action artifact to extract aar file as a library and add it to your project dependencies, enabling the prefab feature.// build.gradle android { buildFeatures { prefab true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) }
Then use the following in
CMakeLists.txt:find_package(FakeLinker REQUIRED CONFIG) ... target_link_libraries( your_target PRIVATE # Dynamic library dependency FakeLinker::fakelinker # Static library dependency # FakeLinker::fakelinker_static )
-
CMake Integration
Use
FetchContentto directly download and build from a remote source by adding the following code to yourCMakeLists.txt:include(FetchContent) FetchContent_Declare( fakelinker GIT_REPOSITORY https://github.com/sanfengAndroid/fake-linker.git GIT_TAG main SOURCE_SUBDIR library/src/main/cpp ) FetchContent_MakeAvailable(fakelinker)Then, add the
fakelinkermodule dependency in yourCMakeLists.txt:target_link_libraries( your_target PRIVATE # Dynamic library dependency fakelinker # Static library dependency # fakelinker_static )
Note: This method only uses the
nativelibrary offake-linkerand does not use theJavacode FakeLinker and ErrorCode. You can manually copy these files to your project or use only thenativeinterfaces.
Both the aar package import and CMake integration methods have already imported the fakelinker dependency. Therefore, you can directly link the fakelinker dynamic library in your CMakeLists.txt:
target_link_libraries(
your_target
PRIVATE
# Dynamic library dependency
fakelinker
# Static library dependency
# fakelinker_static
)Since the module already depends on fakelinker, there are two ways to load it. One method is to use the old version's Java layer to call the initialization method under FakeLinker. The second method is to directly use System.loadLibrary(hookModule). It relies on libfakelinker.so, which will automatically load and initialize fakelinker.
Essentially, this involves loading libfakelinker.so and the hook module. However, due to the different methods used, the loading process varies. When libfakelinker.so is initialized, it will automatically load the hook module and call back the fakelinker_module_init method. Note: If the initialization of fake-linker fails, it will not load or call back the hook module.
-
Project Self-Usage
You can directly call
FakeLinker.initFakeLinkerto load it from theapk. -
Usage in
XposedModulesThe LSPosed framework has already handled the native library search paths internally. Therefore, you only need to configure the following settings in the
build.gradlefile to disablesocompression (which is enabled by default whenminSdk >= 23), and then callFakeLinker.initFakeLinkerto load from theXposedmoduleAPK.android { packagingOptions { jniLibs { useLegacyPackaging true } } }
For
non-LSPosedframeworks or lower versions, you need to set the native library search path and then load it. See method 3 for details. -
Dynamically Setting the Native Library Search Path
Version
v3.1.0+provides the interfaces FakeLinker.addHookApkNativePath or FakeLinker.addNativeLibraryPath to change the native library search path of the classloader. The default classloader is the one associated with theFakeLinkerclass. Since the loading call is fromFakeLinker, you can keep the default. You can also call it to change the search path of other classloaders. Set the search paths forfakelinkerand the hook module, then callFakeLinker.initFakeLinkerto initialize. -
Manually Installing Libraries to a Specified Location
Install the
fake-linkerand Hook modules to a path accessible by the application, such as/data/local/tmp, and then load it directly using the absolute path. It is not recommended if you can set the native library search path and then load.
-
Include the
fake_linker.hheader file and implement thefakelinker_module_initmethod. This method needs to be exported; otherwise,fakelinkercannot call it back.#include <fake_linker.h> C_API API_PUBLIC void fakelinker_module_init(JNIEnv *env, SoinfoPtr fake_soinfo, const FakeLinker *fake_linker) { // Set global so here, relocate already loaded so, etc. }
Since the hook module depends on libfakelinker.so, you can directly call System.loadLibrary("hook-module") to automatically initialize fakelinker. This method does not require any Java code related to FakeLinker since all functionalities are merely wrappers around the internal native FakeLinker structure. This usage differs from Method One.
Directly load the hook module:
try {
System.loadLibrary("hook-module");
} catch (UnsatisfiedLinkError e) {
// Handle the error
}At this point, fakelinker is automatically initialized and the native methods under the com/sanfengandroid/fakelinker/FakeLinker class are registered by default. If they do not exist, they will not be registered.
This method no longer requires the fakelinker_module_init function. Instead, you can directly obtain the const FakeLinker* pointer through get_fakelinker and ensure that fake_linker->is_init_success() returns true before using it.
// hook_module.cpp
C_API API_PUBLIC jint JNI_OnLoad(JavaVM *vm, void *reserved) {
const FakeLinker *fake_linker = get_fakelinker();
// Obtain the FakeLinker pointer and check if initialization is successful
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
async_safe_fatal("JNI environment error");
}
// Initialize fake-linker
init_fakelinker(env, static_cast<FakeLinkerMode>(FakeLinkerMode::kFMSoinfo | FakeLinkerMode::kFMNativeHook | FakeLinkerMode::kFMJavaRegister), nullptr);
if (fake_linker->is_init_success()) {
// Get own soinfo
int error;
SoinfoPtr thiz = fake_linker->soinfo_find(SoinfoFindType::kSTAddress, nullptr, &error);
if (thiz) {
// Similar to Method One, set global SO, relocate already loaded SOs, etc.
...
}
// Call init_fakelinker to check if native hook initialization is successful
if (init_fakelinker(env, FakeLinkerMode::kFMNativeHook, nullptr) == 0) {
LOGI("native hook init success");
}
// Register custom Java API
if (init_fakelinker(env, FakeLinkerMode::kFMJavaRegister, "java/to/your/class") == 0) {
LOGI("register custom java api success");
}
} else {
// Error handling, cannot use fake_linker related methods
}
return JNI_VERSION_1_6;
}Starting from version v3.1.0, the fakelinker_static static library is available, which allows you to customize fakelinker or use some of its provided native APIs. Static linking does not include JNI_OnLoad, so you need to initialize it manually. You can call init_fakelinker to initialize the required functionalities as needed. Please ensure that the initialization is successful before using the corresponding functions. The usage is similar to Method Two mentioned above. Common code is as follows:
#include <fake_linker.h>
C_API API_PUBLIC jint JNI_OnLoad(JavaVM *vm, void *reserved) {
const FakeLinker *fake_linker = get_fakelinker();
// Obtain the FakeLinker pointer and check if initialization is successful
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
async_safe_fatal("JNI environment error");
}
if (init_fakelinker(env, static_cast<FakeLinkerMode>(FakeLinkerMode::kFMSoinfo | FakeLinkerMode::kFMNativeHook | FakeLinkerMode::kFMJavaRegister), nullptr) == 0) {
// Initialization successful
}
}Another common use of static library linking is for the fake-linker to act both as the fake-linker framework and the hook module. After initialization, it can be set as the global so to take effect.
- Unlike directly setting the
LD_PRELOADenvironment variable, which typically cannot intercept thedlsymmethod since intercepting it would require implementing symbol lookup manually (with higher versions also imposing caller address restrictions), this module provides adlsymcall through the intermediaryfake-linkermodule. Thus, thehookmodule can interceptdlsymand offer moreLinker-relatedfunctionalities.
- When using frameworks like
Xposedto hook system processes, please ensure you have backup and removal measures in place to avoid system process crashes that might prevent the device from booting. - Depending on the module loading timing, relocate already loaded modules. Typically, when loading the
hookmodule, some system libraries have already been loaded, such aslibjavacore.so,libnativehelper.so,libnativeloader.so,libart.so,libopenjdk.so, etc. To make these libraries effective, you need to manually call methods likeFakeLinker.call_manual_relocation_by_namesin native code. - Set the
hookmodule as a global library so that subsequently loadedsofiles will automatically take effect. - Before using any
fake-linkerrelated functionalities, ensure you callinit_fakelinkerto check if the corresponding functionality is successfully initialized.init_fakelinkercan be called multiple times without causing reinitialization.
-
Relocate already loaded
sofiles to make thehookmodule effectivevoid fakelinker_module_init(JNIEnv *env, SoinfoPtr fake_soinfo, const FakeLinker *fake_linker) { const char* loaded_libs[] = { "libart.so", "libopenjdk.so", "libnativehelper.so", "libjavacore.so", }; fake_linker->call_manual_relocation_by_names(fake_soinfo, 4, loaded_libs); }
-
Set the
hookmodule as a global libraryC_API API_PUBLIC jint JNI_OnLoad(JavaVM *vm, void *reserved) { const FakeLinker *fake_linker = get_fakelinker(); // Obtain the FakeLinker pointer and check if initialization is successful JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) { async_safe_fatal("JNI environment error"); } if (init_fakelinker(env, static_cast<FakeLinkerMode>(FakeLinkerMode::kFMSoinfo | FakeLinkerMode::kFMNativeHook | FakeLinkerMode::kFMJavaRegister), nullptr) == 0) { // Initialization successful if (SoinfoPtr thiz = fake_linker->soinfo_find(SoinfoFindType::kSTAddress, nullptr, nullptr)) { // Set the hook module as a global library; subsequent `so` loads will trigger the hook if (fake_linker->soinfo_add_to_global(thiz)) { LOG("soinfo add to global success"); } } } return JNI_VERSION_1_6; } // After the necessary hooks have taken effect, you can remove the global so setting. // This will not affect already loaded libraries. fake_linker->soinfo_remove_global(thiz);
-
For more usage examples, refer to the method pointers in the FakeLinker struct. We promise to maintain API compatibility starting from version
v3.1.0.