VMProtect1.09分析

freakish發表於2017-09-25

前言

  • 有段時間沒有寫文章了,感覺快要斷了線了,最近略有時間,閒暇又手癢抄起OllyDBG擼幾發。這次把玩的物件是VMProtect的一個低階版本VMProtect1.09,只所以選擇這個版本,還是因為想再回顧一遍VMProtect的核心脈絡,減少高版本里夾帶的各種垃圾指令干擾,也正好做一個記錄,以前的分析都是隨手分析完就算了,以後爭取看過的東西都有個記錄,方便自己和別人參考。

環境準備

  • VMProtect1.09
    • 之前從看雪下載的一個軟體包里拉取的版本
  • 測試Demo
    • Visual Studio2013環境下,寫下如下的測試程式碼:
    • 測試程式碼
    • 這裡我沒有SDK,於是編譯完成直接拖入IDA,找到testVMP函式,記下地址,我這裡的地址是 0x00432C40,開啟VMProtect1.09,新建流程,切到保護選項這一欄,我們主要的目的是分析虛擬機器,這裡就不勾選其他選項,我設定的結果如下:
    • OK,直接點選綠色的編譯按鈕即可,至此我們的試驗環境準備完畢了!

開始分析(主體流程)

  • VMProtect系列保護的分析,還是需要動靜結合,就這個低版本來說,我們測試被保護的程式碼很少,基本可以單獨跟蹤一遍,那就廢話不說,我們把test.vmp.exe載入OllyDBG,然後找到testVMP的位置(可以根據前面記錄下來的地址,也可以根據VC程式的特徵來尋找testVMP函式),於是進入了VMProtect的核心流程,我貼出虛擬機器的核心程式碼塊:

  • 這裡是這個版本的虛擬機器本身的程式碼了,總共30行左右的程式碼,VMProtect也是費心了,為了那一句 mov eax,1生生的加了這麼多的程式碼,這段程式碼本身比較簡單, 可以在OD裡面單步跟蹤下來,總體上就是:

    1. 壓入兩個常量(具體程式碼就是1、6兩行), 使用這兩個值計算出被虛擬的偽碼的起始位置(esi)
    2. 從某一個記憶體地址開始(第8行)作為虛擬機器執行過程中的私有堆疊和臨時變數存放區域
    3. 引擎進入一個迴圈,這個迴圈從上面計算出來的esi的位置開始,不斷的從esi中取出資料,執行虛擬邏輯
    4. 執行完所有的esi指向的偽碼,退出虛擬機器(退出到虛擬機器之外,具體來說就是第一張圖的第18行程式碼,return 0;)
  • 上面提到的結果,只要是有些彙編功底的人,都可以比較容易的跟蹤出上面的結果,那麼為什麼這樣的一段30行的虛擬機器程式碼就可以達到虛擬的目的了呢,要徹底明白這樣的問題,還需要看下被VMProtect加殼虛擬之後,程式變成了什麼樣了。於是我們繼續前面載入OllyDBG之後的流程,Alt+M 開啟程式的記憶體檢視,我們發現程式在原本的基礎上增加了2個段:

    1. 地址=011EF000 大小=00005000 (20480.) 屬主=test_vmp 01140000 區段=.vmp0 初始訪問=RWE
    2. 地址=011F4000 大小=00005000 (20480.) 屬主=test_vmp 01140000 區段=.vmp1 初始訪問=RWE

      • 先來看第2個段,0x11F4000 正是引擎程式碼的第8行,也就是說這個段的記憶體主要是用來給VMProtect執行時記憶體使用,如VM_Context
      • 第一個段,0x11EF000的地址,根據前面的程式碼跟蹤,我們發現,28行程式碼 lea edx,dword ptr ds:[eax*4+11EF10F]中,這個0x11EF10F就是VMProtect Handler陣列啊, 那我們直接Ctrl+G跳轉到0x11EF000這個段的最開頭,檢視彙編程式碼,看彙編形式,很容易認出來這些就是各個Handler的實現,這些一個個的小函式的程式碼一直持續到 0x11EF10F的位置未知,接下來的記憶體,就是一個函式指標陣列,也稱為跳轉表,總共256項,為什麼是256項呢,從26 27 28行可以知道,28行中的函式索引eax是 al 這個單位元組擴充套件來的,最大是FF=255,加上0這一項,那總共最大就是256了,再接著往下,就到了我們看到的虛擬機器引擎程式碼的位置了,接著的記憶體就是大量的偽碼存放的位置了,為了直觀,還是上一張圖:
  • 搞清楚了記憶體結構,再來理解一下虛擬機器程式碼就簡單一些了,通過跟蹤流程,我們可以簡單的歸納成一個上圖色塊部分之間的互動如下(大家俗稱這個過程為dispatcher):

  • 這就是最著名的VMProtect解碼迴圈了

分析小結

  • 所以,經過上面的一系列分析,我們可以做這樣的總結,經過VMProtect的加殼,我們的程式變化了:
    1. 被保護部分的程式碼原來的位置變成了虛擬機器引擎程式碼
    2. 加殼程式按照殼的設計,把被保護程式的程式結構進行了重新排布,這樣載入進記憶體之後,按照虛擬機器引擎的程式碼執行下來,就可以達到用虛擬碼模擬被保護程式碼功能的目的(所以,這樣看來,你的程式不是被隱藏了,而是被刪掉了,然後加殼程式又按照你的邏輯給你用另一套複雜的指令模擬相同的功能來執行)

需要下回再續

  • 基本搞清楚了VMProtect的流程,那整個跟蹤下來,有沒有發現一頭霧水,感覺自己完全不懂彙編了(這就是虛擬化的目的),其實這裡才是VMProtect真正難搞的部分,那就是怎麼理解虛擬碼的內容,怎麼爆破VMProtect保護的關鍵邏輯,給你一段被保護的程式碼,你能知道它原來的邏輯嗎,有空再繼續吧,畢竟夜深了…

二次分析(暫存器+堆疊使用)

  • VMProtect是基於堆疊的虛擬機器,這是眾所周知的了,那我們來詳細的跟蹤一遍,虛擬機器是怎麼樣利用所謂的堆疊來完成虛擬指令模擬被保護邏輯的。
  • 在進入虛擬機器之前,我們先記下當前的記憶體狀態和暫存器狀態(vESP = EBP, VM_Context=EDI),我以實際跟蹤的結果為準,記錄如下:
  • 虛擬機器準備完畢,開始執行第一個Handler的時候(第一次執行到虛擬機器的28行),我們再來觀察記憶體:
  • 從這裡觀察可以知道,在VMProtect開始執行任何程式碼之前,先把進入VMProtect虛擬機器之前的暫存器環境先儲存起來(後面知道,在退出虛擬機器之後,要恢復這些暫存器,以保證退出虛擬機器之後的程式碼正常執行),然後我們繼續開始單步,這個時候發現虛擬機器開始進入ESI的解碼迴圈了,一直跟蹤,發現真實堆疊的內容在逐漸的移位,直到到達這個狀態:
  • 這個狀態,觀察下來,我們把VM_Context這個記憶體看做VMProtect的堆疊,那麼我們觀察現在這個記憶體狀態,可以發現,現在這個狀態是經過虛擬機器的操作之後,把真實的暫存器的值儲存到了VM_Context中去,以備虛擬機器退出的時候恢復現場使用
  • 接著我們F8經過2個Handler, 再來觀察記憶體狀態:
  • 現在觀察,發現經過2個Handler,VM_Context+3*4的位置變成了1, 這個1有點熟悉,難道是我們mov eax, 1的這個常量嗎??帶著這個疑問,我們繼續往下F8單步,直到真實暫存器到達這個狀態,而虛擬機器也執行到這個位置:
  • 這裡就是退出虛擬機器了,那我們再來觀察各個位置的記憶體狀態:
  • 程式碼停留在 __asm popad __asm popfd
  • 再觀察真實堆疊中的值的分佈,我們發現EAX的值已經是1了,這樣,在退出虛擬機器之前
    1. EAX= 1
    2. 除了EAX的值之外,其他的值都保持不變

二次分析小結

  • 進入虛擬機器之前,虛擬機器引擎會先保護現場,一邊退出虛擬機器之後恢復執行現場
  • 虛擬機器引擎使用 VM_Context結構作為自己儲存中間變數的地方或者其他資訊的地方
  • 除了這個進入前現場保護的幾個Handler, 退出前恢復現場的幾個Handler, 中間的2個Handler就是模擬__asm mov eax, 1 這句話的具體實現了

三次分析(虛擬碼加密)

  • 前面的分析中,VMProtect的加密選項只開啟了指令虛擬化, 這次我們為了分析虛擬碼加密之後的流程, 再次修改VMProtect的配置,開啟虛擬碼加密選項,配置如下:
  • 虛擬化保護的程式碼還是那塊程式碼,於是拖入OllyDBG中,快速定位到虛擬機器的主流程,通過對比我們發現,虛擬機器主流程發生了一些變化,多了一些操作,請看圖中標註的部分:
  • 通過和之前主流程的對比很容易發現, 每次從ESI指向的記憶體取出的結果,要經過中間紅框部分的一系列的操作變換之後才能直接使用。其中一個值得注意的細節是,整個解密的過程中,引入了一個暫存器 bl, 每次解密的過程中都使用了這個暫存器中的值,並且一次解密之後還會更新這個暫存器的值,這就可以理解為 虛擬碼的解密祕鑰是一個動態的變化過程
  • 上面的過程是主流程解密Handler索引的時候的過程,那我們跟蹤進去一個Handler中,會發現,Handler中使用到ESI中指向記憶體的資料的時候也是需要解密的過程的,類似的解密演算法,動態變化的祕鑰,順便給出一個圖做參考:

三次分析小結

  • 增加虛擬碼加密之後:
    1. 靜態虛擬碼被加密
    2. 每次從ESI指向的虛擬碼記憶體中獲取一個位元組之後,都要進行解密之後才使用
    3. 解密過程中使用 BL 暫存器中動態祕鑰參與解密,並在解密之後更新祕鑰

四次分析(API呼叫)

  • 這次我們來看下VMProtect 1.09這個版本,對於Windows API呼叫是如何處理的, 為了研究方便,我們需要修改我們的測試程式碼了,稍作改動,結果如下:
  • 接著我們對 testVMP 這個函式進行虛擬化(保持第一次分析的時候那個VMProtect加密選項配置),明確一下我們的目的是為了分析API呼叫的處理方式,所以testVMP函式中已經有6條x86指令了,為了快速定位關鍵點,所以我選擇在VMProtect的主流程中跳轉到Handler的時候,使用條件斷點記錄下Handler的值,然後等到程式彈出MessageBox的時候, 我通過檢視OllyDBG的 log 可以知道經過了多少個Handler呼叫,就呼叫到了MessageBox函式,繼續展示下我的操作:
  • 設定好了,直接F9, MessageBox彈出來了, 不要點確定,開啟OllyDBG 的log視窗,看到了很多記錄下來的Handler地址,複製到一個帶行號的文字編輯器中,我得到的結果是:
  • 也就是經過了33個Handler的時候,程式跑到了MessageBox的呼叫,所以,如果我們再重新跑一次程式,然後繼續使用條件斷點,讓程式在第30或者20次執行Handler的時候斷下來,然後手動除錯接下來的幾個Handler就可以搞清楚API呼叫是怎麼實現的了。
  • 接下來就是一些體力活了,一路F8跟著VMProtect的大迴圈轉幾圈就看到了如下結果:
    1. 在執行你的API呼叫之前,跟普通PE一樣,輸入表是Ready的,也就是MessageBox地址是準備好的
    2. 從位元組碼中拿出MessageBox在輸入表中位置,然後經過一次重定位(VMProtect自己的重定位方式),獲取到MessageBox的真實地址
    3. 真實堆疊壓入一個進入VMP的函式地址
    4. 真實堆疊壓入MessageBox地址
    5. popad popfd還原前面進入虛擬機器的現場
    6. 退出虛擬機器,ret方式 跳入MessageBox
    7. MessageBox執行完畢之後,MessageBox內部的Ret 直接就返回到了之前準備好的進入虛擬機器函式的入口地址
    8. 進入虛擬機器,繼續執行虛擬程式碼

四次分析小結

  • 完成API呼叫,需要退出虛擬機器程式碼,然後跳入計算出來的API地址, API執行完畢之後,ret方式重新進入虛擬機器,繼續虛擬程式碼執行

問題還在

  • 怎麼進行邏輯還原呢? 還需要繼續研究….(後續更新)

相關文章