news 2026/5/4 13:47:36

安卓逆向04. Native 逆向、JNI 定位与 so 分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
安卓逆向04. Native 逆向、JNI 定位与 so 分析

本章目标是理解 Android App 中的 Native 层:如何发现.so,如何定位 JNI 入口,如何用静态工具分析二进制,如何用 Frida 在授权 Demo 上观察 Native 函数行为。

1. 为什么需要 Native 逆向

很多 App 会把关键逻辑放到 Native 层:

场景为什么放 Native逆向关注点
加密签名增加 Java 层分析难度JNI 调用、密钥来源、算法参数
环境检测检查 root、debug、frida、模拟器系统调用、文件路径、进程信息
完整性校验校验 APK、DEX、签名、so 自身哈希计算、签名证书摘要
性能模块图像、音视频、算法、游戏逻辑输入输出、边界条件
第三方 SDK风控、支付、广告、安全 SDKSDK 行为和数据采集边界

Native 层不是绝对安全,只是提高分析门槛。只要逻辑在客户端执行,就能被观察、调试、Hook 或模拟。

2. so 文件识别

2.1 从 APK 中发现 Native 库

unzip-lapp-debug.apk|grep"lib/.*\\.so"mkdir-pcase-reversedemo/04-nativeunzip-papp-debug.apk lib/arm64-v8a/libreversedemo.so>case-reversedemo/04-native/libreversedemo.sofilecase-reversedemo/04-native/libreversedemo.so

ABI 说明:

ABI说明
arm64-v8a64 位 ARM,现代真机常见
armeabi-v7a32 位 ARM,旧设备或兼容包
x86/x86_64模拟器常见

分析时要确认设备运行的是哪个 ABI 的 so。Hook 地址、寄存器和调用约定会受架构影响。

2.2 基础命令

readelf-hlibreversedemo.so readelf-slibreversedemo.so|grepJava_ readelf-dlibreversedemo.so strings libreversedemo.so|head-100nm-Dlibreversedemo.so|grepJava_

检查重点:

命令看什么
readelf -hELF 架构、位数、端序
readelf -s符号表,是否导出 JNI 函数
readelf -d依赖库
strings字符串线索、URL、路径、错误信息
nm -D动态符号,导出函数

3. JNI 定位方法

3.1 静态注册 JNI

Java/Kotlin:

classNativeGuard{externalfunnativeCheck():Booleancompanionobject{init{System.loadLibrary("reversedemo")}}}

Native:

extern"C"JNIEXPORT jboolean JNICALLJava_com_example_reversedemo_NativeGuard_nativeCheck(JNIEnv*,jobject){returntrue;}

静态注册特点:

  • 函数名包含Java_包名_类名_方法名
  • readelf -snm -D可能直接看到符号。
  • jadx 中能从System.loadLibraryexternal fun反向定位。

3.2 动态注册 JNI

Native 代码可能通过RegisterNatives注册:

staticJNINativeMethod methods[]={{"nativeCheck","()Z",(void*)native_check_impl}};

动态注册特点:

  • 导出符号里可能看不到完整 Java 风格函数名。
  • 需要找JNI_OnLoadRegisterNatives、方法名字符串。
  • Ghidra 中可通过字符串交叉引用定位实现函数。

4. Ghidra 基础分析流程

4.1 导入和自动分析

  1. 新建 Ghidra Project。
  2. Importlibreversedemo.so
  3. 选择默认分析器,执行 Auto Analyze。
  4. 在 Symbol Tree 中查看 Exports、Functions、Strings。
  5. 搜索nativeCheckJNI_OnLoad/system/bin/sufridaTracerPid

4.2 重点窗口

窗口用途
Listing看汇编、跳转、引用
Decompiler看近似 C 代码
Symbol Tree找函数和导出符号
Defined Strings找路径、错误信息、密钥线索
Function Graph看控制流
References找字符串或函数被谁使用

4.3 Demo:分析 root 检测

示例 Native 逻辑:

boolhas_su(){returnaccess("/system/bin/su",F_OK)==0||access("/system/xbin/su",F_OK)==0;}

在 Ghidra 中:

  1. 搜索字符串/system/bin/su
  2. 查看交叉引用。
  3. 进入引用函数。
  4. 看是否调用accessstatopen
  5. 重命名函数为check_su_path
  6. 记录返回值含义。

报告写法:

## Native root 检测 - so:`libreversedemo.so` - 字符串:`/system/bin/su`、`/system/xbin/su` - 调用:`access(path, F_OK)` - 逻辑:任一路径存在则认为环境风险较高 - 风险:如果直接阻断业务,可被 Hook `access` 或修改返回值绕过 - 建议:作为风险评分信号,与服务端风控、设备完整性和行为验证结合

5. Native Frida Hook

5.1 Hook Java 层 Native 方法

最简单方式是先 Hook Java 声明的方法:

Java.perform(function(){constNativeGuard=Java.use("com.example.reversedemo.NativeGuard");NativeGuard.nativeCheck.implementation=function(){constresult=this.nativeCheck();console.log("[nativeCheck] original="+result);returntrue;};});

优点:不需要处理地址和 ABI。缺点:如果 App 在 Native 内部继续调用其他检测函数,Java 层 Hook 可能覆盖不了细节。

5.2 Hook 导出函数

constmoduleName="libreversedemo.so";constsymbol="Java_com_example_reversedemo_NativeGuard_nativeCheck";constaddr=Module.findExportByName(moduleName,symbol);console.log("nativeCheck addr="+addr);Interceptor.attach(addr,{onEnter(args){console.log("[nativeCheck] enter");},onLeave(retval){console.log("[nativeCheck] original retval="+retval);retval.replace(1);}});

验证:

验证项通过标准
找到地址控制台打印非空地址
Hook 命中调用时打印 enter
返回值改变App 行为发生变化
证据保存保存脚本和日志

5.3 Hook libc 函数观察检测路径

constaccessPtr=Module.findExportByName(null,"access");Interceptor.attach(accessPtr,{onEnter(args){this.path=args[0].readCString();},onLeave(retval){if(this.path&&this.path.indexOf("su")>=0){console.log("[access] "+this.path+" => "+retval);}}});

这个脚本用于观察 Demo 是否检查su路径。不要把这类脚本用于未授权绕过第三方 App 风控。

6. Native 防护知识点

6.1 常见检测

检测Native 实现线索局限
root 检测access("/system/bin/su")which su路径可隐藏,调用可 Hook
调试检测ptraceTracerPidprctl可被绕过,可能误伤测试环境
Frida 检测端口、线程名、maps、符号对抗成本高,易产生兼容问题
完整性校验读取 APK/DEX/so 哈希客户端校验仍可被 Patch
签名校验证书摘要比对摘要可被定位和修改

6.2 正确防护思路

Native 防护适合提高成本,但不能承担唯一安全结论:

  • 客户端检测只作为风险信号。
  • 核心权益、支付、提现、订单、身份认证必须服务端校验。
  • 检测结果要和账号、设备、行为、接口频率、服务端风控合并判断。
  • 防护上线后必须复测误报、兼容性、性能和可维护性。

7. 本章 Demo:Native 分析任务

7.1 任务清单

  1. 从 APK 提取libreversedemo.so
  2. filereadelfstrings记录 ELF 信息和字符串线索。
  3. 在 jadx 中找到System.loadLibraryexternal fun nativeCheck()
  4. 在 Ghidra 中定位 JNI 函数。
  5. 找到 root 检测字符串和access调用。
  6. 用 Frida Hook Java 层nativeCheck()
  7. 用 Frida Hook Native 导出函数或 libcaccess()
  8. 输出 Native 分析报告。

7.2 验证矩阵

验证项工具通过标准证据
so 提取unzip得到目标 ABI 的 so01-so-list.txt
ELF 信息readelf记录架构和动态符号02-readelf.txt
字符串线索strings找到检测路径或错误信息03-strings.txt
JNI 定位jadx + GhidraJava 方法和 Native 函数对应04-jni-map.md
Hook JavaFridanativeCheck()命中05-hook-java.log
Hook NativeFridaNative 函数或access命中06-hook-native.log

8. 报告模板

# Native Analysis Report ## so 信息 - 文件: - ABI: - 架构: - 导出符号: - 依赖库: ## JNI 映射 | Java 方法 | Native 函数 | 注册方式 | 证据 | ## 关键字符串 | 字符串 | 引用函数 | 推测用途 | ## 动态验证 | Hook 点 | 参数/返回值 | 行为变化 | ## 风险与建议 - 风险: - 影响: - 局限: - 修复:

9. 常见问题

问题原因处理
找不到 JNI 符号动态注册或符号被 stripJNI_OnLoadRegisterNatives、字符串引用
Ghidra 反编译很乱优化、混淆、控制流复杂先从字符串和导入函数入手
Hook 地址为空so 未加载或符号未导出等待System.loadLibrary后再查,或按基址加偏移
返回值替换无效Hook 点不是最终判断Hook 更下游函数或 Java 调用点
App 闪退ABI、返回类型、调用时机错误先只打印,确认调用约定

10. 本章交付物

case-reversedemo/ 04-native/ 01-so-list.txt 02-readelf.txt 03-strings.txt 04-jni-map.md 05-ghidra-notes.md 06-hook-java.js 07-hook-java.log 08-hook-native.js 09-hook-native.log 10-native-report.md

11. Native 基础补强

11.1 ELF 文件结构

Android.so是 ELF 动态库。逆向时至少要理解:

区域作用分析意义
ELF Header架构、位数、入口、类型判断 arm64/armv7/x86
Program Headers加载段理解内存映射
Section Headers代码、数据、符号等节静态分析入口
.text机器指令反汇编主体
.rodata只读数据和字符串路径、错误信息、常量
.dynsym动态符号导出函数和导入函数
.init_array初始化函数反调试和注册逻辑常见位置
.got/.plt动态链接跳转Hook 和导入调用线索

命令:

readelf-hlibreversedemo.so readelf-Slibreversedemo.so readelf-llibreversedemo.so readelf-dlibreversedemo.so

11.2 ABI 与寄存器基础

架构常见设备参数传递返回值
arm64-v8a现代真机x0-x7x0
armeabi-v7a旧设备r0-r3r0
x86_64模拟器System V 风格rax

Frida Hook Native 函数时,args[0]args[1]对应底层参数。JNI 函数前两个参数通常是:

  • JNIEnv *
  • jobjectjclass

真实 Java 方法参数从args[2]开始。

12. JNI 深度

12.1 JNI 类型映射

Java 类型JNI 类型签名
booleanjbooleanZ
intjintI
longjlongJ
StringjstringLjava/lang/String;
byte[]jbyteArray[B
ObjectjobjectLjava/lang/Object;
static方法jclass第二参数取代jobject

12.2 JNI 方法签名

示例:

externalfunsign(path:String,body:ByteArray,timestamp:Long):String

签名:

(Ljava/lang/String;[BJ)Ljava/lang/String;

理解签名有助于:

  • 识别RegisterNatives表。
  • 在 Ghidra 中重命名函数。
  • Hook Native 参数。
  • 对照 Java 层 external 方法。

12.3 RegisterNatives 分析

动态注册常见结构:

staticJNINativeMethod gMethods[]={{"nativeCheck","()Z",(void*)native_check},{"nativeSign","(Ljava/lang/String;)Ljava/lang/String;",(void*)native_sign},};

Ghidra 查找步骤:

  1. 搜索字符串nativeCheck
  2. 找交叉引用。
  3. 查看附近是否存在方法名、签名、函数指针三元组。
  4. RegisterNatives调用。
  5. 追踪函数指针到真实实现。

13. Native 静态分析套路

13.1 字符串优先

优先搜索这些字符串:

类型字符串
root/system/bin/sumagiskbusybox
debugTracerPidptracegdblldb
fridafridagum-js-loop27042
xposedxposedsubstrateedxp
签名SHA-256MD5signature
APK.apkclasses.dexbase.apk
网络https://apicert
错误invalidtamperdebugger

13.2 导入函数优先级

函数可能用途
access/stat/open文件检测、root 检测
ptrace反调试
fopen/read读取 maps、status、APK
dlopen/dlsym动态加载和符号查找
pthread_create后台检测线程
strcmp/strstr字符串匹配
EVP_*OpenSSL 加密
__system_property_get读取系统属性

13.3 控制流识别

Native 检测函数常见模式:

读取环境 -> 字符串匹配 -> 返回 true/false -> 上报 Java 层 -> 决定页面或接口行为

分析时不要只停在“有 root 检测”,要回答:

  • 检测结果被谁使用。
  • 是否只影响 UI。
  • 是否会上报服务端。
  • 是否会阻断高风险接口。
  • 是否存在误报处理。

14. Ghidra 操作细化

14.1 函数重命名规则

发现建议命名
引用/system/bin/sucheck_su_path
引用TracerPidcheck_tracer_pid
调用RegisterNativesregister_native_methods
计算 SHA-256calc_sha256
读取 APKread_base_apk
比较证书摘要compare_signature_digest
返回环境状态build_risk_flags

命名后要在报告中说明“这是分析者命名,不一定是原始函数名”。

14.2 交叉引用分析

Ghidra 中对字符串按X查看引用。每个引用要记录:

## 字符串引用记录 - 字符串: - 地址: - 引用函数: - 函数输入: - 函数输出: - 推测用途: - 后续 Hook 点:

14.3 Decompiler 结果校验

反编译 C 代码可能不准确。要用以下方式交叉验证:

  • 看汇编条件跳转。
  • 看字符串引用。
  • 看导入函数。
  • 动态 Hook 参数和返回值。
  • 和 Java 层调用结果对照。

15. Native Hook 深入

15.1 等待 so 加载

如果Module.findExportByName返回 null,可能 so 还没加载。

constdlopen=Module.findExportByName(null,"android_dlopen_ext");Interceptor.attach(dlopen,{onEnter(args){this.path=args[0].readCString();},onLeave(retval){if(this.path&&this.path.indexOf("libreversedemo.so")>=0){console.log("[loaded] "+this.path);}}});

15.2 按偏移 Hook

当符号被 strip 时,可用基址加偏移:

constbase=Module.findBaseAddress("libreversedemo.so");consttarget=base.add(0x1234);Interceptor.attach(target,{onEnter(args){console.log("[target] enter");},onLeave(retval){console.log("[target] retval="+retval);}});

偏移来源必须记录清楚:

  • Ghidra 函数地址。
  • so 加载基址。
  • 架构 ABI。
  • APK 版本和 so hash。

15.3 读取 jstring

JNIjstring不能直接readCString。可通过 Java API 或 Native helper 处理。简单场景建议先 Hook Java 层 external 方法,必要时再深入 Native。

记录原则:

  • 如果参数读不出来,不要猜。
  • 先打印指针和返回值。
  • 再定位 Java 层包装方法。
  • 最后处理 JNI 类型转换。

16. Native 风险库

编号风险Native 证据动态验证修复
N-001root 检测单点阻断access("/system/bin/su")Hookaccess风险评分
N-002反调试单点阻断ptraceTracerPidHook 返回多信号判断
N-003硬编码密钥.rodata字符串Hook 加密函数服务端密钥
N-004签名摘要硬编码SHA-256 常量Patch 比较服务端校验
N-005JNI 动态注册隐藏逻辑RegisterNativesHook Java external只提高成本
N-006frida 检测误伤frida字符串兼容性测试分级响应
N-007so 完整性只本地校验读取自身 hashPatch 返回服务端挑战
N-008Native 崩溃暴露tombstone/logcat触发异常错误处理

17. Native Demo 作业

17.1 作业一:JNI 映射

任务:

  1. 找到 Java 层NativeGuard
  2. 找到System.loadLibrary
  3. 提取目标 ABI so。
  4. readelf找导出符号。
  5. 用 Ghidra 定位真实函数。
  6. 输出 Java 方法到 Native 函数的映射表。

17.2 作业二:root 检测分析

任务:

  1. 搜索/system/bin/su
  2. 找引用函数。
  3. 识别access调用。
  4. Hookaccess打印路径。
  5. Hook JavanativeCheck()观察返回值。
  6. 写出该检测的局限。

17.3 作业三:签名函数观察

任务:

  1. 在 Native 中加入一个nativeSign(String input)
  2. 用 Ghidra 找到输入字符串处理。
  3. 用 Frida Hook Java external 方法。
  4. 记录输入、输出和调用时机。
  5. 判断密钥是否出现在客户端。

18. Native 报告评分

项目分值要求
so 信息10ABI、hash、架构
JNI 映射20Java 和 Native 对应
静态分析20字符串、导入函数、控制流
动态 Hook20Java 或 Native 命中
风险判断20说明影响和局限
证据归档10命令、截图、脚本、日志

19. Native、JNI 与 so

Native 分析要把 Java external 方法、so 符号、JNI 注册、字符串引用、系统调用和动态 Hook 串起来。

ELF 与 so

知识点核心理解Demo/验证常见误区
ELF Header说明架构、位数、类型和入口信息。执行readelf -h libreversedemo.soABI 不同仍混用地址。
Section.text.rodata.dynsym分别对应代码、字符串、符号。readelf -S查看。只看导出符号,不看字符串。
动态符号未 strip 的 so 可能暴露 JNI 函数。nm -Dreadelf -s没有符号就停止分析。
字符串引用路径、错误文案、算法名常在.rodata搜索/system/bin/suTracerPid不追交叉引用。
导入函数导入函数揭示文件、属性、加密和线程行为。accessptracedlsym只看自定义函数名。

JNI 知识

知识点核心理解Demo/验证常见误区
静态注册通过Java_包名_类名_方法名绑定。找到nativeCheck导出符号。包名转义规则理解错误。
动态注册通过RegisterNatives绑定方法名、签名和函数指针。搜索方法名字符串。没有Java_符号就认为无 JNI。
JNI_OnLoadso 加载时执行,常做注册和检测。Ghidra 定位JNI_OnLoad只分析被 Java 调用的方法。
JNI 签名(Ljava/lang/String;[B)Z描述参数和返回值。把 external 方法转成签名。Hook 参数时不理解类型。
JNIEnvJNI 函数前两个参数通常是 JNIEnv 和 jobject/jclass。Native Hook 时从args[2]看真实参数。args[0]当业务参数。

Native 防护与 Hook

知识点核心理解Demo/验证常见误区
root 检测常检查 su、Magisk、系统目录和属性。Hookaccess打印路径。把 root 检测作为唯一安全门。
反调试常检查 ptrace、TracerPid、调试端口。搜索TracerPid认为反调试不可绕过。
Frida 检测检查端口、线程名、maps、符号。搜索fridagum-js-loop过度检测导致误伤。
按偏移 Hookstrip 后用基址加偏移定位函数。Ghidra 地址转 Frida 偏移。不同版本混用偏移。
libc HookHook 系统函数可观察 Native 行为。Hookopenaccessptrace只 Hook Java 层看不到底层调用。
Native 报告必须记录 so hash、ABI、地址、函数、证据。输出native-report.md报告里没有 hash 和地址,无法复现。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/4 13:47:14

告别《Honey Select 2》加载失败!200+模组一键安装完整指南

告别《Honey Select 2》加载失败!200模组一键安装完整指南 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch 还在为心爱的角色卡片无法加载而烦恼吗&a…

作者头像 李华
网站建设 2026/5/4 13:46:26

审核7条原则——不是走过场,是铁律

📋 审核概论系列 第2篇/共10篇审核7条原则——不是走过场,是铁律违反任何一条,审核结论都不可信。这7条原则是审核员的"宪法"📊 真实场景:去年参加一家工厂的第三方审核,到了末次会议&#xff0…

作者头像 李华
网站建设 2026/5/4 13:46:18

5分钟快速配置Unity游戏AI翻译插件:XUnity.AutoTranslator终极指南

5分钟快速配置Unity游戏AI翻译插件:XUnity.AutoTranslator终极指南 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 还在为外语Unity游戏而烦恼吗?想轻松玩转全球游戏却受限于语言障…

作者头像 李华
网站建设 2026/5/4 13:45:06

智能时代的伦理与未来:DeepSeek V4 驱动下的责任边界与重塑之路

智能时代的伦理与未来:DeepSeek V4 驱动下的责任边界与重塑之路 导言:从工具到文明的转折点 大型语言模型的飞速发展,尤其是 DeepSeek V4 这样在架构复杂度和能力上限上均实现飞跃的模型,不仅是工程技术上的胜利,更标志…

作者头像 李华