Android Protectors 是一个用于研究和演示 Android Native 保护技术的示例项目。当前重点模块是 jnionload,实现了基于 ELF .dynsym 的 JNI_OnLoad 入口地址篡改方案,用于展示 Android linker 加载 .so 时的初始化顺序、动态符号表解析、内存权限修改以及 JNI 入口路由。
说明:本项目用于 Android 安全研究、加固原理学习和防护技术验证,请仅在合法授权的环境中使用。
android-protectors/
├── app/ # 示例 Android App,用于触发和验证保护模块
├── jnionload/ # JNI_OnLoad 篡改技术模块
│ ├── readme.md # 技术原理说明
│ └── src/main/cpp/ # Native 实现
├── gradle/ # Gradle Version Catalog
├── build.gradle.kts # 根 Gradle 配置
└── settings.gradle.kts # 模块配置
jnionload 模块实现了一个完整的 JNI_OnLoad .dynsym 地址篡改流程:
- 通过
__attribute__((constructor))注册.init_array构造函数。 - Android linker 在调用
JNI_OnLoad之前先执行.init_array。 - 构造函数解析当前
.so的 ELF Header 和 Program Header。 - 定位
PT_DYNAMIC,解析DT_SYMTAB、DT_STRTAB、DT_STRSZ、DT_GNU_HASH/DT_HASH。 - 在
.dynsym中查找导出的JNI_OnLoad符号。 - 使用
mprotect临时修改.dynsym所在页权限。 - 将
JNI_OnLoad的st_value从原始函数地址改为fake_JNI_OnLoad。 - linker 后续通过
dlsym(handle, "JNI_OnLoad")查询时,会拿到被篡改后的入口。 fake_JNI_OnLoad先执行保护逻辑,再委托给real_JNI_OnLoad。
app 模块依赖 :jnionload,在 MainActivity 中调用:
JniOnLoadLibrary.init()该调用会触发:
System.loadLibrary("jnionload")
↓
linker 加载 libjnionload.so
↓
执行 .init_array
↓
篡改 .dynsym 中 JNI_OnLoad 的 st_value
↓
dlsym 查询 JNI_OnLoad
↓
实际进入 fake_JNI_OnLoad
↓
委托 real_JNI_OnLoad
| 文件 | 说明 |
|---|---|
jnionload/src/main/cpp/jni_onload_tamper.c |
核心 Native 实现 |
jnionload/src/main/cpp/CMakeLists.txt |
CMake 构建配置 |
jnionload/src/main/java/com/demo/jnionload/JniOnLoadLibrary.kt |
Kotlin 加载入口 |
app/src/main/java/com/demo/android_protectors/MainActivity.kt |
示例调用入口 |
jnionload/readme.md |
.dynsym 篡改技术原理说明 |
- Android Studio
- Android Gradle Plugin
9.0.1 - Gradle Wrapper
- CMake
3.22.1 - Android NDK
- minSdk
24 - compileSdk
36.1
在项目根目录执行:
./gradlew assembleDebug仅构建 native 模块:
./gradlew :jnionload:externalNativeBuildDebug安装并运行 app 后,使用 logcat 过滤以下 tag:
JniOnLoadTamper
JniOnLoadLibrary
MainActivity
如果篡改成功,会看到类似日志:
[init_array] +++ .init_array executing +++
[patch] Self base address: ...
[patch] DT_SYMTAB: ...
[patch] DT_STRTAB: ...
[patch] DT_GNU_HASH: nsyms = ...
[patch] Found 'JNI_OnLoad' at symbol index ...
[patch] ★★★ st_value PATCHED: ... -> ... ★★★
[fake_JNI_OnLoad] +++ Wrapper JNI_OnLoad called +++
[real_JNI_OnLoad] Real JNI_OnLoad executing
如果看到:
[JNI_OnLoad] WARNING: Original JNI_OnLoad called directly!
说明 .dynsym patch 没有生效,程序会走 fallback 路径,仍然调用 real_JNI_OnLoad。
- Android linker 对
.dynamic中d_ptr的处理在不同环境下可能存在差异,代码中同时兼容d_ptr已重定位和未重定位两种情况。 .gnu.hash在 ELF64 中的 bloom filter 是Elf64_Addr[],不是uint32_t[]。- 遍历
.dynsym和.dynstr时需要结合/proc/self/maps、DT_STRSZ和边界检查,避免越界访问导致 SIGSEGV。 - 修改
.dynsym前需要对目标页执行mprotect(PROT_READ | PROT_WRITE)。 - 修改完成后恢复页面为只读,降低异常风险。
本项目代码仅用于:
- Android Native 加固机制学习
- ELF/linker 行为研究
- 自有 App 的安全防护验证
- 安全课程、实验室或授权测试环境
请勿将本项目用于未授权的应用篡改、绕过检测或攻击行为。
本项目使用 MIT License,详见 LICENSE。