1.基礎(chǔ)知識
1.1.VMProtect虛擬機(jī)簡介
虛擬機(jī)加密,是指像VMP這樣的保護(hù)程序,它會把源程序的X86指令變成自定義的偽指令,等到執(zhí)行的時候,VMP內(nèi)置在保護(hù)程序中的VM就會啟動,讀取偽指令,然后解析執(zhí)行。
VMP是一個堆棧虛擬機(jī),它的一切操作都是基于堆棧傳遞的。在VMP中,每一個偽指令就是一個handler,VM中有一個核心的Dispatch部分,它通過讀取程序的bytecode,然后在DispatchiTable里面定位到不同的handler中執(zhí)行。絕大多數(shù)情況下,在一個handler中執(zhí)行完成后,程序?qū)⒒氐紻ispatch部分,然后到next handler中執(zhí)行。
http_imgload.jpg下載此附件需要消耗2Kx,下載中會自動扣除。
在上面的框架中,核心的部件就是Dispatch部分,下面并列的部件就是handlers。
經(jīng)過VMP加密的X86指令,一條簡單的指令被分解成數(shù)條VMP的偽指令,它按照自己的偽指令排列去實現(xiàn)原指令的功能,在加上其他的花指令混亂等等,你將完全看不到源程序指令。VMP自帶的各種機(jī)制都不再是以X86指令的形式去實現(xiàn),而是用自己的偽指令去測試。
在VMP的VM運行過程中,各個寄存器的基本用途是:EBP和EDI是VM堆棧指針(不是常規(guī)的堆棧);ESI是偽指令指針(相當(dāng)于常規(guī)的EIP);EAX是VM解密數(shù)據(jù)的主運算寄存器;EBX是VM解密數(shù)據(jù)的輔運算寄存器;ECX是常規(guī)的循環(huán)計數(shù)器;ESP是常規(guī)的堆棧棧頂指針。EDX是讀取偽指令表數(shù)據(jù);
EDI、EBP分別指向VM堆棧的上下限位置,EBP指向堆棧的下限并向上發(fā)展,EDI指向堆棧的上限并使用[EDI+EAX]的方式向下發(fā)展;ESI指向的內(nèi)存塊里包括要執(zhí)行的偽指令序列,而不同的是,當(dāng)VM要是使用到立即數(shù)時,也是從ESI讀取?梢奅SI內(nèi)存塊里面是精心構(gòu)建的數(shù)據(jù)塊,只有VM自身執(zhí)行過程中,才能知道下一個數(shù)據(jù)是代表偽指令還是立即數(shù);在VM運算中EAX寄存器很多時候通常只有AL參與運算然后在存取時再以AX或EAX得方式存取;EBX在很多加密數(shù)據(jù)運算中,都會參與到EAX值的計算中,協(xié)助運算中正確的值。而每次EAX的值運算結(jié)束后,反過來會計算好下一次運算中EBX的值。所以EBX的數(shù)據(jù)一旦出錯,下一個數(shù)據(jù)解密必然錯誤;在VM運行中,通常一切操作都是在VM堆棧內(nèi)完成的,所以絕大多數(shù)情況下對ESP的操作都是花指令或junk code。在一些虛擬與現(xiàn)實(比如說調(diào)用系統(tǒng)函數(shù))交接的地方,系統(tǒng)并不知道VM堆棧的存在,這就需要把數(shù)據(jù)(比如系統(tǒng)函數(shù)的調(diào)用參數(shù))移動到常規(guī)ESP棧頂。EDX是一個較少使用的寄存器,只在一些解密循環(huán)里面參與運算。而它的一個主要的運用是在DISPATCH部件里,根據(jù)ESI的值來獲取DispatchTable的數(shù)據(jù),讓VM執(zhí)行下一條偽指令。
1.2.VM堆棧
VMP的VM是基于堆棧的虛擬家,理解好VM的堆?臻g劃分和操作,是理解整個VM運行的基礎(chǔ)。
VMProtect2.04加殼程序是從TLS開始運行的,我們首先點擊OD的options菜單,修改Startup and exit選項,讓OD中斷在TLS callback里。加殼程序運行后,VMP初始化VM,并進(jìn)入DISPATCH部分。這里我們就以初始化后的堆棧為例。
VM的堆棧一共使用61個DWORD,上下分別有2個堆棧指針,下面為EBP指針,上面為EDI指針。下面是VM初始化時,給EDI和EBP指針賦值后的堆棧。
EDI=0013F8BC
EBP=0013F9B0
CPU Stack
Locked Value ASCII Comments
0013F8BC 009539E8 9. ;這里是EDI指向
0013F8C0 00950000 ...
0013F8C4 00150000 ...
0013F8C8 00000080 ...
0013F8CC 019314D6
0013F8D0 0013F8A8 .
0013F8D4 7C92E920 |
0013F8D8 00000000 ....
0013F8DC 00000000 ....
0013F8E0 00000000 ....
0013F8E4 FFFFFFFF
0013F8E8 7C98FEFF |
0013F8EC 7C00ADE7 .|
0013F8F0 00000000 ....
0013F8F4 00150000 ...
0013F8F8 0013F6F0 .
0013F8FC 0013F940 @.
0013F900 0013F944 D.
0013F904 7C92E920 |
0013F908 7C9301E0 |
0013F90C FFFFFFFF
0013F910 7C9301DB |
0013F914 7C9314D6 |
0013F918 7C931514 |
0013F91C 7C99E120 |
0013F920 7C9314EA |
0013F924 5ADF1158 XZ
0013F928 00000001 ...
0013F92C 00000000 ....
0013F930 7FFDA000 .
0013F934 7FFDF000 .
0013F938 00158070 p.
0013F93C 0013F890 .
0013F940 00000000 ....
0013F944 0043D759 YC.
0013F948 0000E9ED ..
0013F94C 409B0002 .@
0013F950 00000020 ...
0013F954 0013F9CC .
0013F958 0013F96C l.
0013F95C 0043E9ED C.
0013F960 000359F4 Y.
0013F964 00000020 ...
0013F968 004253CD SB.
0013F96C 409B0000 ..@
0013F970 00000020 ...
0013F974 0013F9CC .
0013F978 0013F98C .
0013F97C 00000000 .... ;這里是EBP指向
0013F980 00000000 .... ;這里是VM初始化保存的各個寄存器
0013F984 00000246 F..
0013F988 000359F4 Y.
0013F98C 00000020 ...
0013F990 00000000 ....
0013F994 0013F9CC .
0013F998 004253CD SB.
0013F99C 000359F4 Y.
0013F9A0 00400000 ..@.
0013F9A4 0013F9C0 .
0013F9A8 C456C619 V ;這里是VMP的2個加密數(shù)據(jù)
0013F9AC 2EF6420A .B.
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;這里是TLS進(jìn)來時的棧頂
關(guān)于2個加密數(shù)據(jù)和初始化的過程我們后續(xù)來說,這里我們主要關(guān)注VM的堆棧劃分。
我把上面的EDI指向的堆棧稱為EDISTACK,把EBP指向的堆棧稱為EBPSTACK。在VM中,EBPSTACK是運算區(qū),各類數(shù)據(jù)的運算操作在這里完成;EDISTACK是存儲區(qū)包括長期存儲數(shù)據(jù)和臨時存儲EBPSTACK的運算數(shù)。
下面我們來看一條數(shù)據(jù)移動偽指令:
命名:
VM_MOVdw_EDISTACKdw_EBPSTACKdw
代碼:
0043DC19 |. F6D8 NEG AL ; *
0043DC26 |. C0C8 07 ROR AL,7 ; *
0043DC34 |. 2C 20 SUB AL,20 ; *
0043DC41 |. 24 3C AND AL,3C ; *
0043E080 |$ 8B55 00 MOV EDX,DWORD PTR SS:[EBP] ; *
0043E086 |. 83C5 04 ADD EBP,4 ; *
0043D3D7 /> /891438 MOV DWORD PTR DS:[EDI+EAX],EDX ; *
功能:
把1個dword的數(shù)據(jù)從EBPSTAK棧頂移動到EDISTACK,使用EAX作為偏移量
在EDISTACK的數(shù)據(jù)移動中,使用[EDI+EAX]的方式來存儲與獲取各個值。通過計算不同的EAX的值,可以到達(dá)EDISTACK中不同位置。在計算EAX值時,實際真正計算的是AL的值,我們來考慮一下AL的最小值和最大值,AL=00時[EDI+EAX]=[0013F8BC+00000000]=0013F8BC,AL=FF時[EDI+EAX]=[0013F8BC+000000FF]=0013F9BB,這是使用[EDI+EAX]可以讀取的上下限。但是,在VM的AL值計算過程中,最后有一條AND AL,0x3C指令,0x3C=00111100bit由于這條指令的限制,無論AL為任何值(從00000000bit到11111111bit),通過AND操作,只能有1111bit的活動空間大小,1111bit相當(dāng)于16,所以EDISTACK最多可以讀取16個dword;由于00111100bit最后兩個00位的限制,任何數(shù)字與它AND后,后兩位都必然為0,變成與4對齊的值,說明VM都是按照0013F8BC 0013F8C0 0013F8C4 0013F8C8這樣的4對齊來讀取。在讀取時,VM可以讀取byte word dword,但是VM將不會去讀取0013F8BE。
由于EDISTACK堆棧向下發(fā)展,EBPSTACK堆棧向上發(fā)展,EDISTACK有0x3C控制著范圍,而EBPSTACK是操作區(qū),沒有硬性的范圍控制。為了預(yù)防兩個空間相撞,在每次往EBPSTACK移動數(shù)據(jù)后,VM都有相應(yīng)的邊界檢測指令如下:
0043CE5A |. 8D47 50 LEA EAX,[EDI+50] ; *
0043EE5D |. 39C5 CMP EBP,EAX ; *
0043F08C |.^\0F87 29F6FFFF JA 0043E6BB ; *
比較結(jié)果 大于 ,這個正常的情況,在這個VM跟蹤過程中,我沒有發(fā)現(xiàn)一次小于的情況。如果小于,也就是EBPSTACK棧頂已經(jīng)到達(dá)[EDI+50]位置,VM將會重新分配堆?臻g。0x50的偏移量比0x3C的偏移量多5個dword的緩沖區(qū)。我們來手動修改EBP指針,看看VM的對于兩個堆?臻g即將相撞的處理情況:
CPU Disasm
Address Hex dump Command Comments
0043F092 |. 52 PUSH EDX ;
0043D6C1 |. 8D5424 08 LEA EDX,[ARG.2] ; *EDX獲得的是原來EDI指針地址0013F8BC
0043DF38 |. 8D4F 40 LEA ECX,[EDI+40] ; *0x40的偏移量是0x3C的偏移量數(shù)據(jù)1個dword結(jié)束后的位置
0043DF46 |. 29D1 SUB ECX,EDX ; *減法計算出數(shù)據(jù)存儲量
0043DF4B |. 8D45 80 LEA EAX,[EBP-80] ; *增加0x80的空間
0043DF5C |. 24 FC AND AL,FC ; *按4對齊
0043DF68 |. 29C8 SUB EAX,ECX ; *在增加原來數(shù)據(jù)大小的堆?臻g
0043DF6E |. 89C4 MOV ESP,EAX ; *
0043DF7E |. 56 PUSH ESI ; |Arg1 = NOTEPAD.425748, *
0043DF87 |. 89D6 MOV ESI,EDX ; |*
0043DB3A /$ 8D7C01 C0 LEA EDI,[EAX+ECX-40] ; *
0043EC1E . 89C7 MOV EDI,EAX ; *
0043EEED |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ ; *移動原來EDISTACK中存儲的數(shù)據(jù)
0043EEF7 |. 8B7C24 10 MOV EDI,DWORD PTR SS:[ESP+10] ; *
0043EEFF |. 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10] ; *
這里我們可以看到,每次發(fā)現(xiàn)兩個堆?臻g即將相撞,VM都重新給EBP分配堆棧,并把原來EDISTACK存儲的數(shù)據(jù)移動到新的空間內(nèi)。
下面是使用OD跟蹤VM堆棧的幾個小技巧:
在OD中跟蹤VM數(shù)據(jù)移動時,雙擊0013F8BC地址,OD將會以0013F8BC為基址,顯示上下各個地址與它的偏移量,如圖:
CPU Stack
Locked Value ASCII Comments
$-C 759D0000 ..u
$-8 00000001 ...
$-4 0013F8FC .
$ ==> 009539E8 9. ;這里是0013F8BC,雙擊后的效果
$+4 00950000 ...
$+8 00150000 ...
$+C 00000080 ...
$+10 019314D6
在跟蹤VM時,在數(shù)據(jù)移動偽指令中的AND AL,0x3C的下一條指令下斷點,這樣每次進(jìn)行數(shù)據(jù)移動,你都可以在這個斷點看到,數(shù)據(jù)的去向和來源,這是極其有用的。在很多復(fù)雜的運算地方,你需要在草稿紙上記下,EDISTACK中一些空間的數(shù)據(jù)時來自于什么時候?比如標(biāo)志位ZF檢測+跳轉(zhuǎn)是VM的一個重要操作,而EFLAGS標(biāo)志數(shù)都是相差不多或類似的00000286 00000246等等,如果你不能準(zhǔn)確知道[EDI+EAX]存儲或讀取的位置,你將無法理解VM的操作。這非常的重要,請牢記!必要時連OD得數(shù)據(jù)窗口也一起配合顯示VM堆棧
把OD里的堆棧窗口拉高,讓它竟可能多的顯示數(shù)據(jù),在高分辨率的電腦上,最好是能夠顯示出整個VM的堆棧。默認(rèn)情況下,堆棧窗口是隨著ESP指針的變化而自動顯示的,這對于我們要時刻盯著VM堆棧的需求不相符,在堆棧窗口-->右鍵-->Lock address 打鉤,這樣OD就會鎖定堆棧窗口。
到這里,關(guān)于堆?臻g的介紹就結(jié)束了。對堆棧的理解是本文的根基。
本文導(dǎo)航
- 第1頁: 首頁
- 第2頁: VMProtect虛擬機(jī)簡介
- 第3頁: VMProtect2.04偽指令匯總
- 第4頁: .NAND(與非門)
- 第5頁: NOTEPAD全程跟蹤
- 第6頁: VMP外殼函數(shù)獲取
- 第7頁: 虛擬執(zhí)行環(huán)境與調(diào)試器檢測