Android应用篇 - app 安全防护
這篇文章來總結下 Android app 的安全防護手段。
?
目錄:
?
?
1.?資源混淆
資源混淆可以使用微信團隊的?AndResGuard,這樣能保護我們設計師辛苦設計的成果。AndResGuard 教程:APP瘦身大法--AndResGuard的使用。
?
2.?代碼混淆
這個就不多說了,基本都會做。教程:Android 代碼混淆 混淆方案
?
3.?簽名校驗
簽名驗證,就是在 APP 中寫入自己私鑰的 hash 值,和一個獲取當前簽名中的私鑰 hash 值的函數兩個值相一致,那么就說明 APP 沒有被改動允許 APP 運行。如果兩值不一致那么說明 APP 是被二次打包的,APP 就自我銷毀進程。
可以在兩個地方進行校驗:
- app 啟動類,校驗代碼是放在 Java 層的 (雖然私鑰 hash 放在 so 中),但是還是容易被調試出 hash 值。
- 在 JNI_OnLoad 中做,缺點是比較復雜。原理是通過載入 so (Java 層),然后在?build_gradle 加入預編譯語句,最后會自動觸發?JNI_OnLoad() 函數,然后在里面做簽名校驗。但是這種方式,如果破解者找到了代碼,再通過各種手段注釋掉加載 so 的代碼,也可以破解。只不過在代碼混淆后難度大了很多。
?
4.?反調試
有兩種方式:
- 一個進程同時最多只能被一個進程所調試,所以可以自己使用 ptrace() 函數假裝自己在調試自己,占住調試的位置以此來拒絕別的進程的調試請求。
- 查看 /proc/{pid}/status 文件如果發現 TracerPid 的值不等于 0 (TracerPid 是調試進程的 pid,如果不為 0 則表示有進程在調試),則 kill 掉自己。
要強調兩點,一個是這里是反調試只有 ida 進行動態分析時才能起到防護效果,ida 靜態打開還是不能阻止的。第二個是這里是反調試,自己開發過程中使用 IDE debug 也是調試,如果加了以下代碼那 IDE debug 時進程也會自我銷毀的(實際發現 IDE 中 run 也是不行的)。
?
5.?組件安全
(1) Activity
首先是訪問權限控制,組件處理不好會導致你的應用被惡意程序進行惡意的頁面調用。作為研發人員,我們可以從以下三點來預防和避免這個問題:
- 私有的 Activity 不應該被其他應用啟動且應該確保相對是安全的。
- 關于 Intent 的使用要謹慎處理接受的 Intent,以及其攜帶的信息盡量不發送敏感信息,并進行數據校驗。
- 設置 Android:exported 屬性,不需要被外部程序調用的組件應該添加 android:exported="false"?屬性。
(2) Service
常見的漏洞有 Java 空指針異常導致拒絕服務和類型轉換異常導致的拒絕服務兩種。應對 Service 帶來的安全問題給大家三點建議:
- 我們應用在內部盡量使用 Service 設為私有,如果確認這個服務是自己私有的服務,就把它確認為私有,不要作為一個公開的服務。
- 針對 Service 接收到的數據應該進行校驗。
- 內部的 Service 需要使用簽名級別的 protectionle。
(3)?BroadcastReceiver
BroadcastReceiver 會處理 Intent 發起的異步請求,要求注意以下安全要點:
- 聲明 exported 屬性。默認情況下,接收器會被導出,而且可以由任何其他應用調用,類似于 Service,要求顯示地聲明android:exported 屬性。
- 設置收發權限。如果 BroadcastReceiver 預期供其他應用使用,應該在應用清單中向接收器設置安全權限。這樣可防止沒有相應權限的應用向 BroadcastReceiver 發送 intent。
?
6.?Webview 的代碼執行漏洞
關于這個,可以看看我這篇文章:Android應用篇 - WebView 與 JS 全解與實戰
?
7.?加固
可以使用第三方加固平臺進行加固,當然加固也有被脫殼的風險,不過也是一層保護。
?
8.?編碼安全
- 重要的字符不要硬編碼在 Java 層,因為 String 內容是不會被混淆的,可以放在 JNI 中,JNI 函數也可以再加上一層保護措施。
- 限制對變量的訪問,讓每個量的方法都成為 final,盡量使你的類不要被克隆,如果一旦允許被克隆,它可能會繞過這個類輕易地復制類的屬性。
- Android 中很多會使用到序列化,如果自己覺得這個類是有風險的,或者說安全性是很高的,盡量不要讓它序列化。
?
9.?動態加載
Android 官方表示,強烈建議不要從應用 APK 外部加載代碼。這樣做不僅會明顯加大應用因代碼注入或代碼篡改產生問題的可能性,還會增加版本管理和應用測試的難度。這最終會導致無法驗證應用的行為,因此,某些環境中可能會禁止采用此做法。
因此,盡管插件化和熱更新技術在國內表現得比較熱門,但是其風險性值得關注。
如果必要適用動態加載,要求:
- 動態加載的代碼需要擁有與應用本身相同的安全策略和權限水平。
- 外部代碼必須來自可驗證的信任來源。
不安全的位置包括:通過未加密的協議從網絡上下載,外部存儲設備。這類代碼可能遭到篡改,從而執行某些惡意操作。
?
10.?hook
如何防止應用被 hook,以支付寶防 hook 為例:
- Xposed 框架將 hook 信息存儲在字段 fieldCache,methodCache,constructorCache 中, 利用 Java 反射機制獲取這些信息,檢測 hook 信息中是否含有支付寶 App 中敏感的方法,字段,構造方法。
- 檢測進程中使用 so 名中包含關鍵 "hack|inject|hook|call" 的信息,這個主要是防止有的人不用這個 Xposed 框架,而是直接自己寫一個注入功能。
- root 檢測原理是:是否含有 su 程序和 ro.secure 是否為 1。
- 防止被 hook 的方式就是可以查看 XposedBridge 這個類,有一個全局的 hook?開關,所有有的應用在啟動的時候就用反射把這個值設置成 true,這樣 Xposed 的 hook 功能就是失效了。
- 如果應用被 Xposed 進行 hook 操作之后,拋出的異常堆棧信息中就會包含 Xposed 字樣,所以可以通過應用自身內部拋出異常來檢測是否包含 Xposed 字段來進行防護。
?
11.?數據存儲安全
- SharedPreferences 存儲加密解密方式:對 key 和 value 同時加密,存儲類型都為 String 類型,數據讀取時根據需要進行類型轉換。
- File 文件存儲加密解密方式:對數據流進行加密解密。
- SQLite 數據庫存儲加密解密方式:基于 Sqlcipher 進行實現。
數據使用對稱加密,肯定會涉及到秘鑰的安全問題:
密鑰的保存如果將密鑰保存到手機文件中,或者通過硬編碼的方式寫在代碼中,容易被逆向出來,在通常情況下,采用對稱加密密鑰需要保存在用戶手機中,這和安全性想違背。通常最好的方式是不要保存密鑰,通過固定數據或者字符串做加密密鑰因子,例如用戶唯一賬號屬性等。
編碼方式 Android 代碼主要有 Java 編碼,打包文件時 Java 代碼打包成 dex 文件防到安裝包文件中,但是 dex 文件容易被逆向回 smali 代碼或者 Java 文件。雖然目前混淆和加殼甚至是虛擬機保護 (VMP)?技術已經很成熟,簡單逆向工作無法獲取代碼邏輯和硬編碼字符串,但是 Java 代碼依然存在很高的安全風險。因此,將加解密相關操作通過 Native 代碼實現很有必要,不僅保證效率而且在 so 保護技術之上安全性更高。
在 Android 數據存儲安全中,由于 Android 系統的安全機制,用戶獲取 root 權限后可以訪問手機所有目錄,包括應用私有目錄,因此,數據存儲要考慮到一個白盒環境,或者非可信環境。這種情況下,數據加密的密鑰成為關鍵。一機一密、動態密鑰、密鑰白盒等手段各有優缺點。
- 一機一密需要保護密鑰生成方法邏輯。可以使用加鹽處理,加入隨機規則,當然這個鹽服務器需要存儲。
- 動態密鑰需要考慮密鑰時效性,有效性以及鏈路安全。
- 密鑰白盒由于目前沒有廣泛認可,在兼容性安全性方面有待考驗。?
?
12.?數據傳輸安全
數據傳輸安全主要是網絡傳輸,可以使用 https,關于 https 可以看看這篇文章:網絡篇 - https協議中的數據是否需要二次加密
防護辦法:
使用 CA 機構頒發證書的方式可行,但是如果與實際情況相結合來看的話,時間和成本太高,所以目前很少有用此辦法來做。由于手機應用服務器其實是固定的,所以證書也是固定的,可以使用"證書或公鑰鎖定"的辦法來防護證書有效性未作驗證的問題。
- 公鑰鎖定
將證書公鑰寫入客戶端 apk 中,https 通信時檢查服務端傳輸時證書公鑰與 apk 中是否一致 (實現 X509TrustManager接口)。
public final class PubKeyManager implements X509TrustManager{private static String PUB_KEY = "30820122300d06092a864886f70d0101" + "0105000382010f003082010a0282010100b35ea8adaf4cb6db86068a836f3c85" +"5a545b1f0cc8afb19e38213bac4d55c3f2f19df6dee82ead67f70a990131b6bc" + "ac1a9116acc883862f00593199df19ce027c8eaaae8e3121f7f329219464e657" +"2cbf66e8e229eac2992dd795c4f23df0fe72b6ceef457eba0b9029619e0395b8" + "609851849dd6214589a2ceba4f7a7dcceb7ab2a6b60c27c69317bd7ab2135f50" +"c6317e5dbfb9d1e55936e4109b7b911450c746fe0d5d07165b6b23ada7700b00" + "33238c858ad179a82459c4718019c111b4ef7be53e5972e06ca68a112406da38" + "cf60d2f4fda4d1cd52f1da9fd6104d91a34455cd7b328b02525320a35253147b" + "e0b7a5bc860966dc84f10d723ce7eed5430203010001";// 鎖定證書公鑰在apk中public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {if (chain == null) {throw new IllegalArgumentException("checkServerTrusted: X509Certificate array is null");}if (!(chain.length > 0)) {throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");}if (!(null != authType && authType.equalsIgnoreCase("RSA"))) {throw new CertificateException("checkServerTrusted: AuthType is not RSA");}// Perform customary SSL/TLS checkstry {TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");tmf.init((KeyStore) null);for (TrustManager trustManager : tmf.getTrustManagers()) {((X509TrustManager) trustManager).checkServerTrusted(chain, authType);}} catch (Exception e) {throw new CertificateException(e);}// Hack ahead: BigInteger and toString(). We know a DER encoded Public Key begins// with 0×30 (ASN.1 SEQUENCE and CONSTRUCTED), so there is no leading 0×00 to drop.RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey();String encoded = new BigInteger(1 /* positive */, pubkey.getEncoded()).toString(16);// Pin it!final boolean expected = PUB_KEY.equalsIgnoreCase(encoded);if (!expected) {throw new CertificateException("checkServerTrusted: Expected public key: " + PUB_KEY + ", got public key:" + encoded);}} }公鑰不要硬編碼在 Java 代碼中,可以放在 JNI 中。
?
- 證書鎖定
即為客戶端頒發公鑰證書存放在手機客戶端中 (使用 keystore),在 https 通信時,在客戶端代碼中固定去取證書信息,不是從服務端中獲取,當然應用得先加固保證證書安全。
?
13.?內存數據安全
對于重要的內存數據,比如密碼,最好不要長時間駐留在內存中,用完立即銷毀。如果非要長時間駐留,可以考慮加密加鹽方式。
?
總結
以上是生活随笔為你收集整理的Android应用篇 - app 安全防护的全部內容,希望文章能夠幫你解決所遇到的問題。