某DEX_VMP安全分析與還原
一思路整理
還原VMP需要哪些鋪墊?
(1)定位VMP字節碼
(2)分割VMP字節碼
(3)還原成SMALI
(1)為什么要找VMP字節碼的位置?
因為如果目標方法的字節碼地址,都找不到,還原也就沒法展開了。

(2)為什么要分割VMP字節碼?
如果要反匯編成smali,起碼要知道這條smali對應的字節碼一共幾個字節。在確定一條指令占幾個字節后,還要知道這幾個字節中,誰是操作碼,誰是操作數。

(3)還原為SMALI
有了前兩步鋪墊,最終我們可以解讀一條完整的smali的含義。

二某安卓VMP入口特征(2021.8月樣本)
跳板方法

進入native后的參數處理邏輯。

為了處理不同類型的返回值, 定義了多個jni方法。

對應jni函數入口指令情況。

三定位VMP字節碼
邏輯

根據上述邏輯,則一定存在函數F,向F輸入index可得到對應codeitem_addr
F(index) == codeitem_addr。
我們看一下這個函數,從index到codeitem_addr的過程
(0x2dce->0xcac85880)。


如何在十幾萬數量級的匯編中定位到這段代碼的?
通過Trace記錄REG信息,用到了兩個關鍵數值,0x2dce(index)與0xcac85880(codeitems),標記兩個數值出現的中間區間即可。


展開上面的定位方式的兩個前提條件:
我們已經有了關鍵數據0x2dce,但還需要知道另一個提前條件,
即codeitem是0xcac85880,所以這個信息是從哪得知的?
這里是本章的關鍵。
如何分析出codeitem的地址是0xcac85880?
(1) 已知明文
(2) 沙箱日志獲取切入點
(3) JNI參數回溯
(4) 內存訪問統計
(1)已知明文
目標APP內很多的onCreate()方法,其內部普遍調用了,
NBSTraceEngine.startTracing();以及super.onCreate()

我們選一個被vmp保護了的onCreate()作為分析目標, ZxWebViewActivity.onCreate()
(2) 沙箱日志獲取切入點
① ZxWebViewActivity.onCreate內必定存在NBSTraceEngine.startTracing();以及super.onCreate()
② startTracing為靜態方法,會被編譯器編譯為invoke-static
③ super.onCreate()為超類調用,會被編譯器編譯為invoke-super
④我們猜測vmp對invoke-static模擬實現借助了JNI函數,
所以我們觸發ZxWebViewActivity.onCreate()執行,截取其調用序列,效果如下:

大致邏輯為:


(3) JNI參數startTracing來源回溯
我們在trace中找到這條GetStaticMethodID()的出現位置,
然后作為起點向上展開回溯,希望找到其參數”startTracing”的最早出處,
如果有自動化的腳本和條件可進行污點分析,由于邏輯不是很復雜,這里人工回溯完成。

具體過程省略……
在trace中對參數”startTracing”來源進行一番回溯,最終發現了一個起到決定性作用的偏移值0x000081de。可以簡單理解成,它以base+0x000081de的形式確立的參數”startTracing”。
結論:
如果0x000081de是那個起到決定性意義的數值,那么毫無疑問0x000081de來自codeitem。
在trace中找到0x81de的出現位置,
發現它來自于內存位置0xcac858a8。

(4) 內存訪問統計
0x81de來自0xcac858a8,由于這個地址可能是codeitem,
因此我們檢索一下,trace中對這片內存區域的訪問情況。
0xcac858a8取前5個高位,忽略后3個地位,即檢索對0xcac85???的訪問。

找到19條指令,而對0xcac85???的訪問,最早的第一條指令,出現在編號5691的位置,對應的內存地址為0xcac85890,說明這里是ZxWebViewActivity.onCreate()第一條字節碼。
由于codeitem第一條字節碼之前0x10個字節還存在一些固定內容,
所以0xcac85890-0x10取得codeitem地址0xcac85880,
即codeitem的地址是0xcac85880。


四分割VMP字節碼

現在已經有了某廠vmp codeitems全部內容,但是還沒法反匯編成smali,
因為還不知道,第一條指令一共占幾個字節,第二條指令一共占幾個字節,
依次......
dalvik指令是不等長,反匯編成smali的話,起碼要知道這條smali對應的字節碼一共幾個字節。在知道了每條指令占幾個字節后,還要知道這幾個字節中,誰是操作碼,誰是操作數。
通過觀察codeitem的內存段的讀取情況,可以達到這個目的。


如何快速區分出操作碼和操作數?
一般opcode后面會有一個EOR解密指令,以及一串類似定位handle的CMP指令操作,而operand沒有,這就為區分opcode和operand提供了特征依據。


opcode解密邏輯?
由eor指令向上回key出現的位置,即可確定key的來源,以及解密邏輯.大致邏輯:off1 = sub( codeitem當前指令地址, codeitem基址 )off2 = lsl( off1, 1)key = load( base + off2 )de_opcode = xor(en_opcode, key)
五VMP字節碼還原為SMALI
1、標準dalvik指令反匯編過程


2、VMP指令反匯編過程
由于使用了已知明文條件作為切入點,已知分析目標ZxWebViewActivity.onCreate()中,必定會調用startTracing()方法,
即必定存在invoke-static {v0}, method@00da6f // ...startTracing
又通過上面的分析得知關鍵值81de出現在這條invoke-static中,且充當操作數的角色,那么按照我們按照標準invoke-static反匯編規則進行解析,就可以得到結論。



VMP指令由標準指令基礎上修改而來,有哪些異同?

3、還原VMP所有指令需要什么?

4、沒有opcode對照表時如何展開還原?
(1)接口猜測法
method相關的invoke系列指令,可以通過JNI執行情況猜測。
Field相關的get set系列指令,也可以通過JNI執行情況猜測。

(2)參數推導法
方法調用前,會先準備參數,通常是聲明類型的指令,可以很大程度縮小猜測的候選指令范圍。

(3)標準dalvik指令格式的信息利用
由于vmp指令是由dalvik標準指令略微修改/變異而來,只做了較小的改動,仍然保留了BIT位分布特征這樣信息。在做還原時,可以利用這些信息,一定程度縮小候選范圍。
https://source.android.com/devices/tech/dalvik/instruction-formats
https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions
六攻擊面總結
1、分析路徑

2、攻擊面總結 && 啟示
(1) 被VMP的方法內部存在已知明文指令。

(2) VMP的實現高度依賴JNI函數,通過HOOK拿到其調用信息,是非常有效的切入點與突破口。

(3) codeitems的連續性,集中存儲的特性,通過內存訪問統計最終被發現。

(4)某vmp指令由標準dalvik指令基礎上略改而來,整體仍然保留了很多可用信息。

七深入VMP還原的一些問題
略。
八調試與工具總結
核心問題:
獲取程序完整的執行&&數據信息 (trace)。
目前公開的主流的獲取trace的方案:
① GDB調試
② FridaStalker編譯執行
③ 脫機unicorn模擬執行
主流的獲取trace的方案的弊端和缺陷:
① IDA / GDB
速度極慢,且會遭遇反調試。
② FridaStalker
不支持arm指令的thumb模式,且BUG多,遭遇vmp.so中的花指令時,基本無法正常使用。
③ PC上脫機unicorn模擬執行
vmp.so中存在大量jni call和system call,需要手動實現它們,unicorn才能完成運行。
基于以上問題的嘗試:
實現原始APP進程環境 && 原始context中,通過unicorn構造虛擬化CPU,執行目標function,獲得trace,無已知檢測和對抗手段,簡單過anti。
基于trace進行離線分析:
① trace形態可視化
文本 / json / 數據庫 / EXCEL可視化表格 / 動態CFG圖
② 基本的分析
地址含義解析 調用符號識別
③ 程序分析
污點分析 相似性分析等






