常见 Android 文件格式
库文件
一系列代码功能接口与资源数据的有机集合 android SDK等,三方库有风险(供应链攻击)
jar包:zip格式压缩包,存放编译后java的class文件集合,签名信息保存在 META-INF 目录。主要分析工具有jadx,jd-gui,JEB等。
aar包:可包含代码,任何在开发中用到的资源数据,AS使用的全新库文件格式。
- AndroidManifest.xml:可用于定义 aar 包的名称、编译器版本等
- classes.jar:包含 aar 库文件中所有代码生成后的 class 文件
- res 目录:存放所有资源
- aidl 目录(本文件不存在):存放 AIDL 接口文件
- assets 目录(本文件不存在):存放 Asset 资源
- jni 目录(本文件不存在):存放编译好的不同版本的 so 库
- libs 目录:存放 aar 包引用的第三方 jar 包
APK
Android Package:android系统软件安装包文件
apk文件结构:
就是zip文件格式,完整的apk文件包含:
- AndroidManifest.xml:编译好的 AXML 二进制格式的文件
- META-INF 目录:保存 APK 的签名信息
- classes.dex:程序的可执行代码。如果开启了 MultiDex,则会有多个 DEX 文件
- res 目录:程序中使用的资源信息。针对不同分辨率的设备,可使用不同的资源文件
- resources.arsc:编译好的二进制格式的资源信息
- assets 目录:程序使用 Asset 系统来存放 Raw 资源,所有的资源都将存放在这个目录下
apk生成流程
Android Studio 时代整个打包过程被抽象成了将编译模块和依赖项打包成 DEX 文件、将 APK Packager 打包成 APK 和签名两个步骤,底层的操作细节被掩盖
APK 的安装流程
- 通过系统程序安装(开机时安装):通过开机启动的packagemanagerservice服务完成,在启动时扫描 /system/app 并重新安装所有程序
- 通过android市场安装:如果放到/system/app下,apk自动安装;或者等待下载完成apk包,根据提示引导用户安装(Android 6.0 开始,软件安装完成后会弹出权限控制界面,允许用户对软件所使用的权限进行细粒度的管理)
- 通过adb安装:android sdk工具,通过连接PC进行安装,命令 adb install apk路径
- 手机SD卡安装:运行apk包,调用 Android 系统程序 packageinstaller.apk,提示引导用户安装。
分析 packageinstaller.apk 的实现步骤
通过 Android 手机的文件管理程序打开apk,弹出安装界面,这个界面就是packageinstaller的packageinstallerActivity,请求安装的时候就会启动,并接收通过intent传递的apk信息
源码位于android源码的packages/apps/PackageInstaller目录,activity启动时,会初始化PackageManager对象与PackageParser.package对象,然后调用packageUtil.getpackageinfo()解析程序包内容,解析错误就失败返回,成功就调用setContentView(),设置packageinstallerActivity显示视图,调用PackageUtil.getAppSnippet()和PackageUtil.initSnippetForNewApp()来设置packageinstallerActivity控件,显示程序名、图标,最后调用initiateInstall()方法进行初始化。
PackageInstallerActivity 的 onCreate() 方法如下:
- 首先调用 parsePackageName() 从 AndroidManifest.xml 中获取程序包名,接着构建了一个 Package 对象,然后逐一处理 AndroidManifest.xml 中的标签。在处理 application 标签时使用了 parseApplication(),该方法负责解析 activity、receiver、service、provider,并将它们添加到传递进来的 Package 对象的 ArrayList 中
- 在 onCreate() 中,getPackageInfo() 返回后调用了 initiateInstall()、initiateInstall() 负责检测该程序是否已安装。若已安装,就调用 startInstallConfirm() 显示安装界面;若没安装,则调用 showDialogInner(DLG_REPLACE_APP) 弹出替换程序对话框。
- startInstallConfirm() 设置了安装与取消按钮的监视器。最后是 onClick() 的按钮点击响应,安装按钮使用 startActivity() 启动一个 Activity 类 InstallAppProgress,该类在初始化 onCreate() 时调用 initView(),后者最终调用 PackageManager 类的installPackage() 来安装 APK
- installPackage() 是 PackageManager 类的一个虚函数,PackageManagerService.java 实现了它。installPackage() 调用了 installPackageWithVerification(),该方法先验证调用者是否具有安装程序的权限,然后通过消息处理的方式调用 processPendingInstall() 进行安装。processPendingInstall() 调用了 installPackageLI()。installPackageLI() 经过一系列验证,最终调用 replacePackageLI() 或 installNewPackageLI() 来替换或安装程序。
- 最终会调用 scanPackageLI(),实例化一个 PackageParser 对象,然后调用其 parsePackage() 来解析 APK 程序包。在代码的最后调用了 scanPackageLI() 的另一版本,此版本的 scanPackageLI() 将完成 APK 的依赖库检测、签名验证、sharedUser 的签名检查、Native 库目录文件的更新、组件名称的检查等工作,并调用 mInstaller 的 install() 来安装程序。
- install() 执行完毕,会通过 socket 回传结果,最终由 PackageInstaller 根据返回结果进行相应的处理, 到此一个APK 就安装完成
classes.dex
其中包含 APK 的可执行代码,是分析 Android 软件时最常见的目标
DEX 文件结构
参考:https://blog.csdn.net/zlmm741/article/details/104706841
在 Android 源码文件 dalvik/libdex/DexFile.h 中,有 DEX 文件可能用到的所有数据结构和常量定义。
AndroidManifest.xml
参考:https://blog.csdn.net/zlmm741/article/details/104717139
其中存放了 APK 的大量配置信息:软件名称、图标、主题、包名、组件配置等
合理、安全地配置组件是安全开发中最重要的一课
AndroidManifest.xml 文件的格式
采用 XML 文本格式,在开发阶段,所有的配置信息都可直接以可视化的方式编辑。
所有的配置都属于 manifest 标签,与程序配置相关的部分属于 Android 标签,程序中使用的四大组件也在这里声明
- App Manifest 的详细信息可在 Android 的 API Guides(https://developer.android.google.cn/guide/topics/manifest/application-element )中找到
- android:allowBackup 允许系统在进行备份操作时备份程序的应用数据,典型的操作是在终端执行 adb backup 命令,或点击手机设置界面上的“备份操作”按钮。对数据安全比较敏感的话,可设置为“false”
AXML 文件格式
- AS 在编译 APK 时,会将 AndroidManifest.xml 处理后打包进去。打包进去的 AndroidManifest.xml 被编译成二进制格式的文件。解压 APK 后,用文本编辑器打开它,会发现内容是乱码。这个打包后的 AndroidManifest.xml 称“AXML”,其格式称“AXML 文件格式”
- APK 用 AXML 而非纯文本格式 XML 存放数据,主要目的应是解决 APK 加载时的性能问题。在 Android 设备内存资源与能耗极其有限的情况下,二进制的 AXML 在分析处理速度和内存占用方面都比纯文本的 XML 有明显优势。但 AXML 的内容不能直接显示,因此,逆向分析 APK 时,对其格式有所了解,才能知道它原来的内容
- Android 官方没明确给出 AXML 的二进制布局规范,可通过阅读 APK 打包流程和系统加载 APK 的代码掌握它的文件格式。在 Android 系统源码文件 frameworks/base/include/androidfw/ResourceType.h 中列举了 AXML 使用的大部分数据结构和常量定义
- 学习 AXML 文件格式过程中,在了解数据结构的同时,可使用 010 Editor 辅助分析(官方模板比较粗糙,可下载链接: https://pan.baidu.com/s/1hr0sH5UWvzvz5wqUenARPw 提取码: r9c4)
- AXML 文件格式简图。在 AXML 中,数据块用 chunk 表示。从整体结构看,一个 AXML 由文件头 ResFileHeader、字符串池 ResStringPool、资源 ID 块 ResIDs、XML 数据内容块 ResXMLTree 四部分线性地组成
AXML 文件的修改
部分 APK 保护工具及一些厂商的加固方案利用 Android 系统解析 AXML 的漏洞,在编译 APK 时构造畸形的 AXML,是系统能正常安装 APK,但无法运行 ApkTool 等反编译工具。这时就要对 AXML 进行修改。最直接的修改方式:配合使用 010 Editor 及 AXML 模板查看文件格式,找到异常部分进行修改
对一些已出现的 AXML 加固方案,可用现成的工具修改:
- AmBinaryEditor:https://github.com/ele7enxxh/AmBinaryEditor
- AndroidManifestFix:https://github.com/zylc369/AndroidManifestFix
resources.arsc
参考:https://blog.csdn.net/zlmm741/article/details/104724669
Android 软件开发过程中有个重要的目录,即 app 工程下的 java/res 目录。这里存放了软件使用的各种类型的资源文件。这些资源文件在编译成 APK 时会被统一打包存放在 APK 的 res 目录。其中,jpg、png 图片文件按照原样存放,layout、drawbale、color 目录的 xml 配置文件会以 AXML 格式存放,所有的文件在打包时会以原来的文件名保存。连接程序代码和资源的桥梁是 R.java,该文件由编译器自动生成,里面保存的是不同类型的资源的 ID 值。这些 ID 值要以一种方式定位自己属于哪个资源,而这正是 res/values/public.xml 要解决的问题。第二章中就是通过 res/values/public.xml 文件定位字符串“unsuccessed”所对应的 ID 值的
一个资源包含资源的名称、类型、值及所在的 Package。resources.arsc 包含不同语言环境中 res 目录下所有资源的类型、名称与 ID 对应的信息
ARSC 文件格式
resources.arsc 文件的格式称“ARSC 文件格式”,在 APK 中只有 resources.arsc 文件用这种格式
ARSC 使用的数据结构同样位于 ResourceTypes.h 文件中。和 AXML 一样,它表示数据块使用 chunk。ARSC 中也引用了不同的 AXML 中的数据结构。一个 ARSC 从整体结构上看,由文件头 ResTableHeader、资源项值字符串池 ResStringPool、Package 数据内容块 ResTablePackage 三部分线性地组成
分析 ARSC 文件格式,同样用 010 Editor。使用模板库中的 AndroidResource.bt 模板。该模板同时支持 AXML 和 ARSC 文件的解析
下面是一幅 ARSC 文件格式简图:
ARSC 文件的修改
修改 ARSC 文件的目的:阻止或修正 APK 的反编译(一些经过特殊处理的 ARSC,不仅在 APK 安装时能正常被系统加载,且能阻止 ApkTool 等工具的反编译);修改 ARSC 里 string 类别中的字符串或其他资源信息,通常用于软件的汉化与修改
修改方式:
- 使用 010 Editor 编辑特定的字段
- 若修改的地方很多,建议使用 ApkTool 反编译后修改 XML 文件
META-INF 目录
参考:https://blog.csdn.net/zlmm741/article/details/104740233
APK 包中有个“META_INF”目录,里面存储了 APK 签名有关的信息
通过unzip解压apk可以在“META_INF”目录获取签名信息
CERT.RSA
CERT.RSA 文件中存放了 APK 的开发者证书与签名信息。通过该文件可试别开发者的身份,以及判断 APK 是否被篡改。CERT.RSA 文件是由 DER 编码的证书。由于在 DER 内部使用了 ASN1 进行编码,使用任何 ASN1 解码库都能对其进行解码,如 GNU 的 libtasn1 库。这里使用 OpenSSL 提供的解码功能来查看 CERT.RSA 的证书内容
- OpenSSL 是一款跨平台的加解密库管理套件
- 安装:
- Windows:访问 OpenSSL 官网下载完整的可执行文件包,或在 Cygwin 中搜索安装
- Ubuntu:执行 sudo apt-get install openssl 命令
- macOS:执行 brew install openssl
- 安装完成后,执行openssl 命令可查看 CERT.RSA 中开发者证书的内容
- 输出的证书信息是 APK 合法和有效的凭证,在对 APK 进行保护时,其中的许多项都是用来鉴别 APK 是否已被修改的有力证据
MANIFEST.MF
- MANIFEST.MF 是签名的清单文件,是一个文本文件
- 可看出,打包该文件的工具是 Android Gradle 3.5.3,下面的每组信息都包括 Name 和 SHA1-Digest,表示 APK 中每个文件的路径和它的 SHA-1 散列值的 Base64 值
- 以 res/layout/notification_action.xml 文件为例,执行下述命令,对比它的值:
- “vUd0FW2wrq1c1vqQxRuZryvEKPg=” 与 MANIFEST.MF 文件中的值是一样的。这证明了在 MANIFEST.MF 中存放的是 APK 中所有包含的文件列表的 SHA-1 散列值的 Base64 值,从而保证了在进行 APK 签名验证时 APK 中所有文件均未被修改
CERT.SF
- CERT.SF 是签名信息文件,也是一个文本文件
- 比较发现CERT.SF比MANIFEST.MF多了一个SHA1-Digest-Manifest的值,这个值其实是MANIFEST.MF文件的SHA1再base64编码的值,可以手动验证,也可以从android源码分析。
ODEX
参考:https://blog.csdn.net/zlmm741/article/details/104758848
- 在 Android 5.0 前,主要使用的虚拟机是 Dalvik。当 APK 首次安装,或系统升级、重新启动时,为提高 DEX 的执行效率,Dalvik 会对 APK 中的 DEX 进行一定程度的优化。具体做法:解析 DEX 并生成一个 ODEX 文件,将其存放在 Android 设备的 /data/dalvikcache 目录下。以后在运行这个程序时,就不会读取 APK 中的 DEX,而是直接加载这个优化过的 ODEX,从而大大节省每次运行程序时在优化上花费的时间
- 系统生成 ODEX 的方法是内部调用系统命令 dexopt。此命令不允许直接调用生成 ODEX,但 Android 在 Dalvik 时代的早期版本中,会在系统源码的 build/tools/dexpreopt/dexopt-wrapper 目录下提供 dexopt-wrapper 工具,可用于手动生成 ODEX
- ODEX 比 DEX 多了如下内容:
- DexOptHeader:ODEX 文件头,描述 ODEX 的基本信息
- Dependences:依赖库列表,描述 ODEX 加载时可能使用的依赖库
- ClassLookups:优化数据块的类索引列表信息,用于提高类搜索速度
- RegisterMaps:优化数据块的寄存器图(Register Map)信息,主要用于帮助 Dalvik 虚拟机进行精确的垃圾回收(Garbage Collection)
ODEX 转换成 DEX
- 经过优化的 ODEX 中包含与设备相关的依赖库列表 Dependeces 结构信息,不同的 Android 设备的底层 bootClassPath 环境变量中存放的系统加载库列表不尽相同,因此,将 ODEX 转换成 DEX 的过程是设备相关的
- 为将 ODEX 还原成 DEX,要先将 ODEX 反编译成 smali 文件,再将 smali 文件编译成 DEX。这个过程称“deodex”
- 反编译和编译 smali 文件使用的工具都是 smali。在使用 baksmali 命令反编译 ODEX 时,要加入参数 -d,以指定与 ODEX 相关的设备的 framework 目录。因为依赖库都来源于 Android 设备的 /system/framework 目录,所以第一步操作是将设备上的 framework 目录拉(pull)到本地。
1.执行命令要有 Root 权限,命令如下:
adb pull /system/framework ~/Program/framework_19
2.执行如下命令完成反编译:
baksmali -a 19 -x crackme0201.odex -d ./framework -o ./outdex
- -a 参数指定 Android 设备的版本,“19”表示当前设备是 4.4 版本。-x 参数指定要操作的 ODEX。-d 参数指定刚 pull 下来的 framework 目录。-o 参数指定输出 smali 文件的目录。完成上述操作后,若没出现错误,会在 outdex 目录生成一系列 smali 文件。这时只要执行 smali 命令即可生成 DEX
OAT
参考:https://blog.csdn.net/zlmm741/article/details/104774994
- OAT 文件是在 Android 4.4 中引入的。OAT 是优化过的、用于 ART 虚拟机执行的 DEX 文件,类似 Dalvik 的 ODEX
- 对新版本的 Android 程序进行安全研究,无论是对 OAT 文件的 Hook 还是加密保护,都要了解 OAT 文件格式
ART 虚拟机
ART 使用 AOT(Ahead-of-Time)编译技术,在 APK 第一次安装或系统升级、重启时,通过调用 dex2oat 命令将 APK 中的 DEX 文件静态编译成 OAT 并存放到 Android 设备的 /data/dalvik-cache 或 /data/app/package 目录。
dex2oat 与 dexopt 不同,dex2oat 更像一个编译器,将 DEX 中的 Dalvik 字节码编译成 Native 机器码。经过这样的操作,以后启动程序时,ART 就会提高 APK 的启动速度,从而执行生成的 OAT 文件而不是 APK 中的 DEX 文件。
从 Android 5.0 开始,系统默认将 ART 作为虚拟机,程序运行速度明显变快。但有个缺点,即 OAT 的静态编译操作会影响 APK 的安装效率,导致 Android 4.4 后 APK 安装速度更长。为了使运行和安装速度都更快,Android 7.0 增加了 JIT(just-in-Time)编译。新版本的 Android 使用的是基于 JIT on AOT 的编译技术
生成 OAT 文件
系统在安装 APK 时,会调用 dex2oat 自动生成 OAT 文件。也可手动执行 dex2oat 命令,为指定的 DEX 生成 OAT 文件
具体操作:在执行 dex2oat 命令时使用 --dex-file 参数指定传入的 DEX 路径(若有多个 DEX,可多次指定该参数),然后为 --oat-file 参数指定 OAT 的输出路径(在指定传入的路径时,该路径要对当前的 adb shell 用户有可写权限)
OAT 文件格式
和 ODEX 一样,OAT 也有自己的格式。Android 原生程序以 ELF 格式作为程序格式基础,并在此基础上增加了属于 Android 的应用程序二进制接口(Application Binary Interface, ABI)
arm-linux-androideabi 中包含 ELF 文件格式在 Android 上的参数传递、浮点指针、栈展开、异常处理等规范信息。OAT 文件格式在设计之初就考虑到最终执行的程序是 ELF 格式的这一事实,为避免过多的文件格式设计带来的麻烦,最终将 OAT 文件格式完全融入 Android 所特有的 ELF 格式
- file 命令根据文件头 magic 与布局格式,识别出这是一个 ARM Linux 动态链接的 ELF 文件。Android 特有的 ELF 文件格式之后会学到,这里只关注 OAT 在 ELF 上的特别部分
一个 OAT 文件必须包含 oatdata、oatexec、oatlastword 三个符号
- oatdata 符号指向的地址是 OAT 所在 ELF 的 .rodata 段,这里存放的是 OAT 文件头 OATHeader、OAT 的 DEX 文件头 OATDexFile、原始的 DEX 文件 DexFile、OAT 的 DEX 类 OatClass 等信息
- oatexec 符号指向的地址是 OAT 所在 ELF 的 .text 段,这里存放的是编译生成的 Native 指令代码
- oatlastword 符号指向的地址是 OAT 文件结束处在 ELF 中的文件偏移,通过它可确定 OAT 文件的内容在哪里结束
将 OAT 文件转换成 DEX 文件
- 方法:OAT 文件中包含完整的 DEX 文件,定位 OAT 中的 DexFile 结构体,将其完整数据导出即可
本文来自博客园,作者:xyxyxyxyxyxy,转载请注明原文链接:https://www.cnblogs.com/my1127/p/16145670.html