某數字公司VMP脫殼簡記---native的OnCreate還原

360加固-脫殼修復

最近花了一些時間學習逆向脫殼,這方面一直投入的時間比較少。樣本經過某加固寶進行加固,這裏簡單記錄一下脫殼過程和思路,感謝某數字公司對安全加固的無私貢獻,讓我有機會小小的提高一下這方面的技能。

DUMP classes.dex

打開APK包中的classes.dex看一下:

 

已經變成了殼代{過}{濾}理,沒有一點原APK的代碼。在assets中,有兩個殼相關的SO:

 

嘗試從內存中DUMP原classes.dex。

考慮到在Dalvik下,360可能會自己實現從內存中加載classes.dex的代碼,不容易找到DUMP的點。而ART下,可操作空間就小多了,所以我是在ART下操作的。

具體的,我是在ClassLinker::DefineClass函數處得到dex_file的begin和size,然後DUMP出原來的classes.dex。我看到有人在dex2oat的地方DUMP,但我覺得如果360 HOOK execv,阻止dex2oat對原classes.dex作oat轉換的話,會不會就脫不出來了呢?不過我對Android虛擬機不太瞭解,可能有更好的DUMP點。

成功DUMP出原classes.dex:

  

但是可以看到,有些方法(圖中onCreate)的指令被抽走了,並且改爲了native方法。同時,在static代碼塊中,有一行調用StubApp.interface11(16)。

可以猜測:當該class被加載時,static代碼塊會首先被執行,這樣StubApp.interface11方法就會將onCreate註冊到殼SO的某個native方法上。這樣,當執行onCreate時,就會執行相應的native方法,該native方法會首先找到onCreate對應的指令,然後解密,最終解釋執行。

interface11以及onCreate對應的native方法,以及解釋器並沒有實現在上圖中的libjiagu.so中,而是實現在另一個運行時從內存中加載的SO中(暫且稱其爲解釋器SO)。

DUMP解釋器SO

APK運行後,會首先加載libjiagu.so,並執行其JNI_Onload方法。該方法最終會調用到__fun_a_18,這個方法進行了控制流混淆,流程對於我來說是非常複雜的。

 

剛開始,我以爲它用了o-llvm進行混淆編譯。但仔細看一下彙編代碼,應該不是。應該就是自己在源碼中利用while-switch實現了“控制流平坦化”的混淆算法。

怎麼破?我沒有什麼好辦法,只有硬看,不斷的調試,參考大神們的帖子。

由於我手機是自己編譯的系統,對於某些反調試天然免疫,所以遇到的反調並不多。下面簡述我是怎麼過反調並DUMP出解釋器SO的,因爲這個混淆算法應該每個版本都有所變化,所以這個流程並不一定適用別的樣本。

第一處反調,來自case 37:

 

繼續執行:

 

來到這個位置,看到R3保存的是rtld_db_dlactivity符號的地址,我擔心它是要作SIGTRAP信號反調,所以手動將R3的值修改爲0。

繼續執行。當第4次進入case 32時:

 

繼續執行,來到下面的位置:

 

注意此時R1的值,要將600B0010修改爲200B0010,否則會執行R4地址處的代碼。

R4地址處的代碼是什麼?看一下:

就是終止進程的代碼。

過了此處反調之後,繼續執行,來到case 31:

 繼續,來到如下位置,這裏就是加載解釋器SO的函數了。

 

注意,這裏不是通過調用dlopen函數來加載解釋器SO的,而是自己實現的類似於linker的加載代碼。

其實linker的工作原理並不複雜,簡單來說就是將目標SO文件的LOAD段映射內存,解析文件格式,做好符號重定位,再調用init/init_array方法等等。

繼續執行,來到解密ELF Header的地方:

 

解密完畢:

 

根據R3的值,將ELF Header先DUMP出來。

 

繼續執行,來到解密Program Header的地方:

解密完畢:

 

將Program Header也DUMP出來:

 繼續執行,來到如下位置:

 

這個方法是要將解釋器SO的LOAD段映射到內存,然後完成整個加載過程。

根據so_addr和so_size將整個SO DUMP出來,但這裏DUMP出來的SO是沒有ELF Header和ProgramHeader的,但這兩個頭前面已經DUMP出來了。最後三者拼在一起,就是完整的解釋器SO了。

還原onCreate

有了解釋器SO,就可以繼續分析了,核心的內容都在這個SO裏面。

由於我編譯的系統,加了很多日誌,所以在執行到前面的onCreate方法時,看到如下日誌:

 

就像前面猜測的那樣,當執行該onCreate方法時,先執行class的static代碼塊,調用interface11方法,該方法將onCreate註冊到了一個地址爲0x75c74e2d的native方法上。該native方法就實現在解釋器SO中。用0x75c74e2d減去load_base,可知是解釋器SO中的sub_10E2C方法。

 

跟蹤並分析該方法。

繼續動態調試,在解釋器SO中偏移0xFAAE處下斷點:

觀察此時R1寄存器的值爲0xBE027450,跳到該處內存,並得到0xBE027458處的值爲0x75EA5418。

跳到0x75EA5418內存處。

觀察0x75EA5418內存處的值,得到本次解釋執行的方法是:com.xxx.xxxActivity->onCreate。

繼續,在解釋器SO中偏移0x35C80處下斷點:

觀察此時R7寄存器的值爲0x75E96A10,在Hex View中,跳到0x75E96A10內存處:

觀察此時0x75E96A18地址處存儲的值爲0x7699C61C,在Hex View中跳到0x7699C61C內存處:

在0x7699C61C內存處存儲的就是DexCode結構,DexCode的結構定義如下:

由此可知“com.xxx.xxxActivity->onCreate”方法的insns指令數組大小是0x93*2=294個字節,指令數據流是:

這個指令數據流是經過加密的,解密key存儲在0x75E96A2C地址處,值爲0x3B。

繼續調試的話,就是執行switch型的解釋器了。這個解釋器和Dalvik解釋器類似。在android2.x版本中曾有2種形式的C實現的解釋器,一種goto的,一種switch的。後來谷歌把switch型解釋器去掉了,因爲執行效率沒有goto的好。再後來就有了ART,貌似把C語言實現的解釋器都去掉了。再再後來到了7.0,goto和switch型的C解釋器又都回來了。

簡單來說,就是解釋執行整個onCreate方法的指令數據流,每條指令在執行前會解密。那怎麼將這些指令還原回原本的dalvik字節碼指令,達到脫殼目的呢?可以根據每個case的實現,來得到當前執行的opcode對應dalvik字節碼指令中的哪一條,然後對應還原。

 

比如,這裏的0xAE,在解釋執行時,跳轉到case 174處執行。假設拿case 174處的代碼對比分析Dalvik解釋器,發現case 174執行的是invoke-static指令,那麼這條指令就還原出來了。

這樣有點麻煩。

有一個好點的辦法就是:自己在onCreate方法中將所有的dalvik指令,一共200多條全部寫出來。然後用360加固,動態調試,總結出每條dalvik指令對應的360解釋器的case處理指令的偏移,最後得到一張指令映射表。這樣,後續在脫殼的時候,就可以根據解釋執行代碼的偏移,還原出原來的指令。當然,360解釋器也是在不斷變化的,所以,這個表也是要跟着變化的。

那能寫自動脫殼機嗎?只要這張指令映射表是有效的,脫殼就可以自動化完成。

那如果指令映射表失效了,能通過代碼自動生成新的指令映射表嗎?仔細分析解釋器,每個指令的處理邏輯沒大變化的,也許可以。

扯遠了,下圖是某個onCreate方法還原前後:

由於時間和水平都有限,很多地方還沒仔細分析,本篇筆記只是簡單記錄一下本次操作的大致過程和一些思路。最後,感謝傾聽。

發佈了265 篇原創文章 · 獲贊 164 · 訪問量 218萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章