[来源]蛋的帖子
[原文网址]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------------------------------------
[结束]
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)