安卓架构

image-20251229110511710

  1. 内核层:操作系统内部的核心模块,负责所有系统资源的调度;
  2. 运行层
    • 安卓虚拟机: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文件里面。
  3. 框架层:Java SDK,核心是适用于Android的java api库
  4. 应用层:用java代码实现业务功能(调用NDK和JAVA SDK)

APK打包

android_compile_flow

  1. 资源分析&预处理
    • aapt分析AndroidManifest.xml
    • 生成对应的R.javaresources.arsc(部分资源文件直接编译成二进制,resources.arsc就是生成二进制文件的中间产物,类似于CMakeLists.txt)文件
    • aidl(Android Interface Define Language)工具生成接口文件(安卓接口不同于普通的java接口,主要用来IPC进程间通信,在定义java接口的基础之上,需要额外指定很多参数)
  2. 编译:将所有的java文件(包括第三方的JAR包)由D8/R8最终生成DEX文件(Darlvik Executable File,一个dex文件中最多容纳的65535个方法,当方法数量超过65535时,生成多个DEX文件)
  3. 打包:通过AAPT2 link功能,将上面所有DEX文件、arsc文件、AndroidManifest.xml等等归档成一个APK压缩文件
  4. 优化
    • 使用类似于zipalign的工具,将APK压缩文件内的每个小模块进行4字节对其,使运行时内存访问更快;
    • 使用类似于apksigner的工具,对生成的APK进行V1/V2/V3/V4签名
  5. 自动化:上面所有的步骤都可以由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”的混合编译策略

jvm_vs_dvm_flow

OIP-C.webp

安全加固

代码隐藏

阶段 原理 逆向分析
代码混淆 把原先的代码通过变量替换(成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实现逻辑

android_common_harden_tech_compare

mermaid

反调试

  1. 反调试
  2. root检测
  3. 反抓包(禁止代理、SSL Pining)

查壳

APK就是一个压缩包,解压之后就能看到很多代码文件以及二进制文件。查壳的原理就是将apk解压之后,读取里面所有”.so”类型的文件名,根据文件名判断对应的壳类型,与cms指纹识别是一样的道理,关键在于指纹库的强大。

  1. 自己写一个脚本(ApkScan-PKID早就不更新了,且指纹太老,不建议使用)

  2. Detect It Easy(DIE):开源免费,且一直更新,可通过Microsoft Store下载安装。功能比较强大,里面整合了多个工具的功能。

    image-20251202211432654

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

    image-20251202212021068

  4. ApkCheckPack:根据“.so”的文件名和路径执行指纹进行匹配,还能检测反root、反调试、反模拟器采用的手段,以及检测apk中的硬编码信息。https://github.com/moyuwa/ApkCheckPack/releases