看了PEDIY天易LOVE的《深入淺出VMP爆破》,https://bbs.pediy.com/thread-127020.htm,受益匪淺,之前一直搞不懂萬用閘的概念。不過天易LOVE的演示程序是VMP1.2,我用VMP2.08對文章中的示例加殼以進行一次實戰。如果你對VMP沒有概念,建議先看看我在看雪的一篇文章,https://bbs.pediy.com/thread-224204.htm。
我簡單觀察了一下VMP2.X版本虛擬流程(無變異)后的代碼,目測代碼混淆只使用了數據流的插入死碼,以及控制流的間接轉移,算是吃了一顆定心丸。
原版未加密的程序很簡單。
00453F18 |. E8 87FFFFFF call VMP原版.00453EA4 00453F1D |. 84C0 test al,al 00453F1F |. 74 0C je short VMP原版.00453F2D 00453F21 |. B8 743F4500 mov eax,VMP原版.00453F74 ; Good job! 00453F26 |. E8 458AFDFF call VMP原版.0042C970 00453F2B |. EB 0A jmp short VMP原版.00453F37 00453F2D |> B8 883F4500 mov eax,VMP原版.00453F88 ; Try again! 00453F32 |. E8 398AFDFF call VMP原版.0042C970 00453F37 |> 33C0 xor eax,eax
00453EA4 /$ 83F8 7B cmp eax,0x7B 00453EA7 |. 74 06 je short VMP原版.00453EAF 00453EA9 |. B8 00000000 mov eax,0x0 00453EAE |. C3 retn 00453EAF |> B8 01000000 mov eax,0x1 00453EB4 \. C3 retn
VMP是如何實現減法指令的?
可以看看PEDIY海風月影前輩的帖子https://bbs.pediy.com/thread-82618.htm,我簡單說下我的理解。
水平有限,如有錯誤,還望指正。
VMP的邏輯運算指令只有一條,記為VM_NOR。
VM_NOR的一般結構如下:
POP EDX NOT EDX //NOTa NOT DWORD PTR SS:[ESP] //NOTa AND DWORD PTR SS:[ESP],EDX // NOT(a) and NOT(a)= not(a) PUSHFW
NOT指令不影響標志位,可是AND指令影響標志位,我們一般爆破找JZ/JNZ指令,這些指令測試的是ZF標志位。
怎么測試EFLAGS里的ZF標志位?VM_ NOR (NOT(EFLAGS),NOT(0x40)),其結果就是把EFLAGS里面除ZF位以外的標志位全部清0了。ZF標志位在第6位,然后把結果再右移(shr)6位,就可以把ZF位移動到最低位,結果就變成了0和1,然后,再使用VMP里面的唯一一條跳轉指令,VM_JUMP根據結果計算出轉移地址。ZF為1時,EFLAGS的值是0x40。如果不能理解,找一張EFLAGS的布局圖,把0x40轉換為二進制,低位對低位的填上去看看即可。這里稍微注意一下,天易LOVE前輩說取出的ZF標志位是反的,可是我個人覺得取出的就是一樣的,差別就在於傳VM_NOR參數時EFLAGS是否取反。(如有錯誤,望指正)
所以,JZ跳轉成立的條件可以這樣表達:
VM_NOR(NOT(EFLAGS), NOT(0x40)) == 0x40
SHR(VM_NOR(EFLAGS, NOT(0x40)), 6) == 0x1
至於上文提到的VM_JUMP,一般形式為。
mov e?x,[ebp] add ebp,4 mov esi, e?x
可是EFLAGS是怎么來的?這個就涉及到VMP模擬減法的知識了。我看了海風月影前輩的帖子,可惜百度空間現在已經被百度下架了,找不到關於減法的內容,僥幸在天易LOVE前輩的帖子里面撿到寶了。
NOT(a) = -a - 1
a - b = NOT(NOT(a) + b)
推到過程如下:
a - b = NOT(a - b) + 1 = NOT(a - b - 1) = NOT(NOT(a) + 1 - 1 + b) = NOT(NOT(a) + b)
VM_NOR是怎么模擬NOT的呢?
NOT(a) = VM_NOR(a,a)
那么,剛剛推導出的NOT模擬減法公式可以用VM_NOR表達。
a - b = VM_NOR(VM_NOR(a,a) + b, VM_NOR(a,a) + b)
至於EFLAGS是由EFLAGS1和EFLAGS2運算產生的。
在運算VM_NOR(a,a) + b時,得到EFLAG1。運算完VM_NOR(VM_NOR(a,a) + b, VM_NOR(a,a) + b)的最外層的那條VM_NOR時,得到EFLAG2。
EFLAGS = VM_ NOR(NOT(EFLAG1),NOT(0x815)) + VM_NOR(NOT(EFLAG2),0x815)
0x815,轉換成二進制,對應的是AF、OF、CF、PF標志位為1,也就是說,把SF、ZF標志位清0了。大家考慮一下,運算a - 1 + b時得到的EFLAG1,與最終結果的EFLAGS有哪些差別呢?首先ZF肯定不同,SF因情況而有異數。
接着計算EFLAG2,此時得出了ZF、SF位。最后得出最終結果EFLAGS。
=========================原理分析完,開始實戰搞一波==============================
首先,關鍵點在於我們能不能找到VM_NOR這條Handler,找到了的話基本可以說是萬事OK了。
這里的思路參考了半斤八兩前輩的一篇文章,http://www.cnblogs.com/BjblCracked/p/4152193.html。(.......原來我的傻逼方法是找到VM Dispatcher,記錄下每一個調用的Handler,記錄后有上千個,去重復后有幾十個,一個一個找…………)
首先,搜索字符串引用,在0x00453EA4這個地方下一個斷點(這樣做是為了少記錄一點無用代碼)。
點擊菜單,查看——RUN跟蹤。打開RUN TRACE窗口,右鍵,記錄到文件,選擇你的記錄保存在哪里。然后一直跟蹤到VM Dispatch,也就是,0045E670 8B1485 A3DE4500 mov edx,dword ptr ds:[eax*4+0x45DEA3](強迫症)
然后點擊,調試——跟蹤步入,或者按CTRL+F11。等有提示框彈出的時候,打開你保存的記錄文件,進行搜索工作。(有點卡哦,我測試應該有1s左右的延遲)
打開記錄文件,搜索EAX=00000040(應該也有可能是EDX=00000040)
若干次查找后,來到此處。
這里找到了VM_NOR閘。
接着我們試試看直接查找0045E88D 主 not eax ; EAX=00000040
若干次后,來到此處。
結果是0,EDX是EFLAGS,EAX是0x40。在OllyDbg里面下一個條件斷點試試看。
在0045E8A0 > 9C pushfd
下一個條件斷點,EAX==0。
若干次中斷后,發現有EDX==286的情況。
把EAX修改為0x40。
把斷點刪除,F9運行。
剛剛ESI的值是49375B,可以改進一下這個硬件斷點。
條件改為ESI == 0x49375B
中斷時把EAX修改為0x40即可。
要打補丁很簡單,用VEH機制即可。可能需要注意的地方就是KiUserExceptionDispatcher有沒有被HOOK拿來反調試。
試煉程序下載:
鏈接: https://pan.baidu.com/s/1dG019Bj 密碼: a9my