Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android逆向分析案例——某地圖的定位請求分析

Android逆向分析案例——某地圖的定位請求分析

編輯:關於Android編程

微信裡面有個“附近的人”,相信大家都知道,大概的原理就是調用騰訊地圖的sdk進行定位,然後把個人的定位信息發到微信服務器上進行處理,再把一定范圍內的人的信息返回並用列表的形式顯示出來。

因為剛踏入逆向分析這行,所以抱著學習的態度,研究一下大公司的東西,漲一下知識,嗯,本次的案例就是分析騰訊地圖中定位請求數據的加解密,以及搭建簡易的APK模擬實現定位請求。

為了分析這一案例,是要掌握一定的分析工具的,其中包括APKTOOL, Sublime-Text, Xposed框架,Wireshark等。

嗯,作為分析筆記,我就不細說了,只是將分析的步驟一步步地列舉出來。

 

首先,下載微信APK,利用APKTOOL在DOS下敲命令生成smali文件夾,這個過程我就不說了,前面博客已介紹過APKTOOL的使用方法。對於初學者來說,他們喜歡看到直接明了的源代碼,所以他們會喜歡用dex2jar去反編譯,並利用jd-gui去查看代碼,包括我當初也是如此,但現在我為什麼不用dex2jar和jd-gui去分析呢?再往下看看

\

 

然後用smali文件查看工具Sublime-Text 3打開其剛剛生成的smali文件夾,如下圖:

\

 

嗯,反編譯出來的文件數量令人感到窒息的,是不是有點感到逆向分析的痛苦了?要在如此多的smali文件裡找到對應的類,並定位到關鍵的代碼,這是需要一定的技巧的,不然你就只能碼海撈針了。順便一提,這貨混淆得太好了,裡面的可讀性非常差,而且通過smali可以看到代碼是有一定的復雜性的,特別多goto語句,所以如果用jd-gui浏覽代碼的話,是很不准確的,而且有一些內部類jd-gui是不會顯示出來的等等。。。總而言之,smali的表達基本是正確的,但jd-gui轉變過程並沒有那麼智能,所以jd-gui所表達的信息很大程度是錯的,只能輔助用,不能依賴。

既然我們要找的是地圖地位請求,那麼肯定涉及到網絡,那麼又怎麼能少了抓包過程呢?好,進入微信,打開wireshark,在點擊“附近的人”按鈕同時觀察wireshark抓到的包,主要是Http協議的包。很好抓,如下圖:

\

 

接著,我們右鍵打開選項,並選擇“追蹤流”,查看Http請求數據的TCP流,並嘗試去從請求中找到關鍵字符串:

\

可以從抓到的包看到,在請求行和請求頭中有我們想要的關鍵字符串,至於請求體就不用細看了,已被加密過,但我們的目的就是要分析其加解密原理。

SO,接下來一步就是通過關鍵字符串,在碼海中定位到關鍵的代碼。

先回到剛才用Sublime-Text打開smali文件夾的界面,在界面中找到ct包(包名已被混淆,實際就是集成地圖sdk的jar包),在該包下的所有smali文件查找剛才抓包的關鍵字符串,這裡只用了“/loc?c=1&mars=0&obs=1”中的“mars”進行查找。

\

我們可以看到,在搜索結果裡找到了該字符串在哪裡被調用了,很好,就在br.smali這個文件中,那麼我們找到br.smali文件,並打開它:

\

然後,可以看到字符串是在br.smali中的a(II)方法中調用的,那麼我們就繼續順籐摸瓜~根據smali語法,構造搜索字符串“Lct/br;->a(II)”, 通過這個字符串,我們可以利用Find in Floder的搜索功能,知道在哪裡調用了這個函數:

\

不錯,已經找到了核心文件bz.smali,我就先說了吧,該類有兩個內部類一個bz$1,另一個bz$a(美元符號代表內部類),bz$1是一個繼承了Runnable的一個線程類,bz$a就是一個封裝數據的類,包括加密後的數據和請求地址。

因為我們是要找到加解密的原理,所以在找關鍵代碼的時候,要多留心帶有String類,和byte[]數組參數的函數,這些往往就是我們的Target~

so,不妨大致浏覽一下bz.smali裡涉及到String和byte[]的函數:

\

看到紅色圈出來的代碼了嗎,那就是關鍵代碼了。我就先直接說了吧,1:String為數據明文 , 2. 3:明文.getBytes("gbk")得到byte[], 4:b$a類的a方法,該方法就是利用java中的DeflaterOutputStream壓縮處理, 5:將壓縮後的byte[]作為參數放到 bz類的a方法中處理,該方法內還調用了so庫的本地方法進行異或加密,這個後面會說明, 6:初始化一個bz$a類,該類封裝了加密後的數據byte[]和地址String。

當初始化bz$a類後,它會被壓進一個LinkedBlockingQueue隊列中,即調用其offer方法;然後在bz$1線程類,再次take出來,將其byte[] get出來,作為Http請求體的數據。

那麼,有人就問,為什麼你就確定一定會調用這個函數呢?Good Question,別看我說得那麼輕松,實際在通過關鍵字符找關鍵代碼的同時,有必要通過Xposed框架的hook技術來驗證是否調用了這個函數,又或者hook這個函數,查看其參數和參數的變化等,這也是很重要的。Xposed框架使用前面博客有介紹,這裡不費口水了。

下面就是我通過Xposed框架中的一個模塊,hook到這個函數的:

\

從上圖可以看到,我hook了bz的函數a,並獲得其第一個參數String,並打印在Log中,可見確實是一段明文,這段明文是Json格式的,裡面包含了各種信息,包括手機imei, imsi,mac, 以及基站和周邊wifi的信息。

這樣一來,我們就可以確定這個函數確實是被調用了。

明文getBytes("gbk")調用了壓縮處理的函數, b$a->a([B)[B (smali格式的調用),那麼我們可以利用jd-gui打開b.smali文件,並查看其內部類a中的a方法(丫的,混淆得太惡心了,都是什麼abcd什麼鬼的~),切記,不要依賴jd-gui,上面的信息只能作為大概參考:

\

從紅色圈住的代碼看到,就是DeflaterOutputStream的壓縮處理,該a方法的下面還有一個b方法,處理的原理反過來的,即用InflaterInputStream對http的返回數據進行解壓縮處理:

\

除了對數據進行解壓縮處理外,還有一個異或加密,我們上面提到的步驟5:將壓縮後的byte[]作為參數放到 bz類的a方法中處理,該方法內還調用了so庫的本地方法進行異或加密,實際上可以通過順籐摸瓜找到最終函數:

\

調用了e類的 o方法,該方法是native方法,用一般的方法是看不了so庫內的代碼的,但IDA PRO貌似是可以看其匯編代碼分析的,這個是有一定難度的。不過,這裡我們可以假定它裡面的處理只是一個簡單的異或加密,那麼我們就可以修改參數,將byte[]全部置0,通過異或運算找出其key。這是關鍵點,通過000000^key=key。

因為 e的o函數調用內嵌在bz的a([BI)方法裡,所以我們只需要hook外面那層函數就可以了,注意,這裡的bz有兩個a方法,混淆得惡心,但參數是不一樣的:

\

我這裡new了一個長度為100,值全為0的byte[],key和0異或等於key本身,那麼我們看看是否這個本地方法裡的處理是不是異或?

\

從Log中可以看到,before是0,after是8個字節為一循環體的字節數組,顯然,這是個以 8個字節為key的異或加密,很好,看臉破解了就省事了,哈哈~

至此,整個數據從明文到密文就分析完成了,至於http返回後的數據實際上只需要解壓操作就出明文了,不再需要異或處理一次,所以就不講返回數據的處理了。

 

接下來,數據處理完了,那麼就分析是怎麼發送請求的。

還記得上面抓包的那個圖不?可以在請求數據中看到請求頭有關鍵字符串“Halley-sdk.......”,通過這個字符串,在sublime text中“find in folder”,在文件夾內搜索到該關鍵字符串,然後順籐摸瓜,後面你懂的,找到文件後,分析一下smali,大概就是用了apache接口的HttpClient進行一個請求而已。

那麼,到這裡,我們需要梳理一下,直接上流程圖吧:

\

好了,有了思路,那麼就用java進行模擬吧,我是在eclipse上模擬的,當然,也可以在Android Studio上寫個簡單APK。

eclipse中我用了兩種Http請求,一種是HttpClient,地圖sdk中用得方法,需要導入httpclient,httpcore, httpmine三個jar包,貌似還需要有個logging的jar包,都需要到Apache的官網下載;第二種是HttpURLConnection,這個java本身提供,雖然實現起來稍麻煩,但還是可以的。之所以要用第二種方法,是因為Android Studio把第一種方法中的四個包都導進去的時候由於工具本身bug會導致打包不成功,生成不了APK,很郁悶~

下面把代碼貼出來:

 

public class MainActivity extends Activity {

    private Button btnRequest;
    private TextView tvRequest, tvResponse;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvRequest=(TextView)findViewById(R.id.tv_request_data);

        tvResponse=(TextView)findViewById(R.id.tv_response_data);

        btnRequest=(Button)findViewById(R.id.btn_request);
        btnRequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tvRequest.setText(generateJsonObject().toString());
                tvResponse.setText("");
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            URL url=new URL("http://lbs.map.qq.com/loc?c=1&mars=0&obs=1");
                            HttpURLConnection httpConn=(HttpURLConnection)url.openConnection();
                            httpConn.setDoOutput(true);
                            httpConn.setDoInput(true);
                            httpConn.setUseCaches(false);
                            httpConn.setRequestMethod("POST");
                            httpConn.setRequestProperty("Connection", "Keep-Alive");

                            DataOutputStream dos=new DataOutputStream(httpConn.getOutputStream());
                            dos.write(xorEncrypt(deflater(generateJsonObject().toString().getBytes("gbk"))));
                            dos.flush();
                            dos.close();

                            int resultCode=httpConn.getResponseCode();
                            Log.d("zz", "resultCode:"+resultCode);
                            if(resultCode==HttpURLConnection.HTTP_OK){
                                Log.d("zz", "http response ok"+resultCode);
                                final ByteArrayOutputStream baos=new ByteArrayOutputStream();
                                byte[] buffer=new byte[1024];
                                InputStream is=httpConn.getInputStream();
                                int i=0;
                                while((i=is.read(buffer))!=-1){
                                    baos.write(buffer, 0, i);
                                }
                                tvResponse.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        try{
                                            tvResponse.setText(new String(inflater(baos.toByteArray()), "gbk"));
                                        }catch (Exception e){

                                        }

                                    }
                                });
                            }


                        }catch (Exception e){

                        }
                    }
                }).start();
            }
        });

    }

    //拼接一個明文JSONObect
    private JSONObject generateJsonObject(){
        JSONObject dataJson=new JSONObject();
        try{
            dataJson.put("version", "4.5.9.3");
            dataJson.put("address", 1);
            dataJson.put("source", 203);
            dataJson.put("access_token", "9d9e8211360ba90b8fbda2fcdbed21");
            dataJson.put("app_name", "wechat");
            dataJson.put("app_label", "微信_6.3.18");
            dataJson.put("bearing", 1);
            dataJson.put("control", 2);
            dataJson.put("pstat", 5);
            dataJson.put("wlan", getWlanInfo());
            dataJson.put("attribute", getAttributeInfo());
            dataJson.put("location", new JSONObject());
            dataJson.put("cells", getCellsInfo());
            dataJson.put("wifis", getWifisInfo());
        }catch (JSONException jsonException){

        }
        return dataJson;
    }

    private JSONObject getWlanInfo(){
        WifiManager wifiManager=(WifiManager)getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfo=wifiManager.getConnectionInfo();
        int rssi=wifiInfo.getRssi();
        String ssid=wifiInfo.getSSID();
        String bssid=wifiInfo.getBSSID();

        JSONObject wlanJson=new JSONObject();
        try{
            wlanJson.put("mac", bssid);
            wlanJson.put("rssi", rssi);
            wlanJson.put("ssid", ssid);
        }catch (JSONException e){

        }
        return wlanJson;
    }

    private JSONObject getAttributeInfo(){
        WifiManager wifiManager=(WifiManager)getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfo=wifiManager.getConnectionInfo();
        String mac=wifiInfo.getMacAddress().replace(":", "");

        TelephonyManager telephonyManager=(TelephonyManager)getSystemService(TELEPHONY_SERVICE);
        String imei=telephonyManager.getDeviceId();
        String imsi=telephonyManager.getSubscriberId();
        
        JSONObject attributeJson=new JSONObject();
        try{
            attributeJson.put("mac", mac);
            attributeJson.put("imsi", imsi);
            attributeJson.put("imei", imei);
            attributeJson.put("qq", "");
            attributeJson.put("phonenum", "");
        }catch (JSONException e){

        }
        return attributeJson;
    }

    private JSONArray getCellsInfo(){
        TelephonyManager telephonyManager=(TelephonyManager)getSystemService(TELEPHONY_SERVICE);
        JSONArray jsonArray=new JSONArray();
        JSONObject cellJson=new JSONObject();
        try {
            cellJson.put("mcc", 460);
            cellJson.put("mnc", 0);
            cellJson.put("lac", 42360);
            cellJson.put("cellid", 45370604);
            cellJson.put("rss", -88);
            cellJson.put("seed", 1);
        }catch (JSONException e){

        }
        jsonArray.put(cellJson);
        return jsonArray;
    }

    private JSONArray getWifisInfo(){
        JSONArray jsonArray=new JSONArray();
        WifiManager wifiManager=(WifiManager)getSystemService(Context.WIFI_SERVICE);
        wifiManager.startScan();
        List wifis=wifiManager.getScanResults();
        for(ScanResult wifi:wifis){
            JSONObject wifiJson=new JSONObject();
            try{
                wifiJson.put("mac", wifi.BSSID);
                wifiJson.put("rssi", wifi.level);
                jsonArray.put(wifiJson);
            }catch (JSONException e){

            }
        }
        return jsonArray;
    }

    //壓縮
    public byte[] deflater(byte[] bytes) throws IOException{
        if (bytes == null)
            return null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DeflaterOutputStream dos = new DeflaterOutputStream(baos);
        dos.write(bytes, 0, bytes.length);
        dos.finish();
        dos.flush();
        dos.close();
        return baos.toByteArray();
    }

    //解壓
    public static byte[] inflater(byte[] bytes) throws IOException{
        if (bytes == null)
            return null;
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        InflaterInputStream iis = new InflaterInputStream(bais);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int i = 0;
        while((i=iis.read(buffer))!=-1)
        {
            baos.write(buffer, 0, i);
        }
        baos.flush();
        return baos.toByteArray();
    }

    //異或加密
    public byte[] xorEncrypt(byte[] bytes){
        byte[] key=new byte[]{-67, 39, -29, 21, 51, 115, 19, -105};
        byte[] encryptedBytes=new byte[bytes.length];
        for(int i=0; i布局文件:

 

 




    

    
        
    

    

    
        
    
記得加上權限:

 

 

    
    
    
    
    

運行效果:

 

\

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved