淺談VB6逆向工程(2)

看雪資料發表於2004-12-23

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

        ***************************************************
        可以看出,列舉型別在程式碼裡是直接用常量數值代替的。
        
        
                       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 的值了。

相關文章