【原創】VB P-code -- 虛擬碼的奧祕
*歲末大盤點*
VB P-code -- 虛擬碼的奧祕
作者:cyclotron
歡迎回到VB P-code虛擬碼世界。
前兩期我為大家介紹了VB P-code虛擬機器的執行機制和P-code專用偵錯程式WKTVBDebugger,相信大家已經對VB P-code程式有所瞭解了。我們的任務當然不僅僅侷限於理論研究,解析VB P-code的最終目的還是加密解密和逆向工程。由於Microsoft沒有公開VB P-code虛擬碼的技術文件,我們無法獲得現成的虛擬碼指令說明,而單憑VB P-code反編譯器給出的助記符資訊是遠遠不夠的,這就要求我們自行發掘虛擬碼執行的奧祕。
可能有些朋友還不太明白,既然WKTVBDebugger作為一個虛擬碼級的偵錯程式已經遮蔽了VB P-code虛擬機器的解釋過程,為什麼還費神要去了解這些偽指令執行的細節呢?這裡我請大家思考一個問題:假設你現在用WKTVBDebugger跟蹤AddVar這句偽指令,你如何知道它的運算元和操作結果分別是多少?也許有人說,既然VB P-code虛擬機器是基於堆疊的,那麼運算元和操作結果一定存放在堆疊裡了。不錯,這些內容確實存在於堆疊中,但是你知道它的存放形式嗎?是單個的運算元,是指標,還是其他複雜的資料結構?我敢說,如果你是第一次除錯這句VB P-code指令,它一定會令你不知所措,即使你能最終解決這個問題,也一定會花上不少時間,結果也會令你覺得不可思議。此外,對於不同的P-code偽指令,其存放形式是各不相同的。如果你在除錯一個軟體時,不能看到每句指令的運算元和操作結果,那與靜態反編譯何異之有?WKTVBDebugger的除錯功能還不是形同虛設?當然,這只是其中的一個方面。如果你連偽指令的抽象動作都不明白(畢竟不是所有的偽指令都能從其助記符看出其含義的),那又該如何呢?正如我前面所言,要理解這些偽指令的執行細節,是要費一番功夫的。
既然如此,我們應當如何解決這些問題呢?答案將由黃金組合OllyDBG+WKTVBDebugger+VBParser來揭曉。為了方便,這次使用的例子程式還是上兩篇使用的VB P-code.exe,我們將通過跟蹤虛擬機器的解釋過程來研究P-code虛擬碼的執行細節。
首先使用ljtt的VBParser(非常專業的VB P-code反編譯器)解析VB P-code.exe,得到虛擬碼如下:
程式碼:
FileName: D:\Contribution\VB P-code\VB P-code--虛擬碼的奧祕\VB P-code.exe -----=====-----=====-----=====--Pcode--=====-----=====-----=====----- [CommandButton] Private Sub Command1_Click() '-=-=-=-=-=-=-= ProcAddr Range: [004019C4 - 00401A54] , ProcSize: 90 =-=-=-=-=-=-=- 004019C4: 27 9C FE LitVar_Missing PushVarError 80020004 (missing) 004019C7: 27 BC FE LitVar_Missing PushVarError 80020004 (missing) 004019CA: 27 DC FE LitVar_Missing PushVarError 80020004 (missing) 004019CD: 27 FC FE LitVar_Missing PushVarError 80020004 (missing) 004019D0: 27 1C FF LitVar_Missing PushVarError 80020004 (missing) *********** Referent String: "Input" *********** | 004019D3: 3A 4C FF 00 00 LitVarStr PushVarString Ptr_00401434 004019D8: 4E 3C FF FStVarCopyObj [local_C4]=vbaVarDup(Pop) 004019DB: 04 3C FF FLdRfVar Push local_C4 *********** Referent String: "Please input an integer" *********** | 004019DE: 3A 6C FF 01 00 LitVarStr PushVarString Ptr_00401400 004019E3: 4E 5C FF FStVarCopyObj [local_A4]=vbaVarDup(Pop) 004019E6: 04 5C FF FLdRfVar Push local_A4 004019E9: 0B 02 00 1C 00 ImpAdCallI2 Call Ptr_00401020; check stack 001C; Push EAX 004019EE: 46 7C FE CVarStr 004019F1: FC F6 8C FE FStVar (……省略)
以上就是Command_Click事件響應程式碼的開頭部分,是不是一下子覺得有些手足無措?沒關係,我們一句一句來除錯。如果你還記得我在第一篇中所描述的VB P-code虛擬機器執行機制,那麼你應該能想到下一步該怎麼做。VB P-code虛擬機器以流的形式順序讀入每一句偽指令,然後通過一個跳轉地址表找到相應的解釋程式碼,我們要跟蹤它解釋偽指令的細節,就必須在偽指令的操作碼上下記憶體訪問斷點。現在我們看到第一句偽指令LitVar_Missing從004019C4(虛擬地址)開始,那麼用OllyDBG載入VB P-code.exe,在轉存視窗中來到004019C4,對第一個位元組(操作碼)下記憶體訪問斷點,然後按下F9讓它執行,接著點OK。看看我們中斷在哪裡:
程式碼:
6A37D153 MOV AL,BYTE PTR DS:[ESI] ;中斷在這句,開始讀操作碼了,注意esi的值為004019C4 6A37D155 INC ESI ;使esi指向運算元 6A37D156 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;根據跳轉地址表和操作碼定址解釋單元
F8往下走,我們來到這句偽指令的解釋單元:
程式碼:
6A37D39F MOVSX EDI,WORD PTR DS:[ESI] ;把字運算元帶符號擴充套件到edi 6A37D3A2 ADD ESI,2 ;esi指向下一句偽指令的操作碼 6A37D3A5 MOV WORD PTR DS:[EDI+EBP],0A ;ebp顯然是程式堆疊區某處的基址,但不是堆疊頂的指標,它把0A儲存到edi指向的偏移地址處 6A37D3AB MOV DWORD PTR DS:[EDI+EBP+8],80020004 ;向下8個位元組處存入80020004,根據VBParser的說明,這個數字表示空引數(預設引數),事實上我在原始碼中確實沒有提供這個引數 6A37D3B3 ADD EDI,EBP ;這次edi得到0A所在的虛擬地址 6A37D3B5 PUSH EDI ;在堆疊中壓入這個虛擬地址 6A37D3B6 XOR EAX,EAX ;清空eax,準備讀取下一句偽指令 6A37D3B8 MOV AL,BYTE PTR DS:[ESI] ;讀取下一句偽指令的操作碼 6A37D3BA INC ESI ;esi指向下一句偽指令的次級操作碼或運算元 6A37D3BB JMP DWORD PTR DS:[EAX*4+6A37DA58] ;根據地址跳轉表和操作碼定址解釋單元
我們來看一下這些指令執行完以後的堆疊:
程式碼:
0012F458 0012F494 ;棧頂 0012F45C 00000000 0012F460 00000000 …………………… …………………… 0012F488 00000000 0012F48C 00000000 0012F490 00000000 0012F494 0000000A ;這就是剛才壓入棧頂的資料了 0012F498 00000000 0012F49C 80020004 0012F4A0 00000000
現在這個偽指令的動作很清楚了,LitVar_Missing執行以後,把一個虛擬地址壓入堆疊,這個虛擬地址指向0000000A,00000000,80020004。實際上,這句偽指令的功能就是在堆疊中提供一個空引數,其堆疊完全沒有參考價值。但我要據此說明的是,對所有的偽指令,我們都將使用這種方法來跟蹤。在下面的說明中我將省略對虛擬機器虛擬碼讀取引擎的註釋,因為這部分都是一樣的。
下面我們來看看004019D3處的偽指令LitVarStr。老規矩,在004019D3處設記憶體訪問斷點,F9中斷在下面的地方:
程式碼:
6A37D3B8 MOV AL,BYTE PTR DS:[ESI] ;esi=004019D3 6A37D3BA INC ESI 6A37D3BB JMP DWORD PTR DS:[EAX*4+6A37DA58]
執行跳轉來到:
程式碼:
6A37D3C2 MOVSX EDI,WORD PTR DS:[ESI] ;第一個字運算元FF4C(堆疊區偏移量)帶符號擴充套件到edi 6A37D3C5 MOVZX EAX,WORD PTR DS:[ESI+2] ;第二個字運算元0000(資料區偏移量)無符號擴充套件到eax 6A37D3C9 MOV EDX,DWORD PTR SS:[EBP-54] ;根據下一句指令來看,這是P-code程式資料區的基址 6A37D3CC MOV EAX,DWORD PTR DS:[EDX+EAX*4] ;根據eax產生偏移量,取得資料區的資料,這裡我們看到eax最後取得 一個虛擬地址,指向Unicode字串"Input" 6A37D3CF ADD EDI,EBP ;edi(堆疊區偏移量)指向堆疊區即將儲存資料的地方 6A37D3D1 MOV WORD PTR DS:[EDI],8 ;存入表示型別的資料 6A37D3D6 MOV DWORD PTR DS:[EDI+8],EAX ;向下偏移8個位元組處存入指向Unicode字串"Input"的虛擬地址 6A37D3D9 PUSH EDI ;最後堆疊區資料指標入棧 6A37D3DA XOR EAX,EAX 6A37D3DC MOV AL,BYTE PTR DS:[ESI+4] 6A37D3DF ADD ESI,5 6A37D3E2 JMP DWORD PTR DS:[EAX*4+6A37DA58]
同樣地,有必要觀察一下堆疊:
程式碼:
0012F444 0012F544 ;棧頂 0012F448 0012F514 ;下面是前面其他指令形成地堆疊 0012F44C 0012F4F4 0012F450 0012F4D4 0012F454 0012F4B4 0012F458 0012F494 0012F45C 00000000 …………………… …………………… 0012F540 00000000 0012F544 00000008 0012F548 00000000 0012F54C 00401434 UNICODE "Input" 0012F550 00000000
結合上述跟蹤,LitVarStr偽指令運算元的觀察方法就很明顯了:首先在dump視窗觀察0012F544處的內容,向後移8個位元組,得到虛擬地址00401434,再從dump視窗觀察00401434處的內容,就是入棧的字串引數了。
相應地,下面我們在WKTVBDebugger中演示一下操作的過程:
1.WKTVBDebugger載入VB P-code.exe;
2.在Form Manager中對Command1控制元件設斷點;
3.點選OK;
4.WKTVBDebugger中斷在下面的地方:
程式碼:
004019C4: 27 LitVar_Missing 0012F474h 004019C7: 27 LitVar_Missing 0012F494h 004019CA: 27 LitVar_Missing 0012F4B4h 004019CD: 27 LitVar_Missing 0012F4D4h 004019D0: 27 LitVar_Missing 0012F4F4h 004019D3: 3A LitVarStr 'Input' ;這句就是我們在OllyDBG中跟蹤的LitVarStr偽指令 004019D8: 4E FStVarCopyObj 0012F514h 004019DB: 04 FLdRfVar 0012F514h 004019DE: 3A LitVarStr 'Please input an integer' 004019E3: 4E FStVarCopyObj 0012F534h 004019E6: 04 FLdRfVar 0012F534h 004019E9: 0B ImpAdCallI2 rtcInputBox on address 73472265h
5.注意我所註釋的這句偽指令,當我們單步走過這句指令時,右上角堆疊視窗顯示如下(為了便於觀察,在右側的單選框中選擇DWORD):
程式碼:
0012F424: 0012F524 0012F4F4 ;注意這裡的棧頂0012F524 0012F41C: 0012FE3C 0000004E …………………… …………………… 0012F3B4: 00000000 00000000 0012F3AC: 77E6780F 0000008C
6.按下Ctrl+M開啟轉存視窗,在Address to Dump組合框中輸入0012F524,我們看到:
程式碼:
0012F524:08 00 00 00 00 00 00 00 0012F52C:34 14 40 00 00 00 00 00 ;好了,還記得那個8個位元組的偏移嗎?00401434就是我們需要的那個資料區字串的指標了! 0012F534:00 00 00 00 00 00 00 00
7.記下這個指標,輸入到Address to Dump組合框,現在這個Unicode字串終於露出了真面目:
程式碼:
00401434:49 00 6E 00 70 00 75 00 I.n.p.u. 0040143C:74 00 00 00 00 00 00 00 t....... 00401444:00 00 00 00 E1 4E AD 33 ....N?
當然,就這個指令本身而言,WKTVBDebugger已經在偽指令視窗中給出了其運算元,所以我們要觀察這個字串大可不必那麼麻煩,但是對於其他沒有註釋的偽指令,這恐怕是唯一的辦法了。
由此可見,要熟練地除錯VB P-code程式,整理出全部偽指令運算元的定址方式是必要的一步。在OllyDBG和WKTVBDebugger面前,P-code偽指令並不神祕,它們只不過是一些人為定義的符號罷了。在WKTVBDebugger中,偽指令像是一個黑盒子,虛擬機器隱藏了黑盒子的全部祕密。既然虛擬機器的內部對OllyDBG是可見的,P-code偽指令又怎會遙不可及呢?下期為大家獻上《VB P-code -- 最後的戰役》,通過一個CrackMe的實戰破解為我們的VB P-code之旅畫上一個圓滿的句號。
相關文章
- 【原創】VB
P-code -- 虛擬機器的藝術2004-12-26虛擬機
- 虛擬偶像的商業邏輯和技術奧祕你看懂了嗎?2021-07-05
- 【原創】Linux虛擬化KVM-Qemu分析(六)之中斷虛擬化2020-11-21Linux
- 【原創】Linux虛擬化KVM-Qemu分析(四)之CPU虛擬化(2)2020-10-11Linux
- 有關VB程式P-CODE程式碼逆向工程入門淺說2015-11-15
- 【原創】Linux虛擬化KVM-Qemu分析(五)之記憶體虛擬化2020-11-07Linux記憶體
- 【原創】Linux虛擬化KVM-Qemu分析(七)之timer虛擬化2020-12-05Linux
- 【原創】Linux虛擬化KVM-Qemu分析(二)之ARMv8虛擬化2020-08-29Linux
- 學習的奧祕2016-07-03
- 高效jQuery的奧祕2013-12-05jQuery
- 語言的奧祕2013-01-04
- 【原創】Linux虛擬化KVM-Qemu分析(一)2020-08-15Linux
- 【原創】Linux虛擬化KVM-Qemu分析(三)之KVM原始碼(1)2020-09-12Linux原始碼
- 虛擬辦公室,解鎖創業人群的辦公密碼2021-06-16創業密碼
- 【原創】Linux虛擬化KVM-Qemu分析(十一)之virtqueue2021-03-28Linux
- [原創]測試環境搭建虛擬機器工具介紹2013-11-27虛擬機
- 【原創】MySQL5.7 虛擬列實現表示式索引薦2015-11-13MySql索引
- Netty 原始碼分析之拆包器的奧祕2019-03-04Netty原始碼
- 關於VB P-CODE的一些總結 (10千字)2015-11-15
- 如何製作VB的P-Code偵錯程式(譯自:WKTVBDE的作者)2015-11-15
- 【原創】Linux虛擬化KVM-Qemu分析(八)之virtio初探2021-01-24Linux
- 探索低程式碼高擴充性背後的奧祕2021-12-14
- MyBatis和Spring整合的奧祕2020-07-07MyBatisSpring
- Android App秒開的奧祕2019-09-20AndroidAPP
- 【原創】Linux虛擬化KVM-Qemu分析(十)之virtio驅動2021-02-24Linux
- 【原創】Linux虛擬化KVM-Qemu分析(九)之virtio裝置2021-02-13Linux
- Vue $mount的掛載入口的奧祕2018-08-24Vue
- LaTeX 插入虛擬碼2024-05-09
- 硬體輔助虛擬化:開創普遍虛擬化計算時代2007-09-24
- MySQL 資料庫設計的“奧祕”2021-06-27MySql資料庫
- 探索Google App Engine背後的奧祕2013-07-29GoAPP
- [深入理解Java虛擬機器]第二章 HotSpot虛擬機器物件探祕2015-10-04Java虛擬機HotSpot物件
- 詳解蘋果的黑魔法 – KVO 的奧祕2015-12-23蘋果
- 圖資料庫奧祕初探2017-03-30資料庫
- 小論文虛擬碼的使用2020-12-23
- 【原創】MySQL 模擬條件索引2021-09-09MySql索引
- 【原創】【Android】揭祕 ART 細節 ---- Garbage collection2014-07-01Android
- 蜻蜓點水說說Redis的ziplist的奧祕2020-08-06Redis