【原創】VB P-code -- 虛擬機器的藝術
*歲末大盤點*
VB P-code -- 虛擬機器的藝術
作者:cyclotron
初學crack的時候,總有不少大蝦好心地提醒菜鳥們:別碰VB P-code的東東。不可否認,這確實是善意的提醒。VB P-code特殊的運作方式以及由此造成的天然的除錯困難,是不少早期cracker難以忘卻的惡夢。然而,隨著各類專業的VB P-code Debugger和Decompiler層出不窮,VB P-code已經不再像以前那樣遙不可及了。退一步來說,即使是使用通用偵錯程式SoftICE或OllyDBG來跟蹤VB P-code程式,只要掌握其原理,它也並非是不可戰勝的。
VB P-code到底是怎樣一種機制?它與普通的可執行程式有何不同?下面由我為大家來揭開VB P-code神秘的面紗……
眾所周知,採用VB編寫的應用程式有兩種編譯方式,一種是Native Code方式,另一種就是P-code方式。事實上,VB 4.0以前只採用P-code編譯,VB Native Code是在VB 5.0以後發展起來的,其目的是為了在一定程度上改善VB應用程式的執行速度。VB P-code的執行速度較慢,這是由它本身的執行機制所決定的。
P-code,即Pseudo Code(虛擬碼),這一概念最早出現在Pascal編譯器中,它是為了提供跨平臺可移植性而產生的,實現這一編譯機制的Pascal編譯器被稱為"Pascal P Compiler"。Sun公司在其推出的Java語言上也成功地實現了這種機制。Java程式的偽編譯程式碼由一系列代表一定意義的位元組碼(byte code)組成,它們同屬於一套特定的指令集,這種位元組碼不能由不同的CPU直接執行,而是要透過特殊的直譯器翻譯為CPU可以識別的指令才能執行,這種直譯器就是我們常說的"虛擬機器"。只要在不同的平臺上提供虛擬機器,把位元組碼翻譯為對應的CPU指令集,也就實現了所謂的跨平臺特性。Microsoft推出的VB P-code,實際上也是一組自定義的指令集,必須透過基於堆疊的虛擬機器翻譯為80X86上的指令集才能執行,擔任虛擬機器任務的就是msvbvm50.dll和msvbvm60.dll這兩個動態連結庫檔案。由於在檔案執行過程中多出了這一個解釋的步驟,自然要影響到其執行的速度。正如我們所看到的,VB P-code並沒有實現所謂的跨平臺執行特性,這對於Pseudo這個詞的起源是不恰當的;另一方面,採用P-code形式編譯的VB應用程式的體積要小於採用Native Code形式編譯的同樣程式(這是由於P-code指令集的每一條指令對應於一組80X86指令所完成的任務),所以VB P-code實際上意味著VB Packed-code(壓縮程式碼),用以強調VB P-code程式較小的程式碼體積。
作為程式設計師,他們現在已經擁有了充分的選擇依據--速度,還是體積;然而,作為cracker,僅僅知道這些是遠遠不夠的,正如本文的標題所道出的,我們需要了解的,是虛擬機器的運作方式--這才是在除錯中真正起作用的東西。
為了說明虛擬機器的運作方式,我們寫個小程式來除錯一下,下面是這個小程式的原始碼:
程式碼:
Private Sub Command1_Click() X = InputBox("Please input an integer", "Input") If X <> "" Then Y = X + 2 X = Y * 3 Out.Text = X End If End Sub
這個程式只處理Command_Click事件,同時用到了InputBox,便於我們用rtcInputBox函式設斷,最後記得用P-code方式編譯。
現在拿出我們心愛的偵錯程式OllyDBG載入這個程式,在命令列外掛中輸入bp rtcInputBox L(注意區分大小寫),按下F9執行,點選按鈕,我們中斷在下面的地方:
程式碼:
6A360CF2 >PUSH EBP ;這裡就是rtcInputBox的第一句指令了 6A360CF3 MOV EBP,ESP 6A360CF5 SUB ESP,54 6A360CF8 MOV EAX,DWORD PTR SS:[EBP+1C] 6A360CFB PUSH EBX 6A360CFC PUSH ESI 6A360CFD PUSH EDI 6A360CFE CMP WORD PTR DS:[EAX],0A 6A360D02 MOV EDI,80020004 6A360D07 JNZ MSVBVM60.6A360E6C 6A360D0D CMP DWORD PTR DS:[EAX+8],EDI 6A360D10 JNZ MSVBVM60.6A360E6C 6A360D16 OR DWORD PTR SS:[EBP-8],FFFFFFFF
按下Ctrl+F9返回,當InputBox彈出以後隨意輸入一個整數,然後繼續單步跟蹤,直到我們來到下面的程式碼處:
程式碼:
6A37D2CD MOVSX EAX,WORD PTR DS:[ESI] 6A37D2D0 PUSH DWORD PTR DS:[EAX+EBP] 6A37D2D3 XOR EAX,EAX 6A37D2D5 MOV AL,BYTE PTR DS:[ESI+2] 6A37D2D8 ADD ESI,3 6A37D2DB JMP DWORD PTR DS:[EAX*4+6A37DA58] ;注意這句 6A37D2E2 MOVSX EAX,WORD PTR DS:[ESI] ;我們來到這裡 6A37D2E5 ADD EAX,EBP 6A37D2E7 PUSH EAX 6A37D2E8 XOR EAX,EAX 6A37D2EA MOV AL,BYTE PTR DS:[ESI+2] 6A37D2ED ADD ESI,3 6A37D2F0 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;注意這句 6A37D2F7 MOVSX EAX,WORD PTR DS:[ESI] 6A37D2FA POP EBX 6A37D2FB MOV WORD PTR DS:[EAX+EBP],BX 6A37D2FF XOR EAX,EAX 6A37D301 MOV AL,BYTE PTR DS:[ESI+2] 6A37D304 ADD ESI,3 6A37D307 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;注意這句
你可能已經注意到了,每一段程式碼都包括了下面這些指令:
程式碼:
XOR EAX,EAX MOV AL,BYTE PTR DS:[ESI+2] ADD ESI,3 JMP DWORD PTR DS:[EAX*4+6A37DA58]
如果不明白這些指令是幹什麼的,跟蹤時就會覺得老是在原處打轉轉。事實上,這幾條指令正是虛擬機器讀取P-code虛擬碼的引擎部分。為了說明這些指令的功能,我們先來看看VB P-code虛擬碼的格式。
3A 6C FF 03 00
操作碼 運算元
這是一句典型的VB P-code指令,其助記符為LitVarStr,表示將一個Variant型的字串入棧。3A是指令的操作碼,0003是運算元,是以某種形式標記的字串地址,6CFF在虛擬機器引擎中沒有用到,暫不清楚起什麼作用。現在我們可以來解釋虛擬機器所做的一切了:
程式碼:
6A37D2E2 MOVSX EAX,WORD PTR DS:[ESI] ;esi指向待解釋指令的運算元 6A37D2E5 ADD EAX,EBP ;取得某種形式的字串指標 6A37D2E7 PUSH EAX ;壓棧 6A37D2E8 XOR EAX,EAX ;eax清零 6A37D2EA MOV AL,BYTE PTR DS:[ESI+2] ;取下一條指令的操作碼 6A37D2ED ADD ESI,3 ;移至下一條指令的運算元 6A37D2F0 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;根據跳轉地址表到下一條指令的解釋單元
這裡我們看到,虛擬機器並沒有用簡單的條件判斷來識別操作碼,而是採用了一種很巧妙跳轉地址表法,把解釋流程直接導向相應的解釋單元。6A37DA58是地址跳轉表的首地址,eax儲存了下一條指令的操作碼,由於每一個跳轉地址是一個dword,所以用eax乘以4的值加上跳轉表的基地址來索引下一條指令的解釋單元。當然,由於每一條指令的長度是不同的,所以前面提到的讀取P-code虛擬碼的引擎部分並不完全相同。明白了虛擬機器的解釋原理,跟蹤P-code程式就方便多了,一旦看到讀取P-code虛擬碼的引擎部分,我們就知道虛擬機器開始解釋下一條指令了。儘管如此,除錯P-code程式還是比除錯普通的本地機器碼編譯的可執行程式困難得多,因為在虛擬機器的流程下,我們已經無法隨時回顧前面執行過的指令了。
下面我們來看看加法在虛擬機器中是如何被解釋執行的。按F8跟蹤程式碼來到:
程式碼:
6A37D3B3 ADD EDI,EBP 6A37D3B5 PUSH EDI 6A37D3B6 XOR EAX,EAX 6A37D3B8 MOV AL,BYTE PTR DS:[ESI] 6A37D3BA INC ESI 6A37D3BB JMP DWORD PTR DS:[EAX*4+6A37DA58] ;al=FBh
走到這裡時al的值為FB,這是Variant型變數算術運算偽指令的操作碼,esi則指向了該操作碼後的一個次操作碼94,代表加法運算。跟隨跳轉地址表我們來到:
程式碼:
6A37D9BA XOR EAX,EAX 6A37D9BC MOV AL,BYTE PTR DS:[ESI] 6A37D9BE INC ESI 6A37D9BF JMP DWORD PTR DS:[EAX*4+6A37DE58] ;al=94h
這裡是一個二級跳轉表,我們注意到6A37DE58這個值和前面的跳轉地址表基址不同了,這說明VB P-code算術運算偽指令採用了二級跳轉來解釋執行。我們知道一個位元組的操作碼可以表示的操作指令種類最多為256個,為了解決指令數不夠的問題,Microsoft對部分偽指令進行二級跳轉解釋執行。這次跳轉以後,我們來到:
程式碼:
6A384628 LEA EBX,DWORD PTR DS:[__vbaVarSub] 6A38462E JMP SHORT MSVBVM60.6A384610 6A384630 LEA EBX,DWORD PTR DS:[__vbaVarMul] 6A384636 JMP SHORT MSVBVM60.6A384610 6A384638 LEA EBX,DWORD PTR DS:[__vbaVarDiv] 6A38463E JMP SHORT MSVBVM60.6A384610 6A384640 LEA EBX,DWORD PTR DS:[__vbaVarIdiv] 6A384646 JMP SHORT MSVBVM60.6A384610 6A384648 LEA EBX,DWORD PTR DS:[__vbaVarMod] 6A38464E JMP SHORT MSVBVM60.6A384610 6A384650 LEA EBX,DWORD PTR DS:[__vbaVarAdd] ;我們跳轉到這裡 6A384656 JMP SHORT MSVBVM60.6A384610 6A384658 LEA EBX,DWORD PTR DS:[__vbaVarAnd] 6A38465E JMP SHORT MSVBVM60.6A384610 6A384660 LEA EBX,DWORD PTR DS:[__vbaVarOr] 6A384666 JMP SHORT MSVBVM60.6A384610 6A384668 LEA EBX,DWORD PTR DS:[__vbaVarXor] 6A38466E JMP SHORT MSVBVM60.6A384610 6A384670 LEA EBX,DWORD PTR DS:[__vbaVarEqv] 6A384676 JMP SHORT MSVBVM60.6A384610 6A384678 LEA EBX,DWORD PTR DS:[__vbaVarImp] 6A38467E JMP SHORT MSVBVM60.6A384610
很明顯,Variant變數的算術邏輯運算偽操作都分佈在這裡,即將呼叫的函式__vbaVarAdd地址被裝入ebx,繼續向下看到:
程式碼:
6A384610 MOVSX EDI,WORD PTR DS:[ESI] ;取加法指令的運算元 6A384613 ADD EDI,EBP 6A384615 PUSH EDI ;運算元入棧 6A384616 CALL EBX ;這裡呼叫了函式__vbaVarAdd執行加法操作 6A384618 PUSH EDI ;運算結果儲存在堆疊結構中 6A384619 XOR EAX,EAX 6A38461B MOV AL,BYTE PTR DS:[ESI+2] ;繼續取下一條偽指令的操作碼 6A38461E ADD ESI,3 ;指向下一條偽指令的運算元 6A384621 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;跳向下一條偽指令的解釋單元
經過上面的跟蹤,VB P-code虛擬機器的解釋過程已經很清楚了,其他的相關指令讀者可以透過除錯自行分析。
現在我們知道SoftICE或者OllyDBG完全可以勝任除錯VB P-code程式的任務,然而,在大多數情況下,它們並非是我們最好的選擇,根據除錯的習慣,我們總是希望偵錯程式能夠直接跟蹤可執行檔案本身的每一句指令,這樣我們可以直觀地瞭解程式實現的功能。從這個角度來說,SoftICE或者OllyDBG是除錯VB P-code虛擬機器的優秀工具,但是它們無法讓我們將更多的注意力集中在P-code程式本身的功能上。伴隨著這樣一種想法,功能強大的VB P-code專用偵錯程式WKTVBDebugger應運而生了。這又是一種怎樣的偵錯程式?它究竟給VB P-code程式除錯帶來了怎樣一種變革?敬請關注《VB P-code -- 偵錯程式的革命》。
附件:VB_P-code.rar
相關文章
- 【原創】VB
P-code -- 虛擬碼的奧祕2004-12-26
- [原創]測試環境搭建虛擬機器工具介紹2013-11-27虛擬機
- Dalvik虛擬機器、Java虛擬機器與ART虛擬機器2018-08-22虛擬機Java
- 反虛擬機器技術總結2018-04-08虛擬機
- 虛擬機器檢測技術攻防2013-08-27虛擬機
- java虛擬機器和Dalvik虛擬機器2020-04-04Java虛擬機
- Android 虛擬機器 Vs Java 虛擬機器2018-12-30Android虛擬機Java
- 深入理解 python 虛擬機器:原來虛擬機器是這麼實現閉包的2023-10-07Python虛擬機
- 虛擬機器2011-01-13虛擬機
- 容器技術和虛擬機器技術的對比2024-08-24虛擬機
- 虛擬機器的概念2018-11-03虛擬機
- 虛擬機器的克隆2018-06-08虛擬機
- 虛擬機器的搭建2024-08-21虛擬機
- 連線虛擬機器oracle 和虛擬機器KEY2013-03-10虛擬機Oracle
- 虛擬機器(三)虛擬機器配置靜態Ip2024-04-16虛擬機
- 【虛擬機器資料恢復】EXSI虛擬機器誤還原快照的資料恢復案例2022-08-31虛擬機資料恢復
- 【虛擬機器資料恢復】ESXI虛擬機器被誤還原快照的資料恢復案例2023-05-06虛擬機資料恢復
- 虛擬化技術之kvm虛擬機器建立工具qemu-kvm2020-08-21虛擬機
- PD虛擬機器 18 for Mac(Mac虛擬機器軟體)2022-09-15虛擬機Mac
- JVM 虛擬機器2020-07-16JVM虛擬機
- JVM虛擬機器2019-03-21JVM虛擬機
- Neo 虛擬機器2018-12-27虛擬機
- VMware虛擬機器2024-08-30虛擬機
- VMware虛擬機器優化,提高虛擬機器執行速度的方法?2020-09-30虛擬機優化
- 虛擬機器入門二,虛擬機器的三種網路模式2016-09-01虛擬機模式
- 虛擬機器arm虛擬環境搭建2018-09-08虛擬機
- KVM虛擬機器的管理2020-10-21虛擬機
- 建立新的虛擬機器2020-12-30虛擬機
- 【虛擬機器的瞭解】2017-03-26虛擬機
- 網路安全新契機,KiwiVM虛擬機器的創新發布2017-12-11虛擬機
- 虛擬化技術之kvm虛擬機器建立工具virt-install2020-08-18虛擬機
- Java虛擬機器7:記憶體分配原則2015-09-26Java虛擬機記憶體
- LUA指令碼虛擬機器逃逸技術分析2020-08-19指令碼虛擬機
- VMware虛擬機器如何設定使主機和虛擬機器不同IP2021-01-21虛擬機
- 深入理解虛擬機器之虛擬機器類載入機制2018-05-05虛擬機
- Java 虛擬機器之三:Java虛擬機器的記憶體結構2018-09-02Java虛擬機記憶體
- 憑什麼別的虛擬機器叫虛擬機器,Python的叫直譯器?2018-07-27虛擬機Python
- Ubuntu虛擬機器進入虛擬環境的流程2024-04-03Ubuntu虛擬機