Android安全交流群:478084054
0x00序
最近花了一些時間學(xué)習(xí)逆向脫殼,這方面一直投入的時間比較少。樣本經(jīng)過某加固寶進行加固,這里簡單記錄一下脫殼過程和思路,感謝某數(shù)字公司對安全加固的無私貢獻,讓我有機會小小的提高一下這方面的技能。
0x01 DUMP classes.dex
打開APK包中的classes.dex看一下:
已經(jīng)變成了殼代理,沒有一點原APK的代碼。
在assets中,有兩個殼相關(guān)的SO:
嘗試從內(nèi)存中DUMP原classes.dex。
考慮到在Dalvik下,360可能會自己實現(xiàn)從內(nèi)存中加載classes.dex的代碼,不容易找到DUMP的點。而ART下,可操作空間就小多了,所以我是在ART下操作的。
具體的,我是在ClassLinker::DefineClass函數(shù)處得到dex_file的begin和size,然后DUMP出原來的classes.dex。我看到有人在dex2oat的地方DUMP,但我覺得如果360 HOOK execv,阻止dex2oat對原classes.dex作oat轉(zhuǎn)換的話,會不會就脫不出來了呢?不過我對Android虛擬機不太了解,可能有更好的DUMP點。
成功DUMP出原classes.dex:
但是可以看到,有些方法(圖中onCreate)的指令被抽走了,并且改為了native方法。同時,在static代碼塊中,有一行調(diào)用StubApp.interface11(16)。
可以猜測:當該class被加載時,static代碼塊會首先被執(zhí)行,這樣StubApp.interface11方法就會將onCreate注冊到殼SO的某個native方法上。這樣,當執(zhí)行onCreate時,就會執(zhí)行相應(yīng)的native方法,該native方法會首先找到onCreate對應(yīng)的指令,然后解密,最終解釋執(zhí)行。
interface11以及onCreate對應(yīng)的native方法,以及解釋器并沒有實現(xiàn)在上圖中的libjiagu.so中,而是實現(xiàn)在另一個運行時從內(nèi)存中加載的SO中(暫且稱其為解釋器SO)。
0x02?DUMP解釋器SO
APK運行后,會首先加載libjiagu.so,并執(zhí)行其JNI_Onload方法。該方法最終會調(diào)用到__fun_a_18,這個方法進行了控制流混淆,流程對于我來說是非常復(fù)雜的。
剛開始,我以為它用了o-llvm進行混淆編譯。但仔細看一下匯編代碼,應(yīng)該不是。應(yīng)該就是自己在源碼中利用while-switch實現(xiàn)了“控制流平坦化”的混淆算法。
怎么破?我沒有什么好辦法,只有硬看,不斷的調(diào)試,參考大神們的帖子。
由于我手機是自己編譯的系統(tǒng),對于某些反調(diào)試天然免疫,所以遇到的反調(diào)并不多。下面簡述我是怎么過反調(diào)并DUMP出解釋器SO的,因為這個混淆算法應(yīng)該每個版本都有所變化,所以這個流程并不一定適用別的樣本。
第一處反調(diào),來自case 37:
繼續(xù)執(zhí)行:
來到這個位置,看到R3保存的是rtld_db_dlactivity符號的地址,我擔(dān)心它是要作SIGTRAP信號反調(diào),所以手動將R3的值修改為0。
繼續(xù)執(zhí)行。當?shù)?次進入case 32時:
繼續(xù)執(zhí)行,來到下面的位置:
注意此時R1的值,要將600B0010修改為200B0010,否則會執(zhí)行R4地址處的代碼。
R4地址處的代碼是什么?看一下:
就是終止進程的代碼。
過了此處反調(diào)之后,繼續(xù)執(zhí)行,來到case 31:
繼續(xù),來到如下位置,這里就是加載解釋器SO的函數(shù)了。
注意,這里不是通過調(diào)用dlopen函數(shù)來加載解釋器SO的,而是自己實現(xiàn)的類似于linker的加載代碼。
其實linker的工作原理并不復(fù)雜,簡單來說就是將目標SO文件的LOAD段映射內(nèi)存,解析文件格式,做好符號重定位,再調(diào)用init/init_array方法等等。
繼續(xù)執(zhí)行,來到解密ELF Header的地方:
解密完畢:
根據(jù)R3的值,將ELF Header先DUMP出來。
繼續(xù)執(zhí)行,來到解密Program Header的地方:
解密完畢:
將Program Header也DUMP出來:
繼續(xù)執(zhí)行,來到如下位置:
這個方法是要將解釋器SO的LOAD段映射到內(nèi)存,然后完成整個加載過程。
根據(jù)so_addr和so_size將整個SO DUMP出來,但這里DUMP出來的SO是沒有ELF Header和Program Header的,但這兩個頭前面已經(jīng)DUMP出來了。最后三者拼在一起,就是完整的解釋器SO了。
0x03還原onCreate
有了解釋器SO,就可以繼續(xù)分析了,核心的內(nèi)容都在這個SO里面。
由于我編譯的系統(tǒng),加了很多日志,所以在執(zhí)行到前面的onCreate方法時,看到如下日志:
就像前面猜測的那樣,當執(zhí)行該onCreate方法時,先執(zhí)行class的static代碼塊,調(diào)用interface11方法,該方法將onCreate注冊到了一個地址為0x75c74e2d的native方法上。該native方法就實現(xiàn)在解釋器SO中。用0x75c74e2d減去load_base,可知是解釋器SO中的sub_10E2C方法。
跟蹤并分析該方法。
繼續(xù)動態(tài)調(diào)試,在解釋器SO中偏移0xFAAE處下斷點:
觀察此時R1寄存器的值為0xBE027450,跳到該處內(nèi)存,并得到0xBE027458處的值為0x75EA5418。
跳到0x75EA5418內(nèi)存處。
觀察0x75EA5418內(nèi)存處的值,得到本次解釋執(zhí)行的方法是:com.xxx.xxxActivity->onCreate。
繼續(xù),在解釋器SO中偏移0x35C80處下斷點:
觀察此時R7寄存器的值為0x75E96A10,在Hex View中,跳到0x75E96A10內(nèi)存處:
觀察此時0x75E96A18地址處存儲的值為0x7699C61C,在Hex View中跳到0x7699C61C內(nèi)存處:
在0x7699C61C內(nèi)存處存儲的就是DexCode結(jié)構(gòu),DexCode的結(jié)構(gòu)定義如下:
由此可知“com.xxx.xxxActivity->onCreate”方法的insns指令數(shù)組大小是0x93*2=294個字節(jié),指令數(shù)據(jù)流是:
這個指令數(shù)據(jù)流是經(jīng)過加密的,解密key存儲在0x75E96A2C地址處,值為0x3B。
繼續(xù)調(diào)試的話,就是執(zhí)行switch型的解釋器了。這個解釋器和Dalvik解釋器類似。在android 2.x版本中曾有2種形式的C實現(xiàn)的解釋器,一種goto的,一種switch的。后來谷歌把switch型解釋器去掉了,因為執(zhí)行效率沒有g(shù)oto的好。再后來就有了ART,貌似把C語言實現(xiàn)的解釋器都去掉了。再再后來到了7.0,goto和switch型的C解釋器又都回來了。
簡單來說,就是解釋執(zhí)行整個onCreate方法的指令數(shù)據(jù)流,每條指令在執(zhí)行前會解密。那怎么將這些指令還原回原本的dalvik字節(jié)碼指令,達到脫殼目的呢?可以根據(jù)每個case的實現(xiàn),來得到當前執(zhí)行的opcode對應(yīng)dalvik字節(jié)碼指令中的哪一條,然后對應(yīng)還原。
比如,這里的0xAE,在解釋執(zhí)行時,跳轉(zhuǎn)到case 174處執(zhí)行。假設(shè)拿case 174處的代碼對比分析Dalvik解釋器,發(fā)現(xiàn)case 174執(zhí)行的是invoke-static指令,那么這條指令就還原出來了。
這樣有點麻煩。
有一個好點的辦法就是:自己在onCreate方法中將所有的dalvik指令,一共200多條全部寫出來。然后用360加固,動態(tài)調(diào)試,總結(jié)出每條dalvik指令對應(yīng)的360解釋器的case處理指令的偏移,最后得到一張指令映射表。這樣,后續(xù)在脫殼的時候,就可以根據(jù)解釋執(zhí)行代碼的偏移,還原出原來的指令。當然,360解釋器也是在不斷變化的,所以,這個表也是要跟著變化的。
那能寫自動脫殼機嗎?只要這張指令映射表是有效的,脫殼就可以自動化完成。
那如果指令映射表失效了,能通過代碼自動生成新的指令映射表嗎?仔細分析解釋器,每個指令的處理邏輯沒大變化的,也許可以。
扯遠了,下圖是某個onCreate方法還原前后:
0x04后序
由于時間和水平都有限,很多地方還沒仔細分析,本篇筆記只是簡單記錄一下本次操作的大致過程和一些思路。
最后,感謝論壇中各大神的分享,讓我學(xué)習(xí)到了很多東西。
End