fake-linker
是一款为 android
应用程序提供Linker 修改
, PLT Hook
, Java Native Hook
等功能的框架, 其实现原理是进程内动态查找和修改 Linker
数据, 对外提供基于 类LD_PRELOAD
模式的 PLT hook
和各种操作 soinfo
, namespace
的接口. 原理分析请查看Android 动态修改 Linker 实现 LD_PRELOAD 全局库 PLT Hook
支持 Android 5.0 ~ Android 14+
设备的 x86
, x86_64
, arm
, arm64
指令集, 支持鸿蒙系统 2.x
和 3.x
版本, 3.x
之后系统未测试.
现在提供全新的 v3.0.0+
版本, 主要更新如下:
- 移除不同
Android Api Level
使用不同的libfakelinker.so
, 现在内部采用函数表的方式自定适配不同Android
版本 - 新增稳定的
C/C++
FakeLinker 函数表, 后续版本保证向前兼容, 不会改变FakeLinker
已有的函数指针偏移, 可通过FakeLinker.get_fakelinker_version
获取当前版本号 - 新增
fake-linker
静态库支持, 可自定义fake-linker
以及仅使用其中的 elf_reader.h, jni_helper.h, maps_util.h 等接口, 可实现fake-linker
自身也是hook
模块, 减少so
数量 - 可选的
Java FakeLinker Api
, 新版不再强制需要Java Api
可按需删除, 也可以自定义类名动态注册Java Api
以下是针对子项目描述
library
项目是fake-linker
的核心实现, 提供fakelinker
静态库与动态库, 注意链接fakelinker
动态库时仅可使用fake_linker.h
头文件, 其它头文件需要链接fakelinker_static
静态库才支持emulator-testapp
是在模拟器中测试fake-linker
功能,支持houdini
架构,即虽然采用动态翻译支持arm
架构,但实际是可以加载x86
/x64
的库,因此实际加载fake-linker
的x86
/x64
架构来提供功能fakelinker-test
是正常apk
测试x86
,x86_64
,arm
,arm64
架构,包含androidTest
与可执行测试程序,androidTest
可以在导入Android Studio
中通过UI
运行FakelinkerGTest
或执行命令行./gradlew :fakelinker-test:connectedDebugAndroidTest
连接Android
手机测试. 可执行测试程序则通过adb shell
推送fake-linker\fakelinker-test\src\main\cpp\build\Debug\xxxx\fakelinker_static_test
到手机中添加可执行权限然后直接运行测试Stub
项目仅提供私有api
接口, 不打包到apk
中
-
源码构建
参考 local.properties.sample 说明配置构建参数, 然后重命名为
local.properties
, 后续将项目导入Android Studio
或直接gradle
命令行编译, 可以参考 工作流注意新版本添加了
BaseDexClassLoader.getLdLibraryPath
,BaseDexClassLoader.addNativePath
私有 api,在增量编译时可能报错...fake-linker\library\src\main\java\com\sanfengandroid\fakelinker\FakeLinker.java:153: error: cannot find symbol ((BaseDexClassLoader) loader).addNativePath(paths);
可以清理下工程或者命令行调用 gradlew 添加 --rerun-tasks 参数清除构建缓存再编译,后续只要不改动
library
项目代码编译成功后就不会影响增量编译 -
使用构建库
下载最新 Release 版本
aar
或最新测试版本 Action 工件提取 aar 文件作为库添加至项目依赖, 开启 prefab 功能// build.gradle android{ buildFeatures { prefab true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) }
然后在
CMakeLists.txt
中使用find_package(FakeLinker REQUIRED CONFIG) ... target_link_libraries( your_target PRIVATE # 动态库依赖 FakeLinker::fakelinker # 静态库依赖 # FakeLinker::fakelinker_static )
-
CMake 集成
使用
FetchContent
直接远程下载构建,添加以下代码到CMakeLists.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)
然后在
CMakeLists.txt
中添加fakelinker
模块的依赖target_link_libraries( your_target PRIVATE # 动态库依赖 fakelinker # 静态库依赖 # fakelinker_static )
注意: 该方式只使用了
fake-linker
的native
库, 没有使用Java
代码 FakeLinker 和 ErrorCode, 可自行拷贝文件到项目, 也可仅使用native
接口
使用 aar
包导入和 CMake
集成方式均已导入了 fakelinker
依赖, 因此直接在 CMakeLists.txt
中链接 fakelinker
动态库
target_link_libraries(
your_target
PRIVATE
# 动态库依赖
fakelinker
# 静态库依赖
# fakelinker_static
)
因为模块已经对 fakelinker
有依赖, 所以有两种加载方式, 一种是采用旧版本 Java 层调用 FakeLinker
下的初始化方法, 方式二是直接 System.loadLibrary(hookModule)
它依赖 libfakelinker.so
会自动加载初始化fakelinker
本质是加载 libfakelinker.so
和 hook
模块, 但由于使用的方式不同因此加载的方式不同, 当初始化 libfakelinker.so
后会自动加载 hook
模块并回调 fakelinker_module_init
方法, 注意:当fake-linker
初始化失败时不会加载和回调hook
模块
-
项目自身使用
可直接调用
FakeLinker.initFakeLinker
从apk
内加载 -
Xposed
模块使用LSPosed 框架内部已处理了
native
库搜索路径, 因此只需在build.gradle
中以下配置, 关闭so
压缩(它在minSdk >= 23
默认开启),然后调用FakeLinker.initFakeLinker
从Xposed
模块APK
中加载android { packagingOptions { jniLibs { useLegacyPackaging true } } }
非
LSPosed
框架或低版本则需要设置native
库搜索路径然后加载, 见方式 3 -
动态设置
native
库搜索路径v3.1.0+
版本提供 FakeLinker.addHookApkNativePath 或 FakeLinker.addNativeLibraryPath 接口更改类加载器的native
库搜索路径, 默认类加载器是FakeLinker
类所属的类加载器, 因为从FakeLinker
调用加载保持默认即可, 当然也可以调用它修改其它类加载器的搜索路径, 设置好fakelinker
和hook
模块的搜索路径再调用FakeLinker.initFakeLinker
初始化即可 -
手动安装库到指定位置,将
fake-linker
和Hook
模块安装到应用有权限访问的路径,如:/data/local/tmp
, 然后直接绝对路径加载, 不推荐可以设置native
库搜索路径后加载
-
包含
fake_linker.h
头文件实现fakelinker_module_init
方法, 需要导出fakelinker_module_init
该方法, 否则fakelinker
无法回调它#include <fake_linker.h> C_API API_PUBLIC void fakelinker_module_init(JNIEnv *env, SoinfoPtr fake_soinfo, const FakeLinker *fake_linker){ // 在这里设置全局 so, 重定位已加载的 so 等等操作 }
由于 hook
模块依赖 libfakelinker.so
, 所以可以直接调用 System.loadLibrary("hook-module")
自动初始化 fakelinker
, 该方式可以完全不需要 FakeLinker
相关的 Java
代码, 因为所有功能都只是对内部的 native FakeLinker
结构体包装, 使用方式与方式一有区别
直接加载 hook
模块
try{
System.loadLibrary("hook-module");
}catch(UnsatisfiedLinkError e){
// 检查错误
}
此时已自动初始化了 fakelinker
并默认注册 com/sanfengandroid/fakelinker/FakeLinker
类下的 native
方法, 如果不存在则不会注册
该方式不再需要 fakelinker_module_init
函数, 直接通过 get_fakelinker
获取 const FakeLinker *
指针, 在使用之前应确保 fake_linker->is_init_success()
初始化成功才使用
// hook_module.cpp
C_API API_PUBLIC jint JNI_OnLoad(JavaVM *vm, void *reserved){
const FakeLinker *fake_linker = get_fakelinker();
// 获取 FakeLinker 指针并检查初始化是否成功
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
async_safe_fatal("JNI environment error");
}
// 初始化 fake-linker
init_fakelinker(env, static_cast<FakeLinkerMode>(FakeLinkerMode::kFMSoinfo |FakeLinkerMode::kFMNativeHook |FakeLinkerMode::kFMJavaRegister), nullptr);
if(fake_linker->is_init_success()){
// 获取自身 soinfo
int error;
SoinfoPtr thiz = fake_linker->soinfo_find(SoinfoFindType::kSTAddress, nullptr, &error);
if(thiz){
// 同方式一设置全局 so, 重定位已加载的 so 等等操作
...
}
// 调用 init_fakelinker 初始化判断 native hook 是否成功
if(init_fakelinker(env, FakeLinkerMode::kFMNativeHook, nullptr) == 0){
LOGI("native hook init success");
}
// 注册自定义Java api
if(init_fakelinker(env, FakeLinkerMode::kFMJavaRegister, "java/to/your/class") == 0){
LOGI("register custom java api success");
}
}else{
// 错误处理,不能使用 fake_linker 相关方法
}
return JNI_VERSION_1_6;
}
从 v3.1.0
起提供 fakelinker_static
静态库, 可以自定义 fakelinker
或使用它提供的部分 native api
, 静态连接不包含 JNI_OnLoad
因此需要自行初始化, 可以按需调用 init_fakelinker
初始化相应功能, 请在确定初始化成功后才使用对应功能, 使用方式类似上面的方式二,常用代码如下
#include <fake_linker.h>
C_API API_PUBLIC jint JNI_OnLoad(JavaVM *vm, void *reserved){
const FakeLinker *fake_linker = get_fakelinker();
// 获取 FakeLinker 指针并检查初始化是否成功
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){
// 初始化成功
}
}
静态库链接的另一种常见用法是 fake-linker
既是 fake-linker
框架,又是 hook
模块, 初始化后将自身设置为全局 so
即可生效.
- 项目不同于直接设置
LD_PRELOAD
环境变量, 直接设置通常无法拦截dlsym
方法, 因为一旦拦截则需要自己实现查找符号且高版本还有caller
地址限制, 而该模块通过中转模块fake-linker
提供调用dlsym
方法, 因此hook
模块可以拦截dlsym
, 且提供更多Linker
相关功能
- 在使用
Xposed
等类似框架Hook
系统进程时请做好备份删除工作, 避免因为出错导致系统进程死亡无法开机的情况 - 根据模块加载时机, 重定位已经加载的模块, 通常加载
hook
模块时一些系统库已经加载, 如libjavacore.so
,libnativehelper.so
,libnativeloader.so
,libart.so
,libopenjdk.so
等等, 要想这些库生效则需要native
中手动调用FakeLinker.call_manual_relocation_by_names
等方法 - 将
hook
模块设为全局库, 后续加载的so
才会自动生效 - 在使用
fake-linker
相关功能时应先 调用init_fakelinker
判断对应功能是否初始化成功,init_fakelinker
可重复调用并且不会重复初始化
-
对已经加载的
so
执行重定位, 使hook
模块生效void 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); }
-
将
hook
模块设置为全局库C_API API_PUBLIC jint JNI_OnLoad(JavaVM *vm, void *reserved) { const FakeLinker *fake_linker = get_fakelinker(); // 获取 FakeLinker 指针并检查初始化是否成功 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) { // 初始化成功 if (SoinfoPtr thiz = fake_linker->soinfo_find(SoinfoFindType::kSTAddress, nullptr, nullptr)) { // 设置hook模块为全局库,之后所有 so 加载 hook 均会生效 if (fake_linker->soinfo_add_to_global(thiz)) { LOG("soinfo add to global success"); } } } } // 当已经对需要的 so hook 生效之后可以取消全局 so 设置,它不会影响已加载的库 fake_linker->soinfo_remove_global(thiz);
-
其它更多使用 参考 FakeLinker 结构体中方法指针, 现承诺自
v3.1.0
版本起保持api
兼容