一次app抓包引發的Android分析(續)

wyzsk發表於2020-08-19

Elegance · 2014/09/11 15:01

0x00起因


首先說明下,本文不是正統的《一次app抓包引發的Android分析記錄》的續篇,只是分析的某APP和起因是一樣的,故借題。然而本文所做的分析解決了《一次app抓包引發的Android分析記錄》所留下的問題,故稱為續。

移動應用已不像起初,burpuite代理改遍天下。交叉編譯、傳輸加密、DEX加殼等防護方式開始再慢慢應用,我們已經很難再肆意的抓包改資料了。

文中所分析的APP也是,將加密演算法交叉編譯至so庫中,對傳輸引數進行整體加密。分析arm彙編程式碼是一條路子,但已然令大部分人望而生畏。然而安全總是充滿著各種奇思淫意,就算不走彙編,依舊能找到其它路子。

接下來通過這篇文章向各位分享下分析過程。

0x01整體思路


我們此次分析的目的是為了還原傳輸過程中的request和response,當然更重要的是要能控制request中的引數。為此我們需要了解引數是如何加密的。通過《一次app抓包引發的Android分析記錄》我們知道加密函式是libGoblin.so中的e函式,但似乎通過它要還原出解密函式不是那麼容易。

不過既然知道了引數是何函式進行加密,那我們只要再知道引數是什麼樣的格式,直接呼叫這個函式來加密偽造後的資料,而後抓包替換掉對應的引數不就可以了嗎?

為此我採取的策略是在json資料進入加密函式之前將其截獲,這樣就能知道引數是以什麼樣的格式傳遞。知道了具體的引數格式,便可偽造資料抓包改包。

0x02解析request


首先來看下一個request,如下圖引數都是加密的,本小節目的就是為了搞清楚下面這一串是什麼東西。

2014091023582979037.png

萬年不變的第一步,apktool反編譯、dex2jar反編譯。當然程式碼混淆了,不過這不影響我們分析。android傳送網路請求主要採用JDK的HttpURLConnection和Apache的HttpClient,搜尋跟這兩個相關的函式和欄位以及請求的URL。而這些特徵函式和欄位,通常是不會進行混淆的。Andbug動態跟蹤亦可,本次通文采用靜態程式碼分析,故如此!

通過AndroidManifest.xml瞭解app的結構,找到主包名等等。jd-gui檢視java程式碼,request是POST請求,所以搜尋post、HttpPost字眼看看:

2014091023593412283.png

利用如上方式,反覆通過搜尋http請求方法及請求URL等特徵縮小範圍,最終定位到AbstractHttpRequest類,該類是抽象類並擁有很多有趣的方法。而CommonTask類擴充套件了AbstractHttpRequest類。通過閱讀程式碼知道該類為關鍵類。《一次app抓包引發的Android分析記錄》文中亦說明了,此處不再贅述!

而c引數被AbstractHttpRequest的chrome()處理,跟進該函式:

2014091100005867568.png

str為字串常量“6000lex”和json資料一起進入NetworkParam的convertValue(),跟進:

2014091100024792775.png

在convertValue()中最終呼叫了加密函式,因此我們在Goblin.e加密paramString1和paramString2之前將這兩個引數通過log列印出來就知道請求的資料到底是什麼東西了。

2014091100031885901.png

用文字編輯器開啟com.xxx.net下的NetworkParam.smali定位到convertValue()函式,在Goblin.e處新增如下程式碼:

2014091100041466275.png

通過修改smali程式碼將加密前後的字串列印出來,重打包簽名後安裝,檢視logcat日誌:

2014091100045620668.png

如上圖所示,現在我們已經知道了引數傳遞的格式了。然而日誌中還出現了預料之外的資料。照上分析p1應該為常量“6000lex”,而圖中卻出現了時間戳。對比傳輸的資料(下圖),發現上圖第二個request-e是b引數的密文。而b的明文正是登陸資訊。搜尋convertValue發現b也是呼叫該函式加密,故導致此現象。

2014091100054441062.png

通過如上分析,最終確定了登陸時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分析記錄》中所記錄的:

2014091100063691127.png

利用Goblin.d()解密後為:

2014091100070015786.png

至於《一次app抓包引發的Android分析記錄》中為何解密會失敗,下面章節會說明到!

0x03解析response


如下,response響應報文亦是亂七八糟的一堆。這樣就算我們能改包了,也無法判斷結果到底是否正確。因此在開始改包之前,必須先解密response。

2014091100090539984.png

通過0x02的分析知道app是用Apache的HttpClient進行post請求。而HttpClient獲取response報文是通過getEntity()函式。故直接搜尋getEntity,有了0x02的分析,輕鬆定位到AbstractHttpRequest類的getResult方法,由於此處dex2jar反編譯錯誤。所以直接分析smali程式碼。

開啟AbstractHttpRequest.smali:

2014091100094127866.png

2014091100101178907.png

getEntity()結果為v0,v0最終進入到dealWithResponse(),而dealWithResponse返回一個Object而不是String,所以繼續跟進dealWithResponse():

2014091100104472979.png

dealWithResponse只有一個引數,故跟蹤p1:

2014091100111966691.png

p1最終進行到parseProtoResponse()和buildHttpResultString()中,跟進parseProtoResponse():

2014091100120155024.png

parseProtoResponse()中呼叫了Goblin中的函式,可能這個函式就是我們想要的。先放著。我們再看看buildHttpResultString()這個函式,此處直接jd-gui檢視:

2014091100123887602.png

buildHttpResultString()是個抽象類,具體程式碼在CommonTask、MultiTask、PollTask中實現。而這幾個類中最終都是呼叫Response類的pareResponse()方法,而pareResponse()也呼叫了Goblin中的解密函式。parseProtoResponse()也是Response類一個方法。所以我們將解密響應報文的函式定位在這兩個函式中。接下來就是驗證下是否正確。

修改Response.smali中的parseProtoResponse()和pareResponse(),將解密後的結果通過日誌列印出來:

2014091100131736750.png

在parseProtoResponse()和pareResponse()中標記了幾處,但最終只出現如上結果,故確認解密函式為pareResponse()。

0x04控制request


至此我們已經完全看到了request和response傳輸的明文內容,如下所示,一個完成的請求響應過程:

2014091100141294546.png

當然我們目的是為了測試,所以必須要能改request請求才可以。

嘗試一:

既然我們知道了request的加密函式,那麼在自己的APP中呼叫,加密完替換掉burp攔截到得資料即可。

新建一個app引用同樣的Goblin類,進行加解密測試,測試程式碼如下:

2014091100145140718.png

加密可以加密,但實際上加密結果和原始的密文不一樣。而解密卻失敗,失敗的原因正和《一次app抓包引發的Android分析記錄》中所做的測試一樣。

2014091100151565721.png

發生了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被外部訪問。

2014091100175256805.png

這樣我們就能在NetworkParam.smali中使用這個receiver

接下來,我們來修改NetworkParam.smali,使其接收我們的指令。在Goblin.e前後修改如下:

2014091100181216605.png

讓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”
複製程式碼

測試如下:

2014091100184047629.png

和預期一樣

2、 設定攔截指令,即sw為true,攔截資料進入迴圈延遲並等待接收偽造的引數。所以logcat日誌中應該只能看到“request-data-1”—>”json-data-1”,測試如下:

am命令傳送廣播:

2014091100191488877.png

Logcat日誌,如預期,資料被攔截,應用一直處於載入當中:

2014091100193347858.png

3、傳送偽造資料,按照設計。此時logcat日誌中應該能看到

“request-data-1”—>“json-data-1”—>“request-data-2(修改後)”—>“request-data-e”—>“response-data”
複製程式碼

測試如下:

傳送廣播指令,攔截b引數,放行c引數:

2014091100195565253.png

Logcat日誌顯示如下,c(帶600lex的)被放行,b(帶時間戳的)被攔截:

2014091100202519355.png

am命令傳送datab資料,將uname改為test222:

2014091100220530381.png

結果如下,receiver接收到偽造的資料後,將原b引數進行修改後放行:

2014091100223140482.png

和預期一樣。

0x05結語


最終,我們實現了檢視傳輸明文資訊,並控制了request請求報文。即便它採用so加密。我們依舊可以盡情的測試了~

相關文章