安卓逆向之基础知识
安卓架构

- 内核层:操作系统内部的核心模块,负责所有系统资源的调度;
- 运行层
- 安卓虚拟机:ART/Dalvik运行dex代码(
.java --> .class --> .dex --> binary) - Android NDK(native层代码):C/C++编写的底层代码,java代码要想操作硬件,最终都会由NDK代码去实现。C/C++代码最终会被编译成
.so二进制文件(库文件),java代码通过JNI(java native interface)去掉用native层的代码。.so分为共享库文件和本地库文件,共享库库文件是整个操作系统中可以使用的.so,而本地库文件只能给某个应用程序使用,开发时会被打包在这个APK文件里面。
- 安卓虚拟机:ART/Dalvik运行dex代码(
- 框架层:Java SDK,核心是适用于Android的java api库
- 应用层:用java代码实现业务功能(调用NDK和JAVA SDK)
APK打包

- 资源分析&预处理
- aapt分析
AndroidManifest.xml - 生成对应的
R.java和resources.arsc(部分资源文件直接编译成二进制,resources.arsc就是生成二进制文件的中间产物,类似于CMakeLists.txt)文件 aidl(Android Interface Define Language)工具生成接口文件(安卓接口不同于普通的java接口,主要用来IPC进程间通信,在定义java接口的基础之上,需要额外指定很多参数)
- aapt分析
- 编译:将所有的java文件(包括第三方的JAR包)由
D8/R8最终生成DEX文件(Darlvik Executable File,一个dex文件中最多容纳的65535个方法,当方法数量超过65535时,生成多个DEX文件) - 打包:通过AAPT2 link功能,将上面所有DEX文件、arsc文件、AndroidManifest.xml等等归档成一个APK压缩文件
- 优化
- 使用类似于zipalign的工具,将APK压缩文件内的每个小模块进行4字节对其,使运行时内存访问更快;
- 使用类似于apksigner的工具,对生成的APK进行
V1/V2/V3/V4签名
- 自动化:上面所有的步骤都可以由AGP工具自动一键完成
APK结构
| Name | Description |
|---|---|
| assets目录 | 存放没有被编译的静态资源文件(图片/js等等) |
| res目录 | 存放编译后的资源文件 |
| lib目录 | 存放本地库文件,按照架构可以划分几个子目录:armeabi、armeabi-v7a和armeabi-v8a等等 |
| META-INF目录 | 签名相关的文件:xx.MF(APK内所有文件对应的哈希值)、xx.SF(记录xx.MF本身的哈希值以及xx.MF中哈希值的进一步哈希)、xx.RSA(公钥信息) apk签名详见下表 |
| AndroidMainfest.xml | 整个Android程序的配置信息(权限配置、属性配置、SDK信息等等) |
| classes.dex | 编译后的源代码,被ART直接识别并运行的文件 |
| 签名方案 | 引入版本 | 校验方式 | 是否嵌入 APK | 支持密钥轮换 | 安装速度 | 兼容性 |
|---|---|---|---|---|---|---|
| V1 | Android 1.0 | 单个文件哈希 | 是 | 否 | 慢 | 所有版本 |
| V2 | Android 7.0 | 整体结构签名 | 是 | 否 | 快 | Android 7.0+ |
| V3 | Android 9.0 | V2 + 密钥轮换支持 | 是 | 是 | 快 | Android 9.0+ |
| V4 | Android 11 | 增量签名 | 否(.idsig) | 否 | 更快 | Google Play 特定环境 |
安卓虚拟机
| Period | Method |
|---|---|
| 2008–2010 | 早期Android1.x~2.1,依靠Dalvik虚拟机逐条解释执行DEX字节码,每次运行都要重新翻译,CPU与内存使用效率低 |
| 2010–2013 | Android2.2(Froyo)引入JIT(Just in Time)机制,应用在运行时将“热路径”字节码动态编译成机器码并缓存 |
| 2013–2014 | Android4.4(KitKat)首次增加ART虚拟器(可选),安装阶段即通过dex2oat把所有DEX文件预编译为本地ELF(.oat),启动和运行速度提升,但增加了安装的时间和额外的存储空间 |
| 2014–2016 | Android5.0(Lollipop)起,ART成为唯一虚拟机,AOT成为默认模式,系统OTA后仍需再次dex2oat |
| 2016 至今 | Android7.0(Nougat)重新引入JIT机制,形成了“安装时不编译、运行时热编译、空闲时AOT”的混合编译策略 |


安全加固
代码隐藏
| 阶段 | 原理 | 逆向分析 |
|---|---|---|
| 代码混淆 | 把原先的代码通过变量替换(成a,b,c等)方式,使得代码不可读,很难读;花指令:在真实代码中插入一些(垃圾的、无用的)代码/指令/字节,但又不会改变程序的原始逻辑,确保原有程序的正确执行 | 没啥难度,习惯就好,且jadx-gui中自带反混淆功能 |
| 动态加载 | 1. 将原始DEX文件加密后作为资源文件打包到APK中,运行时通过自定义ClassLoader在内存中解密并动态加载,避免原始代码以明文形式存储在APK包内,实现基础的代码隐藏。早期的动态加载技术存在安全问题,dex一旦被解密,系统就会自动在/data/data/xxx/下保存明文的dex代码。 2. 改进之后,现在增加了内存映射、匿名共享内存等技术,直接在内存中完成解密、解析和加载全过程,并确保解密后的代码不写入本地文件系统。 |
静态反编译只能看到壳代码,安卓系统中没有实际的dex文件,可以配合Frida/fart在libart.so里hook DexFile::OpenMemory尝试脱壳 |
| 指令抽取 | 将DEX中的关键方法字节码提取出来,在原位置放置空指令或跳转指令,被抽取的指令加密后单独存储(通常在so库或加密文件中),运行时通过Hook机制动态加载,在方法调用时还原并执行被抽取的指令。 | 内存Dump得到的dex中,有些函数体为空(nop),必须单步跟踪到具体函数,才能抓到那一刻的指令片段 |
| 指令转换(VMP虚拟化保护) | 将标准Dalvik/ART字节码转换为自定义的私有指令格式(操作码映射、寄存器重分配、控制流扁平化等),需要实现自定义的解释器来执行这些转换后的指令 | 反编译得到的就是一段switch-case解释器,真正的业务逻辑在encrypted_bytecode里。动态调试得先还原VM调度循环,再映射回原始语义。总之,需要得到夹谷场上的VMP实现逻辑 |


反调试
- 反调试
- root检测
- 反抓包(禁止代理、SSL Pining)
查壳
APK就是一个压缩包,解压之后就能看到很多代码文件以及二进制文件。查壳的原理就是将apk解压之后,读取里面所有”.so”类型的文件名,根据文件名判断对应的壳类型,与cms指纹识别是一样的道理,关键在于指纹库的强大。
自己写一个脚本(ApkScan-PKID早就不更新了,且指纹太老,不建议使用)
Detect It Easy(DIE):开源免费,且一直更新,可通过Microsoft Store下载安装。功能比较强大,里面整合了多个工具的功能。

Virus Total:专门分析恶意程序的平台,可以用来分析apk,解析相关信息。DIE工具里面就内置了这个平台

ApkCheckPack:根据“.so”的文件名和路径执行指纹进行匹配,还能检测反root、反调试、反模拟器采用的手段,以及检测apk中的硬编码信息。https://github.com/moyuwa/ApkCheckPack/releases
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 少欣安全!