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