一次金融APP的解密歷程
聲明:本文僅限于技術討論與分享,嚴禁用于非法途徑。若讀者因此作出任何危害網絡安全行為后果自負,與本號及原作者無關。
前言:
客戶僅提供官網下載地址給我們測試。但是由于官網的版本不是最新的,APP會強制你升級。而升級后的APP,是進行加固后的,無法使用frida進行hook,注入進程。那同樣也無法使用SSL Unpinning進行限制客戶端校驗證書。新版app使用查殼軟件顯示未加殼,但是查看源代碼明顯少了很多代碼,且很多都是變量聲明而已。
繞過更新:
我們要想能對APP滲透測試,一般都是需要抓包和解密的。首先使用burp進行抓包代理,官網版本的APP(以下統稱舊版APP),是可以輕松抓到APP的包的(該條請求為檢驗APP最新版本的請求)。但是內容使用了加密,具體什么加密是不得而知。
img
獲取到請求密文:
vVAK0jos5eT9gmQJaHOaYbqZ1mgXoBH3bee3MTF3G5wNRHRoPPOYokZLT4MQqaPDN%2BLeEYpIzzDJeErDHcDfhY8muosLfOaw35W3BuCxDNtuNFB86RumMBtOcQXT08qw
響應包未json,urldecode后為:
{"duration":"0ms","note":"","code":1,"resultDES":"UX/jHk6yqix2yxZIrf0rSIuOjCy6oGxjCPUfBL2avG+DWy/++NW16+YQHVFQ+Nj2w9VOWGcH4OxFtGxbR6K7I6pY0Q9hkP9gc0K0JLZ5O+PwOW72nzissCiLG+cHqadKHzkPOQDdBUuBoa4W1Jz7fQ=="}
通過desStr和resultDES,一開始我猜測他為des加密,具體是不是,后續再說。
先進入APP,但是一進入APP就提示更新:
img
通過前言,我們知道是不能更新的。(當然不乏某些技術大佬也可以把新版APP搞定,我技術有限,感覺舊版的比較容易搞)。那我們就明確了目標,要先繞過更新校驗。
對于不了解hook和frida的同學,我這邊推薦先去網上了解下,還有安裝之類的,再來看此篇文章。
首先我們明確一下思路,要怎么繞過這個更新校驗呢?
(1)直接反編譯,修改APP的版本信息為99.99之類的;
(2)通過修改版本驗證請求,使用http層面去繞過;
(3)使用hook,去重寫更新函數,或者繞過更新函數;
第一點要app能支持反編譯且不存在校驗簽名。第二點要能知道加密密文的密鑰。所以我選擇第三種:
通過jadx搜索更新,發現了兩處,成功獲取到源代碼。
img
類名分別為:com.xxxx.AppUpdate和com.xxxx.WelcomeActivity,通過代碼審計可以看到,是先調用的WelcomeActivity,WelcomeActivity再去調用的AppUpdate:
img
跟蹤進入AppUpdate,調用的checkNativeAppVersion():
img
通過上述代碼,我們可以看到,這邊就是用于判斷是否升級的函數。
public void onResponse(Call call, Response response) throws IOException {
try {
JSONObject jSONObject = new JSONObject(C.s2(new JSONObject(URLDecoder.decode(response.body().string(), DataUtil.UTF8)).getString("resultDES"), Config.WHITE_KEY, Config.IV.getBytes()));
if (jSONObject.optInt("code", -1) > 0) {
JSONObject optJSONObject = jSONObject.optJSONObject("object");
if (optJSONObject == null) {
return;
}
if (WakedResultReceiver.CONTEXT_KEY.equals(optJSONObject.optString("isUpdate", ChatConfig.CARD_TYPE))) {
nativeAppVersionInterface.updateApp(optJSONObject.optString("desc", "當前有新版本,是否需要更新"), optJSONObject.optString(ClientCookie.VERSION_ATTR, ""));
} else {
nativeAppVersionInterface.noUpdateApp();
}
} else {
nativeAppVersionInterface.showError(jSONObject.optString("note"));
}
} catch (JSONException e) {
e.printStackTrace();
nativeAppVersionInterface.showError(e.getMessage());
}
}
當JSONObject.optInt("code", -1) > 0時,是會去進行升級的,否則則執行nativeAppVersionInterface.noUpdateApp()。
這邊分析完后,其實我們就可以寫js進行hook操作了。
我們的hook思路可以這樣設置了:
重寫checkNativeAppVersion函數,執行執行nativeAppVersionInterface.noUpdateApp()。
Ps:因為我一開始直接重寫了checkNativeAppVersion,只執行了console.log(“enter checkNativeAppVersion”),沒有對APP進行啟動,這樣就會直接卡死在啟動頁。
附上js代碼:
if(Java.available){
console.log('success');
Java.perform(function(){
var appUpdate = Java.use("com.xxxx.AppUpdate");
appUpdate.checkNativeAppVersion.implementation = function(a,b,c,d,e,f){
console.log("enter AppUpdate");//判斷是否進入該hook函數,進入會執行該命令
f.noUpdateApp();//直接執行不需要更新函數,APP會自動進入
}
});
}
使用命令:frida -U -l .\xxx.js -f 包名 --no-pause
img
成功進入:
img
解密:
已經成功進入該APP,但是如果想成功進行滲透測試的話,還需要能解開APP的加密。通過des字段,初步判斷為des加密,再回頭看看剛剛更新的那個請求,是有用c.s2()函數進行操作的,大概率s2就是解密函數。
JSONObject jSONObject = new JSONObject(C.s2(new JSONObject(URLDecoder.decode(response.body().string(), DataUtil.UTF8)).getString("resultDES"), Config.WHITE_KEY, Config.IV.getBytes()));
可以看到s2的三個參數,即前面響應包中的json字段里面的resultDES參數,然后其次是Config.WHITE_KEY, Config.IV兩個參數,其中Config.IV是以字節數組的形式進行傳參的。通過跳轉可以看到配置文件的參數。
img
然后呢,因為獲取到密鑰和偏移量iv,這樣的話des就可以解了。但是問題是解不開。后續的思路就是如果可以直接hook這兩個加解密函數的話,是不是就可以不用管他的加解密了。
img
s1和s2函數不在java層,那我們就需要hook native層的代碼。Hook so文件。首先我們先把安裝包后綴apk改成zip,然后解壓。就可以找到wkb-1.2.2.so的文件了。(路徑為lib/arm64-v8a/wkb-1.2.2.so,前面的arm64根據自己測試機的CPU架構進行選擇。)直接用ida打開,在導出函數里面搜索des:
img
里面有很多des的相關函數。可使用以下js進行hook導出函數:
if(Java.available){
console.log('success');
Java.perform(function(){
var point = Module.findExportByName("libwkb-1.2.2.so","desDecryptByteArray");
Interceptor.attach(point,{
onEnter: function(args){
console.log("Hook start");
console.log("args[0]=" + args[0]); //打印我們java層第一個傳入的參數
console.log("args[1]=" + args[1]); //打印我們java層傳入的第二個參數
},
onLeave: function(retval){ //onLeave: function(retval)是該函數執行結束要執行的代碼,其中retval參數即是返回值
console.log("return:" + retval); //打印返回值
}
});
});
}
但是這邊很奇怪的是,通過函數findExportByName找到的地址都是為null,一開始以為是還沒加載到so文件,但是后續進入APP后還是一樣為null。(有知道的大佬可以說下)
img
這就比較蛋疼了,得手動計算地址。首先先獲取so文件的地址,看能不能獲取到,若不行,則表示未加載so文件。
var soAddr = Module.findBaseAddress("libwkb-1.2.2.so");
console.log("soAddr:" + soAddr);
img
有地址出來,說明so文件是存在的,可以正常調用。那么這邊就要去計算函數偏移量。之前在網上看到別人的一個公式:
*函數地址=so***初始地址+**函數偏移量+1
但是我后面嘗試了好幾個,好像不同手機不同的計算方法,也可能我操作的有問題。我這邊的函數地址就是:
*函數地址=so***初始地址+**函數偏移量
不用加一。我自己是用這個方法測試計算的:找到一個導出函數可以被查詢到的,比如我這邊使用的就是JNI_OnLoad函數:
img
img
獲取JNI_OnLoad的地址為0x79d5d7883c,然后使用這個地址減去so的地址:
0x79d5d7883c ? 0x79d5d67000 = 1183c
差值剛好為JNI_OnLoad的偏移量,所以我這邊就不用再進行加一操作了。
這樣我們就可以成功hook任意函數了。通過我一個個嘗試發現,以下函數一個都沒調用過:
img
然后呢,我查找了s2函數的用例,發現被decodeSm4的函數調用過。
img
我就嘗試了一下,hook了sm4EncryptByteArr:
img
img
附上js:
var soAddr = Module.findBaseAddress("libwkb-1.2.2.so");
var point = soAddr.add(0x136f0);
Interceptor.attach(point,{
onEnter: function(args){
console.log("Hook start");
console.log("args[0]=" + args[0]); //打印我們java層第一個傳入的參數
console.log("args[2]=" + Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()); //打印我們java層傳入的第三個參數
console.log("args[3]=" + Java.vm.getEnv().getStringUtfChars(args[3], null).readCString()); //打印我們java層傳入的第四個參數
},
onLeave: function(retval){ //onLeave: function(retval)是該函數執行結束要執行的代碼,其中retval參數即是返回值
console.log("return:" + Java.vm.getEnv().getStringUtfChars(retval, null).readCString()); //打印返回值
// retval.replace(0); //替換返回值為0
// return retval;
}
});
Ps:通過ida里面的參數,我們可以看到第二個參數為類,我們就沒給他打印出來。
我人傻了,一開始的des字眼和偏移量這些都符合des的加密方式,誤導了我好久,一直往des方向去找。
尾聲:
其實很早我就已經解密成功了,直接通過java層,剛剛發現調用s2的decodeSm4函數,直接hook那邊即可成功獲取請求和響應的明文:
img
但是若通過js去操作修改數值,實在太麻煩了,要獲取密鑰和加密方式,通過腳本自動去加解密,所以我才會去hook native層,獲取到密鑰。因為上述密鑰Config.WHITE_KEY,其實是還有一層加密的,通過hook decodeWhiteKey函數的返回值,成功獲取了密鑰。
img
img
其實后續我也嘗試去修改版本號繞過,但是事實證明,代碼存在驗簽:
09fb86b8fbc058e8ecab0e5a8a04be6
可以看到,把版本號修改為99.9.99,成功繞過了更新檢測,但是他還存在一個盜版驗簽檢測:
5db5880584da7a62dea2030e6c00a57
驗簽代碼一樣需要用hook去繞過。所以前面說的方法一也是行不通的。然后我又突發奇想,有沒有可能他密文里面就包含版本信息,那如果我使用99.9.99的版本,抓取密文,然后再安裝舊版APP,在他去請求版本更新時,替換密文,是不是可以繞過呢?經過嘗試,結果是:可以。他的版本校驗就是在服務端,這種方法也可以繞過。
總結:
不用輕易相信別人留下的信息,還是得根據自己的分析得出結論。其實后續我一直在想為什么那個字段是des呢,感覺之前是des加密,后續金融行業都進行了國密改造,然后字段并未更改,導致這種現象,當然只是猜測。至此,已完成對這APP的抓包和加解密。