1
.绪论
本文研究的主要内容正如其题目所示是设计并编写一个先进的反病毒引擎。首先需要对这“先进”二字做一个解释,何为“先进”?众所周知,传统的反病毒软件使用的是基于特征码的静态扫描技术,即在文件中寻找特定十六进制串,如果找到,就可判定文件感染了某种病毒。但这种方法在当今病毒技术迅猛发展的形势下已经起不到很好的作用了。原因我会在以下的章节中具体描述。因此本文将不对杀毒引擎中的特征码扫描和病毒代码清除模块做分析。我们要讨论的是为应付先进的病毒技术而必需的两大反病毒技术--虚拟机和实时监控技术。具体什么是虚拟机,什么是实时监控,我会在相应的章节中做详尽的介绍。这里我要说明的一点是,这两项技术虽然在前人的工作中已有所体现(被一些国内外先进的反病毒厂家所使用),但出于商业目的,这些技术并没有被完全公开,所以你无论从书本文献还是网路上的资料中都无法找到关于这些技术的内幕。而我会在相关的章节中剖析大量的程序源码(主要是2.4节中的一个完整的虚拟机源码)或是逆向工程代码(3.3.3节和3.4.3节中三个我逆向工程的某著名反病毒软件的实时监控驱动程序及客户程序的反汇编代码),并同时公布一些我个人挖掘的操作系统内部未公开的机制和数据结构。下面就开始进入正题。
目录
1.1
背景
1.2当今病毒技术的发展状况
1.2.1系统核心态病毒
1.2.2驻留病毒
1.2.3截获系统操作
1.2.4加密变形病毒
1.2.5反跟踪/反虚拟执行病毒
1.2.6直接API调用
1.2.7病毒隐藏
1.2.8病毒特殊感染法
1.1
背景
本文涉及的两个主要技术,也是当今反病毒界使用的最为先进的技术中的两个,究竟是作何而用的呢?首先说说虚拟机技术,它主要是为查杀加密变形病毒而设计的。简单地来说,所谓虚拟机并不是个虚拟的机器,说得更合适一些应该是个虚拟CPU(用软件实现的CPU),只不过病毒界都这么叫而已。它的作用主要是模拟INTEL X86 CPU的工作过程来解释执行可执行代码,与真正的CPU一样能够取指,译码并执行相应机器指令规定的操作。当然什么是加密变形病毒,它们为什么需要被虚拟执行以及怎样虚拟执行等问题会在合适的章节中得到解答。再说另一个重头戏--实时监控技术,它的用处更为广泛,不仅局限于查杀病毒。被实时监控的对象也很多,如中断(Intmon),页面错误(Pfmon),磁盘访问(Diskmon)等等。用于杀毒的监控主要是针对文件访问,在你要对一个文件进行访问时,实时监控会先检查文件是否为带毒文件,若是,则由用户选择是清除病毒还是取消此次操作请求。这样就给了用户一个相对安全的执行环境。但同时,实时监控会使系统性能有所下降,不少杀毒软件的用户都抱怨他们的实时监控让系统变得奇慢无比而且不稳定。这就给我们的设计提出了更高的要求,即怎样在保证准确拦截文件操作的同时,让实时监控占用的系统资源更少。我会在病毒实时监控一节中专门讨论这个问题。这两项技术在国内外先进的反病毒厂家的产品中都有使用,虽然它们的源代码没有公开,但我们还是可以通过逆向工程的方法来窥视一下它们的设计思路。其实你用一个十六进制编辑器来打开它们的可执行文件,也许就会看到一些没有剥掉的调试符号、变量名字或输出信息,这些蛛丝马迹对于理解代码的意图大有裨益。同时,在反病毒软件的安装目录中后缀为.VXD或.SYS就是执行实时监控的驱动程序,可以拿来逆向一下(参看我在后面分析驱动源代码中的讨论)。相信至此,我们对这两项技术有了一个大体的了解。后面我们将深入到技术的细节中去。
1.2
当今病毒技术的发展状况
要讨论怎样反病毒,就必须从病毒技术本身的讨论开始。正是所谓“知己知彼,百战不殆”。其实,我认为目前规定研究病毒技术属于违法行为存在着很大的弊端。很难想象一个毫无病毒写作经验的人会成为杀毒高手。据我了解,目前国内一些著名反病毒软件公司的研发队伍中不乏病毒写作高手。只不过他们将同样的技术用到了正道上,以‘毒’攻‘毒’。所以我希望这篇论文能起到抛砖引玉的作用,期待着有更多的人会将病毒技术介绍给大众。当今的病毒与DOS和WIN3.1时代下的从技术角度上看有很多不同。我认为最大的转变是:引导区病毒减少了,而脚本型病毒开始泛滥。原因是在当今的操作系统下直接改写磁盘的引导区会有一定的难度(DOS则没有保护,允许调用INT13直接写盘),而且引导区的改动很容易被发现,所以很少有人再写了;而脚本病毒以其传播效率高且容易编写而深得病毒作者的青睐。当然由于这两种病毒用我上面说过的基于特征码的静态扫描技术就可以查杀,所以不在我们的讨论之列。我要讨论的技术主要来自于二进制外壳型病毒(感染文件的病毒),并且这些技术大都和操作系统底层机制或386以上CPU的保护模式相关,所以值得研究。大家都知道DOS下的外壳型病毒主要感染16位的COM或EXE文件,由于DOS没有保护,它们能够轻松地进行驻留,减少可用内存(通过修改MCB链),修改系统代码,拦截系统服务或中断。而到了WIN9X和WINNT/2000时代,想写个运行其上的32位WINDOWS病毒绝非易事。由于页面保护,你不可能修改系统的代码页。由于I/O许可位图中的规定,你也不能进行直接端口访问。在WINDOWS中你不可能象在DOS中那样通过截获INT21H来拦截所有文件操作。总之,你以一个用户态程序运行,你的行为将受到操作系统严格的控制,不可能再象DOS下那样为所欲为了。另外值得一提的是,WINDOWS下采用的可执行文件格式和DOS下的EXE截然不同(普通程序采用PE格式,驱动程序采用LE),所以病毒的感染文件的难度增大了(PE和LE比较复杂,中间分了若干个节,如果感染错了,将导致文件不能继续执行)。因为当今病毒的新技术太多,我不可能将它们逐一详细讨论,于是就选取了一些重要并具有代表性的在本章的各小节中进行讨论。
1.2.1
系统核心态病毒
在介绍什么是系统核心态病毒之前,有必要讨论一下核心态与用户态的概念。其实只要随便翻开一本关于386保护模式汇编程序设计的教科书,都可以找到对这两个概念的讲述。386及以上的CPU实现了4个特权级模式(WINDOWS只用到了其中两个),其中特权级0(Ring0)是留给操作系统代码,设备驱动程序代码使用的,它们工作于系统核心态;而特权极3(Ring3)则给普通的用户程序使用,它们工作在用户态。运行于处理器核心态的代码不受任何的限制,可以自由地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问(此时处理器状态和控制标志寄存器EFLAGS中的IOPL通常为0,指明当前可以进行直接I/O的最低特权级别是Ring0)。以上的讨论只限于保护模式操作系统,象DOS这种实模式操作系统则没有这些概念,其中的所有代码都可被看作运行在核心态。既然运行在核心态有如此之多的优势,那么病毒当然没有理由不想得到Ring0。处理器模式从Ring3向Ring0的切换发生在控制权转移时,有以下两种情况:访问调用门的长转移指令CALL,访问中断门或陷阱门的INT指令。具体的转移细节由于涉及复杂的保护检查和堆栈切换,不再赘述,请参阅相关资料。现代的操作系统通常使用中断门来提供系统服务,通过执行一条陷入指令来完成模式切换,在INTEL X86上这条指令是INT,如在WIN9X下是INT30(保护模式回调),在LINUX下是INT80,在WINNT/2000下是INT2E。用户模式的服务程序(如系统DLL)通过执行一个INTXX来请求系统服务,然后处理器模式将切换到核心态,工作于核心态的相应的系统代码将服务于此次请求并将结果传给用户程序。下面就举例子说明病毒进入系统核心态的方法。
在WIN9X下进程虚拟地址空间中映射共享系统代码的部分(3G--4G)中除了最上面4M页表有页面保护外其它地方可由用户程序读写。如果你用Softice(系统级调试器)的PAGE命令查看这些地址的页属性,则你会惊奇地发现U RW位,这说明这些地址可从用户态直接读出或写入。这意味着任何一个用户程序都能够在其运行过程中恶意或无意地破坏操作系统代码页。由此病毒就可以在GDT(全局描述符表),LDT(局部描述符表)中随意构造门描述符并借此进入核心态。当然,也不一定要借助门描述,还有许多方法可以得到Ring0。据我所知的方法就不下10余种之多,如通过调用门(Callgate),中断门(Intgate),陷阱门(Trapgate),异常门(Fault),中断请求(IRQs),端口(Ports),虚拟机管理器(VMM),回调(Callback),形式转换(Thunks),设备IO控制(DeviceIOControl),API函数(SetThreadContext) ,中断2E服务(NTKERN.VxD)。由于篇幅的限制我不可能将所有的方法逐一描述清楚,这里我仅选取最具有代表性的CIH病毒1.5版开头的一段代码。
人们常说CIH病毒运用了VXD(虚拟设备驱动)技术,其实它本身并不是VXD。只不过它利用WIN9X上述漏洞,在IDT(中断描述符表)中构造了一个DPL(段特权级)为3的中断门(意味着可以从Ring3下执行访问该中断门的INT指令),并使描述符指向自己私有地址空间中的一个需要工作在Ring0下的函数地址。这样一来CIH就可以通过简单的执行一条INTXX指令(CIH选择使用INT3,是为了使同样接挂INT3的系统调试器Softice无法正常工作以达到反跟踪的目的)进入系统核心态,从而调用系统的VMM和VXD服务。以下是我注释的一段CIH1.5的源代码:
; *************************************
; *
修改IDT以求得核心态特权级 *
; *************************************
push eax
sidt [esp-02h]
;取得IDT表基地址
pop ebx
add ebx, HookExceptionNumber*08h+04h
;ZF = 0
cli
;读取修改系统数据时先禁止中断
mov ebp, [ebx]
mov bp, [ebx-04h]
;取得原来的中断入口地址
lea esi, MyExceptionHook-@1[ecx]
;取得需要工作在Ring0的函数的偏移地址
push esi
mov [ebx-04h], si
shr esi, 16
mov [ebx+02h], si
;设置为新的中断入口地址
pop esi
; *************************************
; *
产生一个异常来进入Ring0 *
; *************************************
int HookExceptionNumber
;产生一个异常
|
当然,后面还有恢复原来中断入口地址和异常处理帧的代码。
刚才所讨论的技术仅限于WIN9X,想在WINNT/2000下进入Ring0则没有这么容易。主要的原因是WINNT/2000没有上述的漏洞,它们的系统代码页面(2G--4G)有很好的页保护。大于0x80000000的虚拟地址对于用户程序是不可见的。如果你用Softice的PAGE命令查看这些地址的页属性,你会发现S位,这说明这些地址仅可从核心态访问。所以想在IDT,GDT随意构造描述符,运行时修改内核是根本做不到的。所能做的仅是通过加载一个驱动程序,使用它来做你在Ring3下做不到的事情。病毒可以在它们加载的驱动中修改内核代码,或为病毒本身创建调用门(利用NT由Ntoskrnl.exe导出的未公开的系统服务KeI386AllocateGdtSelectors,KeI386SetGdtSelector,KeI386ReleaseGdtSelectors)。如Funlove病毒就利用驱动来修改系统文件(Ntoskrnl.exe,Ntldr)以绕过安全检查。但这里面有两个问题,其一是驱动程序从哪里来,现代病毒普遍使用一个称为“Drop”的技术,即在病毒体本身包含驱动程序二进制码(可以进行压缩或动态构造文件头),在病毒需要使用时,动态生成驱动程序并将它们扔到磁盘上,然后马上通过在SCM(服务控制管理器)注册并最终调用StartService来使驱动程序得以运行;其二是加载一个驱动程序需要管理员身份,普通帐号在调用上述的加载函数时会返回失败(安全子系统要检查用户的访问令牌(Token)中有无SeLoadDriverPrivilege特权),但多数用户在大多时候登录时会选择管理员身份,否则连病毒实时监控驱动也同样无法加载,所以留给病毒的机会还是很多的。
1.2.2
驻留病毒
驻留病毒是指那些在内存中寻找合适的页面并将病毒自身拷贝到其中且在系统运行期间能够始终保持病毒代码的存在。驻留病毒比那些直接感染(Direct-action)型病毒更具隐蔽性,它通常要截获某些系统操作来达到感染传播的目的。进入了核心态的病毒可以利用系统服务来达到此目的,如CIH病毒通过调用一个由VMM导出的服务VMMCALL_PageAllocate在大于0xC0000000的地址上分配一块页面空间。而处于用户态的程序要想在程序退出后仍驻留代码的部分于内存中似乎是不可能的,因为无论用户程序分配何种内存都将作为进程占用资源的一部分,一旦进程结束,所占资源将立即被释放。所以我们要做的是分配一块进程退出后仍可保持的内存。
病毒写作小组29A的成员GriYo 运用的一个技术很有创意:他通过CreateFileMappingA 和MapViewOfFile创建了一个区域对象并映射它的一个视口到自己的地址空间中去,并把病毒体搬到那里,由于文件映射所在的虚拟地址处于共享区域(能够被所有进程看到,即所有进程用于映射共享区内虚拟地址的页表项全都指向相同的物理页面),所以下一步他通过向Explorer.exe中注入一段代码(利用WriteProcessMemory来向其它进程的地址空间写入数据),而这段代码会从Explorer.exe的地址空间中再次申请打开这个文件映射。如此一来,即便病毒退出,但由于Explorer.exe还对映射页面保持引用,所以一份病毒体代码就一直保持在可以影响所有进程的内存页面中直至Explorer.exe退出。
另外还可以通过修改系统动态连接模块(DLL)来进行驻留。WIN9X下系统DLL(如Kernel32.dll 映射至BFF70000)处于系统共享区域(2G-3G),如果在其代码段空隙中写入一小段病毒代码则可以影响其它所有进程。但Kernel32.dll的代码段在用户态是只能读不能写的。所以必须先通过特殊手段修改其页保护属性;而在WINNT/2000下系统DLL所在页面被映射到进程的私有空间(如Kernel32.dll 映射至77ED0000)中,并具有写时拷贝属性,即没有进程试图写入该页面时,所有进程共享这个页面;而当一个进程试图写入该页面时,系统的页面错误处理代码将收到处理器的异常,并检查到该异常并非访问违例,同时分配给引发异常的进程一个新页面,并拷贝原页面内容于其上且更新进程的页表以指向新分配的页。这种共享内存的优化给病毒的写作带来了一定的麻烦,病毒不能象在WIN9X下那样仅修改Kernel32.dll一处代码便可一劳永逸。它需要利用WriteProcessMemory来向每个进程映射Kernel32.dll的地址写入病毒代码,这样每个进程都会得到病毒体的一个副本,这在病毒界被称为多进程驻留或每进程驻留(Muti-Process Residence or Per-Process Residence )。
1.2.3
截获系统操作
截获系统操作是病毒惯用的伎俩。DOS时代如此,WINDOWS时代也不例外。在DOS下,病毒通过在中断向量表中修改INT21H的入口地址来截获DOS系统服务(DOS利用INT21H来提供系统调用,其中包括大量的文件操作)。而大部分引导区病毒会接挂INT13H(提供磁盘操作服务的BIOS中断)从而取得对磁盘访问的控制。WINDOWS下的病毒同样找到了钩挂系统服务的办法。比较典型的如CIH病毒就是利用了IFSMGR.VXD(可安装文件系统)提供的一个系统级文件钩子来截获系统中所有文件操作,我会在相关章节中详细讨论这个问题,因为WIN9X下的实时监控也主要利用这个服务。除此之外,还有别的方法。但效果没有这个系统级文件钩子好,主要是不够底层,会丢失一些文件操作。
其中一个方法是利用APIHOOK,钩挂API函数。其实系统中并没有现成的这种服务,有一个SetWindowsHookEx可以钩住鼠标消息,但对截获API函数则无能为力。我们能做的是自己构造这样的HOOK。方法其实很简单:比如你要截获Kernel32.dll导出的函数CreateFile,只须在其函数代码的开头(BFF7XXXX)加入一个跳转指令到你的钩子函数的入口,在你的函数执行完后再跳回来。如下图所示:
;; Target Function
(要截获的目标函数)
……
TargetFunction:
(要截获的目标函数入口)
jmp DetourFunction
(跳到钩子函数,5个字节长的跳转指令)
TargetFunction+5:
push edi
……
;; Trampoline
(你的钩子函数)
……
TrampolineFunction:
(你的钩子函数执行完后要返回原函数的地方)
push ebp
mov ebp,esp
push ebx
push esi
(以上几行是原函数入口处的几条指令,共5个字节)
jmp TargetFunction+5
(跳回原函数)
……
|
但这种方法截获的仅仅是很小一部分文件打开操作。
在WIN9X下还有一个鲜为人知的截获文件操作的办法,说起来这应该算是WIN9X的一大后门。它就是Kernel32.dll中一个未公开的叫做VxdCall0的API函数。反汇编这个函数的代码如下:
mov eax,dword ptr [esp+00000004h]
;取得服务代号
pop dword ptr [esp]
;堆栈修正
call fword ptr cs:[BFFC9004]
;通过一个调用门调用3B段某处的代码
|
如果我们继续跟踪下去,则会看到:
这是个用以陷入VWIN32.VXD的保护模式回调
有关VxdCall的详细内容,请参看Matt Pietrek的《Windows 95 System Programming Secrets》。
当服务代号为0X002A0010时,保护模式回调会陷入VWIN32.VXD中一个叫做VWIN32_Int21Dispatch的服务。这正说明了WIN9X还在依赖于MSDos,尽管微软声称WIN9X不再依赖于MSDos。调用规范如下:
my_int21h:push ecx
push eax
;类似DOS下INT21H的AX中传入的功能号
push 002A0010h
call dword ptr [ebp+a_VxDCall]
ret
|
我们可以将上面VxdCall0函数的入口处第三条远调用指令访问的Kernel32.dll数据段中用户态可写地址BFFC9004Υ娲⒌?FWORD'六个字节改为指向我们自己钩子函数的地址,并在钩子中检查传入服务号和功能号来确定是否是请求VWIN32_Int21Dispatch中的某个文件服务。著名的HPS病毒就利用了这个技术在用户态下直接截获系统中的文件操作,但这种方法截获的也仅仅是一小部分文件操作。
1.2.4
加密变形病毒
加密变形病毒是虚拟机一章的重点内容,将放到相关章节中介绍。
1.2.5
反跟踪/反虚拟执行病毒
反跟踪/反虚拟执行病毒和虚拟机联系密切,所以也将放到相应的章节中介绍。
1.2.6
直接API调用
直接API调用是当今WIN32病毒常用的手段,它指的是病毒在运行时直接定位API函数在内存中的入口地址然后调用之的一种技术。普通程序进行API调用时,编译器会将一个API调用语句编译为几个参数压栈指令后跟一条间接调用语句(这是指Microsoft编译器,Borland编译器使用JMP
DWORD PTR [XXXXXXXXh]
)形式如下:
push arg1
push arg2
……
call dword ptr[XXXXXXXXh]
|
地址XXXXXXXXh在程序映象的导入(Import Section)段中,当程序被加载运行时,由装入器负责向里面添入API函数的地址,这就是所谓的动态链接机制。病毒由于为了避免感染一个可执行文件时在文件的导入段中构造病毒体代码中用到的API的链接信息,它选择运用自己在运行时直接定位API函数地址的代码。其实这些函数地址对于操作系统的某个版本是相对固定的,但病毒不能依赖于此。现在较为流行的做法是先定位包含API函数的动态连接库的装入基址,然后在其导出段(Export Section)中寻找到需要的API地址。后面一步几乎没有难度,只要你熟悉导出段结构即可。关键在于第一步--确定DLL装入地址。其实系统DLL装入基址对于操作系统的某个版本也是固定的,但病毒为确保其稳定性仍不能依赖这一点。目前病毒大都利用一个叫做结构化异常处理的技术来捕获病毒体引发的异常。这样一来病毒就可以在一定内存范围内搜索指定的DLL(DLL使用PE格式,头部有固定标志),而不必担心会因引发页面错误而被操作系统杀掉。
由于异常处理和后面的反虚拟执行技术密切相关,所以特将结构化异常处理简单解释如下:
共有两类异常处理:最终异常处理和每线程异常处理。
其一:最终异常处理
当你的进程中无论哪个线程发生了异常,操作系统将调用你在主线程中调用SetUnhandledExceptionFilter建立的异常处理函数。你也无须在退出时拆去你安装的处理代码,系统会为你自动清除。
PUSH OFFSET FINAL_HANDLER
CALL SetUnhandledExceptionFilter
……
CALL ExitProcess
;************************************
FINAL_HANDLER:
……
;(eax=-1 reload context and continue)
MOV EAX,1
RET
;program entry point
……
;code covered by final handler
……
;code to provide a polite exit
……
;eax=1 stops display of closure box
;eax=0 enables display of the box
|
其二:每线程异常处理
FS
中的值是一个十六位的选择子,它指向包含线程重要信息的数据结构TIB,线程信息块。其的首双字节指向我们称为ERR的结构:
1st dword +0 pointer to next err structure
(
下一个err结构的指针)
2nd dword +4 pointer to own exception handler
(
当前一级的异常处理函数的地址)
|
所以异常处理是呈练状的,如果你自己的处理函数捕捉并处理了这个异常,那么当你的程序发生了异常时,操作系统就不会调用它缺省的处理函数了,也就不会出现一个讨厌的执行了非法操作的红叉。
下面是cih的异常段:
MyVirusStart:
push ebp
lea eax, [esp-04h*2]
xor ebx, ebx
xchg eax, fs:[ebx]
;交换现在的err结构和前一个结构的地址
; eax=前一个结构的地址
; fs:[0]=现在的err结构指针(在堆栈上)
call @0
@0:
pop ebx
lea ecx, StopToRunVirusCode-@0[ebx]
;你的异常处理函数的偏移
push ecx
;你的异常处理函数的偏移压栈
push eax
;前一个err结构的地址压栈
;构造err结构,记这时候的esp(err结构指针)为esp0
……
StopToRunVirusCode:
@1 = StopToRunVirusCode
xor ebx, ebx
;发生异常时系统在你的练前又加了一个err结构,
;所以要先找到原来的结构地址
mov eax, fs:[ebx]
; 取现在的err结构的地址eax
mov esp, [eax]
; 取下个结构地址即eps0到esp
RestoreSE:
;没有发生异常时顺利的回到这里,你这时的esp为本esp0
pop dword ptr fs:[ebx]
;弹出原来的前一个结构的地址到fs:0
pop eax
;弹出你的异常处理地址,平栈而已
|
1.2.7
病毒隐藏
实现进程或模块隐藏应该是一个成功病毒所必须具备的特征。在WIN9X下Kernel32.dll有一个可以使进程从进程管理器进程列表中消失的导出函数RegisterServiceProcess ,但它不能使病毒逃离一些进程浏览工具的监视。但当你知道这些工具是如何来枚举进程后,你也会找到对付这些工具相应的办法。进程浏览工具在WIN9X下大都使用一个叫做ToolHelp32.dll的动态连接库中的Process32First和Process32Next两个函数来实现进程枚举的;而在WINNT/2000里也有PSAPI.DLL导出的EnumProcess可用以实现同样之功能。所以病毒就可以考虑修改这些公用函数的部分代码,使之不能返回特定进程的信息从而实现病毒的隐藏。
但事情远没有想象中那么简单,俗话说“道高一尺,魔高一丈”,此理不谬。由于现在很多逆项工程师的努力,微软力图隐藏的许多秘密已经逐步被人们所挖掘出来。当然其中就包括WINDOWS内核使用的管理进程和模块的内部数据结构和代码。比如WINNT/2000用由ntoskrnl.exe导出的内核变量PsInitialSystemProcess所指向的进程Eprocess块双向链表来描述系统中所有活动的进程。如果进程浏览工具直接在驱动程序的帮助下从系统内核空间中读出这些数据来枚举进程,那么任何病毒也无法从中逃脱。
有关Eprocess的具体结构和功能,请参看David A.Solomon和Mark E.Russinovich的《Inside Windows2000》第三版。
1.2.8
病毒特殊感染法
对病毒稍微有些常识的人都知道,普通病毒是通过将自身附加到宿主尾部(如此一来,宿主的大小就会增加),并修改程序入口点来使病毒得到击活。但现在不少病毒通过使用特殊的感染技巧能够使宿主大小及宿主文件头上的入口点保持不变。
附加了病毒代码却使被感染文件大小不变听起来让人不可思议,其实它是利用了PE文件格式的特点:PE文件的每个节之间留有按簇大小对齐后的空洞,病毒体如果足够小则可以将自身分成几份并分别插入到每个节最后的空隙中,这样就不必额外增加一个节,因而文件大小保持不变。著名的CIH病毒正是运用这一技术的典型范例(它的大小只有1K左右)。
病毒在不修改文件头入口点的前提下要想获得控制权并非易事:入口点不变意味着程序是从原程序的入口代码处开始执行的,病毒必须要将原程序代码中的一处修改为导向病毒入口的跳转指令。原理就是这样,但其中还存在很多可讨论的地方,如在原程序代码的何处插入这条跳转指令。一些查毒工具扫描可执行文件头部的入口点域,如果发现它指向的地方不正常,即不在代码节而在资源节或重定位节中,则有理由怀疑文件感染了某种病毒。所以刚才讨论那种病毒界称之为EPO(入口点模糊)的技术可以很好的对付这样的扫描,同时它还是反虚拟执行的重要手段。
另外值得一提的是现在不少病毒已经支持对压缩文件的感染。如Win32.crypto病毒就可以感染ZIP,ARJ,RAR,ACE,CAB 等诸多类型的压缩文件。这些病毒的代码中含有对特定压缩文件类型解压并压缩的代码段,可以先把压缩文件中的内容解压出来,然后对合适的文件进行感染,最后再将感染后文件压缩回去并同时修改压缩文件头部的校验和。目前不少反病毒软件都支持查多种格式的压缩文件,但对有些染毒的压缩文件无法杀除。原因我想可能是怕由于某种缘故,如解压或压缩有误,校验和计算不对等,使得清除后压缩文件格式被破坏。病毒却不用对用户的文件损坏负责,所以不存在这种担心。【未完待续】
主要参考文献
David A. Solomon, Mark Russinovich
《Inside Microsoft Windows 2000》September 2000
David A. Solomon
《Inside Windows NT》 May 1998
Prasad Dabak
,Sandeep Phadke,Milind Borate 《Undocumented Windows NT》October 1999
Matt Pietrek
《Windows 95 System Programming Secrets》 March 1996
Walter Oney
《System Programming for Windows 95》 March 1996
Walter Oney
《Programming the Windows Driver Model》 1999
陆麟 《WINDOWS9X文件读写Internal》2001