<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/5409

            Author: 上海交通大學密碼與計算機安全實驗室軟件安全小組GoSSIP

            第一題


            0x1 分析

            APK界面

            題目下載

            本次比賽的第一個題目是一個APK文件,安裝后,需要用戶輸入特定的密碼,輸入正確會顯示破解成功。該題目的APK文件沒有太多的保護,可以直接使用各種分析工具(如jeb等)反編譯得到Java代碼。

            獲得正確注冊碼的代碼邏輯為: 1. 從logo.png這張圖片的偏移89473處,讀取一個映射表,768字節編碼成UTF-8,即256個中文表 2. 從偏移91265處讀取18個字節編碼的UTF-8(即6個中文字符)為最終比較的密碼。然后通過輸入的字符的轉換,轉換規則就是ASCII字符編碼,去比較是否和最終密碼相等。

            0x2 巧妙的解法

            我們在這里提供一種非常愉快的解法,不需要復雜的工具和分析,大家可以參見視頻

            打開app后,我們使用adb logcat并加上這個app獨有的 lil 標簽過濾日志輸出,發現app輸出日志中有table,pw以及enPassword。隨意輸入字符串如123456789,發現enPassword中有對應的中文輸出,根據輸出反饋,可以知道有如下對應關系

            通過觀察Logcat輸出可知,最終目標pw應為義弓么丸廣之,根據上述table中的對應關系,我們可以得到最終密碼為:

            581026
            

            第二題


            0x1 分析

            APK界面

            題目下載

            本次比賽的第二個題目仍然是一個獨立的APK文件,安裝后,需要用戶輸入特定的密碼,輸入正確會顯示成功。第二題APK在Java層代碼中并沒有關鍵邏輯,將用戶輸入直接傳給native so層中securityCheck這個native method(securityCheck方法在libcrackme.so中),由native code來決定返回正確與否。

            用IDA工具打開libcrackme.so,首先看下程序的大致流程,可以看到在securityCheck這個方法調用前,在init_array段和JNI_Onload函數里程序都做了些處理,而在securityCheck方法的最后有一個判斷,將用戶輸入和wojiushidaan做比較。嘗試直接輸入wojiushidaan,發現密碼錯誤,因此可以猜測前面一大段邏輯的作用就是會把這個最終的字符串改掉。此時的思路是只需知道最終判斷時候這個wojiushidaan地址上的變換后的值就行了。嘗試使用IDA調試發現一旦attach上去,整個程序就退出,想必一定是在之前的代碼中有反調試的代碼。

            0x2 巧妙的解法

            同上一題一樣,我們提供一種非常巧妙的解法:

            注意到在最終比較之前,程序使用了android_log_print函數,當我們直接運行程序時,發現這里固定輸出了

            I/yaotong ( XXX): SecurityCheck Started...
            

            這時候我們想,是否可以直接patch這個libcrackme.so,修改打印的內容,直接利用這個函數幫我們輸出此時真正需要比較的值。

            我們選擇patch的方法是直接把這個log函數往下移,因為在0x12A4地址處正好有我們需要的打印的數據地址賦值給了R2寄存器(本來是為了給后面做比較用的),因此將代碼段從0x1284到0x129C的地方都用NOP改寫,在0x12AC的地方調用log函數,同時為了不影響R1的值,把0x12A0處的R1改成R3。

            下面是對比patch前和patch后的圖:

            Patch前代碼

            Patch后代碼

            參考視頻給出了完整的解決過程:

            通過觀察Logcat輸出可知,最終密碼為:

            aiyou,bucuoo
            

            第三題


            題目下載

            在介紹本次比賽第三道題目之前,首先要介紹一個我們GoSSIP小組開發的基于Dalvik VM的插樁分析框架InDroid,其設計思想是直接修改AOSP上的Dalvik VM解釋器,在解釋器解釋執行Dalvik字節碼時,插入監控的代碼,這樣就可以獲取到所有程序運行于Dalvik上的動態信息,如執行的指令、調用的方法信息、參數返回值、各種Java對象的數據等等。InDroid只需要修改AOSP的dalvik vm部分代碼,編譯之后,可直接將編譯生成的新libdvm.so刷入任何AOSP支持的真機設備上(目前我們主要使用Nexus系列機型特別是Nexus4和Galaxy Nexus)。在本次比賽的第三題和第四題分析過程中,我們使用該工具進行分析,大大提高了分析效率。具體細節可以參考我們發表的CIT 2014論文DIAS: Automated Online Analysis for Android Applications

            回到題目上,將第三題的APK進行反編譯后發現代碼使用了加殼保護,對付這類加殼的APK,最方便的方法就是使用InDroid來進行動態監控,因為靜態加密的DEX一定會在執行時在Dalvik上時解密執行,這樣我們可以直接在InDroid框架里對解釋執行過程中釋放出來的指令進行監控。在我們自己使用的工具里,我們開發了一個動態讀取整個dex信息的接口,執行時去讀DexFile這個結構,然后對其進行解析(解析時直接復用了Android自帶的dexdump的代碼)。這樣,我們的插樁工具在運行程序后,能夠直接得到程序的dex信息,同未經保護時使用dexdump后得到的結果基本一致。雖然我們得到的信息是dalvik字節碼,沒有直接反編譯成Java代碼那么友好,但由于程序不大,關鍵邏輯不多,因此對我們的分析效率影響并不大。

            使用InDroid進行脫殼的演示視頻:

            在得到脫殼之后的dexdump結果后,我們可以對代碼進行靜態分析。我們發現用戶的輸入會傳遞給繼承自Class timertask的Class b,被Class b的run方法處理。在run方法中,如果sendEmptyMessage方法被調用時的參數為0,就會導致Class c的handleMessage這個方法中得到的messagewhat值為0,進而導致103除0跳入異常處理中,觸發成功的提示。

            繼續分析這個run方法的邏輯,可以知道用戶的輸入會被傳遞到Class e的a方法中,做個類似摩爾斯譯碼的過程(其譯碼與標準的摩爾斯電碼不太一樣),然后經過下面一系列大量的混淆用的無用處理和不可能相等的比較后,將譯碼后得到的字符串送入到關鍵的判斷中去。這個判斷成功的條件比較復雜:對于譯碼后得到的字符串的前兩個字節,要求使用hashcode方法的結果等于3618,并且這兩個字節相加等于168,才會進入后面的比較。我們窮搜索一下符合這類輸入的字符串:

            #!java
            for ( size_t i = 33; i < 127; ++i )
            {
                for ( size_t j = 33; j < 127; ++j )
                {
                    String x =String.valueOf((char)j)+String.valueOf((char)i);
                    if (x.hashCode()==3618 && (i+j) == 168)
                    {
                        System.out.println(x);
                        System.out.println(j+i);
                    }
                }
            }
            

            輸出為:

            s5
            168
            

            也就是說只有s5滿足hashcode為3618,而相加等于168這個條件。

            確定前兩個字符后,后面還有四個字符需要同Class e和Class a的Annotation值比較。因為我們做脫殼的時候直接使用了dexdump的代碼,而dexdump即使到最新版里也無法很好地處理Annotations:

            // TODO: Annotations.
            

            不過沒關系,我們還有動態分析工具這一利器,因為最終目的是得到getAnnotation方法的返回值,依然可以用InDroid在Dalvik執行到getAnnotation方法時監控返回值,就能得到Annotation的具體值。使用InDroid獲取具體信息的視頻如下:

            最后可知,符合程序需求的字符串是

            s57e1p
            

            使用程序內部的對應表,對其進行逆變換,能夠讓程序輸入成功提示的輸入應該是:

            … _____ ____. . ..___ .__.
            

            第四題


            APK界面

            題目下載

            本次移動安全挑戰賽的第四題和第三題一樣,是一個包含了加殼dex的APK文件,我們使用同解決上一題一樣的方法,用InDroid得到原始dex文件的dexdump結果:

            使用InDroid進行脫殼的演示視頻:

            Dex整體處理過程和上一題也類似,使用handleMessage處理最后的判斷輸入成功與否,只有sendEmptyMessage(0)后,觸發除以0的異常才能成功。不過這一題將用戶輸入轉成byte后,傳給一個native的方法:ali$aM$j方法,另外參數還包括一個常數48和Handler。看樣子逆向native庫勢在必行了。這一題的lib文件夾下文件和上一題是一樣的,有三個文件,其中libmobisecy.so其實是個zip文件,解壓后是個classes.dex,直接反匯編后,類和方法的名字都在,只是里面的代碼都是

            throw new RuntimeException();
            

            libmobisecz.so直接就是一堆binary數據,猜測應該是運行時會被解密,通過某種方式映射到為真正的代碼執行。因此我們的目標就是libmobisec.so這個ELF文件。

            直接用IDA打開libmobisec.so,發現IDA會崩潰。用readelf發現正常的節區頭數據都被破壞了,因此應該這個so本身也被加過殼了,很多數據只有在動態運行時才會解開,所以直接使用動態的方法,先運行這個程序后,直接在內存中把這個so dump出來。

            首先需要在輸入框中隨便輸入些數據后,點擊確定,保證用戶輸入數據執行到native方法里后再做dump。我們使用的方法是查看maps后,使用dd命令把整個so都dump出來。

            輸入命令:

            [email protected]:/ # ps | grep crackme.a4
            u0_a73    1935  126   512204 48276 ffffffff 400dc408 S crackme.a4
            [email protected]:/ # cat /proc/1935/maps
            5e0f2000-5e283000 r-xp 00000000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
            5e283000-5e466000 r-xp 00000000 00:00 0 
            5e466000-5e467000 rwxp 00000000 00:00 0 
            5e467000-5e479000 rw-p 00000000 00:00 0
            5e479000-5e490000 r-xp 00191000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
            5e490000-5e491000 rwxp 001a8000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
            5e491000-5e492000 rw-p 001a9000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
            5e492000-5e493000 rwxp 001aa000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
            5e493000-5e4c1000 rw-p 001ab000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
            

            使用dd命令將libmobisec.so的內存dump出來

            [email protected]:/ # dd if=/proc/1935/mem of=/sdcard/alimsc4 skip=1578049536 ibs=1 count=3993600
            

            dd命令使用的數字都是十進制的,skip就是libmobisec.so的起始地址,count是總長度。 為了讓IDA還能夠識別libmobisec里的libc函數,我們還需要把libc也載入到IDA中,libc就直接從system/lib里拖出來就行了。

            adb pull /system/lib/libc.so ./
            

            用IDA先打開libc,調整好是在內存中的偏移即rebase program,再在load additional binary里載入dd出來的libmobisec.so,通過maps里的偏移后載入。接下來的任務就是在其中找到M\$j這個函數的地址。

            一開始嘗試直接在dd出來的ELF文件中找這個M\$j這個函數名,類似的名字會被處理成

            Java_ali_00024a_M_00024j
            

            類似下圖:

            圖1

            不過我沒找到這個M\$j這個名字,逆過JNI庫的都知道,如果符號表里找不到這個函數名,說明在JNI_Onload的時候,使用RegisterNatives函數重新將一個JNI函數映射為Native函數了。

            正當一籌莫展的時候,我再次想起了InDroid系統。在Dalvik中,每個方法都是一個Method的結構體,其中當這個方法是native的時候,Method的insns這個指針會指向native方法的起始地址。因此我們修改了下InDroid,讓Dalvik在執行M\$j這個方法前,去打印了M\$j方法的insns指針。這時我們得到了一個指向另一片內存區域的值,既不在libdvm中,也不在libmobisec中,并且這片內存頁被映射成了rwx,由此推斷里面也極有可能是代碼,我們繼而又dd出了這塊內存,用IDA打開,使用ARM平臺反匯編,發現該處就一條指令,是LOAD PC到另一個地址,而這個地址恰好在libmobisec中。于是我們直接到IDA中跳到這個地址,發現正好是個壓棧指令,印證了我們的想法,此處就是M$j函數,于是在在IDA里該地址指令處,右擊選擇create function,讓IDA識別這一段匯編指令為函數指令后,就可以通過F5查看看反編譯的C代碼了。

            這個函數本身做了一些控制流混淆,同時還有很多字符串加解密的功能函數,一些簡單的如異或操作,也被展開成與和或的組合等更長更復雜的表達式形式。另外還看到一些變形過的RC4,等等。不過因為我們已經是dump出來執行過的數據,所以必要的數據都已經解密了。如下圖:

            圖2

            通過查看反編譯的C代碼,我發現程序中是直接通過JNI方法調用了Java中的bh類的方法a(在圖2常量中也可以看到)。 再次回到dex層查看a方法,該方法是不斷的將輸入傳遞給不同的函數進行處理,先是cda方法,cCa方法,pa方法,xa方法,ali$aM$d方法(native),aSa方法,xa方法,ali$aM&z方法(native),cda方法,cCa方法,每一個方法都是些簡單的數學運算,編碼,以及密碼學處理等可逆的操作,結合逆向和Indroid對輸入輸出的監控,都可以輕松確定每個Java函數的作用,具體過程如下代碼顯示:

            invoke-static {}, LbKn;.a:()Z // [email protected]
            move-result v3
            invoke-static {v3}, LbKn;.b:(I)V // [email protected]
            add-int/lit8 v0, v5, #int 1 // #01
            invoke-static {v4, v5}, Lcd;.a:([BI)[B // [email protected]
            move-result-object v1
            add-int/lit8 v2, v0, #int 1 // #01
            invoke-static {v1, v0}, LcC;.a:([BI)[B // [email protected]
            move-result-object v0
            add-int/lit8 v1, v2, #int -1 // #ff
            invoke-static {v0, v2}, Lp;.a:([BI)[B // [email protected]
            move-result-object v0
            invoke-static {v0, v1}, Lx;.a:([BI)[B // [email protected]
            move-result-object v0
            add-int/lit8 v2, v1, #int -1 // #ff
            invoke-static {v0, v1}, Lali$a;.M$d:([BI)[B // [email protected]
            move-result-object v0
            add-int/lit8 v1, v2, #int 1 // #01
            invoke-static {v0, v2}, LaS;.a:([BI)[B // [email protected]
            move-result-object v0
            invoke-static {v0, v1}, Lx;.a:([BI)[B // [email protected]
            move-result-object v0
            add-int/lit8 v2, v1, #int 1 // #01
            invoke-static {v0, v1}, Lali$a;.M$z:([BI)[B // [email protected]
            move-result-object v0
            add-int/lit8 v1, v2, #int 1 // #01
            invoke-static {v0, v2}, Lcd;.a:([BI)[B // [email protected]
            move-result-object v0
            add-int/lit8 v2, v1, #int 1 // #01
            invoke-static {v0, v1}, LcC;.a:([BI)[B // [email protected]
            move-result-object v0
            return-object v0
            

            值得注意的是,其中有兩個native的方法,因為InDroid還可以監控調用native方法的參數以及返回值,我們發現這幾個native都沒有對輸入做復雜的處理,只有M\$d對輸入的第四個字節做了減8的處理。

            做了這些逆變換以后我們其實并沒有找到最終比較的處理,不過在解密過的數據中(圖2),不僅有之前需要調用的各種方法和類,還可以發現有個十分可疑的Base64的字符串。

            aJTCZnf6NyBPYJfbrBuLu0wOhRFbPtvqpYjiby5J81M=
            

            并且在native的M\$z方法的反匯編代碼中,可以看到有對這個Base64字符串的長度比較,由于我們并沒有找到真正的比較函數,因此得到這個字符串后,我們直接從M\$z開始向上逆推之前的變換就得到了的答案。

            具體解密代碼如下:

            #!python
            #!/usr/bin/env python
            # encoding: utf-8
            
            from Crypto.Cipher import AES
            
            def Lcda(s):
                return ''.join(map(lambda x: chr((ord(x) + 3) & 0xff), s))
            def de_Lcda(s):
                return ''.join(map(lambda x: chr((ord(x) - 3) & 0xff), s))
            
            def LcCa(s, a):
                return ''.join([chr(((ord(s[i]) ^ a) + i) & 0xff) for i in xrange(len(s))])
            def de_LcCa(s, a):
                return ''.join([chr(((ord(s[i]) - i) & 0xff) ^ a) for i in xrange(len(s))])
            
            def Lpa(s):
                return s[1:] + s[0]
            def de_Lpa(s):
                return s[-1] + s[:-1]
            
            def Lxa(s):
                return s.encode("base64")[:-1]
            def de_Lxa(s):
                return s.decode("base64")
            
            def LaliaMd(s):
                return s[:3] + chr((ord(s[3]) - 8) & 0xff) + s[4:]
            def de_LaliaMd(s):
                return s[:3] + chr((ord(s[3]) + 8) & 0xff) + s[4:]
            
            def LaSa(s):
                BS = 16
                pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
                cc = AES.new("qqVJwt11yyLm7hVK1iI2aw==".decode("base64"), AES.MODE_ECB)
                cipher = cc.encrypt(pad(s))
                return cipher
            def de_LaSa(s):
                cc = AES.new("qqVJwt11yyLm7hVK1iI2aw==".decode("base64"), AES.MODE_ECB)
                cipher = cc.decrypt(s)
                return cipher
            
            res = "aJTCZnf6NyBPYJfbrBuLu0wOhRFbPtvqpYjiby5J81M="
            
            flag = de_Lcda(de_LcCa(de_Lpa(de_Lxa(de_LaliaMd(de_LaSa(de_Lxa(res))))), 49))
            print flag
            

            結果為:

            alilaba2345ba
            

            這里還需要提一下如何尋找M\$dM\$z兩個函數在so庫中的地址的方法,不過這個方法是一些經驗的總結,原因是整個native ELF文件的節區結構是被修改過的。這兩個方法和M\$j不太一樣,因為在dump出的libmobisec里可以找到M\$z的函數名,證明這個方法沒有使用RegiterNatives來做變換,因此我們可以通過符號表來找這個函數與文件頭部的偏移。方法是找M\$z和字符串表的偏移,如0x03FE,然后窮搜整個文件:

            圖3

            因為符號表應該會把字符串表偏移作為一項,這塊區域的結構體,我們對照ELF結構發現并不是標準的符號表,但還是可以大概看出結構體的內容,包括索引,字符串表偏移,以及ELF特殊的標志數,因此推測0x57BE4偏移是M\$z函數。該地址也正好是個壓棧的指令,證明了我們的猜想。

            第五題


            enter image description here

            題目下載

            2015年移動安全挑戰賽的最后一道題目,在規定的比賽時間內,僅有來自我們GoSSIP的wbyang一名選手解決了這道問題,今天我們就來揭開這一道最高難度題目的神秘面紗。

            先把名為AliCrackme_5.apk的文件丟到JEB里看一看:

            enter image description here

            dex文件并沒有進行加殼和混淆,看上去是一個非常簡單的程序,Java代碼部分使用函數Register("Bomb_Atlantis", input)對輸入進行判斷。所以需要分析的邏輯應該都在libcrackme.so里的Register函數中。

            接下來我們用IDA打開這個libcrackme.so,不出所料的發現IDA完全沒法處理,應該是進行了強烈的混淆和加殼處理:

            enter image description here

            使用和解決前面題目相同的技巧,我們繼續使用dd的方法來去處一部分的混淆和加殼。運行一次程序后,從/proc/self/maps里找到libcrackme.so在內存中的位置,使用dd命令從/proc/self/mem中提取出內存中的libcrackme.so,接著使用在解決第四題時使用過的技巧,將libcrackme.solibc.so一起加載到IDA里。

            用IDA打開dump出的代碼后,我們發現仍然有大部分的代碼無法被IDA識別,需要手動定位到需要分析的代碼然后手工定義(IDA快捷鍵C)代碼,同時由于代碼會在THUMB指令集和ARM指令集之間切換,有時候需要用快捷鍵ALT+G來將T寄存器設置為不同的值,設置正確后才能正確翻譯出代碼。這里我們首先遇到的問題是無法定位Register函數,同樣使用第四題中的技巧,用InDroid監控到Register函數的真實地址,就可以在該地址上開始分析。

            libcrackme.so這個動態庫里使用的一些混淆方法,對于處理了前面一些類似混淆后現在的我們來說已經不是問題(^_^)。通過分析代碼,我們定位了幾個函數,這些函數的偏移在不同的設備上應該是不同的。整體的邏輯其實并不復雜,首先會有一個固定的字符串“Bomb_Atlantis”和一個固定的salt去進行一次md5運算,salt是動態生成的,不過由于dump內存的時候這些動態的值已經生成好了,所以能夠直接發現這個salt(出于一些版權原因我們不便公布本題目的一些內部細節,因此該salt值請大家自己分析)

            之后程序會將這個md5值和我們的輸入進行一些異或和計算的操作,經過幾步比較簡單、可逆的變換之后,進入一個比較復雜的函數,經過這個函數處理后直接和一個內存中的值進行比較,返回比較結果。

            enter image description here

            這里說一個我們在做第五題時用到的分析方法——動態hook。由于libcrackme.so中并沒有對調用自身的上層應用進行驗證,這就導致了我們可以自己寫一個程序去加載這個so,調用其中的方法。這也導致了我們在加載libcrackme.so后,可以加載另一個用于hook的so,這樣我們可以hook libcrackme.so中的任意函數,從而知道任意函數的參數和返回值,這對于我們理解程序有著非常大的幫助。這里我們使用的hook框架是著名Android安全研究人員Collin Mulliner開發的Android平臺上的一個二進制注入框架adbi。當然這道題目并不能夠通過注入的方法將我們的so注入進去,因為源程序禁掉了ptrace這樣的系統調用。我們對adbi稍作修改,使之成為一個可以手動加載的動態hook框架。同時由于我們沒法通過符號表來定位函數的地址,所有的hook地址都需要硬編碼,并且要和運行這道題目程序的Android設備內存映射嚴格對應。

            需要指出的是adbi中存在一個小bug,hook.c這個文件的118行應該是

            h->jumpt[0]=0x60
            

            而不是0x30,對應的thumb匯編應該是

            push{r5,r6}
            

            而不是

            push{r4,r5},
            

            這個小bug在解題過程中會造成一些影響。使用adbi來hook這道題目的函數還需要注意一點,這題的代碼中有一些函數使用的THUMB指令集,hook這些函數時,不要忘記人工的對hook地址+1。

            通過hook的方法,我們已經能夠動態的分析libcrackme.so,首先我們驗證了我們對之前幾步變換的分析結果。之后就是分析最后一個復雜的處理函數,通過靜態分析+動態調試,我們發現這是一個類似于白盒密碼學的加密函數。我們的輸入進入函數后,首先經過幾步類似DES的預處理,之后會進行若干輪的查表,通過查詢一個巨大的表將我們的輸入進行某種加密,生成一段密文,再經過幾次簡單的處理后和最后內存中的一段常量(出于一些版權原因我們不便公布本題目的一些內部細節,因此該常量請大家自己分析)進行比較。

            通過動態調試,我們能夠計算出加密算法最后應該輸出的值,但是由于這個加密算法的密鑰融入了整個置換表中,要找出一個逆置換表顯然不太可能。我們簡單過濾了libcrackme.so的其他函數,也沒有發現用于解密的函數,想要正常解密密文是不太現實了。不過根據對加密算法的分析,我們發現這若干輪的置換是相互獨立的,并且每一輪的復雜度并不高,這就意味這我們可以在可以接受的時間內對算法進行爆破。我們一開始的想法是code reuse,直接在Android設備上爆破,但是發現速度太慢,最后只能用笨辦法,通過hook從內存中dump出來置換表,用C代碼重寫了這個算法,有驚無險地在比賽結束前半小時搜索出結果。根據逆推算法推出正確輸入是:

            3EFoAdTxepVcVtGgdVDB6AA=   
            

            好了,我們的2015移動安全挑戰賽全系列回顧就到此為止了!希望大家能和我們多多交流討論,歡迎大家關注我們的微博GoSSIP_SJTU,基本上每天都會有精彩的內容發布哦。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线