Android安全防護之旅---字串批量加密工具(AndStringGuard)原理介紹
一、支付寶防護策略
現在很多應用在為了安全對應用做了很多防護方式,之前我也介紹了很多防護策略: Android中幾行程式碼讓你的應用變得更加安全 ;文中也提到了應用安全防護的最終目標是防止別人看到你的程式碼,我們也知道這裡的最終方式都是加固操作,但是加固有一些缺點,而這些確定導致現在很多公司應用並不會去選擇他,所以一些其他的防護策略就出來了,包括防hook,防除錯等操作。今天我們再來看一下如何把應用中的常量字串資訊進行加密,這個意義還是很大的,而這個主要是看到支付寶應用中是這麼做的:

看到支付寶內部的字串資訊全部使用Base64進行編碼操作了,這樣做的目的也很簡單,因為我們在進行反編譯破解的時候如果是這麼直接的字串資訊,那麼對於破解就太簡單了,我們可以直接全域性搜尋字串資訊很快定位到我們想要的地方,如果這種加密操作我們全域性搜尋字串資訊肯定是找不到的。但是支付寶這種的加密演算法就太簡單了,我們可以寫一個解密方法然後在直接操作即可。
二、工具編寫
我們可能知道把程式碼中的字串常量進行加密之後可以防護,但是如果專案中的常量字串很多,這樣手動去替換是很麻煩的,而且每個開發者不可能總是記住這件事的,無意中就定義了一個常量就操作了,所以我們需要寫一個自動指令碼在專案編譯期間自動替換即可,這個工具就是我們本文需要介紹的重點。工具其實不難,因為是編譯器,那麼我們可能需要對編譯後的class檔案進行操作,我們知道Android中專案編譯流程如下:

我們寫好這個工具然後利用gradle指令碼,在編譯後的class檔案做操作即可,那麼這個工具其實也很簡單,輸入class檔案,輸出就是加密之後的class檔案了,為什麼操作class檔案呢,首先操作class檔案有一個很好的工具asm和javassist,本人偏向於asm,著名的熱修復框架Robust就是依靠這個工具進行操作的: Android熱修復框架Robust原理解析 ;
接下來我們就開始編寫這個工具,asm框架可以自行網上搜索一下用法很簡單,我們輸入是一個class檔案,那麼我們可以編譯這個class檔案的位元組碼資訊,如何找到字串常量資訊呢?這個我們可以藉助Eclipse中的Bytecode外掛進行檢視類的位元組碼資訊,比如這裡編寫一個簡單的案例:

我們看到這裡只要是字串資訊就是LDC指令,那麼我們可以藉助asm庫攔截方法中的每條LDC指令即可:

這裡的引數就是LDC指令的值,這裡可以做一個判斷,如果是字串型別那麼就是我們程式碼中的原始字串資訊,這裡需要呼叫加密演算法進行加密,然後在插入我們需要解密的字串資訊,那麼插入這條指令怎麼編寫呢?這個不用自己去查閱,直接用Bytecode外掛即可:

看到外掛就是如此方便和牛逼,這樣我們就有了這條指令,直接編寫asm程式碼即可,當然如果沒有這個外掛,我們可以 利用javap命令反編譯class檔案檢視 也是可以的:

而且這裡會把類的靜態常量欄位也看的一清二楚位元組指令,這一點Bytecode就沒法看到了。所以javap指令才是最靠譜的,有了這些指令就可以 直接用asm編寫程式碼 了,這裡先用一個簡單的 class檔案 操作:

首先我們需要獲取類中所有的常量欄位值進行儲存,後面進行新增操作:

在類的初始化方法中進行欄位加密操作,這裡注意一定要是方法的 返回指令之前新增 的,然後就是在 LDC指令攔截之後進行新增 :

這樣我們的工具就大致寫完了,用一個案例 class檔案 進行操作一下看看效果:

這裡看到把這個類的所有字串資訊進行加密操作了,這裡是單個檔案操作,因為後面我們需要把這個弄成一個工具整合到專案中操作,所以輸入是class檔案目錄,我們需要遍歷這個目錄獲取所有的class檔案逐一操作,操作完成之後替換原始的class檔案,當然我們還需要新增一些keep操作,就是比如哪些類是不需要進行操作的,比如系統類就可以不用進行操作:

這樣我們就可以 把工具打包成jar 然後整合到專案中:

我們只需要修改一下gradle即可,因為操作的是位元組碼檔案,所以可以在java編譯之後做這件事,新增一個 java執行名 即可,這裡可以 新增keep檔案 。執行一下即可:

然後用jadx工具檢視編譯後的apk檔案,看到內部程式碼的字串常量全部進行加密操作了,這樣我們的apk被別人拿到之後全域性搜尋字串肯定就是失敗了。
三、疑問解答
到這裡我們把工具寫完了,但是這裡可能大家有幾個疑問:
第一個問題:加密演算法
這個加密演算法是需要在兩個地方呼叫,一個是工具中需要呼叫加密方法和解密方法,而專案中也必須要有這麼個加密演算法類,而且類名也必須一樣不然呼叫執行就報錯了,當然大家可以修改一下這個工具實現自定義加密演算法邏輯即可。對於這個加密演算法我們可以放到so中進行操作,然後在ollvm處理一下,這樣安全度就更高了。
第二個問題:這樣就一定安全嗎?
還是那句話,沒有絕對的安全,這樣做了加密操作的確可以防護搜尋了,但是能防護hook嗎?我可以不用解析出加密演算法,因為我們最終的目的是獲取解密後的字串資訊,那麼我們就可以直接hook這個解密方法,列印解密之後的字串資訊即可。這樣做的確比直接搜尋有點麻煩,但是這個也是破解的常規套路,列印訊息可以更快的定位資訊。所以不管你上層如何牛逼的加密,哥不關心,我們只關心結果。要結果就hook!
第三個問題:這樣做的效率問題怎麼辦?
的確我們從上面的程式碼可以看到原本只是從常量池獲取字串資訊的,但是現在得呼叫一個方法,如果這個方法的加密演算法複雜點那麼效率就不可以言語了,所以對於專案中不是很重要的模組,可以keep一下,不要把所有的常量都進行加密,不過反編譯支付寶之後發現他全部用Base64編碼了一下,不知道他們是如何考慮效率和安全這兩個問題的衡量標準的。
第四個問題:這樣做程式碼量增加怎麼辦?
從上面可以看到對每個字串原本的LDC命令後面都插入了一條呼叫解密方法的指令程式碼,那麼如果專案中的字串很多,程式碼可定有所增加,這樣我們就需要對一些不重要的模組字串不採用加密操作。當然如果數量控制在一定範圍內程式碼不會增加很多,應用的包體積也不會增加很多。
本文我們看到或者說以後大家要是對於專案中批量做一件事情,一般就是這個思路,操作位元組碼因為有先有的庫asm和javassist方便快捷,同時直接在編譯java檔案之後進行操作也是最好的時機。
四、結尾
想獲取更多面試資料和高階安卓資料嗎?想提升自己的技術嗎?想進阿里嗎? 關注+點贊+加群:185873940 免費獲取!
本群提供Android高階開發資料分享,高階UI、效能優化、IOC架構設計、架構師課程等Android高階開發資料及面試資料!