APK編譯及安全防護

民生Fintech發表於2019-04-24

One 什麼是APK

APK是AndroidPackage的縮寫,即Android安裝包(apk),可以通過將APK檔案直接傳到Android模擬器或手機中執行即可安裝。APK的本質是一個zip的壓縮包,用壓縮軟體開啟後就可以看到裡面的檔案以及結構。

APK的檔案組成:

APK編譯及安全防護

  • AndroidManifest.xml:清單檔案,主要包含四大元件註冊資訊,應用許可權,和後設資料等資訊,此檔案列出了應用的名稱、版本、訪問許可權和引用的庫檔案。

  • classes.dex:Dalvid虛擬機器上執行的位元組碼檔案,我們在將java程式碼編譯成class位元組碼檔案後,通過android SDK提供的工具將class轉換成dex位元組碼,dex檔案可能有多個。

  • META—INF: 包含 CERT.SF 和 CERT.RSA 簽名檔案,以及 MANIFEST.MF 清單檔案。CERT.SF、MANIFEST.MF是對資源做的SHA1 hash處理,CERT.RSA包含有公鑰證照和簽名。

  • res:包含未編譯到 resources.arsc 中的資源例如:佈局xml、圖片、動畫等。

  • resources.arsc 打包工具會提取此 XML 內容,將其編譯為二進位制檔案形式,並將相應內容進行歸檔。此內容包括語言字串和樣式,以及未直接包含在 resources.arsc 檔案中的內容(例如佈局檔案和圖片)的路徑。

  • lib/:包含特定於處理器軟體層的編譯程式碼。此目錄包含每種平臺型別的子目錄,如 armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 和 mips

Two APK編譯

2.1 編譯流程

APK編譯及安全防護

由上圖可知,APK打包的流程主要有如下幾個步驟:

  1. 打包資原始檔,生成R.java檔案。
  2. 處理aidl檔案,生成對應的java檔案。
  3. 工程原始碼編譯,生成.class檔案,把.class檔案編譯為.dex檔案。
  4. 程式碼檔案和資原始檔打包生成APK。
  5. 對apk檔案進行簽名。
  6. zip對齊,apk中的每個檔案進行4個位元組對齊操作,優化啟動相應速度。

2.1.1. 打包資原始檔,生成R.java檔案

  • 工具aapt

  • 輸入

    • res
    • assets
    • AndroidManifest.xml
    • Android基礎庫(Android.jar)檔案
  • 過程

    • 編譯res和assets目錄下的資源並生成resources.arsc檔案。
    • 呼叫ResourceTable.cpp檔案中的parseAndAddEntry方法生成R.java檔案。
    • 對res目錄下的xml檔案進行編譯,這樣處理過的xml檔案就被簡單“加密”了。
  • 輸出

    • resources.arsc檔案
    • R.java檔案
  • 總結

    • assets和res/raw資源被原封不動地打包進APK,其它的資源都會被編譯或者處理,除了assets資源之外,其他的資源都會被賦予一個資源ID。利用該特點,在apk加固時,可以把加密後的dex、so檔案放到該目錄。
    • R.java是java層面尋找資源的id表,resources.arsc是程式執行時用到的資源表。
    • APK執行時會根據裝置的不同屬性(如螢幕密度)定址,resource.arsc就是通過相同的ID,根據不同的配置索引找到最佳資源。

2.1.2. 處理aidl檔案,生成對應的java檔案

這個主要是在aidl介面檔案,通過SDK提供的AIDL工具生成相應的java檔案,供程式呼叫,沒有用到aidl的工程,並不會有這個過程

2.1.3. 工程原始碼編譯

  • 工具

    • javac: 把java檔案編譯為.class檔案的工具。
    • dx: 把.class檔案打包為.dex檔案的工具。
  • 輸入

    • R.java
    • AIDL生成的java檔案
    • 庫jar檔案,Android.jar
  • 輸出

    • dex檔案
  • 總結:

    • 這個過程主要是javac將java檔案編譯成class檔案,再經dx工具轉換成dex檔案,壓縮常量池,消除冗餘資訊等。

2.1.4. 打包生成APK

  • 工具

    • apkbuilder
  • 輸入

    • 打包後的資原始檔
    • 打包後的類檔案,主要是dex檔案
    • libs檔案,包括so檔案
  • 過程

    • 主要流程是先以resources.arsc檔案為基礎生成一個apk,然後呼叫ApkBuilderMain.java中的addSourceFolder方法新增工程資源,處理包括res和assets目錄中的檔案,新增完資源後呼叫addResourceFromJar方法往apk中寫入依賴庫,接著呼叫addNativeLibraries方法新增工程libs目錄下的native庫,最後呼叫sealApk關閉Apk檔案。
  • 輸出

    • 未簽名的.apk檔案

2.1.5. 對apk檔案進行簽名

  • android應用需要簽名才能在裝置上安裝。

    • 開發時debug包會用系統自帶的debug.keystore為debug包簽名。
    • 正式釋出時需要用公司或者專案的私鑰對apk進行簽名,這個私鑰要妥善保管,一旦洩露,別人就可以任意修改包的內容然後重新打包簽名。
  • 使用JDK自帶簽名工具的jarsigner:

    • jarsigner -keystore ./android.keystore -storepass password -signedjar ./signed.apk ./sign.apk android.keystore

2.1.6. zip對齊

對齊使用的是android-idk/tools目錄下的zipalign工具,主要工作是將apk包中所有的資原始檔起始偏移為4位元組的整數倍,這樣通過記憶體對映訪問apk時的速度會更快。

2.2 編譯細節流程

APK編譯及安全防護

經過這幾步的編譯,輸出的apk檔案,最終是一個具有特定檔案格式的靜態檔案。如果拿到該檔案,分析檔案格式和逆向,最終是可以分析一個破解apk的業務和流程,這就必然對apk產生安全問題。

2.3 Apk v1 & v2簽名

Android 支援以下三種應用簽名方案:

  • v1 方案:基於 JAR 簽名。
  • v2 方案:APK 簽名方案 v2(在 Android 7.0 中引入)。
  • v3 方案:APK 簽名方案 v3(在 Android 9 中引入)。

2.3.1. JAR 簽名v1方案

從一開始,APK 簽名就是Android 的一個有機部分。該方案基於簽名的JAR。 但是v1方案存在以下一些不足:

  • 簽名不保護APK的某些部分,例如ZIP後設資料。

  • APK驗證程式需要處理大量不可信(尚未經過驗證)的資料結構,然後會捨棄不受簽名保護的資料,這會導致相當大的受攻擊面。

  • APK 驗證程式必須解壓所有已壓縮的條目,而這需要花費更多時間和記憶體。

  • 由於這些不足導致apk簽名後可以進行許多修改,可以移動甚至重新壓縮檔案,導致一些安全事件的發生。為了解決這些問題, google在Android 7.0以後引入了V2簽名方案Android 7.0 中引入了APK 簽名方案v2, Android 9之後引入v3(v2+)。

2.3.2. APK 簽名方案v2 & v2+

APK v2簽名方案是一種全檔案簽名方案,該方案能夠:

  • 發現對APK的受保護部分進行的所有更改;
  • 有助於加快驗證速度並增強完整性保證;

使用APK簽名方案v2進行簽名時,會在APK檔案中插入一個APK簽名分塊,該分塊位於“ZIP中央目錄”部分之前並緊鄰該部分。在“APK 簽名分塊”內,v2簽名和簽名者身份資訊會儲存在APK簽名方案v2分塊中。

APK編譯及安全防護

搭載Android 7.0 及更高版本的裝置支援APK 簽名方案v2(v2方案)及更高版本的方案(在Android P(Android 9) 中,v2方案已更新為v3方案,以便在簽名分塊中包含其他資訊,但在其他方面保持相同的工作方式)。該方案會對APK的內容進行雜湊處理和簽名,然後將生成的“APK簽名分塊”插入到APK中。

在驗證期間,v2+方案會將APK檔案視為Blob,並對整個檔案進行簽名檢查。對APK進行的任何修改(包括對ZIP後設資料進行的修改)都會使APK簽名作廢。這種形式的APK驗證不僅速度要快得多,而且能夠發現更多種未經授權的修改。新的簽名格式向後相容,因此,使用這種新格式簽名的APK 可在更低版本的Android裝置上進行安裝(會直接忽略新增到APK 的額外資料),但前提是這些APK 還帶有v1簽名。

驗證程式會對照儲存在“APK簽名分塊”中的v2+簽名對APK的全檔案雜湊進行驗證。該雜湊涵蓋除“APK簽名分塊”(其中包含v2+簽名)之外的所有內容。在“APK簽名分塊”以外對APK進行的任何修改都會使APK的v2+簽名作廢。v2+簽名被刪除的APK也會被拒絕,因為v1簽名指明相應APK帶有v2簽名,所以Android 7.0及更高版本會拒絕使用v1簽名驗證APK。

APK編譯及安全防護

Three APK反編譯

3.1 概述

APK反編譯,就是把編譯後的APK檔案通過逆向工程手段獲取到原始程式碼和資源的一個過程。由於APK檔案是一個具有固定檔案格式的靜態檔案,而且能夠解析和執行的,所以理論上可以對一個APK檔案進行逆向的分析進行反編譯、破解。如果一個APK沒有進行安全方面的防護措施,是可以通過很簡單的幾步給破解掉。

3.2 常用工具

apktool+dex2jar+JD-GUI

3.3 步驟

  • 拿到app.apk
  • 反編譯class.dex檔案 修改app.apk檔案字尾為app.zip,解壓app.zip檔案

APK編譯及安全防護

APK編譯及安全防護
得到class.dex檔案,使用dex2jar工具反編譯為jar檔案。

使用以下命令:./dex2jar.sh classes.dex

APK編譯及安全防護

APK編譯及安全防護

通過JD-GUI開啟classes_dex2jar.jar可以看到大部分源。

APK編譯及安全防護

通過apktool直接反編譯app.apk即可看到資原始檔 ./apktool d app.apk -f -o ./app3

APK編譯及安全防護

反編譯後AndroidMenifest.xml:

APK編譯及安全防護

反編譯後layout:

APK編譯及安全防護

反編譯後color檔案:

APK編譯及安全防護

至此,一個apk中所有的資原始檔、程式碼檔案全部被反編譯出來,所以作為一個app開發者,為了資料、業務的安全,必須要對app進行安全處理,防止對使用者造成資訊、財產方面的損失,以下是幾個對apk進行安全加固的處理方法。

Four APK安全加固

對於apk安全加固主要分為兩個方面的安全防護:

  • 程式碼混淆
  • 簽名對比
  • apk加殼
  • 反除錯、反檢測等技術
  • 其他

4.1 程式碼混淆

混淆程式碼並不是讓程式碼無法被反編譯,而是將程式碼中的類、方法、變數等資訊進行重新命名,把它們改成一些毫無意義的名字。因為對於我們而言可能CellPhone類的call()方法意味著很多資訊,而A類的a()方法則沒有任何意義,但是對於計算機而言,它們都是平等的,計算機不會試圖去理解CellPhone是什麼意思,它只會按照設定好的邏輯來去執行這些程式碼。所以說混淆程式碼可以在不影響程式正常執行的前提下讓破解者對程式碼邏輯的理解難度加大,從而大大提升了程式的安全性。

4.2 簽名對比

  • 原理

APK必須被簽名才能夠安裝到手機中,沒有被簽名的apk是無法安裝到裝置中的。但是簽名在反編譯之後是獲取不到的,所以只能用自己的簽名檔案去簽名,但是在已經安裝了應用裝置中在安裝一個簽名不一致的應用會導致安裝失敗。

  • 應用

利用該原理,可以在程式啟動的時候獲取應用簽名,然後和正確的簽名值做比對,如果不符合就直接退出程式。

APK編譯及安全防護

APK編譯及安全防護

  • 破解方法

既然是在程式的入口進行簽名比對,就可以反編譯找到程式入口處相關的程式碼,進行處理刪除或者把簽名資訊改成自己的簽名值,重新打包即可解決這個問題。

APK編譯及安全防護

4.3 dex檔案加固

  • 加固原理

APK編譯及安全防護

- 加密的源apk

- 自己的殼apk,負責解密apk,並動態載入apk的工作

- 加密工具,負責將源apk進行加密和殼dex合併成新的dex
複製程式碼

APK編譯及安全防護

4.4 反除錯檢測(ptrace)

反除錯檢測是為了應對現在很多破解者使用IDA進行動態方式除錯so檔案,從而獲取重要的資訊,知道IDA進行so動態除錯是基於程式的注入技術,然後使用Linux中的ptrace機制,進行除錯目標程式的附加操作。ptrace機制有一個特點,如果一個程式被除錯了,在它程式的status檔案中有一個欄位TracerPid會記錄除錯者的程式id值,如圖所示:

APK編譯及安全防護

檢視檔案:/proc/[myPid]/status,在第六行,有一個TracerPid欄位,就是記錄了除錯者的程式id。那麼就可以這麼做來達到反除錯的功效了:輪詢遍歷自己程式的status檔案,然後讀取TracerPid欄位值,如果發現它大於0,就代表著自己的應用在被人除錯,所以就立馬退出程式。原理知道了,程式碼實現也很簡單,這裡用pthread建立一個執行緒,然後進行輪詢操作: 使用pthreadcreate建立一個執行緒,執行緒啟動之後執行threadfunction函式:

APK編譯及安全防護

看看thread_funcation函式:

APK編譯及安全防護

4.5 其它

  • Dex-Java2C:將Java程式碼翻譯為C程式碼,並實施Native層的程式碼混淆保護。

  • So檔案加殼:對SO檔案進行整體加殼保護,防止IDA Pro等工具逆向分析。

  • 記憶體加密:防止記憶體資料被篡改或Dump,比如Dump解密後的Java程式碼。

  • 自身虛擬化保護:專業版採用程式碼虛擬化技術對自身程式碼進行保護,防止逆向分析。

對於apk安全方面的技術,層出不窮,安全攻防拉鋸技術不斷改進不斷提升。

Five 結論

apk的編譯是把我們寫的程式碼、資源、庫檔案打包成可以在Android裝置上執行的過程,而為了程式碼、資料、業務安全訴求需要對apk進行安全防護工作。

安全攻防是魔高一尺、道高一丈,特別是金融類app需要高度重視的工作,在此與大家一起學習和分享。

Thanks!

作者簡介

焦亞克, 民生科技有限公司使用者體驗技術部開發工程師。

APK編譯及安全防護

相關文章