深入分析APK文件格式

news2024/12/23 4:24:28

前言

最近在升级项目的 Gradle 及 AGP 版本,在进行APK编辑再压缩时遇到了前后压缩比不一致的问题,所以抽空又一总结了下 APK (ZIP) 文件格式。

无论是使用 7z 进行包体积优化,或是快速构建多渠道包,又或是V2、V3签名等都是基于 APK (ZIP) 文件格式进行的,因此对 APK (ZIP) 格式的了解也颇为必要 。

ZIP文件格式

未进行(V2、V3)签名的 APK 就是一个标准的 ZIP 文件,所以本文会先介绍 ZIP 文件格式,后续再介绍签名后的 APK 与 标准 ZIP 文件有何差异。

首先看看 ZIP 文件格式预览:

    [local file header 1]
    [file data 1]
    [data descriptor 1]
    . 
    .
    .
    [local file header n]
    [file data n]
    [data descriptor n]
    [central directory]
    [end of central directory record]

对结构描述并划分一下,看起来清晰很多:

ZIP 文件由两大区块组成:数据区、中心目录区

下面分别介绍一下两大区块:

数据区

先看数据区,数据区可以理解为一个数组,数组的每一项由 [local file header][file data][data descriptor] 这三项组成。

Local file header

首先看看 local file header (文件头) 的结构定义:

OffsetBytesDescription
04Local file header signature标识 (0x04034b50)
42Version needed to extract解压文件所需 pkware最低版本
62General purpose bit flag通用比特标志位
82Compression method压缩方式
102File last modification time文件最后修改时间
122File last modification date文件最后修改日期
144CRC-32CRC-32校验码
184Compressed size压缩后的大小
224Uncompressed size未压缩的大小
262File name length (n)文件名长度
282Extra field length (m)扩展区长度
30nFile name文件名
30+nmExtra field扩展区

这里需要注意的是每个 Entry 的文件头 都有自己的 Compression method —— 压缩方式,也就是说 ZIP 文件内的不同 Entry 可以选择不同的压缩方式,并不要求里面的文件都采用同一种压缩方式。

不同的压缩方式对应不同的值,具体如下:

compression method: (2 bytes)
    (see accompanying documentation for algorithm
    descriptions)
    0 - The file is stored (no compression)
    1 - The file is Shrunk
    2 - The file is Reduced with compression factor 1
    3 - The file is Reduced with compression factor 2
    4 - The file is Reduced with compression factor 3
    5 - The file is Reduced with compression factor 4
    6 - The file is Imploded
    7 - Reserved for Tokenizing compression algorithm
    8 - The file is Deflated
    9 - Enhanced Deflating using Deflate64(tm)
   10 - PKWARE Data Compression Library Imploding
   11 - Reserved by PKWARE
   12 - File is compressed using BZIP2 algorithm

APK中,使用的是 0-Stored (不压缩)和 8-Deflated (Deflated 压缩算法压缩)

File data

Flie data 则是文件压缩或直接存储后的二进制数据。

Data descriptor

Data descriptor 区块,只有通用比特标志位的第三位bit为1时才会出现此区块。

Data descriptor:
  
  crc-32                          4 bytes
  compressed size                 4 bytes
  uncompressed size               4 bytes
        
This descriptor exists only if bit 3 of the general
purpose bit flag is set (see below).  It is byte aligned
and immediately follows the last byte of compressed data.
This descriptor is used only when it was not possible to
seek in the output .ZIP file, e.g., when the output .ZIP file
was standard output or a non seekable device.  For Zip64 format
archives, the compressed and uncompressed sizes are 8 bytes each.

案例展示

进过上面的理论学习,接下来可以找个APK瞅瞅;

可以看出:在该APK文件中,classes2.dex 文件的压缩方式为 0x0008 (Deflated)。

中心目录区

在 ZIP 文件中,数据区后面紧接着的就是中心目录区,ZIP 里的每个文件 (Entry) 在数据区和中心目录区都分别有一条对应的数据记录 (File Record) 和目录记录 (Dir Record)。(完成V2、V3签名的 APK 中,签名区块则位于数据区与中心目录区之间,签名区块这一点单独讲,先看看中心目录区。)

中心目录区由多条目录文件头 (Fille header) 和一条目录尾部 (End of central directory record) 组成,

File header

Fille header 格式定义如下:

OffsetBytesDescription
04Central directory file header标识(0x02014b50)
42Version made by压缩所用的pkware版本
62Version needed to extract解压所需pkware的最低版本
82General purpose bit flag通用位标记
102Compression method压缩方法
122File last modification time文件最后修改时间
142File last modification date文件最后修改日期
164CRC-32CRC-32校验码
204Compressed size压缩后的大小
244Uncompressed size未压缩的大小
282File name length (n)文件名长度
302Extra field length (m)扩展域长度
322File comment length (k)文件注释长度
342Disk number start文件开始位置的磁盘编号
362Internal file attributes内部文件属性
384External file attributes外部文件属性
424relative offset of local header数据文件头的偏移地址
46nFile name文件名
46+nmExtra field扩展域
46+n+mkFile comment文件注释内容

从 File header 的格式定义中不难看出:目录区文件头比数据区文件头多了几个字段,例如:数据文件头的偏移地址。其他字段基本是重复的。

目录记录 (Dir Record) 只有文件头,因此比起 数据记录 (File Record) 要小很多,解析起来也比较快,所以通过中心目录区可以快速获取压缩文件部分信息而不用解析整个 ZIP ,例如:ZIP 中是否含有某个文件。

同时目录记录里包含了对应的文件记录的偏移量,这样能通过目录快速定位并解压 ZIP 内的单个文件,避免从数据区从头遍历解析。

案例展示

End of central directory record

上面提到,通过中心目录区可以优化数据的查找和解析过程,那如何快速知道中心目录区的起始位置呢?

目录尾部对中心目录进行了简要描述,中心目录区的起始位置就在目录尾 (End of central directory record) 当中,其格式如下:

OffsetBytesDescription
04End of central dir signature标记(0x06054b50)
42Number of this disk当前磁盘编号
62Number of the disk with the start of the central directory中心目录开始位置的磁盘编号
82Total number of entries in the central directory on this disk该磁盘上所记录的中心目录数量
102Total number of entries in the central directory中心目录总数
124Size of central directory (bytes)中心目录的大小
164offset of start of central directory with respect to the starting disk number中心目录起始位置偏移量
202.ZIP file comment length(n)注释长度
22n.ZIP file comment注释内容

通过目录尾部的中心目录起始位置偏移,能快速获取目录位置。

案例展示

所以很有意思的一点:在某些场景下,ZIP 也可以从后往前解析的,而且能提效。常用的 ZipFile 正是使用了从后往前解析的方式。首先找到尾部,通过尾部,获取中心目录区的偏移量和长度,然后通过中心目录区域信息定位实际的数据区获取最终的数据。

ZipFile

这一步分析可能比较复杂,若阅读困难可以先略过~ 详细过程如下:

1)创建ZipFile文件

这一步主要是从目录尾部获取中心目录起始偏移等信息,并建立目录数据Hash与其起始偏移地址映射关系;
在 ZipFile 的构造函数中会调用 ZipFile#open 函数:

[java.util.zip.ZipFile#open] ->
[xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Open]->
[xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Open_Generic] ->
[xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Put_In_Cache0] ->
[xref/libcore/ojluni/src/main/native/zip_util.c#readCEN]

通过进一步阅读可以了解到 [readCEN]函数中只是对中心目录做了粗略的解析。只有在 getEntry 时才会仔细解析单条目录记录,其解析逻辑如下:

再看看尾部数据获取,从文件末尾查找能与尾部数据标志 0x06064B50 匹配的4个字节。

2)通过Hash查找并解析Entry

首先看下 ZipFile#getEntry(String ) 是如何查找并解析出 Entry 的:



今天周末,再深究一下,看看newEntry的解析。

这里就和我们看到头文件(File Header)的字段对应上了,头文件内容基本解析出来并放入ze 对象,ze 对象的指针做为函数的返回值。

看到这里,能体会到 ZipFile 做的优化还是挺细致的,ZipFile 创建的时候只做目录的粗解析,在获取具体 文件(Entry) 信息时只解析需要的头文件,到目前为止只获取了 Entry 基本信息,并没有去解析数据区。

3)二进制数据流读取

再看看 ZipFile 如何获取文件的二进制数据流的?

通过上面的 getEntry,我们已经能拿到 Entry 在中心目录区的头文件对象了,因此java.util.zip.ZipFile#getInputStream(ZipEntry entry)获取数据流时,通过 Entry 对应的指针获取数据记录 (File Record) 的起始地址直接读取数据即可。

[java.util.zip.ZipFile#getInputStream(ZipEntry entry)] ->
[java.util.zip.ZipFile.ZipFileInputStream#read(byte[], int, int)] ->
[xref/libcore/ojluni/src/main/native/java_util_zip_ZipFile.c#ZipFile_read>] ->
[xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Read]

APK文件格式

前文提到:未进行(V2、V3)签名的 APK 就是一个标准的 ZIP 文件,完成V2、V3签名的 APK 中,签名区块则位于数据区与中心目录区之间。

APK Signing Block

APK 签名区块的结构如下:

偏移(Bytes)字节数描述
A18签名区块长度(此8字节不计算在内)
A1 + 8n一组或多组ID-Value
B1 - 248签名区块长度
B1 -1616常量标识 “APK Sig Block42”

A1: 签名区块起始偏移
B2: 中心目录区起始位置

案例展示

1)首先获取目录区域偏移量;

目录尾部标识:06 05 4B 50,中央目录区起始位置偏移量:00 04 4E 7D

2)通过中央目录区偏移量,找到常量标识"APK Sig Block42"及签名区块长度:

同时也能知道签名区块长度为:0F F8;

3)查找 ID-Valuce 的起始位置,以魔术最后一位,前移动 0F F8 :

所以图中的偏移量为 0X043E86 就是ID-Value的起始位。

4)查看 ID-Value 结构:

长度(bytes)
ID-Value 字节总数8字节(不包含本身)
ID4字节
Value“ID-值”对的长度 - 4 个字节

V2 的签名信息存放在 ID = 0x7109871a 的数据块中
V3 的签名信息存放在 ID = 0xf05368c0 的数据块中

  • 第一项 ID-Value
    • 偏移量:0X043E8D
    • 长度:05 6E
    • ID:71 09 87 1A

  • 第二项 ID-Value
    • 偏移量:0x0443FB
    • 长度: 0A 62
    • ID: 72 42 65 77

  • 第二项最后一位刚好与第二位的区块长度衔接

  • 不难发现这里的Value字节全为0,这些 0 本身毫无含义,本以为能删除掉缩减一点包体,但是经查询,这里为了使签名区块为4096 的整数倍与内存页对齐(同时高版本 Android 会检测这个对齐规则,不可删除)。

部分团队的多渠道APK方案,则是在签名区块中加入额外的 ID-Value ,通常是固定一个 ID,Value设置为渠道信息即可。

小结

至此完成部分了 ZIP 及 APK 文件的格式分析,另外 ZIP 相关的其他知识点也比较繁多,很难说一篇文章能盖全,或全面了解,例如各类压缩算法、ZIP 版本差异以及 7z (后续计划讲下 7z ) 等等,但是能掌握到文中介绍的这些,也足以解决日常遇到的绝大多 ZIP 相关问题了。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1069044.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

问卷调查中常见问题及解决方法

随着技术和市场的日益发展,问卷调查已经成为了人们了解客户需求和反馈的一种必要手段。但是,问卷调查也面临着一些问题。在本文中,我们将探讨一些常见问题,问卷调查会遇到什么问题?怎么解决?并提供一些解决…

【斗破年番】火火抱彩鳞把她整害羞啦!女王解锁新造型,身形丰满超有料

Hello,小伙伴们,我是小郑继续为大家深度解析斗破年番系列。 斗破年番已经更新,这集的精彩程度可以说出乎预料。不论是节奏的把控,打戏的呈现还是氛围的营造都十分的出彩。尤其是在萧炎施展三千雷动时运用的三年之约时的BGM简直让观感体验瞬间…

审计和风控做什么——企业审计和风控工作的相同和不同

审计和风控是现代企业管理中两个重要的领域。它们在企业和社会组织的运营中发挥着重要作用。本文将探讨审计和风控的异同点。 一、审计和风控的定义 审计是指对一个组织或个人财务报表、业务过程、内部控制和风险管理等方面进行审核的活动。它的目的是发现潜在的问题、风险和控…

CUDA学习笔记2——CUDA程序基本框架

CUDA向量运算 CUDA程序的基本框架为: 头文件包含 常量定义/宏定义 C 自定义函数和CUDA核函数声明 int main(void) { 分配主机与设备内存 初始化主机中的数据 将部分数据从主机拷贝至设备 调用核函数在设备中进行计算 将部分数据从设备拷贝至主机 释放主机与设备内存…

采集网页数据保存到文本文件---爬取古诗文网站

访问古诗文网站(https://so.gushiwen.org/mingju/) 会显示出这个页面,里面包含了很多的名句,点击某一个名句(比如点击无处不伤心,轻尘在玉琴)就会出现完整的古诗 我们点击鼠标右键,点…

【C++设计模式之责任链模式:行为型】分析及示例

简介 责任链模式是一种行为型设计模式,它允许将请求沿着处理链传递,直到有一个处理器能够处理该请求。这种模式将请求的发送者和接收者解耦,同时提供了更高的灵活性和可扩展性。 描述 责任链模式由多个处理器组成一个处理链,每…

如何批量获取拼多多商品详情数据,拼多多商品详情API接口

批量获取拼多多商品详情数据可以采用以下方式: 使用拼多多开放平台API接口。 拼多多开放平台提供了API接口,可以通过API接口获取拼多多平台上的商品信息,使用API接口需要进行权限申请和认证,操作较为复杂。使用第三方工具。 市面…

Transformer预测 | Pytorch实现基于Transformer的锂电池寿命预测(NASA数据集)

文章目录 效果一览文章概述模型描述程序设计参考资料效果一览 文章概述 Pytorch实现基于Transformer 的锂电池寿命预测,环境为pytorch 1.8.0,pandas 0.24.2 随着充放电次数的增加,锂电池的性能逐渐下降。电池的性能可以用容量来表示,故寿命预测 (RUL) 可以定义如下: SOH(t…

网络安全(黑客)——自学篇

什么是网络安全? 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域,都有攻…

基于FPGA的I2C读写EEPROM

文章目录 前言一、I2C协议1.1 I2C协议简介1.2 物理层1.3 协议层 二、EEPROM2.1 型号及硬件规格2.2 各种读写时序 三、状态机设计四、项目源码:五、实现效果参考资料 前言 本次项目所用开发板FPGA芯片型号为:EP4CE6F17C8 EEPROM芯片型号为:24L…

[C++从入门到精通] 11.回顾类内初始化、默认构造函数、=default

📢博客主页:https://loewen.blog.csdn.net📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉📢现…

Python爬虫(二十二)_selenium案例:模拟登陆豆瓣

本篇博客主要用于介绍如何使用seleniumphantomJS模拟登陆豆瓣,没有考虑验证码的问题,更多内容,请参考:Python学习指南 #-*- coding:utf-8 -*-from selenium import webdriver from selenium.webdriver.common.keys import Keysimp…

辅助寄存器是干什么用的

目录 请问CPU 的 MREQ 引脚和 IORQ 引脚分别是干什么用的 那这里的引脚是什么含义呢? 程序是指令和数据的集合 辅助寄存器是干什么用的 寄存器的用途取决于它的类型 PC 寄存器也叫作“程序指针”,存储着指向 CPU 接下来 要执行的指令的地址。PC 寄存…

jmeter添加断言(详细图解)

先创建一个线程组,再创建一个http请求。 为了方便观察,我们添加两个监听器,察看结果树和断言结果。 添加断言:响应断言,响应断言也是比较常用的一个断言 设置响应断言:正常情况下响应代码是200。选择响应代…

固态硬盘删除的资料能恢复吗?

固态硬盘(SSD)作为一种存储设备,在读写速度和抗摔性方面具有显著优势,因此备受许多用户的青睐。然而,在使用过程中,由于人为误操作或设备内部故障,固态硬盘可能会导致数据丢失。所以固态硬盘删除…

【WinRAR】去除请购买WinRAR许可

新建rarreg.key文件 在WinRAR安装目录新建rarreg.key文件,文件内容如下: RAR registration datawncnUnlimited Company LicenseUID1b064ef8b57de3ae9b5264122122509b52e35fd885373b214a4a64cc2fc1284b77ed14fa2066ebfca6509f9813b32960fce6cb5ffde62890079861be57…

聊聊分布式架构02——Http到Https

目录 HTTP通信协议 请求报文 响应报文 持久连接 状态管理 HTTPS通信协议 安全的HTTPS HTTP到HTTPS的演变 对称加密 非对称加密 混合加密机制 证书机构 SSL到底是什么 HTTPS是身披SSL外壳的HTTP HTTP通信协议 一次HTTP请求的通信流程:客户端浏览器通过…

slam从入门到精通(稍复杂一点的运动控制)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 ros本身只是提供了一个框架,上面对应客户需求,下面对应各个传感器,中间就是各个算法和决策措施。但是robot本身…

SpringCloud之Hystrix高版本熔断器源码解析

Hystrix官方已经停止开发了,Hystrix官方推荐使用新一代熔断器作为Resilience4j。作为新一代的熔断器,Resilience4j有很多优势,比如依赖少,模块化程度较好等优势。 Resilience4j是受Hystrix启发而做的熔断器,通过管理远…

【动手学深度学习】课程笔记 00-03 深度学习介绍及环境配置

目录 00-01 课程安排 02 深度学习介绍 深度学习实际应用的流程 完整的故事 03 环境配置 00-01 课程安排 1. 学习了这门课,你将收获什么? 深度学习的经典和最新模型:LeNet,ResNet,LSTM,BERT&#xff1…