病毒编写教程—1

声明】
~~~~~~~
作者对因对此文档使用不当而造成的任何损失概不负责。这篇教程的目的是教会人们编写病毒和防护一些破坏力大的病毒的破坏。这篇教程仅作为教学目的。所以,如果有人利用这篇文章编写了破坏力很大的病毒,我可不负责任。如果通过这篇文章你看到我鼓励人们破坏数据的字眼,先去买副眼镜再说。

【介绍】
~~~~~~~
亲爱的同志们,大家好,你还记得Billy Belceb的病毒编写教程吗?那是一篇关于过时的MS-DOS病毒的教程。在那篇教程中,我一步一步地介绍了很多有名的DOS病毒技术的知识,而且它是为初学者写的,使他们尽快地入门。现在,我又写了一篇很酷(我希望是)的教程,但是这一次我将介绍现在计算机的新威胁,Win32病毒,毫无疑问,所有的东西都是和那个有关了。我发现现在一个完整的教程很缺,所以我曾问自己...为什么我不写一篇关于这个的教程?所以我又写出来了:)真正的在Win32病毒的先驱是VLAD组织,而用这种方式来写教程的作者是Lord Julus。但是我不会忘记那些写了很多有趣教程的人,和在Lord Julus的教程之前的所有相关东西,当然我在说JHB啦。有趣的技术是由Murkry研究的,后来Jacky Qwerty...我希望我没有忘记在Win32病毒编写(很短)史上的重要的人。注意我从来没有忘本。象在我的病毒编写教程系列里一样,我要谢谢一些音乐组织,如Blind Guardian,HammerFall,Stratovarius,Rhapsody,Marilyn Manson,Iron Maiden,Metallica,Iced Earth,RAMMS+EIN,Mago De Oz,Avalanch,Fear Factory,Korn,Hamlet 和Def Con Dos。所有这些东西营造了写一篇巨大的教程和代码的完美的氛围。
嗨,我的教程的结构已经有了很大的改变,现在我给出一个索引,几乎所有给出的代码都是我编写的,或者基于其他人的但是被我改编了的,或者有一点删改的;)但是,嗨,我已经努力的解决在我的现在已经绝种了的MS-DOS(RIP)版VWG中遇到的所有问题。
我必须向Super/29A问好,是他帮助了这篇教程的一些方面的东西,他是我的beta测试人之一,而且他已经对这篇教程贡献了一些东西。

【病毒编写中的有用的东西】
~~~~~~~~~~~~~~~~~~~~~~~~~
在开始编写病毒之前,你需要一些东西。下面是我给你推荐的程序(如果你没有足够的金钱来买它们...下载!) :)

Windows 95 或 Window NT 或 Windows 98 或 Windows 3.x + Win32s :)
TASM 5.0 包(包括TASM32 和 TLINK 32)
SoftICE 3.23+(或更好) for Win9X,和 for WinNT。
API 列表(Win32.HLP)
Windows95 DDK,Windows98 DDK,Windows2000 DDK...即所有的微软DDK和SDK。
强烈推荐Matt Pietrek关于PE文件头的文章。
Jacky Qwerty的PEWRSEC工具(在你在'.code'里添加代码时用)。
一些hash...哦,shit!它是我想要的! :)
一些电子杂志如29A(#2,#3),Xine(#2,#3,#4),VLAD(#6),DDT(#1)...
一些Windows病毒,如Win32.Cabanas,Win95.Padania,Win32.Legacy...
一些Windows病毒查杀工具(强烈推荐NODICE32)->www.eset.sk
Neuromancer,by William Gibson,它是一本好书。
毫无疑问,这篇教程!

我希望没有忘掉任何重要的东西。

【简要介绍】
~~~~~~~~~~~
好了,开始把你的大脑中的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文件来使它自动完成(就象我做的!)。

【PE文件头】
~~~~~~~~~~
这是这篇文件的最重要的一章。仔细读!

%介绍%
~~~~~~
对PE头的结构很清晰在写我们的Windows病毒很重要。下面我将给出我认为重要的东西,但是并不是关于PE文件的所有的信息,想要知道更多的东西,看看我在上面关于PE文件推荐的资料,在"有用的东西..."这一章。
_______________________________
| |<-----OFFSET=00000000h
| DOS stub |
|_______________________________|
| |<-----OFFSET=[DOS Stub+3Ch]
| PE stuff |
|_______________________________|

让我们对这两大部分进行深入的分析,让我们看看Micheal J. O'Leary的示意图:

__________________________________
| |<----Base of Image Header
| DOS compatible EXE header |--|
|__________________________________| |
| | |
| Unused | |
|__________________________________| |
| | |
| OEM identifier | |
|__________________________________| |
| | |
| OEM info | |-->Uninteresting(DOS Compatibility)
|__________________________________| |
| | |
| Offset to PE Header |----->Very interesting
|__________________________________| |
| | |
| DOS Stub program and reloc table | |
|__________________________________| |
| | |
| Unused |__|
|__________________________________|
| |
| PE header(IMAGE_FILE_HEADER) |--|
|__________________________________| |
| | |
| PE header(IMAGE_OPTIONAL_HEADER) | |
|__________________________________| |-->Very very interesting :)
| | |
| Section Table | |
|__________________________________| |
| | |
| Sections |__|
|__________________________________|

现在你已经对PE文件头已经有了一个大体的了解,确实很新奇(但也很复杂),我们的新目标。Ok,ok,你对那些东西有了一个"大体"的了解,但是,你仍然需要知道PE文件头中IMAGE_FILE_HEADER本身的内部结构。勒紧你的裤腰带!

IMAGE_FILE_HEADER
^^^^^^^^^^^^^^^^^
________________________________
| "PE/0/0" |<----+00000000h
|________________________________| Size:1 DWORD
| Machine |<----+00000004h
|________________________________| Size:1 WORD
| Number Of Section |<----+00000006h
|________________________________| Size:1 WORD
| Time Date Stamp |<----+00000008h
|________________________________| Size:1 DWORD
| pointer To Symbol Table |<----+0000000Ch
|________________________________| Size:1 DWORD
| Number Of Symbols |<----+00000010h
|________________________________| Size:1 DWORD
| Size Of Optional Header |<----+000000014h
|________________________________| Size:1 WORD
| Characteristics |<----+000000016h
|________________________________| Size:1 WORD
Total Size:18h BYTES
我将继续对IMAGE_FILE_HEADER的各个域给出简要的描述。

PE/0/0:

它是每个PE文件都有的标志,只要在编写你的感染程序的时候检查它是否存在。如果它在那儿,它就不是一个PE文件,ok?

Machine:

因为我们所使用的计算机的理想可以是一个非PC兼容的(NT对这些东西有一个开放等级,你知道的),又因为PE文件是普遍的,在这个域中是这个应用程序所编写的代码的机器类型,可以为下面的值:

IMAGE_FILE_MACHINE_I386 equ 14Ch ; Intel 386.
IMAGE_FILE_MACHINE_R3000 equ 162h ; MIPS little-endian,160h big-endian
IMAGE_FILE_MACHINE_R4000 equ 166h ; MIPS little-endian
IMAGE_FILE_MACHINE_R10000 equ 168h ; MIPS little-endian
IMAGE_FILE_MACHINE_ALPHA equ 184h ; Alpha_AXP
IMAGE_FILE_MACHINE_POWERPC equ 1F0h ; IBM PowerPC Little-Endian

Number Of Sections:

我们的感染程序的非常重要的域,它告诉我们这个文件的节(section)的个数。

Time Date Stamp:

保存了从1969.10.31 4:00到文件连结时所过的秒数。

Pointer To Symbol Table:

没意思,因为它仅仅被OBJ文件使用。

Number Of Symbols:

没意思,因为它仅仅被OBJ文件使用。

Size Of Optional header:

保存了IMAGE_OPTIONAL_HEADER域的字节数(看下面IMAGE_OPTIONAL_HEADER的描述)。

Characteristics:

这些标志给了我们关于这个文件的更多信息,对于我们所有人都没意思。

IMAGE_OPTIONAL_HEADER
^^^^^^^^^^^^^^^^^^^^^
________________________________
| Magic |<----+00000018h
|________________________________| Size:1 WORD
| Major Linker Version |<----+0000001Ah
|________________________________| Size:1 BYTE
| Minor Linker Version |<----+0000001Bh
|________________________________| Size:1 BYTE
| Size Of Code |<----+0000001Ch
|________________________________| Size:1 DWORD
| Size Of Initialized Data |<----+00000020h
|________________________________| Size:1 DWORD
| Size of UnInitialized Data |<----+00000024h
|________________________________| Size:1 DWORD
| Address Of Entry Point |<----+00000028h
|________________________________| Size:1 DWORD
| Base Of Code |<----+0000002Ch
|________________________________| Size:1 DWORD
| Base Of Data |<----+00000030h
|________________________________| Size:1 DWORD
| Image Base |<----+00000034h
|________________________________| Size:1 DWORD
| Section ALignment |<----+00000038h
|________________________________| Size:1 DWORD
| File Alignment |<----+0000003Ch
|________________________________| Size:1 DWORD
| Major Operating System Version |<----+00000040h
|________________________________| Size:1 WORD
| Minor Operating System Version |<----+00000042h
|________________________________| Size:1 WORD
| Major Image Version |<----+00000044h
|________________________________| Size:1 WORD
| Minor Image Version |<----+00000046h
|________________________________| Size:1 WORD
| Major Subsystem Version |<----+00000048h
|________________________________| Size:1 WORD
| Minor Subsystem Version |<----+0000004Ah
|________________________________| Size:1 WORD
| Reserved1 |<----+0000004Ch
|________________________________| Size:1 DWORD
| Size Of Headers |<----+00000050h
|________________________________| Size:1 DWORD
| CheckSum |<----+00000054h
|________________________________| Size:1 DWORD
| SubSystem |<----+00000058h
|________________________________| Size:1 DWORD
| Dll Characteristics |<----+0000005Eh
|________________________________| Size:1 WORD
| Size Of Stack Reserve |<----+00000060h
|________________________________| Size:1 DWORD
| Size Of Stack Commit |<----+00000064h
|________________________________| Size:1 DWORD
| Size OF Heap Reserve |<----+00000068h
|________________________________| Size:1 DWORD
| Size Of Heap Commit |<----+0000006Ch
|________________________________| Size:1 DWORD
| Loader Flags |<----+00000070h
|________________________________| Size:1 DWORD
| Number Of Rva And Sizes |<----+00000074h
|________________________________| Size:1 DWORD
Total Size:78h BYTES
(加上IMAGE_FILE_HEADER ^^^^^^^^^)

Magic:

看起来总为010Bh,实际上会使我们认为它是一种签名,没有意思。

Major Linker Version and Minor Linker Version:

产生这个文件的连结器的版本,没有意思。

Size of Code:

它是所有包含可执行代码的段的总字节数。

Size of Initialized Data:

它是所有包含初始数据的段的总大小。

Size of Uninitialized data

未初始数据不占磁盘空间,但是当系统装载这个文件的时候,它会分配一些内存(实际上是虚拟内存)。

Address of EntryPoint:

是装载器开始执行代码的地方。它是一个RVA,当系统装载这个文件的时候和image base相关。非常有意思。

Base Of Code:

是文件的code段开始的RVA。code段在内存中通常在data段之前,在PE文件头之后。这个RVA在用Microsoft连结器产生的EXE文件中通常为0x1000。Borland的TLINK32看起来把image base加到了第一个code段的RVA处,并把结果存储在这个域中。

Base Of Data:

是文件的data段开始的RVA,data段通常在内存中处于最后,在PE文件头和code段之后。

Image Base(基址):

当连结器创造一个可执行文件的时候,它会假定将会内存映射到内存的某个地址当中。这个地址被保存在这个域中,假定的一个装载地址来允许连结器进行优化。如果这个文件确实被装载器内存映射到那个地址,在它可以运行之前代码就不需要任何补丁了。在为Windows NT产生的可执行文件中,缺省的Image Base为0x10000。对DLL来说,缺省的为0x400000。在Win9X中,地址0x10000不能被用来装载EXE文件因为它在被所有进程的共享地址中。因为这个,Microsoft就把Win32的缺省Image Base改为0x400000。老的以基址0x10000进行连结而成的可执行文件在Win9x下装载将会花费更长的时间,因为装载器需要进行基址重定位。

Section Alignment:

当映射到内存中的时候,每一节要保证是这个值的一个倍数的虚拟地址作为开始地址。对于按页的时候,缺省的节对齐方式是0x1000。

File Alingnment:

在PE文件中,构成每一节的原始数据要保证从这个值的倍数开始。缺省的值为0x200字节,可能是为了保证各节总是以磁盘节(disk sectorÿ

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值