• 标 题:Billy Belceb 病毒编写教程for Win32 ----简单介绍
  • 作 者:onlyu
  • 时 间:2004-05-28,12:31
  • 链 接:http://bbs.pediy.com

【简要介绍】
~~~~~~~~~~~
    好了,开始把你的大脑中的16位MS-DOS编码概念,迷人的16位偏移地址,中断,驻留内存的方法...都清除掉。所有这些我们已经使用了很多年的东西,现在已经再也不用了。是的,它们确实现在用不到了。在这篇教程里面,当我说Win32,我的意思是Windows 95(normal,OSR1,OSR2),Windows 98,Windows NT或Windows 3.x+Win32s。最最明显的变化,至少在我看来是由中断变成了API,在这之前是由16位寄存器和偏移地址变到了32位的。Windows给我们开了使用其它语言代替ASM(和C)的方便之门,但是我仍然对ASM情有独钟:利用它能更好的理解一些东西和更容易的优化(hi Super!)。正如我在上面所说的,你必须使用一种新东西叫做API。你必须知道这些参数必须在堆栈中,而且调用这些API是使用的CALL。
    注:在上面我把上面所有提到的平台叫做Win32,我把Win95(它的所有版本)和Win98叫做Win9x,把Windows 2000叫做Win2k。请注意这一点。

%由16位到32位编程的改变%
~~~~~~~~~~~~~~~~~~~~~~~~
     我们现在将会使用双字(DWORD)而不是单字(WORD)了,而这个改变将会给我们一个全新的世界。在已知的CS,DS,ES和SS:FS,GS之外,我们又多了两个段。而且我们有32位寄存器如EAX,EBX,ECX,EDX,ESI,EDI,EBP和ESP。让我们来看看对这些寄存器怎么操作:假如我们要使用EAX的less significant word(简称LSW),我们该怎么做呢?这个部分可以使用AX来访问,即处理LSW。假如EAX=0000000,我们想要在它的LSW放置1234h。我们必须简单地使用一个"mov ax,1234h"就可以了。但是如果我们想要访问EAX的MSW(Most Significant Word),该怎么做呢?为了达到这个目的我们不能使用一个寄存器了:我们必须使用ROL。问题并不是在这里,它是把MSW值移到了LSW。
    当我们得到一个新语言的时候,我们总是要试的一个经典的例子:"Hello world!"  :)

%Win32中的Hello World%
~~~~~~~~~~~~~~~~~~~~~~
    它很简单,我们必须使用"MessageBoxA"这个API,所以我们用大家已经知道的"extrn"命令来定义它,把参数压栈然后调用这个API。注意这个字符串必须为ASCIIZ(ASCII,0),记住参数必须以相反的顺序压栈。

;-------从这里开始剪切----------------------------------------------------

                .386                            ; Processor (386+)
                .model flat                     ; Uses 32 bit registers

 extrn          ExitProcess:proc                ; The API it uses
 extrn          MessageBoxA:proc

;-
;利用"extrn"我们把在程序中要用到的所有API列出来。ExitProcess是我们用来把
;控制权交给操作系统的API,而MessageBoxA用来显示一个经典的Windows消息框。
;-

 .data
 szMessage       db      "Hello World!",0       ; Message for MsgBox
 szTitle         db      "Win32 rocks!",0       ; Title of that MsgBox

;------------------------------------------------------------------------------------------------------------------------------
;这里我们不能把真正病毒的数据放这里了,因为这是一个例子,我们不能
;使用它,而且又因为如果我们不在这里放置一些数据,TASM将会拒绝汇编。
;无论如何...在第一次产生你的病毒主体的时候用它放置数据。
;-

                .code                           ; Here we go!

 HelloWorld:
                push    00000000h               ; Sytle of MessageBox
                push    offset szTitle          ; Title of MessageBox
                push    offset szMessage        ; The message itself
                push    00000000h               ; Handle of owner

                call    MessageBoxA             ; The API call itself

;------------------------------------------- ; int MessageBox(                                                        
;   HWND hWnd,          // handle of owner window                       
;   LPCTSTR lpText,     // address of text in message box               
;   LPCTSTR lpCaption,  // address of title of message box            
;   UINT uType          // style of message box                       
;  );                                                                 
;                                                                     
;在调用这个API之前,我们把参数压栈,如果你还记得,堆栈使用那个迷人的
;东西叫做LIFO(后进先出Last In First Out),所以我们要按相反的顺序来
;压参数。让我们看看这个函数的每个参数的简要描述:
;
; hWnd:标志将要被创建的消息框的宿主窗口(owner window)。
;      如果这个参数是NULL,这个消息框没有宿主窗口。
; lpText:指向以空字符结尾的包含将要显示消息的字符串的指针。
; lpCaption:指向一个以空字符结尾的字符串的指针,这个字符串是这个
;        对话框的标题。如果这个参数是一个NULL,缺省的标题Error被使用。
; uType:以一些位标志来确定对话框的样式和行为。这个参数可为一些标志的组合。
;-

                push    00000000h
                call    ExitProcess

;--------------------------------------------------------------------------------------------------------------------
; VOID ExitProcess(
;   UINT uExitCode      // exit code for all threads
; );
; 这个函数在Win32环境下相当于著名的Int 20h,和Int 21h的00,4C功能等等。
; 它是关闭当前进程的简单方式,即结束程序执行。下面给出唯一的一个参数:
;
; uExitCode:标志进程退出的代码,并作为所有线程终止时的代码。使用
; GetExitCodeProcess函数来刷新这个进程的退出值。使用GetExitCodeThread
; 函数来刷新一个线程的退出值。
;-

 end HelloWorld

;-----到这里为止剪切-------------------------------------------------------

    正如你看到的,编写代码很简单。可能没有16位环境下那么简单,但是如果你考虑到32位所带给我们的优点确实很简单了。现在,既然你已经知道怎么来编写"Hello World",你就有能力来感染整个世界了;)

%Rings%
~~~~~~~
    我知道你对下面的东西很害怕,但是,正如我所演示的,它看起来没有那么难。让我们记住你必须清楚的东西:处理器有4个特权级别:Ring-0,Ring-1,Ring-2和Ring-3,越往后就有越多的限制,而病毒要是用第一个特权级别,几乎编码时没有任何限制。只要记住在迷人的DOS下面,我们总是处于Ring-0...现在想到在Win32平台下你还可以做相同的事情...好了,停止幻想了,让我们开始工作。
    Ring-3还表示"用户"级,在这个级别下,我们有很多的限制,那确实不能我们的需要。Microsoft程序员在他们发行Win95的时候犯了个错误,声称它是"无法感染"的,正如在这个操作系统卖出去之前所表明的,利用可怕的Bizatch(后来改名为Boza,但那是另外一段历史了)。他们认为这些API不能被一个病毒访问和使用,但是他们没有想到病毒编写者们的超级智慧,所以...我们可以在用户级下编写病毒,毫无疑问,你只要看看大量近期发布的新的Win32运行期病毒,它们都是Ring-3级下的...它们不差,不要误解了我,Ring-3病毒是现在有可能感染所有Win32环境下文件的病毒。它们是未来...主要是因为即将发布的Windows NT 5.0(或者Windows 2000)。我们不得不寻找能使我们的病毒(由Bizatch生成的病毒传播很差,因为它对API地址"harcoded",并且它们可能会因Windows版本的改变而改变)存活的API,而且我们可以用其它不同的方法来实现,正如我后面解释到的。
    Ring-0是另外一段历史了,和Ring-3有着很大的区别。在这个级别下我们拥有内核编码的级别,"内核(kernel)级别"。是不是很迷人啊?我们可以访问端口,放置我们还没有梦想过的代码...和原先的汇编最接近的东西。我们不使用一些已知的花招是不能直接访问一些东西的,如IDT修改,SoPinKy/29A在29A#3里发表的"Call Gate"技术,或者VMM插入,在Padania或者Fuck Harry病毒里见到的技术。当我们直接利用VxD的服务时,我们不需要API,而且它们的地址在所有Win9x系统中是被假设为相同的,所以我们"hardcode"它们。我将在fully dedicated to Ring-0这一章里面深入讨论。

%重要的东西%
~~~~~~~~~~~~
    我想无论如何我应该在这篇教程的开头放上这些,然而我知道知道总比不知道好啊:)好了,让我们来讨论Win32操作系统内部的东西。
    首先,你必须清楚一些概念。让我们从selector开始。什么是一个selector呢?相当简单,它是一个非常大的段,而且它组成了Win32的内存,也叫做平坦内存。我们可以用4G内存(4,294,967,295字节),仅仅通过使用32位地址。那所有这些内存是怎么组织的呢?看看下面的示意图:
 __________________________ 
|                          |<-------OFFSET=00000000h <-> 3FFFFFFFh
|    应用程序代码和数据    |
|__________________________| 
|                          |<-------OFFSET=40000000h <-> 7FFFFFFFh
|         共享内存         |
|__________________________|
|                          |<-------OFFSET=80000000h <-> BFFFFFFFh
|           内核           |
|__________________________|
|                          |<-------OFFSET=C0000000h <-> FFFFFFFFh
|         设备驱动         |
|__________________________|
                            结果:我们拥有4G可用内存。是不是很迷人啊?

    注意一件事情:WinNT的后两段是分开的。现在我将给出你必须知道的一些定义,其它的一些本文之外的一些概念,我假设你已经知道了。

    VA:

    VA表示Virtual Address,即某些程序的地址,但是在内存中(记住在Windows中在内存中和在磁盘上是不一样的)。

    RVA:

    RVA表示Relative Virtual Address。清楚这个概念很重要,RVA是文件在内存映射(由你或由系统)时的偏移地址。

    RAW Data:

    RAW Data是我们用来表示数据物理的存储的,也就是说,在磁盘(磁盘上的数据!=内存中的数据)上的存储。

    Virtual Data:

    Virtual Data是指那些已经被系统载入内存的数据。

    File Mapping:

    一种技术,在所有的Win32环境下都有,由一种快速的(并使用更少内存)文件操作方法和比DOS更容易理解的方法组成。所有我们在内存中改变的东西,在磁盘上也会改变。文件映射还是所有Win32环境(甚至NT)下内存之间交换信息的唯一方法。

%怎么来编译东西%
~~~~~~~~~~~~~~~~
    该死,我几乎忘记了这个:)编译一个Win32 ASM程序的通常参数是,至少在这篇教程的所有例子中,按如下(当ASM文件的名字为'program',但是没有任何扩展名):

        tasm32 /m3 /ml program,,;
        tlink32 /Tpe /aa program,program,,import32.lib
        pewrsec program.exe

    我希望足够清晰了。你还可以使用makefiles,或者建立一个bat文件来使它自动完成(就象我做的!)。