Android包结构
Apk的组成结构
AndroidManifest.xml
- Android应用的全局配置文件,包含了:包名、版本号、所需权限、注册服务等
Zip直接解压
- 不是可读的文本,而是机器才认识的二进制数据,所以显示为乱码。
ApkTool解包
- 解包后可读
- 重点关注:
- package
- uses-permission
- activity
- service
- receiver
- provider
- application
res目录
- 资源文件夹,它里面存放的所有文件都会被影射到R文件中,生成对应的资源ID,便于代码中通过ID直接访问。
- 其中的资源文件包括了动画anim、图像drawable、布局layout、常量values、颜色值colors、尺寸值dimens、字符串strings、自定义样式styles等
Zip直接解压
- res中的xml文件无法被直接阅读,只有res中存有的非XML资源可以被直接阅读(比如视频音频)
ApkTool解包
- 可以正常阅读xml文件
classes.dex
- 是Android系统运行于Dalvik Virtual Machine上的可执行文件,也是Android应用程序的核心所在。项目工程中的Java源码通过javac生成class文件,再通过dx工具转换为classes.dex。
- 当方法数超过dex上限的时候,进行分dex操作,也就是划分为classes2.dex等等,Android5.0后dx自带支持,可以划分dx。
- 现在一些热补丁有关的技术,主要便是对dex做各种处理。
Zip直接解压
- 虚拟机执行的字节码,不是源码
- 在AndroidStudip直接打开的时候,可以看见非常多的机器码函数
ApkTool解包
- 使用apktool解包后,dex被转为Smali语言。
Smali
Zip直接解压
- 直接使用Zip解压的话看不见Smali,因为Smali是ApkTool这个工具对classes.dex进行反汇编后产生的结果,是一种描述classes.dex内部指令的文本格式
ApkTool解包
- 详见[[Smali语言]]
assets
用于保存需要保持原始文件的资源文件夹,开发过程中拖了什么到里面,打包完之后里面还是什么
一般是存放音频、网页(帮助网页之类的)、字体等文件。
可以有多级目录,而res只有两级
Zip直接解压
- 存放的是原生文件,保存原始格式
- Data里面的Managed文件夹里面存放的都是dll文件,此外,Managed文件夹外还有一些config、unity3d、json格式的文件(还有一些其他不太常见的格式)
ApkTool解包
- 和assets的结构没什么差,但是部分文件已经可读
so库(lib目录)
- 存放着应用需要的native库文件,比如一些底层实现的图片处理、音视频处理、数据加密的库以so库的形式在该文件夹中。
- 该文件夹下有时会多一个层级,这是根据不同CPU型号而划分的,比如ARM、ARM-v7a、x86等
Zip直接解压
- 针对特定CPU架构编译的原生机器码,比如在我们现在的测试的demo中,我们使用的就是armeabi-v7a架构的原生机器码
ApkTool解包
- 与直接解压大概相同
aar(Android Archive)
- 首先需要提到jar,jar是Java archive的缩写,所以其实aar就是一种类似于jar的打包格式。
- jar包含了编译后的class文件以及一些包含元数据的文本文件,而aar比jar多了一些Android的特定文件,比如layout、drawable还有manifest文件,在Android中就是一个完整的Module。
使用方式
Android Studio导入
File->New->New Module,进入选择弹窗,选择导入jar或aar
系统自动在setting.gradle配置好aar
只需要在app的gradle配置依赖就可以了。
libs导入
libs建立新的文件夹aars,将要使用的aar文件放到aars文件夹中。
在app:build,gradle文件中加入代码repositories {
flatDir {
dirs 'libs/aars'
}}
在dependencies下加入依赖:dependencies {
...
compile(name:'calculatelib-debug', ext:'aar')
}
META-INF文件夹
该目录的主要作用是保证APK的完整性以及安全性
主要分为三个文件
MANIFEST.MF
- 保存了整个apk文件中所有文件的文件名+SHA-1后的base64哈希值。
- 这也就意味着,这个MF文件代表着apk包的完整性。
CERT.RSA
- 这个文件保存了公钥和加密方式的信息
CERT.SF
- 这个文件和MANIFEST.MF的结构一样,只是其编码会被私钥加密
- 如果apk包被修改了,而篡改者没有私钥生成CERT.SF,则无法完成校验
签名(Signature)
- 部分内容与上面重复,因为上面的内容是后面补上的。
Zip直接解压
- 似乎存放在META_INF文件夹里面?.RSA文件是签名文件?不管是不是,在Zip解压中,.RSA文件是二进制数据无法阅读
- 一些关于META-INF的知识
- MANIFEST.MF:清单文件,记录了APK中除了META-INF目录外其他所有文件的路径,以及每个文件内容的SHA-256哈希值(数字指纹,确保包内的每个文件都没有被篡改)
- SF文件:不关心原始文件,只关注MANIFEST.MF,包含了MANIFEST本身的哈希值,还包含了文件中每一项的哈希值,确保清单文件本身没有被伪造或者修改。
- RSA:数字签名,是整个验证链的信任起点。包含了开发者的数字证书(开发者的公钥)以及加密后的签名(通过开发者的私钥对SF文件对哈希值进行加密后产生的数据),保证SF文件不被篡改,并且只有拥有私钥的开发者才能生成这个签名。
- 额外元数据
- 验证过程:
- 读取RSA,取出开发者的公钥
- 使用公钥解密RSA中的签名,得到SF文件的原始哈希值A。系统自己计算一遍SF文件的哈希值,如果不相等,说明被修改过,验证失败。
- SF验证通过后,系统逐一比对SF和MF中的哈希值,确保清单可信。
- 最后,计算每一个实际文件的哈希值,和MF清单中的哈希值进行对比,如果有不一致,说明被篡改,安装失败。
签名流程?或许正确?
- 准备签名密钥库(一次性工作)
keytool -genkeypair -v -keystore [你的密钥库文件名].keystore -alias [你的别名] -keyalg RSA -keysize 2048 -validity 10000- 需要记住密钥库的密码和别名
- 执行签名
./apksigner sign --ks [密钥库文件名].keystore --ks-key-alias [你的别名] [需要签名的APK路径]- 使用的工具apksigner来自Android Studio内部,路径为:
~/Library/Android/sdk/36.0.0/build-tools/apksigner
- 验证签名(可选)
./apksigner verify [已签名的APK路径]
PS:别的说法:VSCode里面自己生成的.app.idsig是v4签名方案,可以在app install的时候直接查找这个文件来签名
ApkTool解包
- RSA无法直接阅读,但是MF和SF可以
resources.arsc
- 所有文件中结构最复杂的
- 记录了资源文件、资源文件位置
- resource相关的索引表
可以选择package,resource.arsc包含多个package的资源
有个Resource Types的列表,可以看到不同的type,比如anim、drawable等。
右边显示了type里有多少个resource,以及对应的ID、Name和default(其实就是资源的路径?)
- 通过这个,我们可以完成通过id+对应的configuration获取对应资源的操作
- 后面的资源混淆的原理,就是修改这里各个维度的值,并且修改对应res里面的文件夹以及文件名实现的
分析方式
可以直接使用Android Studio中的Analyze apk分析apk
混淆(Obfuscation)
架构
ARM架构
arm64-v8a
- 64位处理,必须支持(谷歌要求)
armeabi-v7a
- 支持32位,支持FPU(硬件浮点运算)以及NEON支持(高级单指令多数据流技术,可以并行处理大量数据)
armeabi
- 32位,早期架构,已被淘汰
x86
x86_64
- 64位intel,当前模拟器必备(?),因为大部分电脑是x86_64架构
x86
- 32位桌面架构
Android逆向工具
需要掌握的工具
- 要求:可以熟练使用命令完成逆向
一些有趣的网站
吾爱破解
蓝色星原解密
Android App逆向入门
Analyze APK
Android Studio自带
ApkTool
- apktool本质上就是一个.jar文件,需要JRE才能运行。
- 命令行使用方法:
- 基本命令格式:
java -jar apktool.jar d your_app.apk - 因为我们电脑上直接使用homebrew下载,所以不需要那么多的命令,直接使用
apktool代替java -jar apktool.jar - d代表的是decode,也就是解码,帮助解码apk
- -o:指定解码后的输出目录名
- -f:强制删除已经存在的输出目录
- -s:不反汇编smali文件夹
- -r:不解码资源文件
- -p:指定框架文件所在的目录(通常推荐使用if命令进行全局安装)
- b代表的是build,也就是构造,帮助从解码文件构造成apk
- -o:指定输出的APK文件路径和名称
- -f:构建的时候跳过文件变更检测,强制构建所有文件
- -a:手动置顶aapt或者aapt2工具的路径
apktool if framework-res.apk- 当解码非原生Android应用的时候,如果需要引用厂商自己添加的公共资源,就需要提取所需框架文件。
- -t:为安装的框架文件打上一个标签
- empty-framework-dir:清空已经安装的所有框架文件
- h:显示帮助信息
- v:显示版本号
- 基本命令格式:
nm
安装方式
LLVM自带
使用方式:
nm xxxxx.so
- 列出文件中的符号表
jadx-gui
作用
- 直接将文件拖入jadx,可以直接阅读源码
下载方式:
brew install jadx
使用方式
- 终端直接输入
jadx-gui,打开对应的解包或者apk - 前面的是jadx的GUI使用方式,我们可以不使用这个方式启动软件,而是直接使用jadx来对apk文件进行解码。
- 常见格式:
- -r:不解码resources
- -s:不解码sources
- -d:指定输出文件夹
IDA
启动台启动IDA
PS:如果没有办法启动,试试将输入法切换到英文
使用方法
- 拖拽.so文件到ida中
- 寻找入口点 (Functions Window)
- 在 IDA 的左侧或通过
Shift+F3打开 “Functions Window”(函数窗口)。这里列出了 IDA 在.so文件中识别出的所有函数。 - 寻找那些名字看起来很可疑或有意义的函数,特别是以
Java_开头的函数。这些是 JNI(Java Native Interface)函数,是 Java 代码调用 C/C++ 代码的桥梁。例如,Java_com_example_app_MainActivity_decryptString这个函数,显然就是MainActivity中用于解密字符串的原生方法。
- 在 IDA 的左侧或通过
- 【最关键的一步】反编译成 C 伪代码 (F5)
- 在函数列表中双击一个您感兴趣的函数,IDA 的主视图会跳转到该函数的汇编代码。
- 现在,按下键盘上的
F5键。 - 奇迹发生了! IDA 内置的 Hex-Rays Decompiler 会将当前函数的、复杂的 ARM 汇编代码,瞬间反编译成可读性非常高的 C 语言风格的伪代码。
- 以上操作由gemini生成,因为我还没试过
https://bbs.kanxue.com/thread-266021.htm
AndroidKiller
使用的版本为AndroidKiller4J,因为Mac平台限制
启动方式
进入下载中,直接进入文件夹运行./startup.sh
不知道为什么打不开apk?所以还是放弃这个
PS:完全没有用,之后寻找别的使用方法。
VSCode
下载了插件APKLab,似乎可以直接集成apktool和jadx?
一些常用命令:
- rebuild:右键apktool.yml,直接重建apk
- command + shift + p:>APKLab :有多个指令,可以选择jadx或者apktool解析apk包,APKLab中集成了这几种工具。
IDA教程
Android打包流程
资源
- Android打包流程的第一步,是处理资源文件
处理资源文件
aapt
AndroidManifest.xml、res文件夹以及resource.arsc文件的生成都与其有关
- aapt解析项目代码中的AndroidManifest.xml,收集项目中res文件夹的资源文件以及xml,对其做压缩和编译的处理。
- 在这过程中,分配了资源ID以及生成了R.java以及arsc文件
- 如果项目引入了Android support包,又或许依赖于其他第三方aar库,那么构建前会将aar解压并与本地资源合并。本地资源包括:assets目录、res目录以及Androidmainfest.xml。
- 当第三方依赖中的assets或者res文件与本地文件有冲突的时候,会优先选用本地文件。但是res/value略有不同,此目录下的strings、color、styles等xml文件会被整合到一个叫做values等文件中去,会其他第三方依赖中的values进行内容上的合并,不会直接舍弃。
- 理解:对于assets或者res这种资源型的文件,一般来说本地优先,舍弃第三方库的资源,但是如果是values,会将值进行合并(可以理解,因为这几个文件基本上在每个Android Module中都存在,并且这种键值对方式存储的资源文件确实适合这么合并存储)
- Androidmanifest.xml的合并相比来说则要复杂一些,除了第三方依赖中的manifest,项目还可以在不同目录下分别拥有manifest文件。构建过程中,会根据manifest中元素、属性及赋值来生成一个manifest文件,并应用于后续的打包过程。gradle为不同的manifest赋予了不同的优先级,其顺序如下:
buildType 设置 > productFlavor 设置 > src/main > dependency&library- 后续还有一些具体的规则和错误,可以在文档或者网上查看(实在不想看这个了)
代码
得到R.java文件后,与项目代码一起编译得到class文件,然后打包为jar包。这个过程中,还会有混淆代码这一步骤。
之后,再通过dx工具,将生成的jar包与第三方的jar包一起编译为dex文件,包括了分dex。
签名
- 按步骤生成MANIFEST.MF,CERT.RSA,CERT.SF并放入META-INF文件夹即可。*
业内有关技术小结
apk加固
- 加固工具拿出原始的classes.dex文件,并使用加密算法将其变成一堆无法直接读取的二进制数据。
- 加固工具生成一个新的classes.dex文件,也就是壳DEX,这个壳本身也是一段可执行的代码,但是功能非常单一,就是一个解锁程序。
- 将壳DEX与加密后的原始DEX放回apk中,生成一个新的、加固后的APK
快速多渠道包
减少多渠道打包的时间:
- 其实需要攻破的是META-INF的完整性检验机制
- 两种方案
- META-INF下添加空文件不会破坏签名(文件名为渠道号)
- 原理:Android 的 V1 签名方案在校验时,会严格检查
MANIFEST.MF文件里列出的每一个文件的指纹是否正确。但是,它对于META-INF目录里多出来的、未被列出的文件,会选择忽略。
- 实现步骤:
- 先正常打一个完整的、已签名的“基础包”。这个过程很慢,但我们只需要做一次。
- 需要“华为”渠道包?复制一份基础包,然后用 ZIP 工具在它的
META-INF/目录下创建一个名为huawei的空文件。 - 需要“小米”渠道包?再复制一份基础包,在
META-INF/目录下创建一个名为xiaomi的空文件。
- 原理:Android 的 V1 签名方案在校验时,会严格检查
- apk文件末尾写入信息(本质利用了ZIP文件可以添加comment数据结构的特点)
- 原理:APK 文件本质上是一个 ZIP 压缩包。标准的 ZIP 格式规范允许在文件的末尾附加一段“注释”信息(Comment)。Android 的 V1 签名校验只会检查 ZIP 包里包含的那些文件条目,完全不会去校验这个末尾的“注释”区域。
- 实现步骤:
- 同样,先打一个完整的、已签名的“基础包”。
- 需要“华为”渠道包?复制一份基础包,然后用程序打开这个 APK 文件,在文件末尾的 ZIP 注释区写入
huawei。 - 需要“小米”渠道包?再复制一份,在注释区写入
xiaomi。
- 为什么快:这同样是一个纯粹的文件 I/O 操作,不触及任何已被签名的文件内容,速度飞快。
- App 如何读取渠道号:App 运行时,可以像读取普通文件一样打开自己的 APK,然后从文件末尾读取 ZIP 注释,从而拿到渠道号。
- 可靠性:这个方法比方案一更可靠,因为它利用的是 ZIP 文件格式的标准特性。美团等公司开源的著名多渠道打包工具 Walle 就是基于这个原理并针对 V2 签名方案进行了优化
- META-INF下添加空文件不会破坏签名(文件名为渠道号)
- 这两种方案都不会导致MF文件的改变,就不需要再次打包。
- “快速”的关键在于,将
N次“编译 + 签名”的重度操作,转变成了1次“编译 + 签名” 和N次“轻度文件 I/O” 操作。
资源混淆
资源混淆通过混淆资源路径名以及资源文件名,减少安装包的体积,并且提高破解难度
res/drawable/icon -> r/s/a
入手点皆为:resource.arsc
- 修改刚刚提及的aapt,使得在生成resource.arsc的过程中,就修改掉项目中资源的名称,实现了资源的混淆。
- 打出apk包之后,读入已经生成的resource.arsc文件,进行混淆、改写,同时修改掉res文件夹下的资源名称,完成混淆。最后再重新打包得到混淆后新apk文件。
热补丁
通过native hook的方式,替换class中的方法实现完成热补丁
classloader加载新dex覆盖原有class完成替换的方案
其他
使用 Annotation 自动生成代码,buck exopackage 提高打包速度
Android的模拟器问题
需要在Android Studio上运行一个Unity开发的APK,但是里面用到的so库是armeabi v7a,折腾了一顿发现,我这个版本的Android Studio上没有办法创建armeabi v7a架构的虚拟机,创建后启动虚拟机会直接报错。
设备
工作电脑是2018年的Intel芯片Mac pro,总的来说配置很低,intel芯片在现在的Mac系统上也挺难用。
报错原因
没有定位准确的原因,以下是一些可能的问题参考:
- Intel Mac是x86架构,armeabi-v7a是ARM架构,在x86硬件上直接运行ARM系统需要指令集转换,所以我们无法直接在x86架构的Intel Mac电脑上安装并且运行需要ARM指令集的项目。
- 性能问题
- 系统镜像不兼容,某些系统镜像可能不完全支持对ARM库的翻译功能。
- 环境配置
个人觉得应该是第一种原因,Intel Mac没法直接创建ARM架构的虚拟机。
解决方法
放弃使用x86_64架构的虚拟机,转而使用x86架构的虚拟机。
在x86架构的虚拟机上,可以直接运行armeabi-v7a架构的项目,原因是x86内置有libhoudini翻译器,可以在32位环境中将arm指令翻译为x86指令,从而让.so库在x86 cpu上运行。
x86_64无法成功运行,可能是因为64位到32位到翻译不够直接和稳定,或者说64位缺少了从32位ARM翻译到64位x86_64的翻译器。
Hook
概览
- Hook是一种允许你拦截和修改系统或者应用程序中函数调用、消息或者时间的技术。
- 在Android逆向的语境中,Hook的本质就是改变应用原有的执行流程,将其引导到我们自己编写的代码逻辑中。可以通过这种技术实现以下的目的:
- 动态分析
- 逻辑修改
- 安全绕过
- Hook技术分为两大类:
- Java Hook(Dalvik/ART)
- 针对的是Java或者Kotlin编写的代码,运行在Android运行时。
- 原理:通过修改虚拟机内部的数据结构来改变方法的执行入口,或者利用Java的反射机制。
- Native Hook(ELF)
- 针对C/C++编写的so库文件
- 直接修改内存中加载的ELF文件的机器码或者相关数据表
- Java Hook(Dalvik/ART)
反射Hook
本质其实是Java提供的运行时能力,也就是Java反射,允许程序在运行时检查和操作类、方法、字段等。
需要解释的是,我们通常利用Java反射来实现一些类似Hook的目的,但是它本身并不是一种替换函数实现的Hook。
技术原理
Java反射的核心是java.lang.reflect包下面的一系列API。通过这些API可以实现。
- 在运行时候获取任意一个类的对象。
- 通过class对象,获取该类的所有方法、构造函数以及字段。
- 即使方法是私有的或者受保护的,也可以通过setAccessible来解除访问限制。
- 通过invoke来调用方法,通过Field.get()、Field.set()来读写字段值。
逆向应用
- 调用私有/隐藏的API
- 修改对象状态
- 替换回调实现:如果一个功能室通过设置监听器或者回调来实现的,我们可以通过反射来找到存储该监听器实例的字段,然后将其替换为我们自己实现的监听器实例,这等同于Hook
要实现真正的方法替换,通常需要结合Xposed等框架?
需要注意的是,反射操作的性能远远低于直接调用
MSHook(Cydia Substrate)
MSHook是Cydia Substrate框架提供的一个核心函数,用于在Native层实现inline Hook。
技术原理:inline Hook
- inline Hook是最直接、最强大的Hook方式之一。
- 核心思想:直接在内存中修改目标函数的开头几字节的机器指令,替换为一个跳转指令,使其跳转到我们自己编写的函数。
- 流程:
- 定位函数:首先找到目标函数在内存中的起始地址。
- 指令修改:- 将目标函数头部的若干字节的机器码替换为一个无条件跳转指令(如 ARM64 架构下的
B或BR指令),该指令指向我们的新函数(Hook 函数)。 - 保存原指令:被覆盖掉的原始指令会保存在一个叫做Trampoline的可执行内存区域中。
- 执行流程:
- 当程序调用原函数的时候,实际上会执行我们插入的跳转指令,进入我们的Hook函数。
- 在Hook函数中,我们可以读取/修改参数、执行我们自己的逻辑
- 如果需要调用原始函数的功能,可以通过执行之前保存的Trampoline来实现。Trampoline会执行被覆盖的原始指令,然后跳转回原始函数中未被修改的部分继续执行。
- 原始函数执行完毕后,控制权返回到我们的Hook函数,我们可以在此时读取/修改返回值。(这部分应该有实践更好理解)
- 最后Hook函数返回,完成了整个调用过程。
Cydia Substrate API
- 简化了inline Hook的流程,提供了简单的API,比如:
- MSHookFunction
void MSHookFunction(void *symbol, void *hook, void **old);- symbol是指向原始函数的指针
- hook是指向我们自己编写的新函数地址的指针。
- old用于保存Trampoline地址的指针,以便我们在新函数中可以调用原始函数。
优缺点
- 优点:
- 通用性强:几乎可以Hook任何Native函数,无论是库的导出函数还是内部的静态函数
- 功能强大:可以完全控制函数的执行,包括参数、返回值和执行时机
- 缺点:
- 实现复杂
- 平台依赖性:指令集差异
- 易于被检测,很多安全保护方案会检验函数头部的完整性,一旦被修改,就会判定为被攻击。
And64Hook
And64是一个开源的、专门为Android ARM64架构设计的Native Hook库。
技术原理
主要是实现了两种主流的Native Hook技术
inline Hook
与MSHook的原理相同,直接修改了函数头部的指令。
对AARch64指令集优化。
PLT/GOT Hook
背景知识:
- 为了实现动态链接以及代码共享,当一个.so文件(模块A)调用另一个.so(模块B)文件中的函数的时候,不是直接跳转到绝对地址,而是通过一个中间层来实现。
- PLT:过程链接表,模块A对代码实际调用的是PLT中的一个条目。
- GOT:全局偏移表,PLT条目根据GOT表中的地址进行跳转,GOT中存放着外部函数的真实地址。这个地址在程序第一次调用该函数的时候由动态链接器来填充。
- Hook原理:不去修改目标函数的代码,而是从GOT表中存储的该函数的地址下手。将原本指向原始函数的地址,替换为我们自己编写的Hook函数的地址。
- 执行流程:
- 当模块A调用目标函数的时候,跳转到PLT
- PLT根据GOT去寻找函数地址。
- 此时,它获取到的是我们已经修改过的Hook函数的地址。
- 程序跳转到我们的Hook函数从而实现拦截。
如何从应用层调用到Natvie层?
本质上是Java去调用C/C++
JNI调用流程概述
- Java层
- 编写一个Java类,在其中使用native关键字声明一个或多个方法。这些方法没有方法体。
- 在合适的时机(通常是静态代码块中),使用
System.loadLibrary("库名")来加载编译好的C/C++动态链接库。
- Native层
- 使用C/C++编写代码,实现Java层实现的native方法。这个C/C++函数的命名必须遵循JNI的特定规则。
- 将C/C++代码编译成动态链接库。
- 打包和运行
- 将编译好的.so文件打包到APK的lib目录下。
- 当Java代码调用native方法时,ART虚拟机就会通过JNI规范,在已经加载的.so库中寻找到对应的C/C++函数并执行它。
实战步骤:
- 确保安装NDK
- 在Java层声明Native方法
- 使用static代码加载so库
- 使用native关键字声明一个本地方法
- 在C/C++层实现Native方法
- AS通常会在app/src/main/cpp目录下面自动生成一个文件(名字对应so库名字)
JNIEnv* envJNI环境的指针,通过它可以调用所有JNI函数- jclass 调用该静态方法的java类
- jstring 从Java层传过来的String对象
- 配置CMake构建脚本
app/src/main/cpp/CMakeLists.txtadd_library( native-lib SHARED native-lib.cpp)
- 在应用层调用
- 使用之前声明的类和方法调用
需要注意的点:
- 需要安装NDK?已经安装完毕了,在Tools->SDK Manager->SDK Tools里面有NDK和CMake
对于逆向来说:
- 切入点:分析APK的时候,如果看到出现了native关键字或者loadLibrary,可能说明App的核心逻辑在Native层的.so库。
- 定位实现:反编译后,在lib文件下找到不同架构的so文件
- 静态分析:使用IDA或者其他工具打开so文件,直接搜索遵循JNI命名规则的函数。
- 动态调试:Hook