一個關于APK Signature Scheme v2簽名的神奇bug定位經歷

背景

前段時間因為項目關系,根據官方文檔和Android源碼寫了一個校驗APK是否使用了Android V2 簽名的工具:

Android的APK Signature Scheme v2簽名及一款基于Java環境的校驗工具介紹

。當時的思路 檢查一個APK的ZIP Central Directory前面是否包含V2簽名必需的APK Signing Block,如果有說明這個包是V2簽名的包,然后校驗其V2簽名驗證是否通過 。

在工具寫完以后,官方簽名校驗工具的開發者Alex郵件聯系我告訴官方其實已經提供了類似的工具apksig,對應源碼為: https://android.googlesource.com/platform/tools/apksig ,但是因為工具已經有了,而且和他交流他表示直接使用Android源碼的方式也是可以行的,因此就沒有仔細去對比官方實現和自己方案的異同。也沒有對比官方代碼對校驗工具做優化。

問題現象

工具寫完以后對應用庫里面的應用進行了一次掃描,發現果然很多應用存在問題,當時判斷自己寫的工具是有效的,抽樣檢查發現他們確實無法在7.0 以上設備安裝,也使用官方工具校驗了發現確實沒有問題就沒有再管。

年前上班的最后一天,忽然有開發反饋過來,發現有應用使用工具校驗沒有問題

?  java -jar ~/lib/CheckAndroidV2Signature.jar  ./test.apk
{"ret":0,"msg":"ok","isV2":false,"isV2OK":false}

但是卻在Android 7.0及以上的機器上無法安裝,提示錯誤

?  adb install ./test.apk
Failed to install test.apk Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl2077545153.tmp/base.apk: META-INF/YSDK.SF indicates /data/app/vmdl2077545153.tmp/base.apk is signed using APK Signature Scheme v2, but no such signature was found. Signature stripped?]

拿到應用的apk后用官方的工具驗證存在問題,下面是使用官方工具驗證的結果:

$ANDROID_HOME/build-tools/25.0.0/apksigner verify ./test.apk
DOES NOT VERIFY
ERROR: JAR signer YSDK.RSA: JAR signature META-INF/YSDK.SF indicates the APK is signed using APK Signature Scheme v2 but no such signature was found. Signature stripped?

反編譯apk以后發現 META-INF 文件夾中的摘要文件中關于應用簽名版本的字段的內容如下:

X-Android-APK-Signed: 2

也就是簽名的摘要文件中標記該應用使用Android V2簽名,但是為什么在校驗簽名的時候,又找不到V2簽名必需的APK Signing Block呢?

由于馬上春節放假了,因此沒有足夠時間對問題做詳細的定位,只能是先臨時解決問題,調整了校驗方案改為 檢查一個APK的ZIP Central Directory前面是否包含V2簽名必需的APK Signing Block或者摘要文件中X-Android-APK-Signed的定義是否為2,如果有簽名塊或者定義確實為2說明這個包是V2簽名的包,然后校驗其V2簽名驗證是否通過。

修改工具以后再取校驗有問題的apk

?  java -jar ~/lib/CheckAndroidV2Signature.jar  ./test.apk
{"ret":-3,"msg":"com.bihe0832.checksignature.ApkSignatureSchemeV2Verifier$SignatureNotFoundException: No APK Signing Block before ZIP Central Directory","isV2":true,"isV2OK":false}

恩,雖然沒有找到原因,但是看來這一類有問題的APK應該是可以通過這個方法過濾了。使用優化后的工具一掃,發現竟然存在非常大的一批應用存在這個問題,一度懷疑是工具有問題,通過抽樣檢測驗證確實都有問題以后才放心放假回家。

問題分析

因為發現基本上所有使用了V2簽名但是又檢驗不通過的APK都是因為開發者使用了老的渠道包打包方式但是又不了解V2簽名造成的,而且這個通過這個問題掃出來的應用的量非常大,因此個人初步認為這類APK應該是使用了一種特殊改包方式造成的,而且這種方式在開發者中很流行,應用非常廣泛。

目前應用最廣的幾種APK內注入內容的方法主要有:

反編譯添加內容以后再簽名

這種情況下原則上不會影響簽名,莫非是使用了V2打包,注入以后又用了jarsigner簽名導致的?于是自己選了一個使用V2打的包,先用工具校驗一下:

?  java -jar ~/lib/CheckAndroidV2Signature.jar ./test.apk
{"ret":0,"msg":"ok","isV2":true,"isV2OK":true}

然后反編譯apk

?  java -jar -Duser.language=en -Duser.home=$ANDROID_HOME/build-tools/25.0.2 ~/lib/apktool.jar d -f ./test.apk

在assets文件夾中添加 channel.txt 文件后重新打包并使用jarsigner簽名

?  java -jar -Duser.language=en -Duser.home=$ANDROID_HOME/build-tools/25.0.2 ~/lib/apktool.jar b -o ~/a.apk ./test/
1  jarsigner -verbose -keystore ~/lib/ysdk.keystore -signedjar ./test.apk ./a.apk 'ysdk'
輸入密鑰庫的密碼短語:
   正在添加: META-INF/MANIFEST.MF
   正在添加: META-INF/YSDK.SF
   正在添加: META-INF/YSDK.RSA
  正在簽名: AndroidManifest.xml

…………</code></pre>

然后使用工具校驗

?  java -jar ~/lib/CheckAndroidV2Signature.jar ./test.apk
{"ret":0,"msg":"ok","isV2":false,"isV2OK":false}

也就是完全是一個V1簽名的包,不會有問題,因此可以確定不是這個原因引起的。

直接在zip包的注釋段添加內容

這種是另一種使用比較廣泛的渠道包打包方案,但是這種方案打的包不會破壞V2打包的APK Signing Block,使用第一版的工具就可以校驗出來,這類包使用工具校驗的時候會報下面的錯誤:

?  1  java -jar ~/lib/CheckAndroidV2Signature.jar ./test.apk
{"ret":0,"msg":"get signature failed, throw an SecurityException","isV2":true,"isV2OK":false}

通過腳本在 META-INF 文件夾注入內容

這種方案實現起來比較簡單,而且可操作性比較強,因此是一種使用非常廣泛的方案,他的核心原理是首先生成一個簽名以后的包,然后使用腳本在 META-INF 文件夾中注入一個文件,該文件的內容或者文件名就是需要注入APK的信息。由于在V1簽名下, META-INF 文件夾中注入內容并不會破壞APK的簽名,因此這種方法和上面的注釋段添加內容的方法都被開發者在具體的開發中廣泛的應用。接下來我們通過腳本嘗試一下這種注入會帶來什么問題:

首先準備注入的腳本,這里我用了一個Python寫的注入腳本,功能是使用腳本用數據流在 META-INF 文件夾注入一個 channel_ 的文件,里面保存對應的渠道號。因為注入不是重點,因此不專門說明腳本的運行規則。

#!/usr/bin/python

coding=utf-8

import zipfile import shutil import os import sys

目標apk名字前綴

targetApkNamePrefix = "YSDK-TEST"

當前打包的apk版本

targetApkVersion = "v1.0.0"

target_channel = "channel1"

if len(sys.argv) < 2 :

print("\n\tplease enter the apk name as command para like:")
print("\n\tpython MultiChannelBuildTool.py ./test.apk")
os._exit(0)

src_apk = sys.argv[1]; extension = os.path.splitext(src_apk)[1][1:]

if extension != 'apk': print("first para must be the apk with extension name")

src_apk_file_name = os.path.basename(src_apk)

分割文件名與后綴

temp_list = os.path.splitext(src_apk_file_name)

name without extension

src_apk_name = temp_list[0]

后綴名,包含. 例如: ".apk "

src_apk_extension = temp_list[1]

創建生成目錄,與文件名相關

outputdir = 'bin' + src_apk_name + '/'

目錄不存在則創建

if not os.path.exists(output_dir): os.mkdir(output_dir)

拼接對應渠道號的apk

target_apk = outputdir + targetApkNamePrefix + "" + targetchannel + "" + targetApkVersion + src_apk_extension

拷貝建立新apk

shutil.copy(src_apk, target_apk)

zip獲取新建立的apk文件

zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)

初始化渠道信息

empty_channelfile = "META-INF/channel{channel}".format(channel=target_channel)

寫入渠道信息

zipped.writestr(empty_channel_file, target_channel + "\n")

關閉zip流

zipped.close() print("打包完成")</code></pre>

腳本OK以后,我們選擇測試應用,先用工具檢測,沒有問題

?  java -jar ~/lib/CheckAndroidV2Signature.jar test.apk
{"ret":0,"msg":"ok","isV2":true,"isV2OK":true}

然后運行命令注入內容:

?  1  python ./test.py ./test.apk
打包完成

使用腳本驗證生成的apk

?  1  java -jar ~/lib/CheckAndroidV2Signature.jar ./bin_test/YSDK-TEST_channel1_v1.0.0.apk
{"ret":-3,"msg":"com.bihe0832.checksignature.ApkSignatureSchemeV2Verifier$SignatureNotFoundException: No APK Signing Block before ZIP Central Directory","isV2":true,"isV2OK":false}

就是如此神奇,問題復現了~~

我們把APK包解壓縮了以后檢查一下,果然在 META-INF 文件夾中已經注入一個 channel_ 的文件,打開對應的 *.SF 文件,果然里面的關于應用簽名版本的字段的內容如下:

X-Android-APK-Signed: 2

至此問題終于確定了,找到了復現的辦法以后,問題的原因就很容易理解了~

最終問題

當確定是因為在 META-INF 文件夾注入內容引起APK校驗失效以后,瞬間就可以可理解為什么是這樣了。

  1. 應用使用了V2簽名出包,因此整個包都是一個V2的包,然后在 *.SF 中自然也會記錄為使用V2簽名。
  2. 應用開發者在出包以后使用腳本動態注入了一個文件到 META-INF 文件夾中,此時因為文件內容有變化,APK Signing Block的文件頭位置已經發生了位移,但是這種方式并沒有去修改文件頭起始位置
  3. 當應用安裝的時候,讀取簽名文件中的簽名版本發現是使用V2簽名,因此就去校驗V2,但是發現并不能找到APK Signing Block,所以報上面的錯誤。

解決方案

至此,問題已經定位清楚,可以確定修改以后的腳本可以解決所有的問題。

  • 對于應用開發者,如果想要繼續使用上面的第二第三種打包方式的話,只能是選擇禁用V2簽名,或者是使用新開發的注入方法,目前了解已經有多個團隊都已經實現了基于V2的注入方法,方法和點評團隊開源的類似

  • 對于渠道,如何檢測開發者提供的APK是否使用V2簽名可以繼續使用本文開頭提到的工具,而且目前個人也已經完成了官方校驗工具的封裝,也可以直接使用封裝了官方校驗工具的jar,均在同一個gtihub下開源: https://github.com/bihe0832/AndroidGetAPKInfo

 

來自:http://blog.bihe0832.com/android-v2-issue.html

 

 本文由用戶 scgaliwei 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!