首先說明下,本文不是正統的《一次app抓包引發的Android分析記錄》的續篇,只是分析的某APP和起因是一樣的,故借題。然而本文所做的分析解決了《一次app抓包引發的Android分析記錄》所留下的問題,故稱為續。
移動應用已不像起初,burpuite代理改遍天下。交叉編譯、傳輸加密、DEX加殼等防護方式開始再慢慢應用,我們已經很難再肆意的抓包改數據了。
文中所分析的APP也是,將加密算法交叉編譯至so庫中,對傳輸參數進行整體加密。分析arm匯編代碼是一條路子,但已然令大部分人望而生畏。然而安全總是充滿著各種奇思淫意,就算不走匯編,依舊能找到其它路子。
接下來通過這篇文章向各位分享下分析過程。
我們此次分析的目的是為了還原傳輸過程中的request和response,當然更重要的是要能控制request中的參數。為此我們需要了解參數是如何加密的。通過《一次app抓包引發的Android分析記錄》我們知道加密函數是libGoblin.so中的e函數,但似乎通過它要還原出解密函數不是那么容易。
不過既然知道了參數是何函數進行加密,那我們只要再知道參數是什么樣的格式,直接調用這個函數來加密偽造后的數據,而后抓包替換掉對應的參數不就可以了嗎?
為此我采取的策略是在json數據進入加密函數之前將其截獲,這樣就能知道參數是以什么樣的格式傳遞。知道了具體的參數格式,便可偽造數據抓包改包。
首先來看下一個request,如下圖參數都是加密的,本小節目的就是為了搞清楚下面這一串是什么東西。
萬年不變的第一步,apktool反編譯、dex2jar反編譯。當然代碼混淆了,不過這不影響我們分析。android發送網絡請求主要采用JDK的HttpURLConnection和Apache的HttpClient,搜索跟這兩個相關的函數和字段以及請求的URL。而這些特征函數和字段,通常是不會進行混淆的。Andbug動態跟蹤亦可,本次通文采用靜態代碼分析,故如此!
通過AndroidManifest.xml了解app的結構,找到主包名等等。jd-gui查看java代碼,request是POST請求,所以搜索post、HttpPost字眼看看:
利用如上方式,反復通過搜索http請求方法及請求URL等特征縮小范圍,最終定位到AbstractHttpRequest類,該類是抽象類并擁有很多有趣的方法。而CommonTask類擴展了AbstractHttpRequest類。通過閱讀代碼知道該類為關鍵類。《一次app抓包引發的Android分析記錄》文中亦說明了,此處不再贅述!
而c參數被AbstractHttpRequest的chrome()處理,跟進該函數:
str為字符串常量“6000lex”和json數據一起進入NetworkParam的convertValue(),跟進:
在convertValue()中最終調用了加密函數,因此我們在Goblin.e加密paramString1和paramString2之前將這兩個參數通過log打印出來就知道請求的數據到底是什么東西了。
用文本編輯器打開com.xxx.net下的NetworkParam.smali定位到convertValue()函數,在Goblin.e處添加如下代碼:
通過修改smali代碼將加密前后的字符串打印出來,重打包簽名后安裝,查看logcat日志:
如上圖所示,現在我們已經知道了參數傳遞的格式了。然而日志中還出現了預料之外的數據。照上分析p1應該為常量“6000lex”,而圖中卻出現了時間戳。對比傳輸的數據(下圖),發現上圖第二個request-e是b參數的密文。而b的明文正是登陸信息。搜索convertValue發現b也是調用該函數加密,故導致此現象。
通過如上分析,最終確定了登陸時c和b格式分別為:
c={“adid":"f854a2765b5dcc3e","cid":"C2487","gid":"5EFD7B7D-A648-2F40-9D53-D42F1BCCC468","ke":"1410276980456","ma":"","mno":"310260","model":"sdk","msg":"","nt":"burp","osVersion":"4.4_19","pid":"10010","sid":"352C4C51-F09F-C929-E03A-B5E311BA2808","t":"p_ucLogin","uid":"000000000000000","un":"","vid":"60001060"}
b={"loginT":1,"paramJson":"","prenum":"","pwd":"xxxx","uname":"xxxx","userSecurity":{"communityCode":"0","imeiCode":"000000000000000","imsiCode":"310260000000000","osType":"14","stationId":"0","terminalType":"02"}}
由于c中的ke和b的加密因子一樣。猜測服務端解密時,先通過“6000lex”解密出c,獲取ke的值,再通過ke解密出b參數。
實際上上面那個猜想是正確的,而《一次app抓包引發的Android分析記錄》中所做的猜想也是正確的。Goblin.d()就是解密函數。
《一次app抓包引發的Android分析記錄》中所記錄的:
利用Goblin.d()解密后為:
至于《一次app抓包引發的Android分析記錄》中為何解密會失敗,下面章節會說明到!
如下,response響應報文亦是亂七八糟的一堆。這樣就算我們能改包了,也無法判斷結果到底是否正確。因此在開始改包之前,必須先解密response。
通過0x02的分析知道app是用Apache的HttpClient進行post請求。而HttpClient獲取response報文是通過getEntity()函數。故直接搜索getEntity,有了0x02的分析,輕松定位到AbstractHttpRequest類的getResult方法,由于此處dex2jar反編譯錯誤。所以直接分析smali代碼。
打開AbstractHttpRequest.smali:
getEntity()結果為v0,v0最終進入到dealWithResponse(),而dealWithResponse返回一個Object而不是String,所以繼續跟進dealWithResponse():
dealWithResponse只有一個參數,故跟蹤p1:
p1最終進行到parseProtoResponse()和buildHttpResultString()中,跟進parseProtoResponse():
parseProtoResponse()中調用了Goblin中的函數,可能這個函數就是我們想要的。先放著。我們再看看buildHttpResultString()這個函數,此處直接jd-gui查看:
buildHttpResultString()是個抽象類,具體代碼在CommonTask、MultiTask、PollTask中實現。而這幾個類中最終都是調用Response類的pareResponse()方法,而pareResponse()也調用了Goblin中的解密函數。parseProtoResponse()也是Response類一個方法。所以我們將解密響應報文的函數定位在這兩個函數中。接下來就是驗證下是否正確。
修改Response.smali中的parseProtoResponse()和pareResponse(),將解密后的結果通過日志打印出來:
在parseProtoResponse()和pareResponse()中標記了幾處,但最終只出現如上結果,故確認解密函數為pareResponse()。
至此我們已經完全看到了request和response傳輸的明文內容,如下所示,一個完成的請求響應過程:
當然我們目的是為了測試,所以必須要能改request請求才可以。
嘗試一:
既然我們知道了request的加密函數,那么在自己的APP中調用,加密完替換掉burp攔截到得數據即可。
新建一個app引用同樣的Goblin類,進行加解密測試,測試代碼如下:
加密可以加密,但實際上加密結果和原始的密文不一樣。而解密卻失敗,失敗的原因正和《一次app抓包引發的Android分析記錄》中所做的測試一樣。
發生了JNI WARNING:NewStringUTF input is not valid Modified UTF-8錯誤,而這個錯誤是由于在JNI中,google修改了UTF8的標準,當正常UTF8中包含了不符合這個標準的字節時,checkJNI函數就會報這個錯,導致應用崩潰。
在google中亦有關于此錯誤的報告:
https://code.google.com/p/android/issues/detail?id=64892????????
https://code.google.com/p/android/issues/detail?id=25386
然而直接將原始密文拿來解密卻可以正常解碼,所以問題出可能出在加密環節。折騰半天,沒有發現適合我們這邊的處理辦法。但在測試過程中發現,在原始APP應用中通過修改smali代碼可以正常加解密。難道so中還有檢測環境?當然這個得查看arm匯編才知道。
既然如此,那直接改造原始app吧。
嘗試二:
改造思路:在原始APP內部中攔截參數—>通過外部修改攔截到的參數—>放行參數,后續按正常流程進行
在原始app內部中攔截參數,只要在讓參數再進入加密之前進入到我們控制的函數中即可。為了方便說明。我們先來看看如何通過外部修改內部攔截的參數。
借助android的廣播機制,我們能實現實時的跟app進行交互。因此,也能實時的讓外部跟app內部進行數據交互。新建一個myBroadcast類,代碼如下:
#!java
public class myBroadcast extends BroadcastReceiver{ public static boolean sw = false; public static boolean swc = false; public static boolean swb = false; public static String datac = null; public static String datab = null; public static String kec = "6000lex";
// 接收廣播
public void onReceive(Context context, Intent intent) {
Log.i("broadcast-intent",intent.toString());
String action = intent.getAction();
if(action.equals("com.test.broadcast1")){
sw = intent.getBooleanExtra("sw", true); // 控制是否攔截
swc = intent.getBooleanExtra("swc", false);// 控制攔截c
swb = intent.getBooleanExtra("swb",false); // 控制攔截b
}
else if(action.equals("com.test.broadcast2")){
datac = intent.getStringExtra("datac"); // 接收c
datab = intent.getStringExtra("datab"); // 接收b
}
Log.v("receiver-data:sw:swc:swb|datac:datab",sw+":"+swc+":"+swb+"|"+datac+":"+datab);
}
// 延遲函數
public static void delay(){
try {
Thread.currentThread();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 修改函數
public static String alter(String ke,String json){
Log.v("json-data-1",ke+":"+json);
while(sw){
delay();
if(ke.equals(kec)){
if(swc){
if(datac!=null){
json = datac;
Log.v("json-data1-2",json);
break;
}
}
else{
break;
}
}
else{
if(swb){
if(datab!=null){
json =datab;
Log.v("json-data2-2",json);
break;
}
}
else{
break;
}
}
}
return json;
}
}
利用onReceive實時接收外部數據,利用alter函數檢測外部是否發送了攔截指令。當收到攔截的指令時,調用delay()函數進行循環延時,直到接收到偽造的數據或者關閉攔截。
將myBroadcast類轉化為smali代碼。把myBroadcast.smali放在NetworkParam.smali同級目錄下。并在AndroidManifest.xml中注冊myBroadcast這個receiver,且將exproted設置為true。
即允許該receiver被外部訪問。
這樣我們就能在NetworkParam.smali中使用這個receiver
接下來,我們來修改NetworkParam.smali,使其接收我們的指令。在Goblin.e前后修改如下:
讓p0進入alter()函數,修改后結果保存至v3,讓v3替換p0進入到Goblin.e()中。“request-data-1”打印出原來的參數,“json-data-1”打印出進入alter函數中的參數。“request-data-2”打印出修改后的參數,“request-data-e”,打印出加密后的參數。
修改完后,重新打包、簽名、安裝,運行查看logcat日志:
1、?默認設置是不攔截,所以logcat日志中應該能完整得看到
“request-data-1”—>“json-data-1”—>“request-data-2(不變)”—>“request-data-e”—>“response-data”
測試如下:
和預期一樣
2、?設置攔截指令,即sw為true,攔截數據進入循環延遲并等待接收偽造的參數。所以logcat日志中應該只能看到“request-data-1”—>”json-data-1”,測試如下:
am命令發送廣播:
Logcat日志,如預期,數據被攔截,應用一直處于加載當中:
3、發送偽造數據,按照設計。此時logcat日志中應該能看到
“request-data-1”—>“json-data-1”—>“request-data-2(修改后)”—>“request-data-e”—>“response-data”
測試如下:
發送廣播指令,攔截b參數,放行c參數:
Logcat日志顯示如下,c(帶600lex的)被放行,b(帶時間戳的)被攔截:
am命令發送datab數據,將uname改為test222:
結果如下,receiver接收到偽造的數據后,將原b參數進行修改后放行:
和預期一樣。
最終,我們實現了查看傳輸明文信息,并控制了request請求報文。即便它采用so加密。我們依舊可以盡情的測試了~
?