APK文件结构详解

前言

最近有业务需求,需要向已经打包的APK里面注入渠道(channel)信息,方便APK下载安装之后进行渠道归因。向APK里面注入渠道信息已经有比较成熟的方案美团walle。walle的强大和实现原理本文不再赘述。为了理解walle的代码,并且在出现异常case的时候,能够自己解决。必须要对APK文件的结构了如指掌。

因此,本文的目的就是以一个简单的利用walle向APK里面注入渠道号为例。带你了解APK里面的各种细节,相信读者在看完此篇文章之后,再阅读walle的代码的时候一定豁然开朗。

ZIP和APK文件格式概览

ZIP文件格式概览

APK文件本质上是一个ZIP文件,因此在了解APK的格式之前必须先了解ZIP文件的格式。详情参见zip文件格式详解

ZIP文件的整体格式如下图所示,主要由三部分组成

  • 数据区(Contents of ZIP entries):存储文件压缩内容
  • 中央目录区(Central Directory Header):存储zip文件里面包含的所有目录
  • 中央目录结尾记录(End of Central Directory Record:ECDR):存储zip文件的整体信息

其中,中央目录结尾记录对于理解本文最为重要。最好看完zip文件格式详解 再继续往下阅读。此区域里面有一个值核心目录偏移地址非常的重要,以下图为例的话,核心目录的起始地址就是0x2D(注意:是地址,不是数值)

APK文件格式概览

APK在签名(V2/V3签名)之后,会在数据区和中央目录区之间插入APK签名区(APK Signing Block)

APK签名区的组成结构如下图所示

用表格表示如下图所示,其由4部分组成

  1. Block长度:APK签名区域总长度,包含后面3部分长度的总和
  2. ID-Value序列:后面会详细讲解
  3. Block长度:同1的值完全相同
  4. 魔数:APK Sig Block 42,hex大端表示的话就是:41 50 4b 20 3 69 67 20 42 6c 6f 63 6b 20 34 32

ID-Value序列里面存储了ID-Value列表,ID-Value序列的结构如下图所示。

列表的每一个Item里面由3个元素组成(8字节长度+4字节ID+内容)。其中APK的签名信息就是其中一项item,ID为0x7109871a。序列里面的每一个Item的结构如下图所示:

至于APK是如何计算签名签名信息保存在Item里面,以及Android系统是如何校验APK完整性的,详情参考APK签名机制之——V2签名机制详解walle的实现原理就是向ID-Value列表里面插入一个Item,由于APK安装的时候并不对插入的Item做校验,因此能够保证APK能校验成功的同时还能带上插入的信息

至此,我们对APK文件的基本结构有了一个整体的认识。下面,我将以上述的实例APK为例子,通过二进制分析如何一步一步提取出walle注入的ID=0x71777777的渠道信息

例子简介

基于wall,向APK里面注入渠道号信息。在APK打包的时候,向APK里面注入一下信息。

APK运行的时候,可以成功提取出渠道号信息

实例分析

实例分析前,我们再回顾一下APK的整体格式(这张图对后面步骤的理解很重要)

从APK里面寻找存ID=0x71777777的渠道信息的整体过程如下:

  1. 确定中央目录地址(中央目录地址刚好是APK签名地址的尾地址)
  2. 根据中央目录地址和APK签名块的长度确定ID-Value序列的首地址和尾地址
  3. 分析ID-Value块的结构,找到ID=0x71777777的内容部分,找到的内容就是我们注入的信息

注意,在阅读二进制文件的时候都要把实际的数字转成小端来看

ZIP文件是按照小端存储的(除特殊部分,比如说magic部分),比如说0x7109871A,实际存储16进制存储为:1A 87 09 71。关于大端和小端的区别可以参考大端和小端

1、确定中央目录的起始地址

中央目录结构如下所示,zip文件格式详解 里面详细介绍了中央目录结尾记录的结构

(1)我们首先根据ECDR的魔数0x06054b50(hex:50 4b 05 06)确定ECDR部分的起始地址为0xBD396

(2)往后偏移16个地址(参考上图),即可计算出中央目录起始地址为0x0b4f18

2、计算ID-Value块起始地址

我们再简单回顾一下APK签名块的格式

(1)我们首先跳转到0x0b4f18地址所在位置

  • 从右边的预览里面我们可以看到地址的前面刚好就是签名块结尾的魔数APK Sig Block 42
  • 可以证明此地址刚好就是中央目录起始地址

(3)往前16个地址(魔数长度)取8个字节(block长度占8字节)既为APK签名块的长度

  • 8405000000000000 16进制小端为 0x0584

(4)因此从APK Sig Block 42的尾地址(0xb4f17)往前0x0584个地址,就是ID-Value部分的起始地址

  • 可以看出前8个字节刚好也是8405000000000000,两个长度Block的值相同,和预期相符合
  • 最下面标红部分可以看出计算出来的长度刚好是0x584
  • ID-VALUE的起始地址=0xb4f17-0x584+1=0xb4994
  • 最下面是开始地址(0xb4994)和结束地址(0xb4f17)

3、解析出ID=0x71777777的注入信息

再复习一下ID-Value序列Item每一个Item的结构

根据步骤二我们可以得知ID-Value部分是从(43 05 00 到6E 22 7D,上图的两个红框之间)。理论上,我们根据上述Item的结构,顺序从ID-Value序列的首地址解析到尾地址即可找出我们的注入的信息。为了方便起见,我们直接在这部分搜索Id=0x71777777(hex: 77 77 77 71)的内容。可以看出

  • 长度:00 00 00 00 00 00 00 19=25 (长度是25字节)
  • ID:71 77 77 77 (我们自定义的)
  • 存入内容: {"channel":"meituan"} 21字节
  • 21+4=25,刚好等于长度的值

至此,我们就成功的通过分析APK的二进制数据,得到了walle注入的数据。当然,walle注入的过程也是类似的,非常的简单。除此之外walle还考虑了V3对齐的问题

当你经过上述步骤分析之后,再去看walle的代码,你会发现,妈的!原来这么简单!!

参考文献

转载于:https://juejin.im/post/5c6d6d6a6fb9a049f06ad97e

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值