Android apk文件结构及其安装,校验流程

首发于我的博客网站(prajna.top): http://prajna.top/doc/4/278

apk文件

apk实际上就是一个zip文件,可以直接使用zip解压,它包含 classes.dex, 资源文件,证书,动态链接库等。

classes.dex: 代码文件,包含可以被Dalvik解释执行的字节码。build ROM的时候,还可以使用dex2oat把.dex部分代码预编译成 .odex文件,用来提高app运行的速度,因为,odex实质上是一个ELF格式的可执行文件,它里面是机器码,可以直接在虚拟机运行。 当存在 .odex文件的时候,虚拟机不需要使用JIT来运行.dex文件。

动态链接库: native代码,如JNI库。它的目录结构是 lib/{abi}/*.so。如:

 

apk 签名

签名最大的作用是保证apk不被篡改,如果apk被重新签名,那么原apk也不会被替换。
可以做一个测试:首先, 下载一个第三方的A.apk,安装到设备上。然后,运行命令行工具apksigner使用自己的key来签名,

apksigner sign --ks  my-release-key --out my-A.apk A.apk

再使用 adb install -r my-A.apk,来安装被篡改了签名的apk时,系统就会报错。因为,虽然包名相同,但是签名被改了,系统不会去替换之前的app。

Android 7 以下,使用的是'JAR signing (v1 scheme)',它是一个基于"signed JAR"文件格式。签名文件在META-INF目录,主要有如下几个文件。

  • MANIFEST.MF -- 记录各文件的hash值。
  • CERT.SF -- 证书签名文件。
  • CERT.RSA -- 证书。

Android7 引入了 'APK Signature Scheme v2'。

 

ABI: Application Binary Interface

主要内容包括:机器指令集,可执行文件的格式,内存对齐方式等等。目前,Android系统大概有如下几类:

  • armeabi--基于ARM的CPU,至少支持ARMv5TE指令集。
  • armeabi-v7a-- armeabi的扩展指令集,主要是增加了Thumb-2 和VFP hardware-FPU等指令。
  • arm64-v8a-- 基于ARMv8的64位CPU,支持 AArch64,NEON 和VFPv4指令集
  • x86--基于x86和IA-32指令集的CPU
  • x86_64--64位的x86。

Application.mk 里面使用可以使用APP_ABI变量来指定 ABI,如:

 APP_ABI := armeabi-v7a arm64-v8a x86
 APP_ABI := all

 

AndroidManifest.xml安装相关的一些配置

<application android:multiArch = ["true", "false"]

             android:installLocation = ["auto","internalOnly","preferExternal"]

 

pm instll 命令

常用的参数如下:

-r 重新安装,替换已安装的包。

-t 允许测试。

-s 安装到外置存储卡。

-f 安装到手机内部空间。

-d 允许降级。

--abi 指定cpu架构。

--user

 

APK的安装流程

以pm install命令为例,流程的起点是,Pm --> INIT_COPY message --> InstallParams.handleStartCopy()

log 信息:

Calling main entry com.android.commands.pm.Pm
PackageManager:  init_copy idx=0:InstallParams { file=/data/local/tmp/x.com.apk cid=null}
PackageManager:  startCopy UserHandle{-1}: InstallParams{...}

1:先得到 apk的基本信息,确定安装位置。
DefaultContainerService.getMinimalPackageInfo()--主要是确定安装位置,检查存储空间,在空间不足的情况下,系统会释放cache,再进行一次尝试。

  • 首先,读取 AndroidManifest.xml中的属性:installLocation, versionCode,revisionCode和coreApp。
  • 然后,再解决安装位置,PackageHelper.resolveInstallLocation()会根据用户选择来确定apk是安装到外部空间(SD卡),还是内部存储空间。
  1. 优先选择apk指定的安装位置。
  2. 未指定安装位置,优先选择该apk已被安装的位置(升级安装)。
  3. 默认选择内部空间。
  4. 内部空间不足的情况下,选择外部空间。
2:检查安装包。
是否允许覆盖(重装)(-r),是否允许降级(-d),以及一些安全方面的校验,校验结束后,发送 CHECK_PENDING_VERIFICATION 消息。

 

3:拷贝apk文件。

安装位置是sd卡,则走AsecInstallArgs.copyApk(),否则走FileInstallArgs.copyApk()这一路。这两路的过程都差不多,下面以安装到内部空间,为例,来详细说明一下。

  • 创建临时文件夹。

PackageInstallService.allocateInternalStageDirLegacy()创建一个随机的临时文件夹。 如:/data/app/vmd1823593572.tmp

  • 拷贝apk文件-- apk到拷贝成'临时目录下/base.apk'。
  • 从apk包中,提取动态库(*.so)。

扫描apk里面lib目录下的{abi}文件夹,优先扫描 'lib/<primary-abi>/',如果不存在, 则继续扫描 'lib/<secondary-abi>/',如果能找到so文件,则把他们拷贝到'临时目录/lib/'

  • 签名校验
  1. 通过CERT.RSA的签名,确认它和 CERT.SF的完整性。
  2. 通过CERT.SF里面的hash值来验证MANIFEST.MF文件的完整性。
  3. 通过MANIFEST.MF里面的hasn值来验证各个文件的完整性。
  • 重命名临时文件夹。

把临时文件夹重命名为包名+数字,如果有重复的包名,则末尾数字自动往上加。相 应的工作是installPackageLI() --> FileInstallArgs.doRename()里完 成的。

 

 log 信息:

 DefContainer Copying /data/local/tmp/x.apk to base.apk
 copy native library:/data/app/vmdl823593572.tmp DIR:lib
 installPackageLI: path=/data/app/vmdl823593572.tmp
 Renaming /data/app/vmdl823593572.tmp to /data/app/com.x-2 (重复安装)

 

primary-abi 和 secondary-abi

primary-abi: 设备支持的指令集。
secondary-abi: 设备兼容的指令集。

系统会访问三个属性(abilist, abilist32, abilist64)来得到设备所支持的ABI,如:

[ro.product.cpu.abilist32]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: []
[ro.product.cpu.abilist]  : [armeabi-v7a,armeabi]

上面数组,索引值越低,优先级越高,上面例子中 armeabi-v7a的优先级比armeabi 高。 primary-abi是 'armeabi-v7a',secondary-abi是 'armeabi'。

系统在解包动态库的时候,如果 multiArch设置为true,那么 abilist32 和 abilist64这两个数组都被选中;否则,在abilist和 abilist32间选择一个合适的数组。选择好abilist后,数组中的<primary-abi>/*.so 会被拷贝出来,如果不存在,则选择 secondary-abi下的so文件。

 

签名校验

1: 证书校验

读取证书签名文件'CERT.SF'内容,并计算其hash值,与证书'CERT.RAS'中的签名进行比对,验证证书和证书签名文件的完整性。这个过程主要在 JarVerifier.readCertificates()里,文件位置在:libcore/luni/src/main/java/java/util/jar/

程序流程是:PackageParser.parseApkLite() --> collectCertificates() --> new StrictJarFile(apkPath)

2: MANIFEST.MF校验

计算'MANIFEST.MF'的hash值,与 CERT.SF 文件中的 xx-Digest-Manifest 属性 值(hash)进行比较,验证.MF文件的完整性。如果,校验失败,则依次对 读取.MF文件中各个段的内容,并计算其hash值与 CERT.SF中的记录进行对比,校验.MF文件中文件段是否被篡改,如:

CERT.SF

Name: res/drawable-hdpi-v4/radiobutton.png
SHA1-Digest: d9QDKL5z9PY+Z7F13tVkg5iXO3Y=

MANIFEST.MF

Name: res/drawable-hdpi-v4/radiobutton.png
SHA1-Digest: xvssj1CTSdsivtCO/0Z3JHycuQw=

CERT.SF记录的就是 MANIFEST.MF文件里面对应内容的hash值,如果 MANIFEST.MF SHA1-Digest的值被篡改,那么hash校验就会失败。

代码在 JarVerifier.verifyCertificate()的最后面

  // Use .SF to verify the whole manifest.
String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
if (!verify(attributes, digestAttribute, manifestBytes, 0, 		    
        manifestBytes.length, false, false)) {
           …..
    while (it.hasNext()) {
        Map.Entry<String, Attributes> entry = it.next();
        Manifest.Chunk chunk = manifest.getChunk(entry.getKey());
           …..
        if (!verify(entry.getValue(), "-Digest", manifestBytes,
              chunk.start, chunk.end, createdBySigntool, false)) {
            throw invalidDigest(signatureFile, entry.getKey(), jarName);
        }
   }
}

我们可以故意在 MANIFEST.MF的第2行版本号后加个空格,导致 digestAttribute 校验不过,然后,把entry key, start,end都打印出来看一下。

digestAttribute: -Digest-Manifest
entry.key:res/drawable-mdpi-v4/icnfinder.png s:3935 e:4022
entry.key:res/drawable-hdpi-v4/splash3.png s:11570 e:11655
entry.key:res/drawable-hdpi-v4/notify_selected.png s:4289 e:4382
entry.key:res/drawable-hdpi-v4/backarrow_n.png s:9568 e:9657

3: 所有文件的校验

确认了MANIFEST.MF的完整性后,就可以扫描APK内的所有文件,逐一计算hash值,并与 MANIFEST.MF中对应的文件hash属性值进行校验。PackageParser.collectCertificates(),遍历所有的文件,通过loadCertificates()调用JarFileInputStream.read()一边读取文件,一边使用 VerifierEntry.write()计算hash值,最后,与MANIFEST.MF中的hash值进行比较。

至此,签名校验结束,在这个流程下,想要篡改某个文件是不可能的。

假如,修改了某个文件A吧,那么MANIFEST.MF中的A的hash值就对不上了,不得不修改.MF文件。这样一来,连锁反应,CERT.SF里面的hash也对不上了,只好再来修改.SF中的hash值。接下来,CERT.RSA的校验过不了。 当你重新计算了 CERT.SF文件的hash值,踌躇满志地准备修改CERT.RSA签名的时候,顿时傻眼了,因为--.RSA里面的签名信息是用开发者的私钥加密的, 也就意味着,你无法对它进行修改,你算不出hash加密后的数据。

这个时候,你恶向胆边生,干脆用自己的私钥重新签名了这个apk,这下,虽然,文件校验可以通过了,但是,apk的签名key被改变,安装的时候,如果,系统里面有该apk,你的山寨货就会被拒绝。如果,系统没有该apk,呢?

恭喜你,该手机就能安装上你的山寨apk了。所以,下载apk一定要确认来源,是否打开"未知来源"选项也要慎重。

 

PS:

1:SHA1 是 digest的算法中的一种,其它还有 "SHA-512","SHA-384","SHA-256"。

不同的算法,对应.SF和.MF文件内容会有差异,有的可能会是SHA-512-Digest:xxx

 

可以使用下面命令查看证书信息。

openssl pkcs7 -inform DER -in CERT.RSA -noout -print_certs -text

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值