史萊姆論壇

史萊姆論壇 (http://forum.slime.com.tw/)
-   程式 & 網頁設計技術文件 (http://forum.slime.com.tw/f138.html)
-   -   VB6逆向工程淺析(1-5全) (http://forum.slime.com.tw/thread175377.html)

psac 2006-05-25 03:58 PM

VB6逆向工程淺析(1-5全)
 
訊息來源:[OCN]破解組織論壇(ocn.e5v.com/bbs1)
nnd,前幾天跟蹤一個vb程序算法,幾天沒進展。找了一堆資料,給大家共享!
==================================================

前言

VB的編譯有兩種方式,一種是P-Code方式,一種是本地機程式碼。P_Code方式是VB
從早期版本保留下來的,也是比較難逆向的一種。而本地機程式碼方式編譯的程序已經
和VC很接近了。這裡只探討以本地機程式碼方式編譯的程序。由於微軟在VB的實現方面
的資料很少,使人覺得VB的逆向比較難。其實掌握一些規律,VB的程序還是很容易
入門的。
這裡所提到的知識都是個人的經驗,沒有什麼官方的資料作為依據。所以錯誤
之處難免,如果你發現什麼錯誤,歡迎指正。


1. 從簡單變數的實現開始

一個VB簡單變數的表示至少需要12個字元。通常前2個字元是表示檔案類型訊息的。
從第灌水限制節到第8個字元並不總是使用到,實際上很少被使用。我們不妨先叫它
輔助檔案類型訊息。從第9個字元開始就是真正的變數的值了。這裡有可能儲存於一個游標
值,也可能是資料,具體是什麼取決於變數檔案類型。
另一個值得注意的事實是VB的記憶體是以4個字元對齊的。即使你使用一個字元,
那至少也要4個字元來表示。而且編譯器只啟始化它需要的那些字元,剩餘的字元
可能是隨機資料。下面我們將會看到這些。


想弄明白編譯器在內部怎麼實現的,最好的方法就是編一段程序跟蹤執行看看。
我編寫的程式碼如下:

Dim a, i As Byte
Dim b, j As Integer
Dim c, k As Long
Dim d, l As Boolean
Dim e, m As String
Dim f, n As Date
Dim g, o As Double
Dim h, p As Single

a = &H30
b = 330
c = 66000
d = True
e = "hello"
f = Now
g = 3.1415
h = 1.27

i = a
j = b
k = c
l = d
m = e
n = f
o = g
p = h
這段程式碼在VB的預設值設定(速度最佳化)下編譯。然後用od反彙編出來如下:
去掉了部分無關內容,其餘的我在這段程式碼的實現裡做了註釋:

00401B02 MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaVa>; MSVBVM60.__vbaVarMove
00401B08 XOR EDI,EDI
00401B0A MOV EBX,2
00401B0F MOV DWORD PTR SS:][EBP-DC],EDI
00401B15 LEA EDX,DWORD PTR SS:[EBP-DC]
00401B1B LEA ECX,DWORD PTR SS:[EBP-4C]
00401B1E MOV DWORD PTR SS:[EBP-28],EDI
00401B21 MOV DWORD PTR SS:[EBP-4C],EDI
00401B24 MOV DWORD PTR SS:[EBP-5C],EDI
00401B27 MOV DWORD PTR SS:[EBP-6C],EDI
00401B2A MOV DWORD PTR SS:[EBP-7C],EDI
00401B2D MOV DWORD PTR SS:[EBP-8C],EDI
00401B33 MOV DWORD PTR SS:[EBP-9C],EDI
00401B39 MOV DWORD PTR SS:[EBP-AC],EDI
00401B3F MOV DWORD PTR SS:[EBP-BC],EDI
00401B45 MOV DWORD PTR SS:[EBP-CC],EDI

00401B4B MOV DWORD PTR SS:[EBP-D4],30 //30h = &H30
00401B55 MOV DWORD PTR SS:[EBP-DC],EBX //EBX = 2: integer型
00401B5B CALL ESI ; <&MSVBVM60.__vbaVarMove>

**** a =&H30 即[ebp-4c]

00401B5D LEA EDX,DWORD PTR SS:[EBP-DC]
00401B63 LEA ECX,DWORD PTR SS:[EBP-5C] //變數b
00401B66 MOV DWORD PTR SS:[EBP-D4],14A //14Ah = 330
00401B70 MOV DWORD PTR SS:[EBP-DC],EBX //EBX = 2: integer型
00401B76 CALL ESI
**** b = 330 即[ebp-5c]

00401B78 LEA EDX,DWORD PTR SS:[EBP-DC]
00401B7E LEA ECX,DWORD PTR SS:[EBP-6C]
00401B81 MOV DWORD PTR SS:[EBP-D4],101D0 //101D0 = 66000
00401B8B MOV DWORD PTR SS:[EBP-DC],3 //3:long型
00401B95 CALL ESI

**** c = 66000 即[ebp-6c]

00401B97 LEA EDX,DWORD PTR SS:[EBP-DC]
00401B9D LEA ECX,DWORD PTR SS:[EBP-7C]
00401BA0 MOV DWORD PTR SS:[EBP-D4],-1 //-1 TRUE
00401BAA MOV DWORD PTR SS:[EBP-DC],0B //12:boolean型
00401BB4 CALL ESI

**** d = TRUE 即[ebp-7c]

00401BB6 LEA EDX,DWORD PTR SS:[EBP-DC]
00401BBC LEA ECX,DWORD PTR SS:[EBP-8C]
00401BC2 MOV DWORD PTR SS:[EBP-D4],工程1.00401948 ; UNICODE "hello" //"hello"
00401BCC MOV DWORD PTR SS:[EBP-DC],8 //8:string型
00401BD6 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarCo>; MSVBVM60.__vbaVarCopy

**** e = "hello" 即][ebp-8c]

00401BDC LEA EAX,DWORD PTR SS:[EBP-CC]
00401BE2 PUSH EAX
00401BE3 CALL DWORD PTR DS:[<&MSVBVM60.#546>] //Now
00401BE9 LEA EDX,DWORD PTR SS:[EBP-CC] //Now
00401BEF LEA ECX,DWORD PTR SS:[EBP-9C]
00401BF5 CALL ESI

**** f = Now 即[ebp-9c]

00401BF7 MOV EBX,5
00401BFC LEA EDX,DWORD PTR SS:[EBP-DC]
00401C02 LEA ECX,DWORD PTR SS:[EBP-AC]
00401C08 MOV DWORD PTR SS:[EBP-D4],C083126F
00401C12 MOV DWORD PTR SS:[EBP-D0],400921CA //3.1415
00401C1C MOV DWORD PTR SS:[EBP-DC],EBX //EBX = 5 :double型
00401C22 CALL ESI

**** g = 3.1415 即[ebp-ac]

00401C24 LEA EDX,DWORD PTR SS:[EBP-DC]
00401C2A LEA ECX,DWORD PTR SS:[EBP-BC]
00401C30 MOV DWORD PTR SS:[EBP-D4],851EB852
00401C3A MOV DWORD PTR SS:[EBP-D0],3FF451EB //1.27
00401C44 MOV DWORD PTR SS:[EBP-DC],EBX //EBX = 5 :double型
00401C4A CALL ESI

// h = 1.27

00401C4C LEA ECX,DWORD PTR SS:[EBP-4C]
00401C4F PUSH ECX
00401C50 CALL DWORD PTR DS:[<&MSVBVM60.__vbaUI1Va>; MSVBVM60.__vbaUI1Var
//取byte, al中

**** i = a
00401C56 LEA EDX,DWORD PTR SS:][EBP-5C]
00401C59 PUSH EDX
00401C5A CALL DWORD PTR DS:[<&MSVBVM60.__vbaI2Var>; MSVBVM60.__vbaI2Var
//取integer, ax中

**** j = b
00401C60 LEA EAX,DWORD PTR SS:][EBP-6C]
00401C63 PUSH EAX
00401C64 CALL DWORD PTR DS:[<&MSVBVM60.__vbaI4Var>; MSVBVM60.__vbaI4Var
//取long, eax中

**** k = c
00401C6A LEA ECX,DWORD PTR SS:][EBP-7C]
00401C6D PUSH ECX
00401C6E CALL DWORD PTR DS:[<&MSVBVM60.__vbaBoolV>; MSVBVM60.__vbaBoolVar
//取boolean, ax中

**** l = d
00401C74 LEA EDX,DWORD PTR SS:][EBP-8C]
00401C7A PUSH EDX
00401C7B CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrVa>; MSVBVM60.__vbaStrVarCopy
//取string, eax中是位址

**** m = e
00401C81 MOV EDX,EAX
00401C83 LEA ECX,DWORD PTR SS:][EBP-28]
00401C86 CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrMo>; MSVBVM60.__vbaStrMove

00401C8C LEA EAX,DWORD PTR SS:][EBP-9C]
00401C92 PUSH EAX
00401C93 CALL DWORD PTR DS:[<&MSVBVM60.__vbaDateV>; MSVBVM60.__vbaDateVar
//取date, 浮點棧中

**** n = f
00401C99 LEA ECX,DWORD PTR SS:][EBP-AC]
00401C9F FSTP ST
00401CA1 PUSH ECX
00401CA2 CALL DWORD PTR DS:[<&MSVBVM60.__vbaR8Var>; MSVBVM60.__vbaR8Var
//取double, 浮點棧中

**** o = g
00401CA8 LEA EDX,DWORD PTR SS:][EBP-BC]
00401CAE FSTP ST
00401CB0 PUSH EDX
00401CB1 CALL DWORD PTR DS:<&MSVBVM60.__vbaR4Var>; MSVBVM60.__vbaR4Var
00401CB7 FSTP ST
//取single, 浮點棧中

**** p = h

==========================================
先總結一下:byte 和 integer一樣,內部都是使用integer檔案類型表示,single和double一樣,內部都是使用double檔案類型表示。date佔用8個字元。boolean檔案類型佔用2個字元&HFFFF表示TRUE,而0表示FALSE.
這些簡單變數在記憶體中的表示如下:前4個字元含有變數檔案類型,接著4個字元(意義還不清楚),我們不妨先叫他輔助檔案類型訊息,過了這8個字元就是真正的變數資料了。
如變數a的記憶體表示如下:63F3C4 : 02 00 00 00
63F3C8 : 00 00 00 00
63F3CC : 30 00 00 00
可以看到,63F3CC處是真正的變數值。如果是字串串,這個位置可以看到一個游標。


簡單變數裡沒有提到的檔案類型是Currency,Decimal,這些你可以自己實際觀察一下。關於簡單變數就總結這些,不知道你看懂了沒有。我沒有辦法把機器搬上來讓你看。所以還是那句話:如果你想弄明白我所講的到底是怎麼一回事,就把上面那段程序編譯一下,然後用ollydbg跟蹤一下看看。

在本文即將結束時要提到的一個事實是:VB並不是強檔案類型語言。例如你可以把一個boolean檔案類型的變數賦值給一個整數,然後再列印出這個整數的值,你可以看到TRUE的輸出為-1,而FALSE的輸出為0。當然在你決定這樣做的時候你要保證兩個變數所佔記憶體空間大小一樣或者被賦值的變數的空間要大於所賦值的變數的空間。否則你可能會得到一個溢位錯誤。你可以試試把date檔案類型的變數賦值給一個integer檔案類型的變數
2. 複雜變數的內部實現

這裡所提到的複雜變數(我自己的叫法 ),是指枚舉,陣列和記錄檔案類型的變數。


1) 枚舉檔案類型的實現

先定義一個枚舉檔案類型如下:
Enum myweek
sun
mon
tues
wednes
thurs
fri
satur
End Enum
然後再編寫一段使用枚舉檔案類型的程式碼:
Dim a As myweek
Dim b As Integer

a = sun
b = a
Print b

預設值設定編譯這段程式碼,接著我們看看編譯器產生了什麼。
; 37 : Dim a As myweek
; 38 : Dim b As Integer
; 39 :
; 40 : a = sun
; 41 : b = a

xor ecx, ecx // a = sun ,即 a = 0
call DWORD PTR __imp_@__vbaI2I4 // b = a

; 42 : Print b

push eax // b
push esi
push OFFSET FLAT:___vba@006255A0
call DWORD PTR __imp____vbaPrintObj //Print

***************************************************
可以看出,枚舉檔案類型在程式碼裡是直接用常量數值替代的。

psac 2006-05-25 03:59 PM

2) 陣列檔案類型的實現

陣列的概念比較複雜,為了研究方便,這裡只討論一維陣列,並且不是嵌套的。
先看看靜態陣列的定義與實現。

程式碼:
Dim a(3 To 6) As Integer

反彙編程式碼:

004019FF PUSH 2
00401A01 LEA EAX,DWORD PTR SS:[EBP-2C] // 陣列變數
00401A04 XOR ESI,ESI
00401A06 PUSH 工程1.00401694 // 指向程式碼段
00401A0B PUSH EAX
00401A0C MOV DWORD PTR SS:[EBP-34],ESI
00401A0F CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryConstruct2>] // 構造一個陣列

指行到這裡時看[ebp-2c]的內容:
0063F3E4 01 00 92 00 02 00 00 00 .?...
0063F3EC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3F4 04 00 00 00 03 00 00 00 ......

這些資料除了63F3F0處的位址是__vbaAryConstruct2函數填進去的,其餘的都是從
401694處拷貝過來的。因此__vbaAryConstruct2函數的作用可以這樣理解:先從401694
處拷貝24個字元到ebp-2c處,然後分配一塊空間,把指向新分配的空間的游標填到63F3F0
處。
那麼上面這些資料到底是什麼意思呢?看下面的分析.


00401A18 PUSH 工程1.00401A30 //指向結束位址
00401A1D LEA EDX,DWORD PTR SS:[EBP-34]
00401A20 LEA ECX,DWORD PTR SS:[EBP-2C] //要解壓縮的陣列變數
00401A23 PUSH EDX
00401A24 PUSH 0
00401A26 MOV DWORD PTR SS:[EBP-34],ECX
00401A29 CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryDestruct>] // 解壓縮一個陣列

為了弄清楚上面提到的那些記憶體資料的含義,我分別定義了不同大小不同檔案類型的陣列來比較,
下面是dump出來的典型資料:

Dim a(3 To 6)
0063F3E4 01 00 92 08 10 00 00 00 .?...
0063F3EC 00 00 00 00 2C 01 41 00 ....,A.
0063F3F4 04 00 00 00 03 00 00 00 ......

Dim a(3 To 6) As String
0063F3E4 01 00 92 01 04 00 00 00 .?...
0063F3EC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3F4 04 00 00 00 03 00 00 00 ......

Dim a(3 To 6) As Integer
0063F3E4 01 00 92 00 02 00 00 00 .?...
0063F3EC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3F4 04 00 00 00 03 00 00 00 ......

我總結的陣列變數記憶體資料的說明:
0063F3E4 處的兩個字元代表陣列的維數
0063F3E6 處的一個字元 92 代表靜態陣列
0063F3E7 處的一個字元隨著不同檔案類型的變數有不同的變化。
08 : 變體檔案類型
01 : String
00 : Integer,byte,long,single,double,date
0063F3E8 處的兩個字元表示一個陣列元素所佔的記憶體空間字元數。
0063F3EC 處的4個字元總是0,可能是為了對齊。
0063F3F0 處的兩個字元代表分配的空間的位址游標,即陣列資料。
0063F3F4 處的兩個字元代表靜態陣列元素的個數。
0063F3F8 處的兩個字元代表陣列的起始上標。

上面大概的對陣列變數的資料做了說明,為了驗證一下,再看一個3維陣列的定義:

Dim a(1 To 2, 3 To 5, 6 To 9) As Integer

0063F3D4 03 00 92 00 02 00 00 00 .?...
0063F3DC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3E4 04 00 00 00 06 00 00 00 ......
0063F3EC 03 00 00 00 03 00 00 00 ......
0063F3F4 02 00 00 00 01 00 00 00 ......

可以看出,靜態陣列的訊息在編譯時就被編碼到了程式碼段裡。
靜態陣列的構造用 __vbaAryConstruct2
靜態陣列的解壓縮用 __vbaAryDestruct

///////////////////////////////////////////////////////////

動態陣列又是怎樣實現的呢?
程式碼:
Dim a() As Date
ReDim a(2 To 5)

反彙編程式碼:
004019CF PUSH 2 //起始上標
004019D1 PUSH 5 //結束上標
004019D3 PUSH 1 //陣列維數
004019D5 LEA EAX,DWORD PTR SS:[EBP-18]
004019D8 PUSH 7 //變數檔案類型
004019DA PUSH EAX //我們重定義的陣列變數
004019DB XOR ESI,ESI
004019DD PUSH 8 //陣列元素所佔記憶體空間的字元數
004019DF PUSH 80 //動態陣列標記
004019E4 MOV DWORD PTR SS:[EBP-18],ESI
004019E7 CALL DWORD PTR DS:[<&MSVBVM60.__vbaRedim>] // ReDim
004019ED ADD ESP,1C
004019F0 MOV DWORD PTR SS:[EBP-4],ESI
004019F3 PUSH 工程1.00401A05
004019F8 LEA ECX,DWORD PTR SS:[EBP-18] //陣列變數
004019FB PUSH ECX
004019FC PUSH 0
004019FE CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryDestruct>] //解壓縮陣列

當執行到 004019ED 時,我們檢視[ebp-18]處的記憶體資料,可以看到是

0063F3F8 D0 0F 51 00 ?Q.豇

這是一個游標,我們接著 follow dword in dump,可以看到資料如下:

00510FD0 01 00 80 00 08 00 00 00 .€....
00510FD8 00 00 00 00 2C 01 41 00 ....,A.
00510FE0 04 00 00 00 02 00 00 00 ......

這個結構和靜態陣列的結構沒有什麼不同! ^_^
同時也可以看出,動態陣列是動態分配的,這和靜態陣列訊息被編譯到程式碼段裡不同。

總結:
動態陣列的ReDim(重定義)用 __vbaRedim (注:這是可變參數的函數)
動態陣列的解壓縮用 __vbaAryDestruct

///////////////////////////////////////////////////////////
再看一下常用的陣列操作:
先看兩個函數,Lbound和Ubound。它們的實現分別如下:

=====================================
__vbaLbound ;函數 Lbound ,取陣列上標下界

LEA EAX,DWORD PTR SS:[EBP-2C] ;參數1,陣列
PUSH EAX
PUSH 1 ;參數2,陣列維數
CALL DWORD PTR DS:[<&MSVBVM60.__vbaLboun>; MSVBVM60.__vbaLbound
;結果在eax中返回
=====================================
__vbaUbound ;函數 Ubound ,取陣列上標上界

LEA ECX,DWORD PTR SS:][EBP-2C] ;參數1,陣列
PUSH ECX
PUSH 1 ;參數2,陣列維數
CALL DWORD PTR DS:[<&MSVBVM60.__vbaUboun>;MSVBVM60.__vbaUbound
;結果在eax中返回
=====================================
這兩個函數再操作動態陣列時常使用,這裡先記住他們的實現方法。

還有一個常使用的函數:Erase ,這個函數用來重新啟始化靜態陣列的元素,或者
解壓縮動態陣列的儲存於空間。

LEA EAX,DWORD PTR SS:][EBP-18] ;陣列變數的位址
PUSH EAX
PUSH EDI ;0
CALL DWORD PTR DS:[<&MSVBVM60.__vbaErase>] ;函數Erase


下面編寫一段簡單的程式碼分析一下:
Dim a() As Integer
ReDim a(2 To 5)
a(2) = &HAA
Erase a

Dim b(1 To 3) As Integer
b(1) = &H55
Erase b

反彙編程式碼如下:
00401A4F PUSH 2
00401A51 LEA EAX,DWORD PTR SS:[EBP-30]
00401A54 XOR EDI,EDI
00401A56 PUSH 工程1.004016B8
00401A5B PUSH EAX
00401A5C MOV DWORD PTR SS:[EBP-18],EDI
00401A5F MOV DWORD PTR SS:[EBP-38],EDI
00401A62 CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryConstruct2>]

/////////////////////////////////////上面這段是 Dim b(1 To 3) As Integer

00401A68 PUSH 2
00401A6A PUSH 5
00401A6C PUSH 1
00401A6E LEA ECX,DWORD PTR SS:[EBP-18]
00401A71 PUSH 2
00401A73 PUSH ECX
00401A74 PUSH 2
00401A76 PUSH 80
00401A7B CALL DWORD PTR DS:[<&MSVBVM60.__vbaRedim>]

//////////////////////////////////////上面這段是 Dim a() As Integer
////////////////////////////////////// ReDim a(2 To 5)

00401A81 MOV ECX,DWORD PTR SS:[EBP-18]
00401A84 ADD ESP,1C
00401A87 CMP ECX,EDI
00401A89 JE SHORT 工程1.00401AB0
00401A8B CMP WORD PTR DS:[ECX],1
00401A8F JNZ SHORT 工程1.00401AB0
00401A91 MOV EDX,DWORD PTR DS:[ECX+14] //取出上標
00401A94 MOV EAX,DWORD PTR DS:[ECX+10]
00401A97 MOV ESI,2
00401A9C SUB ESI,EDX
00401A9E CMP ESI,EAX
00401AA0 JB SHORT 工程1.00401AAB
00401AA2 CALL DWORD PTR DS:[<&MSVBVM60.__vbaGenerateBoundsError>]
00401AA8 MOV ECX,DWORD PTR SS:[EBP-18]
00401AAB LEA EAX,DWORD PTR DS:[ESI+ESI] //乘以2,即整數所佔儲存於空間
00401AAE JMP SHORT 工程1.00401AB9
00401AB0 CALL DWORD PTR DS:[<&MSVBVM60.__vbaGenerateBoundsError>]
00401AB6 MOV ECX,DWORD PTR SS:[EBP-18]

////////////////////////////////////////上面這段包含了對產生的動態陣列的檢驗

00401AB9 MOV EDX,DWORD PTR DS:[ECX+C] //取出變數a的值位址
00401ABC MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaErase>]
00401AC2 MOV WORD PTR DS:[EDX+EAX],0AA //對第一個元素賦值

////////////////////////////////////////到這裡是 a(2) = &HAA

00401AC8 LEA EAX,DWORD PTR SS:[EBP-18]
00401ACB PUSH EAX
00401ACC PUSH EDI
00401ACD CALL ESI

////////////////////////////////////////到這裡是 Erase a

00401ACF MOV ECX,DWORD PTR SS:[EBP-24]//取得變數b的值位址
00401AD2 LEA EAX,DWORD PTR SS:[EBP-38]
00401AD5 LEA EDX,DWORD PTR SS:[EBP-30]
00401AD8 PUSH EAX
00401AD9 MOV WORD PTR DS:[ECX],55

////////////////////////////////////////到這裡是 b(1) = &H55
00401ADE PUSH EDI
00401ADF MOV DWORD PTR SS:[EBP-38],EDX
00401AE2 CALL ESI

////////////////////////////////////////到這裡是 Erase b

00401AE4 MOV DWORD PTR SS:[EBP-4],EDI
00401AE7 PUSH 工程1.00401B09
00401AEC MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaAryDestruct>]
00401AF2 LEA ECX,DWORD PTR SS:[EBP-18]
00401AF5 XOR EDI,EDI
00401AF7 PUSH ECX
00401AF8 PUSH EDI
00401AF9 CALL ESI //釋a

00401AFB LEA EAX,DWORD PTR SS:[EBP-38]
00401AFE LEA EDX,DWORD PTR SS:[EBP-30]
00401B01 PUSH EAX
00401B02 PUSH EDI
00401B03 MOV DWORD PTR SS:[EBP-38],EDX
00401B06 CALL ESI //解壓縮b

關於陣列就先分析這些,後面還要分析的是 For Each .. Next語句和 Array函數,
不過這兩個都比較複雜些,先放到後面去。


3)結構檔案類型的實現

VB的記錄變數其實就是一些子域的順序排列。

這句話怎麼理解呢?看看下面的程式碼:

Private Type daterec
year As Integer
month As String * 3
day As Integer
End Type

Dim a As daterec

a.year = 2004
a.month = "Jan"
a.day = 21

反彙編程式碼如下:
004019DF XOR EAX,EAX
004019E1 LEA ECX,DWORD PTR SS:[EBP-1E]
004019E4 MOV DWORD PTR SS:[EBP-20],EAX
004019E7 PUSH 工程1.004014CC //"Jan"
004019EC MOV DWORD PTR SS:[EBP-1C],EAX
004019EF PUSH ECX
004019F0 PUSH 3
004019F2 MOV WORD PTR SS:[EBP-18],AX
004019F6 MOV WORD PTR SS:[EBP-20],7D4 // a.year = 2004
004019FC CALL DWORD PTR DS:[<&MSVBVM60.__vbaLsetFixstr>]//a.month = "Jan"
00401A02 MOV WORD PTR SS:[EBP-18],15 // a.day = 15

執行到這裡時看記憶體[ebp-20]:
0063F3F0 D4 07 4A 00 61 00 6E 00 ?J.a.n.
0063F3F8 15 00 6F 17 F4 F8 63 00 .o豇c.

從 0063F3F0 到 0063F3F9 就是記錄變數 a 的值了。
3. VB6的控制結構

下面要提到的vb的控制結構語句有:if語句,select case語句,while語句,do語句,
for語句。

1)if語句

if語句的典型語法是:

If 條件1 Then
語句塊1
ElseIf 條件2 Then
語句塊2
Else
語句塊3
End If
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
程式碼:
Dim a //注意:這裡定義的是變體變數。
//如果不用變體變數,編譯器將最佳化掉太多的程式碼。
//當你熟悉變體檔案類型後,其他的將很容易分析 ^_^
a = 5

If a < 5 Then
MsgBox ("a < 5")
ElseIf a = 5 Then
MsgBox ("a = 5")
Else
MsgBox ("a > 5")
End If
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
反彙編程式碼:
00401A72 XOR ESI,ESI
00401A74 MOV EDI,5
00401A79 MOV DWORD PTR SS:[EBP-74],ESI
00401A7C LEA EDX,DWORD PTR SS:[EBP-74]
00401A7F LEA ECX,DWORD PTR SS:[EBP-24]
00401A82 MOV DWORD PTR SS:[EBP-24],ESI
00401A85 MOV DWORD PTR SS:[EBP-34],ESI ----> 這些變數是為 MsgBox 使用的
00401A88 MOV DWORD PTR SS:[EBP-44],ESI |
00401A8B MOV DWORD PTR SS:[EBP-54],ESI |
00401A8E MOV DWORD PTR SS:[EBP-64],ESI ---
00401A91 MOV DWORD PTR SS:[EBP-6C],EDI // 5
00401A94 MOV DWORD PTR SS:[EBP-74],2 //整數檔案類型
00401A9B CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]//賦值
//到這裡 a = 5 //[ebp-24]
00401AA1 LEA EAX,DWORD PTR SS:[EBP-24]
00401AA4 LEA ECX,DWORD PTR SS:[EBP-74]
00401AA7 MOV EBX,8002
00401AAC PUSH EAX
00401AAD PUSH ECX
00401AAE MOV DWORD PTR SS:[EBP-6C],EDI // 5
00401AB1 MOV DWORD PTR SS:[EBP-74],EBX // 8002
00401AB4 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarTstLt>] // a < 5 ?
00401ABA TEST AX,AX
00401ABD JE SHORT 工程2.00401AE4 //不小於則跳走

00401ABF MOV ECX,80020004
00401AC4 MOV EAX,0A
00401AC9 MOV DWORD PTR SS:[EBP-5C],ECX
00401ACC MOV DWORD PTR SS:[EBP-64],EAX
00401ACF MOV DWORD PTR SS:[EBP-4C],ECX
00401AD2 MOV DWORD PTR SS:[EBP-54],EAX
00401AD5 MOV DWORD PTR SS:[EBP-3C],ECX
00401AD8 MOV DWORD PTR SS:[EBP-44],EAX
00401ADB MOV DWORD PTR SS:[EBP-6C],工程2.004016C4; UNICODE "a < 5"
00401AE2 JMP SHORT 工程2.00401B63

00401AE4 LEA ECX,DWORD PTR SS:[EBP-24]
00401AE7 LEA EDX,DWORD PTR SS:[EBP-74]
00401AEA PUSH ECX
00401AEB PUSH EDX
00401AEC MOV DWORD PTR SS:[EBP-6C],EDI // 5
00401AEF MOV DWORD PTR SS:[EBP-74],EBX // 8002
00401AF2 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarTstEq>]// a = 5 ?
00401AF8 TEST AX,AX
00401AFB MOV ECX,80020004
00401B00 MOV EAX,0A
00401B05 MOV DWORD PTR SS:[EBP-5C],ECX
00401B08 MOV DWORD PTR SS:[EBP-64],EAX
00401B0B MOV DWORD PTR SS:[EBP-4C],ECX
00401B0E MOV DWORD PTR SS:[EBP-54],EAX
00401B11 MOV DWORD PTR SS:[EBP-3C],ECX
00401B14 MOV DWORD PTR SS:[EBP-44],EAX
00401B17 JE SHORT 工程2.00401B5C //不相等則跳走

00401B19 LEA EDX,DWORD PTR SS:[EBP-74]
00401B1C LEA ECX,DWORD PTR SS:[EBP-34]
00401B1F MOV DWORD PTR SS:[EBP-6C],工程2.004016D4; UNICODE "a = 5"
00401B26 MOV DWORD PTR SS:[EBP-74],8
00401B2D CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarDup>]
00401B33 LEA EAX,DWORD PTR SS:[EBP-64]
00401B36 LEA ECX,DWORD PTR SS:[EBP-54]
00401B39 PUSH EAX
00401B3A LEA EDX,DWORD PTR SS:[EBP-44]
00401B3D PUSH ECX
00401B3E PUSH EDX
00401B3F LEA EAX,DWORD PTR SS:[EBP-34]
00401B42 PUSH ESI
00401B43 PUSH EAX
00401B44 CALL DWORD PTR DS:[<&MSVBVM60.#595>]; MSVBVM60.rtcMsgBox
00401B4A LEA ECX,DWORD PTR SS:[EBP-64]
00401B4D LEA EDX,DWORD PTR SS:[EBP-54]
00401B50 PUSH ECX
00401B51 LEA EAX,DWORD PTR SS:[EBP-44]
00401B54 PUSH EDX
00401B55 LEA ECX,DWORD PTR SS:[EBP-34]
00401B58 PUSH EAX
00401B59 PUSH ECX
00401B5A JMP SHORT 工程2.00401B9D
// 其他條件滿足則執行這裡
00401B5C MOV DWORD PTR SS:[EBP-6C],工程2.004016E4; UNICODE "a > 5"
00401B63 LEA EDX,DWORD PTR SS:[EBP-74]
00401B66 LEA ECX,DWORD PTR SS:[EBP-34]
00401B69 MOV DWORD PTR SS:[EBP-74],8
00401B70 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarDup>]
00401B76 LEA EDX,DWORD PTR SS:[EBP-64]
00401B79 LEA EAX,DWORD PTR SS:[EBP-54]
00401B7C PUSH EDX
00401B7D LEA ECX,DWORD PTR SS:[EBP-44]
00401B80 PUSH EAX
00401B81 PUSH ECX
00401B82 LEA EDX,DWORD PTR SS:[EBP-34]
00401B85 PUSH ESI
00401B86 PUSH EDX
00401B87 CALL DWORD PTR DS:[<&MSVBVM60.#595>]; MSVBVM60.rtcMsgBox

流程大概如此。上面出現了一些VB內部函數,如果你不太熟悉那些函數的意義,可以
到DFCG上下載一份我整理的vb內部函數。
===========================================================

psac 2006-05-25 04:00 PM

2)select case 語句

select case 語句的語法如下:

Select Case 測試陳述式
Case 陳述式列表1
語句塊1
Case 陳述式列表2
語句塊2
......
Case Else
語句塊n
End Select
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
程式碼:
Dim a, b
a = 5
Select Case a
Case 3
b = "3"
Case 5
b = "5"
Case Else
b = "0"
End Select
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
反彙編程式碼(速度最佳化編譯)
00401A2F XOR ESI,ESI // 用來啟始化變數
00401A31 LEA EDX,DWORD PTR SS:[EBP-44]
00401A34 MOV DWORD PTR SS:[EBP-44],ESI
00401A37 LEA ECX,DWORD PTR SS:[EBP-24]
00401A3A MOV DWORD PTR SS:[EBP-24],ESI
00401A3D MOV DWORD PTR SS:[EBP-34],ESI
00401A40 MOV DWORD PTR SS:[EBP-54],ESI
00401A43 MOV DWORD PTR SS:[EBP-3C],5 // 5
00401A4A MOV DWORD PTR SS:[EBP-44],2 // 檔案類型
00401A51 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]
// a = 5 //[ebp-24]

00401A57 MOV EBX,DWORD PTR DS:[<&MSVBVM60.__vbaVarCopy>]
00401A5D LEA EDX,DWORD PTR SS:[EBP-24]
00401A60 LEA ECX,DWORD PTR SS:[EBP-54]
00401A63 CALL EBX // 產生一個臨時變數 //[ebp-54]
// Select Case a


00401A65 MOV EDI,DWORD PTR DS:[<&MSVBVM60.__vbaVarTstEq>
00401A6B LEA EAX,DWORD PTR SS:][EBP-54]
00401A6E LEA ECX,DWORD PTR SS:[EBP-44]
00401A71 PUSH EAX
00401A72 PUSH ECX
00401A73 MOV DWORD PTR SS:[EBP-3C],3 // 3
00401A7A MOV DWORD PTR SS:[EBP-44],8002
00401A81 CALL EDI
00401A83 TEST AX,AX
00401A86 JE SHORT 工程2.00401A91 // 不等於3則跳
// Case 3
00401A88 MOV DWORD PTR SS:[EBP-3C],工程2.004016B4 // '3'
00401A8F JMP SHORT 工程2.00401ABC

00401A91 LEA EDX,DWORD PTR SS:[EBP-54]
00401A94 LEA EAX,DWORD PTR SS:[EBP-44]
00401A97 PUSH EDX
00401A98 PUSH EAX
00401A99 MOV DWORD PTR SS:[EBP-3C],5 // 5
00401AA0 MOV DWORD PTR SS:[EBP-44],8002
00401AA7 CALL EDI
00401AA9 TEST AX,AX
00401AAC MOV DWORD PTR SS:[EBP-3C],工程2.004016BC // '5'
00401AB3 JNZ SHORT 工程2.00401ABC // 不等於5則跳
// Case 5

00401AB5 MOV DWORD PTR SS:[EBP-3C],工程2.004016C4 // '0'
00401ABC LEA EDX,DWORD PTR SS:[EBP-44]
00401ABF LEA ECX,DWORD PTR SS:[EBP-34]
00401AC2 MOV DWORD PTR SS:[EBP-44],8 // String檔案類型
00401AC9 CALL EBX // 為變數 b 賦值
// Case Else
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
當然這段程式碼在逆向時也可以用if語句來表達。逆向出來的不如select case
語句那樣直觀。在我看來用if語句表達應該是這樣的:
dim a,b
if a = 3 then
b = '3'
else if a = 5 then
b = '5'
else
b = '0'
end if
===========================================================
3)while語句

while語句的典型語法是:

While 條件
語句塊
Wend
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
程式碼:
Dim a, b
a = 3
b = 0
While a > 0
b = b + a
a = a - 1
Wend
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
反彙編程式碼(速度最佳化編譯)
00401A1F MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]
00401A25 XOR EDI,EDI // 啟始化變數
00401A27 MOV EBX,2
00401A2C MOV DWORD PTR SS:[EBP-54],EDI
00401A2F LEA EDX,DWORD PTR SS:[EBP-54]
00401A32 LEA ECX,DWORD PTR SS:[EBP-24]
00401A35 MOV DWORD PTR SS:[EBP-24],EDI
00401A38 MOV DWORD PTR SS:[EBP-34],EDI
00401A3B MOV DWORD PTR SS:[EBP-44],EDI
00401A3E MOV DWORD PTR SS:[EBP-4C],3 // 3
00401A45 MOV DWORD PTR SS:[EBP-54],EBX // integer檔案類型
00401A48 CALL ESI
// a = 3 //[ebp-24]

00401A4A LEA EDX,DWORD PTR SS:[EBP-54]
00401A4D LEA ECX,DWORD PTR SS:[EBP-34]
00401A50 MOV DWORD PTR SS:[EBP-4C],EDI // 0
00401A53 MOV DWORD PTR SS:[EBP-54],EBX // integer檔案類型
00401A56 CALL ESI
// b = 0 //[ebp-34]

00401A58 MOV EDI,DWORD PTR DS:[<&MSVBVM60.__vbaVarAdd>]
00401A5E MOV EBX,DWORD PTR DS:[<&MSVBVM60.__vbaVarSub>]

00401A64 LEA EAX,DWORD PTR SS:[EBP-24]
00401A67 LEA ECX,DWORD PTR SS:[EBP-54] // 0
00401A6A PUSH EAX
00401A6B PUSH ECX
00401A6C MOV DWORD PTR SS:[EBP-4C],0
00401A73 MOV DWORD PTR SS:[EBP-54],8002
00401A7A CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarTstGt>]
00401A80 TEST AX,AX
00401A83 JE SHORT 工程2.00401ABF // a 不大於0則結束循環
// while a > 0

00401A85 LEA EDX,DWORD PTR SS:[EBP-34]
00401A88 LEA EAX,DWORD PTR SS:[EBP-24]
00401A8B PUSH EDX // b
00401A8C LEA ECX,DWORD PTR SS:[EBP-44]
00401A8F PUSH EAX // a
00401A90 PUSH ECX
00401A91 CALL EDI
00401A93 MOV EDX,EAX // b + a
00401A95 LEA ECX,DWORD PTR SS:[EBP-34] // b
00401A98 CALL ESI
// b = b + a

00401A9A LEA EDX,DWORD PTR SS:[EBP-24] // a
00401A9D LEA EAX,DWORD PTR SS:[EBP-54]
00401AA0 PUSH EDX
00401AA1 LEA ECX,DWORD PTR SS:[EBP-44]
00401AA4 PUSH EAX
00401AA5 PUSH ECX
00401AA6 MOV DWORD PTR SS:[EBP-4C],1 // 1
00401AAD MOV DWORD PTR SS:[EBP-54],2 // integer檔案類型
00401AB4 CALL EBX
00401AB6 MOV EDX,EAX // a - 1
00401AB8 LEA ECX,DWORD PTR SS:[EBP-24]
00401ABB CALL ESI
// a = a - 1

00401ABD JMP SHORT 工程2.00401A64 //注意:這裡往回跳
// Wend
===========================================================
4)do語句

這個語句的格式比較多,典型的格式如下:

Do
語句塊
Loop Until 循環條件
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Dim a, b
a = 3
b = 0
Do
b = a + b
a = a - 1
Loop Until a <= 0
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
反彙編程式碼(速度最佳化)
00401A1F MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]
00401A25 XOR EDI,EDI
00401A27 MOV EBX,2
00401A2C MOV DWORD PTR SS:[EBP-54],EDI
00401A2F LEA EDX,DWORD PTR SS:[EBP-54]
00401A32 LEA ECX,DWORD PTR SS:[EBP-24]
00401A35 MOV DWORD PTR SS:[EBP-24],EDI
00401A38 MOV DWORD PTR SS:[EBP-34],EDI
00401A3B MOV DWORD PTR SS:[EBP-44],EDI
00401A3E MOV DWORD PTR SS:[EBP-4C],3 //3
00401A45 MOV DWORD PTR SS:[EBP-54],EBX //integer
00401A48 CALL ESI
// a = 3 //[ebp-24]

00401A4A LEA EDX,DWORD PTR SS:[EBP-54]
00401A4D LEA ECX,DWORD PTR SS:[EBP-34]
00401A50 MOV DWORD PTR SS:[EBP-4C],EDI //0
00401A53 MOV DWORD PTR SS:[EBP-54],EBX //integer
00401A56 CALL ESI
// b = 0 //[ebp-34]

00401A58 MOV EBX,DWORD PTR DS:[<&MSVBVM60.__vbaVarAdd>]
00401A5E LEA EAX,DWORD PTR SS:[EBP-24] // a
00401A61 LEA ECX,DWORD PTR SS:[EBP-34] // b
00401A64 PUSH EAX
00401A65 LEA EDX,DWORD PTR SS:[EBP-44]
00401A68 PUSH ECX
00401A69 PUSH EDX
00401A6A CALL EBX
00401A6C MOV EDX,EAX
00401A6E LEA ECX,DWORD PTR SS:[EBP-34]
00401A71 CALL ESI
// b = a + b

00401A73 LEA EAX,DWORD PTR SS:[EBP-24] // a
00401A76 LEA ECX,DWORD PTR SS:[EBP-54]
00401A79 PUSH EAX
00401A7A LEA EDX,DWORD PTR SS:[EBP-44]
00401A7D PUSH ECX
00401A7E PUSH EDX
00401A7F MOV DWORD PTR SS:[EBP-4C],1 // 1
00401A86 MOV DWORD PTR SS:[EBP-54],2 // integer
00401A8D CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarSub>]
00401A93 MOV EDX,EAX
00401A95 LEA ECX,DWORD PTR SS:[EBP-24] // a
00401A98 CALL ESI
// a = a - 1
00401A9A LEA EAX,DWORD PTR SS:[EBP-24]
00401A9D LEA ECX,DWORD PTR SS:[EBP-54]
00401AA0 PUSH EAX
00401AA1 PUSH ECX
00401AA2 MOV DWORD PTR SS:[EBP-4C],EDI // 0
00401AA5 MOV DWORD PTR SS:[EBP-54],8002
00401AAC CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarTstLe>]
00401AB2 TEST AX,AX
00401AB5 JE SHORT 工程2.00401A5E
loop until a <= 0
===========================================================
5)for語句

for語句的典型語法如下:

for 循環變數 = 初值 to 終值 [step 步長]
循環體
next 循環變數
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
程式碼:
a = 0
For i = 0 To 100 Step 2
a = a + i
Next i
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
反彙編程式碼(速度最佳化)
00401A42 MOV EBX,DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]
00401A48 XOR ESI,ESI
00401A4A MOV EDI,2
00401A4F MOV DWORD PTR SS:[EBP-54],ESI
00401A52 LEA EDX,DWORD PTR SS:[EBP-54]
00401A55 LEA ECX,DWORD PTR SS:[EBP-34]
00401A58 MOV DWORD PTR SS:[EBP-24],ESI
00401A5B MOV DWORD PTR SS:[EBP-34],ESI
00401A5E MOV DWORD PTR SS:[EBP-44],ESI
00401A61 MOV DWORD PTR SS:[EBP-64],ESI
00401A64 MOV DWORD PTR SS:[EBP-74],ESI
00401A67 MOV DWORD PTR SS:[EBP-84],ESI
00401A6D MOV DWORD PTR SS:[EBP-94],ESI
00401A73 MOV DWORD PTR SS:[EBP-4C],ESI //0
00401A76 MOV DWORD PTR SS:[EBP-54],EDI //integer
00401A79 CALL EBX
// a = 0 //[ebp-34]

00401A7B LEA EAX,DWORD PTR SS:[EBP-54]
00401A7E LEA ECX,DWORD PTR SS:[EBP-64]
00401A81 PUSH EAX // 增量
00401A82 LEA EDX,DWORD PTR SS:[EBP-74]
00401A85 PUSH ECX // 終值
00401A86 LEA EAX,DWORD PTR SS:[EBP-94]
00401A8C PUSH EDX // 初值
00401A8D LEA ECX,DWORD PTR SS:[EBP-84]
00401A93 PUSH EAX //臨時終值
00401A94 LEA EDX,DWORD PTR SS:[EBP-24]
00401A97 PUSH ECX //臨時增量
00401A98 PUSH EDX // 循環變數
00401A99 MOV DWORD PTR SS:[EBP-4C],EDI // 2
00401A9C MOV DWORD PTR SS:[EBP-54],EDI // integer
00401A9F MOV DWORD PTR SS:[EBP-5C],64 // 100
00401AA6 MOV DWORD PTR SS:[EBP-64],EDI // integer
00401AA9 MOV DWORD PTR SS:[EBP-6C],ESI // 0
00401AAC MOV DWORD PTR SS:[EBP-74],EDI // integer
00401AAF CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarForInit>]
//For i = 0 To 100 Step 2

00401AB5 MOV EDI,DWORD PTR DS:[<&MSVBVM60.__vbaVarAdd>]
00401ABB CMP EAX,ESI
00401ABD JE SHORT 工程2.00401AEE

00401ABF LEA EAX,DWORD PTR SS:[EBP-34] //a
00401AC2 LEA ECX,DWORD PTR SS:[EBP-24] //i
00401AC5 PUSH EAX
00401AC6 LEA EDX,DWORD PTR SS:[EBP-44]
00401AC9 PUSH ECX
00401ACA PUSH EDX
00401ACB CALL EDI
00401ACD MOV EDX,EAX
00401ACF LEA ECX,DWORD PTR SS:[EBP-34]
00401AD2 CALL EBX
// a = a + i

00401AD4 LEA EAX,DWORD PTR SS:[EBP-94] //臨時終值
00401ADA LEA ECX,DWORD PTR SS:[EBP-84] //臨時增量
00401AE0 PUSH EAX
00401AE1 LEA EDX,DWORD PTR SS:[EBP-24] // 循環變數
00401AE4 PUSH ECX
00401AE5 PUSH EDX
00401AE6 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarForNext>]
00401AEC JMP SHORT 工程2.00401ABB
// next i
===========================================================
上面出現了我定義的兩個名詞:臨時終值和臨時增量,這兩個值什麼意思呢?
也就是當__vbaVarForInit函數執行完後這兩個值將分別被賦予終值和增量的值。
從上面可以看出,__vbaVarForInit只是執行一次,以後再執行就是__vbaVarForNext
了。因此程序必須知道循環變數到那裡結束,每次步長多少。這兩個值就是儲存這些
訊息的。
當滿足循環條件時,這兩個函數都返回1,不滿足時返回0。

===========================================================

psac 2006-05-25 04:00 PM

到現在為止分析基本的程序結構應該沒問題了。在這裡把前面沒完成的工作
補一下:看看 Array函數和 For Each ... Next 語句。

程式碼:
Dim a
a = Array(5, 6, 7)
For Each x In a
Print x
Next x

編譯後的彙編程式碼:
00401AC3 XOR ESI,ESI
00401AC5 LEA EDX,DWORD PTR SS:[EBP-48]
00401AC8 PUSH ESI
00401AC9 PUSH 2
00401ACB PUSH 1
00401ACD PUSH ESI
00401ACE PUSH EDX
00401ACF PUSH 10
00401AD1 PUSH 880
00401AD6 MOV DWORD PTR SS:[EBP-24],ESI
00401AD9 MOV DWORD PTR SS:[EBP-34],ESI
00401ADC MOV DWORD PTR SS:[EBP-44],ESI
00401ADF MOV DWORD PTR SS:[EBP-48],ESI
00401AE2 MOV DWORD PTR SS:[EBP-58],ESI
00401AE5 MOV DWORD PTR SS:[EBP-68],ESI
00401AE8 MOV DWORD PTR SS:[EBP-78],ESI
00401AEB MOV DWORD PTR SS:[EBP-7C],ESI
00401AEE MOV DWORD PTR SS:[EBP-80],ESI
00401AF1 MOV DWORD PTR SS:[EBP-84],ESI
00401AF7 MOV DWORD PTR SS:[EBP-88],ESI
00401AFD CALL DWORD PTR DS:[<&MSVBVM60.__vbaRedim>] //定義一個變體檔案類型的動態陣列
4. VB內部函數
1) 自訂函數的使用

這裡我們看看和VB的內部函數有關的一些內容。
老規矩,先看一段程式碼:
Function myadd(ByVal a As Variant, b As Variant)
myadd = a + b
End Function

Sub myprint(ByVal a As Variant)
Print a
End Sub

Private Sub Command1_Click()
Dim a, b, c

a = 10
b = 20
c = myadd(a, b)
myprint c
End Sub

這段程式碼裡包含了自訂的程序,函數及函數的兩種參數傳送方式。下面的反彙編程式碼
是程序Command1_Click()的。自訂的函數和程序的反彙編程式碼和這相仿,可以字元反匯
編對比看一下。
反彙編程式碼如下,預設值方式(速度最佳化)編譯的,這次指出的是完整的彙編程式碼:

00401C50 PUSH EBP
00401C51 MOV EBP,ESP
00401C53 SUB ESP,0C

00401C56 PUSH <JMP.&MSVBVM60.__vbaExceptHandler>
00401C5B MOV EAX,DWORD PTR FS:[0]
00401C61 PUSH EAX
00401C62 MOV DWORD PTR FS:[0],ESP //安裝局部執行緒異常

00401C69 SUB ESP,5C //下面這一段和vb編譯器有關
00401C6C PUSH EBX //因為vb是關於com技術實現的
00401C6D PUSH ESI //更詳細的內容可以參考
00401C6E PUSH EDI //<<軟體加密技術內幕>>一書
00401C6F MOV DWORD PTR SS:[EBP-C],ESP
00401C72 MOV DWORD PTR SS:[EBP-8],工程2.004010B0
00401C79 MOV ESI,DWORD PTR SS:[EBP+8]
00401C7C MOV EAX,ESI
00401C7E AND EAX,1
00401C81 MOV DWORD PTR SS:[EBP-4],EAX
00401C84 AND ESI,FFFFFFFE
00401C87 PUSH ESI
00401C88 MOV DWORD PTR SS:[EBP+8],ESI //me
00401C8B MOV ECX,DWORD PTR DS:[ESI]
00401C8D CALL DWORD PTR DS:[ECX+4] ;MSVBVM60.Zombie_AddRef

00401C90 MOV EDI,DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]
00401C96 XOR EBX,EBX
00401C98 MOV DWORD PTR SS:[EBP-64],EBX
00401C9B LEA EDX,DWORD PTR SS:[EBP-64]
00401C9E LEA ECX,DWORD PTR SS:[EBP-24]
00401CA1 MOV DWORD PTR SS:[EBP-24],EBX
00401CA4 MOV DWORD PTR SS:[EBP-34],EBX
00401CA7 MOV DWORD PTR SS:[EBP-44],EBX
00401CAA MOV DWORD PTR SS:[EBP-54],EBX
00401CAD MOV DWORD PTR SS:[EBP-5C],0A //10
00401CB4 MOV DWORD PTR SS:[EBP-64],2 //integer
00401CBB CALL EDI
//a = 10
00401CBD LEA EDX,DWORD PTR SS:[EBP-64]
00401CC0 LEA ECX,DWORD PTR SS:[EBP-34]
00401CC3 MOV DWORD PTR SS:[EBP-5C],14 //20
00401CCA MOV DWORD PTR SS:[EBP-64],2 //integer
00401CD1 CALL EDI
// b = 20
00401CD3 LEA EAX,DWORD PTR SS:[EBP-54]
00401CD6 LEA ECX,DWORD PTR SS:[EBP-34]
00401CD9 PUSH EAX //存放函數的返回值
00401CDA PUSH ECX //引用參數b
00401CDB MOV ECX,DWORD PTR SS:[EBP-24]
00401CDE SUB ESP,10 //這個空間複製變數參數a
00401CE1 MOV EAX,ESP //變體檔案類型,所以要16個字元
00401CE3 MOV EDX,DWORD PTR DS:[ESI]
00401CE5 PUSH ESI //me
00401CE6 MOV DWORD PTR DS:[EAX],ECX
00401CE8 MOV ECX,DWORD PTR SS:[EBP-20]
00401CEB MOV DWORD PTR DS:[EAX+4],ECX
00401CEE MOV ECX,DWORD PTR SS:[EBP-1C]
00401CF1 MOV DWORD PTR DS:[EAX+8],ECX
00401CF4 MOV ECX,DWORD PTR SS:[EBP-18]
00401CF7 MOV DWORD PTR DS:[EAX+C],ECX
00401CFA CALL DWORD PTR DS:[EDX+6F8] //使用函數 myadd
00401D00 CMP EAX,EBX
00401D02 JGE SHORT 工程2.00401D16
00401D04 PUSH 6F8
00401D09 PUSH 工程2.00401644
00401D0E PUSH ESI
00401D0F PUSH EAX
00401D10 CALL DWORD PTR DS:[<&MSVBVM60.__vbaHresultCheckObj>]
00401D16 LEA EDX,DWORD PTR SS:[EBP-54]
00401D19 LEA ECX,DWORD PTR SS:[EBP-44]
00401D1C CALL EDI
//把結果賦值給變數c

00401D1E MOV ECX,DWORD PTR SS:[EBP-44] //c作為變數參數傳送
00401D21 SUB ESP,10 //所以這裡要分配16個字元
00401D24 MOV EAX,ESP
00401D26 MOV EDX,DWORD PTR DS:[ESI]
00401D28 PUSH ESI //me
00401D29 MOV DWORD PTR DS:[EAX],ECX
00401D2B MOV ECX,DWORD PTR SS:[EBP-40]
00401D2E MOV DWORD PTR DS:[EAX+4],ECX
00401D31 MOV ECX,DWORD PTR SS:[EBP-3C]
00401D34 MOV DWORD PTR DS:[EAX+8],ECX
00401D37 MOV ECX,DWORD PTR SS:[EBP-38]
00401D3A MOV DWORD PTR DS:[EAX+C],ECX
00401D3D CALL DWORD PTR DS:[EDX+6FC] //使用myprint
00401D43 CMP EAX,EBX
00401D45 JGE SHORT 工程2.00401D59
00401D47 PUSH 6FC
00401D4C PUSH 工程2.00401644
00401D51 PUSH ESI
00401D52 PUSH EAX
00401D53 CALL DWORD PTR DS:[<&MSVBVM60.__vbaHresultCheckObj>]


00401D59 MOV DWORD PTR SS:[EBP-4],EBX
00401D5C PUSH 工程2.00401D83 //這裡壓入返回位址
00401D61 JMP SHORT 工程2.00401D6D
00401D63 LEA ECX,DWORD PTR SS:[EBP-54]
00401D66 CALL DWORD PTR DS:[<&MSVBVM60.__vbaFreeVar>]
00401D6C RETN

00401D6D MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaFreeVar>]
00401D73 LEA ECX,DWORD PTR SS:[EBP-24]
00401D76 CALL ESI
00401D78 LEA ECX,DWORD PTR SS:[EBP-34]
00401D7B CALL ESI
00401D7D LEA ECX,DWORD PTR SS:[EBP-44]
00401D80 CALL ESI
00401D82 RETN //上面這裡解壓縮變數a,b,c

00401D83 MOV EAX,DWORD PTR SS:[EBP+8] //返回到這裡做善後處理
00401D86 PUSH EAX
00401D87 MOV EDX,DWORD PTR DS:[EAX]
00401D89 CALL DWORD PTR DS:[EDX+8] ; MSVBVM60.Zombie_Release
00401D8C MOV EAX,DWORD PTR SS:[EBP-4]
00401D8F MOV ECX,DWORD PTR SS:[EBP-14]
00401D92 POP EDI
00401D93 POP ESI
00401D94 MOV DWORD PTR FS:[0],ECX //恢復局部執行緒異常
00401D9B POP EBX
00401D9C MOV ESP,EBP
00401D9E POP EBP
00401D9F RETN 4

從這裡可以看出,在使用自程序時還要傳送一個me參數。對於函數來說,還有一個
返回結果要傳送過去。

2) VB的內部函數
VB的內部函數在反彙編程式碼中看起來和我們熟悉的函數名並不一樣。這一點跟蹤過
VB程序的人一定深有體會。大多數函數名都可以從名字上猜出來。但也有相差太多的。
我整理了一份函數列表。不是全部,但包含了大多數內部函數,應該能應付一般的套用。
函數列表在這個帖子的附件中。
VB的運算符大多是用函數實現的。這是一個好消息。這意味著我們不必費力去分析
過多的程式碼而只要能辨別出那些函數名即可。
VB的內部函數並不都是以Stdcall的方式傳送參數,儘管大部分是這樣的。分析
VB程序時要注意這點。
VB的反彙編程式碼中大部分的函數都要傳送一個存放返回值的變數,而且返回值也會
在EAX中或者浮點棧中返回。
這裡只舉一個例子。其他的可以參考我整理的列表。

這是函數instr,常用的一個:

__vbaInStrVar ;函數 InStr(起始位置,源字串串,目標字串串,比較方式)

LEA EDX,DWORD PTR SS:[EBP-24]
PUSH 1 ;起始位置,從1開始
LEA EAX,DWORD PTR SS:[EBP-34]
PUSH EDX ;被搜尋的字串串
PUSH EAX ;要搜的字串串
LEA ECX,DWORD PTR SS:[EBP-54]
PUSH 1 ;比較方式
PUSH ECX ;返回的結果
CALL DWORD PTR DS:[<&MSVBVM60.__vbaInStrVar>]
MOV EDX,EAX ;結果同時在eax中返回


3) VB的外部函數使用
VB對外部函數的使用是如何實現的呢?先看看下面的程式碼:
Private Declare Function MessageBeep Lib "user32" (ByVal wType As Long) As Long
Private Sub Command1_Click()
MessageBeep 0
End Sub
對應的反彙編程式碼如下:

00401A2F PUSH 0 //這裡壓入參數
00401A31 CALL 工程2.004016C8 //這個Call我們要繼續跟進才知道使用的什麼函數
00401A36 CALL DWORD PTR DS:[<&MSVBVM60.__vbaSetSystemError>]


004016C8 MOV EAX,DWORD PTR DS:[4022DC] //第一次使用時為0,以後就使用這裡
004016CD OR EAX,EAX //這個程序裡為0
004016CF JE SHORT 工程2.004016D3
004016D1 JMP EAX
004016D3 PUSH 工程2.004016B0 //注意這個位址,這是指向程式碼段的
//我們先看看這裡有什麼
004016B0 98 16 40 00 A4 16 40 00 ?@.?@.
//再跟進
00401698 75 73 65 72 33 32 00 00 user32..
004016A0 0C 00 00 00 4D 65 73 73 ....Mess
004016A8 61 67 65 42 65 65 70 00 ageBeep.
//是不是看到了要使用的函數了^_^
//其實不用這麼麻煩
004016D8 MOV EAX,<JMP.&MSVBVM60.DllFunctionCall>
004016DD CALL EAX //執行完這一行看eax,看到什麼了
//EAX 84936A78 Thunk to USER32.MessageBeep
004016DF JMP EAX //這裡就是真正的使用了

注意:使用的外部函數名不在程序的匯入表裡,而是在程式碼段裡。程序是使用
函數MSVBVM60.DllFunctionCall來取得外部函數的位址的。
5. VB中的異常處理

這裡我只從跟蹤的角度來談異常處理。很多人說在VB中發生異常是會跟進虛擬機裡,
在裡面打轉。其實VB的異常處理沒有那麼複雜,可以很容易的出來的。
這裡先介紹一個VB的萬能斷點:MSVBVM60.__vbaExceptHandler,VB在每個程序
的開始都要安裝一個執行緒異常處理程序,在OD中對這個函數下斷點百分百有效。需要注意
的事當你找到自己需要的地方時要及時清除這個斷點,否則會在你不期望的時候中斷

下面依舊以一段程式碼為例說明:
Dim a, b, c, d
On Error Resume Next
a = 5
b = 6
c = 0
d = b / c
Print a

到現在為止你應該能看懂下面的程式碼了。所以我只對來源碼註釋:

00401A20 PUSH EBP
00401A21 MOV EBP,ESP
00401A23 SUB ESP,18
00401A26 PUSH <JMP.&MSVBVM60.__vbaExceptHandler>
00401A2B MOV EAX,DWORD PTR FS:[0]
00401A31 PUSH EAX
00401A32 MOV DWORD PTR FS:[0],ESP
00401A39 MOV EAX,74
00401A3E CALL <JMP.&MSVBVM60.__vbaChkstk>
00401A43 PUSH EBX
00401A44 PUSH ESI
00401A45 PUSH EDI
00401A46 MOV DWORD PTR SS:[EBP-18],ESP
00401A49 MOV DWORD PTR SS:[EBP-14],工程2.00401088
00401A50 MOV EAX,DWORD PTR SS:[EBP+8]
00401A53 AND EAX,1
00401A56 MOV DWORD PTR SS:[EBP-10],EAX
00401A59 MOV ECX,DWORD PTR SS:[EBP+8]
00401A5C AND ECX,FFFFFFFE
00401A5F MOV DWORD PTR SS:[EBP+8],ECX
00401A62 MOV DWORD PTR SS:[EBP-C],0
00401A69 MOV EDX,DWORD PTR SS:[EBP+8]
00401A6C MOV EAX,DWORD PTR DS:[EDX]
00401A6E MOV ECX,DWORD PTR SS:[EBP+8]
00401A71 PUSH ECX
00401A72 CALL DWORD PTR DS:[EAX+4]

00401A75 MOV DWORD PTR SS:[EBP-4],1
00401A7C MOV DWORD PTR SS:[EBP-4],2
00401A83 PUSH -1
00401A85 CALL DWORD PTR DS:[<&MSVBVM60.__vbaOnError>]

//On Error Resume Next

00401A8B MOV DWORD PTR SS:[EBP-4],3
00401A92 MOV DWORD PTR SS:[EBP-78],5
00401A99 MOV DWORD PTR SS:[EBP-80],2
00401AA0 LEA EDX,DWORD PTR SS:[EBP-80]
00401AA3 LEA ECX,DWORD PTR SS:[EBP-30]
00401AA6 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]

// a = 5

00401AAC MOV DWORD PTR SS:[EBP-4],4
00401AB3 MOV DWORD PTR SS:[EBP-78],6
00401ABA MOV DWORD PTR SS:[EBP-80],2
00401AC1 LEA EDX,DWORD PTR SS:[EBP-80]
00401AC4 LEA ECX,DWORD PTR SS:[EBP-40]
00401AC7 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]

// b = 6

00401ACD MOV DWORD PTR SS:[EBP-4],5
00401AD4 MOV DWORD PTR SS:[EBP-78],0
00401ADB MOV DWORD PTR SS:[EBP-80],2
00401AE2 LEA EDX,DWORD PTR SS:[EBP-80]
00401AE5 LEA ECX,DWORD PTR SS:[EBP-50]
00401AE8 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]

// c = 0

00401AEE MOV DWORD PTR SS:[EBP-4],6
00401AF5 LEA EDX,DWORD PTR SS:[EBP-40]
00401AF8 PUSH EDX
00401AF9 LEA EAX,DWORD PTR SS:[EBP-50]
00401AFC PUSH EAX
00401AFD LEA ECX,DWORD PTR SS:[EBP-70]
00401B00 PUSH ECX
00401B01 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarDiv>]
00401B07 MOV EDX,EAX
00401B09 LEA ECX,DWORD PTR SS:[EBP-60]
00401B0C CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>] ; MSVBVM60.__vbaVarMove

// d = b / c

00401B12 MOV DWORD PTR SS:[EBP-4],7
00401B19 LEA EDX,DWORD PTR SS:[EBP-30]
00401B1C PUSH EDX
00401B1D MOV EAX,DWORD PTR SS:[EBP+8]
00401B20 PUSH EAX
00401B21 PUSH 工程2.004016E8
00401B26 CALL DWORD PTR DS:[<&MSVBVM60.__vbaPrintObj>]

//print a

為了不佔篇幅,刪去了後面的程式碼.看到onError時你應該小心的處理這個
程序,剛才的斷點還沒有清除吧?如果清除了在__vbaExceptHandler這個函數
上重新下個斷點.執行程序,會在這個斷點上停下來,我們跟進這個函數里.
在OD中按f8一路向下走,你應該在這裡跟飛:
660E3D3A MOV AX,WORD PTR DS:[ESI]
660E3D3D TEST AL,30
660E3D3F JE SHORT MSVBVM60.660E3D4D
660E3D41 CMP DWORD PTR DS:[EDI+14],0
660E3D45 JE SHORT MSVBVM60.660E3D4D
660E3D47 TEST BYTE PTR DS:[EDI+10],2
660E3D4B JE SHORT MSVBVM60.660E3D55
660E3D4D TEST AL,1
660E3D4F JE MSVBVM60.660E3DDB
660E3D55 PUSH ECX
660E3D56 CALL MSVBVM60.660E415C
660E3D5B PUSH DWORD PTR SS:[EBP+8]
660E3D5E MOV DWORD PTR SS:[EBP+C],EAX
660E3D61 CALL MSVBVM60.660E4121
660E3D66 TEST EAX,EAX
660E3D68 JE SHORT MSVBVM60.660E3DDB
660E3D6A CMP DWORD PTR SS:[EBP+C],0
660E3D6E JE SHORT MSVBVM60.660E3D75
660E3D70 TEST BYTE PTR DS:[ESI],1
660E3D73 JE SHORT MSVBVM60.660E3DDB
660E3D75 PUSH EDI
660E3D76 CALL MSVBVM60.66103DBF
660E3D7B CALL MSVBVM60.660CDE2E
660E3D80 XOR EAX,EAX
660E3D82 CMP DWORD PTR SS:[EBP+C],EAX
660E3D85 JNZ MSVBVM60.660E3E4B
660E3D8B TEST BYTE PTR DS:[ESI],30
660E3D8E JE SHORT MSVBVM60.660E3E04
660E3D90 CMP DWORD PTR DS:[EDI+14],EAX
660E3D93 JE SHORT MSVBVM60.660E3E04
660E3D95 TEST BYTE PTR DS:[EDI+10],2
660E3D99 JNZ SHORT MSVBVM60.660E3E04
660E3D9B PUSH EAX
660E3D9C PUSH 1
660E3D9E PUSH EAX
660E3D9F PUSH ESI
660E3DA0 PUSH EDI
660E3DA1 CALL MSVBVM60.660E3F47
660E3DA6 TEST BYTE PTR DS:[ESI],40
660E3DA9 PUSH DWORD PTR DS:[6610EE7C]
660E3DAF JE SHORT MSVBVM60.660E3DE3
660E3DB1 CALL EBX
660E3DB3 MOV ECX,DWORD PTR DS:[EDI+1C]
660E3DB6 MOV EDX,DWORD PTR DS:[ESI+18]
660E3DB9 MOVZX ECX,WORD PTR DS:[EDX+ECX*2+2]
660E3DBE MOV DWORD PTR DS:[EAX+98],ECX
660E3DC4 MOV EAX,DWORD PTR DS:[EDI+14]
660E3DC7 CMP EAX,-2
660E3DCA JE SHORT MSVBVM60.660E3DF9
660E3DCC CMP EAX,-1
660E3DCF JE SHORT MSVBVM60.660E3DEE
660E3DD1 TEST EAX,EAX
660E3DD3 JE SHORT MSVBVM60.660E3DDB
660E3DD5 PUSH EDI
660E3DD6 CALL MSVBVM60.660E408D //這裡跟飛

我們在這裡下個斷點,重新執行程序,到這裡按F7跟進.會來到下面:

660E408D PUSH EBP
660E408E MOV EBP,ESP
660E4090 PUSH ECX
660E4091 MOV EAX,DWORD PTR SS:[EBP+8]
660E4094 AND DWORD PTR SS:[EBP-4],0
660E4098 AND DWORD PTR SS:[EBP+8],0
660E409C PUSH EBX
660E409D MOV ECX,DWORD PTR DS:[EAX+1C]
660E40A0 MOV EDX,DWORD PTR DS:[EAX+C]
660E40A3 MOV DWORD PTR DS:[EAX+18],ECX
660E40A6 PUSH ESI
660E40A7 MOV ECX,DWORD PTR DS:[EDX+10]
660E40AA PUSH EDI
660E40AB MOV ESI,DWORD PTR DS:[ECX]
660E40AD TEST ESI,ESI
660E40AF JLE SHORT MSVBVM60.660E40C6
660E40B1 LEA EDI,DWORD PTR DS:[ECX+4]
660E40B4 MOV EBX,DWORD PTR DS:[EAX+14]
660E40B7 CMP EBX,DWORD PTR DS:[EDI]
660E40B9 JE SHORT MSVBVM60.660E40EA
660E40BB INC DWORD PTR SS:[EBP+8]
660E40BE ADD EDI,8
660E40C1 CMP DWORD PTR SS:[EBP+8],ESI
660E40C4 ^ JL SHORT MSVBVM60.660E40B4
660E40C6 MOVSX ECX,WORD PTR DS:[EDX+2]
660E40CA OR DWORD PTR DS:[EAX+10],2
660E40CE PUSH MSVBVM60.660E3FD3
660E40D3 PUSH DWORD PTR DS:[EAX+8]
660E40D6 LEA EAX,DWORD PTR DS:[ECX+EAX+C]
660E40DA PUSH EAX
660E40DB PUSH DWORD PTR SS:[EBP-4] //這裡就是返回位址了
660E40DE CALL MSVBVM60.66103DAC
660E40E3 POP EDI
660E40E4 POP ESI
660E40E5 POP EBX
660E40E6 LEAVE
660E40E7 RETN 4

為了驗證一下,我們繼續跟進66103dac:
66103DAC PUSH EBP
66103DAD MOV EBP,ESP
66103DAF MOV ECX,DWORD PTR SS:[EBP+14]
66103DB2 MOV EBX,DWORD PTR SS:[EBP+8] //這裡是剛才那個參數
66103DB5 MOV ESP,DWORD PTR SS:[EBP+10]
66103DB8 MOV EBP,DWORD PTR SS:[EBP+C]
66103DBB CALL ECX
66103DBD JMP EBX //返回用戶程序

注意:這裡所提到的是對用戶程序對異常做了處理的情況,否則你可能得到一個
出現錯誤對話視窗程序就結束了.




6. 沒有結束的結束

這一系列的帖子到這裡就告一段落了。工作太忙,一直斷斷續續的在寫,感謝你有耐心
看完。上面所提到的都是從語言這個角度說的。也是我分析大量VB程序的一點經驗。真正的
VB逆向工程只有這點知識遠遠不夠。這是只是說明 你複習語言特性而已。你或許應該去好好
的看看編譯原理,看看C++,看看COM的實現,看看流行的編譯器技術等等。


所有時間均為台北時間。現在的時間是 08:19 AM

Powered by vBulletin® 版本 3.6.8
版權所有 ©2000 - 2024, Jelsoft Enterprises Ltd.

『服務條款』

* 有問題不知道該怎麼解決嗎?請聯絡本站的系統管理員 *


SEO by vBSEO 3.6.1