寫在前面的話:
上一篇文章中,帶領大家一起分析了簡單的壓縮殼ASPACK,今天,就和大家一起來揭開VMP這道神秘的面紗;
【花指令】:擾亂調試器的,並不執行;
【混淆】:對原指令進行拆解或等價替換,會執行;
零、自己寫個程序,加殼
0、為方便我們對VMP殼有更清楚的認識,這里,我們先自己寫個程序,然后,加殼;
通過分析對比壓縮前后的程序,輔助大家進行更深一層的了解,測試代碼如下:
#include <windows.h> int main(int argc, char** argv) { MessageBox(0, L"VMP Simple Example", L"Reginald", 0); ExitProcess(0); return 0; }
1、加殼
已經生成了,這里是問,要不要運行,點擊YES,會運行我們加殼后的程序,我們看一下加殼后的程序:
加殼完成,下面,我們一起來分析下加殼后的程序;
一、分析加殼后的程序:
在我們進行脫殼時,如果事先不知道這是什么種類的殼,方法只能是單步跟蹤了;
這里,在分析前,我們可以從宏觀上看下,OEP處有何變化;
加密后的OEP;
加密前的OEP(VS編譯的OEP特征 call xxx; jmp yyy)
因為在上面加殼的時候,我們選擇了對OEP處的第一條指令加保護,所以,只有第一條指令發生了變化;
我們單步跟蹤下這部分的代碼:
遇到call,F7跟進去;
我們來看下:
1、保存寄存器環境;
2、將0xB50000 壓棧;
3、ESI = *(ESP + 0x2C);
4、EBP = ESP,ESP -= 0xC0;開辟棧幀
5、EDI = ESP,EDI指向新棧頂
6、ESI += *(EBP);ESI內容加上原棧頂內容——我們一會動態分析下;
EAX = *(ESI);
ESI++;
7、跳轉到*(EAX*4 + 0xF661E9);
目前,好像就是在做這些操作,我們貌似看不出什么了,但這里不免有幾個疑問:
A) 0xB50000 有什么含義;
B) 最后跳轉的時候,那個0xF661E9又有什么含義;
我們帶着疑問繼續動態分析:
目前,我們已經了解到了一些有用的信息:
1、EBP保存原始棧頂
2、EDI保存新棧頂(老棧頂 - 0xC0)
3、ESI處經過運算,將兩個無意義的值,變化為了一個有效的地址值【距離VMP段首偏移0x78A處】
4、EAX從ESI處取值,每次1B;
5、進行跳轉時,加的常數也是位於VMP區段的地址值【距離VMP段首偏移0x1E9處】
那我們就在010里瞅瞅,0x78A和0x1E9里到底有什么東西;(VMP段內偏移值)
到現在,我們可以做出如下假設:
0x1E9處相當於函數表首地址;
0x78A處取出下標,根據下標進行定位,找出該地址,跳轉到此地址;
這些地址,也都位於VMP段,這也符合情況,稍后會給出解釋;(什么叫符合情況);
這些地址,都是需要重定位的;
繼續分析,驗證假設:
我們在010里,0x1E9開始,找出0x1C個地址(地址4B):
0xF50000 + (0x4160B0 - 0x400000) = 0xF660B0(重定位后的地址),那我們要跳轉的地址,是這個嗎,在OD里走下:
接下來,我們繼續分析:
為了方便我們的分析,希望大家將下面的棧圖放在心中:
我們已經大致清楚了這部分的邏輯,那我們先把那些用到的下標,找出來,並且找出它所對應的地址,靜態的先分析下:
1C 14 30 38 10 00 0C 34 18 3C 08 28
1C:0xF660B0
14:0xF660B0
30:0xF660B0
38:0xF660B0
10:0xF660B0
00:0xF660B0
0C:0xF660B0
34:0xF660B0
18:0xF660B0
3C:0xF660B0
08:0xF660B0
28:0xF660B0
這些都一樣,我們瞅瞅是干嘛的:
0xF660B0:【ESP、EDI不變,*(EDI + EAX) = *EBP,EBP += 4】
執行完后:【ESP、EDI不變,EBP += 0x30】
再接着看下面的:
62 60 12 40 00
62:0xF666C0
【EBP -= 4,*EBP = 0x401260(原ESP + 0x2C = 0x401260)】
【EBP VS (EDI + 0x50)】
【(原ESP + 0x2C) > (原ESP - 0x70)】end
這里,要注意這個地方:
1、原ESP + 0x2C,這個位置還記得么,就是最初進入VM的時候,push xxx; call yyy的那個push處的地方;
2、這個地址,0x401260,看着這么親民,有什么意義嗎;
這里,為了弄清楚這個地址的含義,我們先看看未VM保護前的程序,其實,這里不得不說單純VM的缺陷了,
它只是在做特別厲害的混淆,但是,不論你怎么混淆,該做的,一定要做的,否則,程序就錯了;
那它應該做什么呢?還記得,我們加VM保護的指令嗎?
OEP:CALL xxx
OEP+5:jmp yyy
CALL xxx = push(OEP+5) && jmp xxx;
我們即使加了VM保護,但是,總要執行xxx處的代碼吧,執行完后,又一定會回到OEP+5那吧;
我們如何得到xxx的地址呢,別慌,我們看下機器碼,知道OEP的地址了,又有E8后的偏移,不是問題:
這是原來的OEP處的數據,E8 AD 03 00 00
第一:0xF5125B + 0x3AD + 5 = F5160D(執行函數)
第二:0xF5125B + 5 = F51260(返回地址)
到這一步,我們回過頭來,再看看剛剛那個親民的地址:0x401260
這么有感覺吧,像不像一個沒有被污染(重定位)的VA,如果是的話,Offset就是0x1260;
再看看那個返回地址0xF51260-0xF50000(當前基址)=0x1260,因此,基本確定,這里就是在搞事情了;
把執行完原該執行的函數后,返回的地址,放到了原ESP+0x2C的位置;一切構造的是那么巧妙,現在知道為啥那個位置要來個push了吧,真是一箭雙雕;
思路清晰的讀者,也許心中還有一個疑問,別忙,接下來就幫你解答:
1E 2B 04 1E
1E:0xF66757
【0xFF660B0逆操作,EDI、ESP不變,EBP -= 4,*EBP = *(EDI + EAX)】
【每次EBP向反方向變化,都要進行如下比較】
【EBP VS EDI + 0x50】
【原ESP + 0x28 > 原ESP - 0x70】end
2B:0xF6619B(神來之筆)
【*(EBP + 4) += *EBP; *EBP = EFLAGS; EDI不變,ESP不變, EBP不變,前倆內容有變化】
這是在干嘛呢,我們一步步分析到這里的時候:
EBP = 原ESP + 0x28;
*(原ESP+0x2C) = 0x401260(未受污染的返回地址OEP+5);
解釋下,就是在執行:
*(ESP+0x2C) += *(EBP)
EBP的值哪里來的,就是上一步過來的,EAX=0x1E 時的那步逆操作,注意EAX & 0x3C后和EDI相加的,也就是0x1C
【0xFF660B0逆操作,EDI、ESP不變,EBP -= 4,*EBP = *(EDI + EAX)】
由於EDI一直未有變化,我們只需要找到什么時候往EDI+0x1C里存東西,就可以了,就是最初的時候:那個1C:
最初的時候,EBP就是原棧頂,1C的時候,執行*(EDI+0x1C) = *EBP,其實就是把當時棧頂的數,放進去了,那當時棧頂的數是什么呢;說出來,你會驚呼它的神奇:
還記得當時,我們認定無效的兩個數么,一個是push xxx; call yyy進入虛擬機的;一個是最后push的那個0xB50000
也就是說,當時棧頂的數據,是當前基址-默認基址,現在那些有疑問的朋友,也許如撥雲見日了吧;
*(原ESP+0x2C) = 0x401260(未受污染的返回地址OEP+5);
*(原ESP+0x2C) += 0xB50000;(進行重定位)
04:0xF660B0
1E:0xF66757 由於兩個對EBP而言是互逆的,因此,不再分析了;EBP還是原ESP+0x28
接下來,我們繼續分析,還有最后一處讓人驚艷的(加法結合律)
D7 0D 16 40 00
D7:0xF666C0
【EBP -= 4,*EBP = 0x40160D 原ESP + 0x24 = 0x40160D】
【EBP VS EDI + 0x50】
【原ESP + 0x24 > 原ESP - 0x70】end
這個套路,我們已經分析過了,繼續走:
4A:0xF6619B
我們已經知道,這是修復重定位的,但是,現在存放該執行函數地址的,是原ESP+0x24的位置,返回地址位置是ESP+0x2C;
烏雲啊,是我們分析錯了嗎,別急,我們也許忽略了一些常識,人最容易忽視的,其實是司空見慣的東西;自認為常識的東西;
重新審視修復重定位的函數:
它執行的操作是什么呢:
*(EBP+4) += *(EBP)
*EBP = EFLAGS;
現在*EBP存放的是0x40160D(有意義的地址);那*(EBP+4)里是啥呢,剛剛在分析上一個重定位的時候,已經知道了,這個位置,是(當前基址 - 默認基址) = 0xB50000
哦,原來如此,這不就是一個結合律的問題嗎,誰先加誰的問題,一個主動的,一個被動的,我們把目光都放在了那個更有意義的點了,忽略了另外的也是有意義的;
至此,我們得出結論:
原ESP+0x2C <=> 應該返回的位置;
原ESP+0x28 <=> 應該執行的位置;
分析至此,可以欣慰以下了,接下來,繼續走,既然准備工作基本完成,猜測,該退出VM了;
04 3E 1A 36 0E 02 12 3A 32 16 1E
04:0xF660B0
3E:0xF66757 互逆
1A:0xF66757
【原ESP + 0x20 > 原ESP - 0x70】end
36:0xF66757
【原ESP + 0x1C > 原ESP - 0x70】end
0E:0xF66757
【原ESP + 0x18 > 原ESP - 0x70】end
02:0xF66757
【原ESP + 0x14 > 原ESP - 0x70】end
12:0xF66757
【原ESP + 0x10 > 原ESP - 0x70】end
3A:0xF66757
【原ESP + 0xC > 原ESP - 0x70】end
32:0xF66757
【原ESP + 0x8 > 原ESP - 0x70】end
16:0xF66757
【原ESP + 0x4 > 原ESP - 0x70】end
1E:0xF66757
【原ESP > 原ESP - 0x70】end
到這里,EBP就是原ESP了,見到曙光了,再接着走:
3B
3B:0xF66091
POP次,ESP的內容剛剛好,0x28(該執行的位置) && 0x2C(該返回的位置)
如此,我們靜態分析了調用過程,為了驗證猜想,直接再這個地方下斷,看下棧結構:
二、總結:
0、單純的VMP殼,只是在混淆代碼(不妨試試,在IAT地址處搞一搞,就清楚了)
1、最初push xxx; call yyy; 這個push一方面和另外的基址之差定位到下標;另一方面,填充一個棧位(其實就是占位的),最終會被call 指令本應push的地址給替換;
2、進入里面后,最后一個push的值,是基址之差,用來修復call重定位的
3、VMP里,大多是混淆后,變着法的操作原堆棧,在操作完成后,就退出了;
4、退出OEP特征碼:89 EC ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? C3
5、其實,經過這方分析后,感覺特征仍有不少,另外,靜態分析,也變成了可能;知道VMP段的RVA后 x4 00,也能幫助定位;
6、對於CALL指令,VM保護,就是如上的,也可以分析別的指令,了解其處理方法,總體而言,單純的VM,仍然離VM有些距離;
希望,能有所幫助;
轉載請注明出處,TKS;