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.so
files for differentAndroid Api Levels
. The internal function table now automatically adapts to differentAndroid
versions. - 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-linker
static library, allowing customization offake-linker
and usage of interfaces such as elf_reader.h, jni_helper.h, maps_util.h individually. This enablesfake-linker
to act as a hook module and reduces the number ofso
files. - Optional
Java FakeLinker API
. The new version no longer requires theJava API
mandatorily; 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
library
project is the core implementation offake-linker
, providing both static and dynamic libraries forfakelinker
. Note that when linking thefakelinker
dynamic library, only thefake_linker.h
header file can be used. Other header files require linking to thefakelinker_static
static library for support. - The
emulator-testapp
project is used to test the functionality offake-linker
in an emulator. It supports thehoudini
architecture, meaning that despite using dynamic translation to support thearm
architecture, it can actually loadx86
/x64
libraries. Therefore, it effectively uses thex86
/x64
architecture offake-linker
to provide functionality. - The
fakelinker-test
project is for normalapk
testing onx86
,x86_64
,arm
,arm64
architectures. It includesandroidTest
and executable test programs.androidTest
can be run via theUI
inAndroid Studio
by importing the project and runningFakelinkerGTest
, or by executing the command./gradlew :fakelinker-test:connectedDebugAndroidTest
to connect to anAndroid
device for testing. The executable test programs can be pushed to a device viaadb shell
and executed after adding executable permissions, using the pathfake-linker\fakelinker-test\src\main\cpp\build\Debug\xxxx\fakelinker_static_test
. - The
Stub
project only provides privateapi
interfaces 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 Studio
or compile it directly using thegradle
command line. For reference, see the workflow.Note: The new version adds the private APIs
BaseDexClassLoader.getLdLibraryPath
andBaseDexClassLoader.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
gradlew
command with the--rerun-tasks
parameter to clear the build cache and then compile again. Once thelibrary
project code is compiled successfully, subsequent incremental builds should not be affected. -
Using the Built Library
Download the latest Release version
aar
or 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
FetchContent
to 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
fakelinker
module 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
native
library offake-linker
and does not use theJava
code FakeLinker and ErrorCode. You can manually copy these files to your project or use only thenative
interfaces.
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.initFakeLinker
to load it from theapk
. -
Usage in
Xposed
ModulesThe LSPosed framework has already handled the native library search paths internally. Therefore, you only need to configure the following settings in the
build.gradle
file to disableso
compression (which is enabled by default whenminSdk >= 23
), and then callFakeLinker.initFakeLinker
to load from theXposed
moduleAPK
.android { packagingOptions { jniLibs { useLegacyPackaging true } } }
For
non-LSPosed
frameworks 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 theFakeLinker
class. 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 forfakelinker
and the hook module, then callFakeLinker.initFakeLinker
to initialize. -
Manually Installing Libraries to a Specified Location
Install the
fake-linker
and 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.h
header file and implement thefakelinker_module_init
method. This method needs to be exported; otherwise,fakelinker
cannot 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_PRELOAD
environment variable, which typically cannot intercept thedlsym
method since intercepting it would require implementing symbol lookup manually (with higher versions also imposing caller address restrictions), this module provides adlsym
call through the intermediaryfake-linker
module. Thus, thehook
module can interceptdlsym
and offer moreLinker-related
functionalities.
- When using frameworks like
Xposed
to 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
hook
module, 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_names
in native code. - Set the
hook
module as a global library so that subsequently loadedso
files will automatically take effect. - Before using any
fake-linker
related functionalities, ensure you callinit_fakelinker
to check if the corresponding functionality is successfully initialized.init_fakelinker
can be called multiple times without causing reinitialization.
-
Relocate already loaded
so
files to make thehook
module 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
hook
module 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
.