分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
本文将对当今先进的病毒/反病毒技术做全面而细致的介绍,重点当然放在了反病毒上,特别是虚拟机和实时监控技术。文中首先介绍几种当今较为流行的病毒技术,包括获取系统核心态特权级,驻留,截获系统操作,变形和加密等。然后分五节详细讨论虚拟机技术:第一节简单介绍一下虚拟机的概论;第二节介绍加密变形病毒,作者会分析两个著名变形病毒的解密子;第三节是虚拟机实现技术详解,其中会对两种不同方案进行比较,同时将剖析一个查毒用虚拟机的总体控制结构;第四节主要是对特定指令处理函数的分析;最后在第五节中列出了一些反虚拟执行技术做为今后改进的参照。论文的第三章主要介绍实时监控技术,由于win9x和winnt/2000系统机制和驱动模型不同,所以会分成两个操作系统进行讨论。其中涉及的技术很广泛:包括驱动编程技术,文件钩挂,特权级间通信等等。本文介绍的技术涉及操作系统底层机制,难度较大。所提供的代码,包括一个虚拟机C语言源代码和两个病毒实时监控驱动程序反汇编代码,具有一定的研究和实用价值。
关键字:病毒,虚拟机,实时监控
文档内容目录
1.绪 论
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病毒特殊感染法
2.虚拟机查毒
2.1虚拟机概论
2. 2加密变形病毒
2.3虚拟机实现技术详解
2.4虚拟机代码剖析
2.4.1不依赖标志寄存器指令模拟函数的分析
2.4.2依赖标志寄存器指令模拟函数的分析
2.5反虚拟机技术
3.病毒实时监控
3.1实时监控概论
3.2病毒实时监控实现技术概论
3.3WIN9X下的病毒实时监控
3.3.1实现技术详解
3.3.2程序结构与流程
3.3.3HOOKSYS.VXD逆向工程代码剖析
3.4WINNT/2000下的病毒实时监控
3.4.1实现技术详解
3.4.2程序结构与流程
3.4.3HOOKSYS.SYS逆向工程代码剖析
结论
致谢
主要参考文献
1.绪 论
本论文研究的主要内容正如其题目所示是设计并编写一个先进的反病毒引擎。首先需要对这“先进”二字做一个解释,何为“先进”?众所周知,传统的反病毒软件使用的是基于特征码的静态扫描技术,即在文件中寻找特定十六进制串,如果找到,就可判定文件感染了某种病毒。但这种方法在当今病毒技术迅猛发展的形势下已经起不到很好的作用了。原因我会在以下的章节中具体描述。因此本论文将不对杀毒引擎中的特征码扫描和病毒代码清除模块做分析。我们要讨论的是为应付先进的病毒技术而必需的两大反病毒技术--虚拟机和实时监控技术。具体什么是虚拟机,什么是实时监控,我会在相应的章节中做详尽的介绍。这里我要说明的一点是,这两项技术虽然在前人的工作中已有所体现(被一些国内外先进的反病毒厂家所使用),但出于商业目的,这些技术并没有被完全公开,所以你无论从书本文献还是网路上的资料中都无法找到关于这些技术的内幕。而我会在相关的章节中剖析大量的程序源码(主要是2.4节中的一个完整的虚拟机源码)或是逆向工程代码(3.3.3节和3.4.3节中三个我逆向工程的某著名反病毒软件的实时监控驱动程序及客户程序的反汇编代码),并同时公布一些我个人挖掘的操作系统内部未公开的机制和数据结构。另外我在文中会大量地提到或引用一些关于系统底层奥秘的大师级经典图书,这算是给喜爱系统级编程但又苦于找不到合适教材的朋友开了一份书单。下面就开始进入论文的正题。
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段某处的代码
如果我们继续跟踪下去,则会看到:
003B:XXXXXXXX int 30h ;这是个用以陷入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 等诸多类型的压缩文件。这些病毒的代码中含有对特定压缩文件类型解压并压缩的代码段,可以先把压缩文件中的内容解压出来,然后对合适的文件进行感染,最后再将感染后文件压缩回去并同时修改压缩文件头部的校验和。目前不少反病毒软件都支持查多种格式的压缩文件,但对有些染毒的压缩文件无法杀除。原因我想可能是怕由于某种缘故,如解压或压缩有误,校验和计算不对等,使得清除后压缩文件格式被破坏。病毒却不用对用户的文件损坏负责,所以不存在这种担心。
2.虚拟机查毒
2.1虚拟机概论
近些年,虚拟机,在反病毒界也被称为通用解密器,已经成为反病毒软件中最引人注目的部分,尽管反病毒者对于它的运用还远没有达到一个完美的程度,但虚拟机以其诸如"病毒指令码模拟器"和"Stryker"等多变的名称为反病毒产品的市场销售带来了光明的前景。以下的讨论将把我们带入一个精彩的虚拟技术的世界中。
首先要谈及的是虚拟机的概念和它与诸如Vmware(美国VMWARE公司生产的一款虚拟机,它支持在WINNT/2000环境下运行如Linux等其它操作系统)和WIN9X下的VDM(DOS虚拟机,它用来在32位保护模式环境中运行16实模式代码)的区别。其实这些虚拟机的设计思想是有渊源可寻的,早在上个世纪60年代IBM就开发了一套名为VM/370的操作系统。VM/370在不同的程序之间提供抢先式多任务,作法是在单一实际的硬件上模式出多部虚拟机器。典型的VM/370会话,使用者坐在电缆连接的远程终端前,经由控制程序的一个IPL命令,模拟真实机器的初始化程序装载操作,于是 一套完整的操作系统被载入虚拟机器中,并开始为使用者着手创建一个会话。这套模拟系统是如此的完备,系统程序员甚至可以运行它的一个虚拟副本,来对新版本进行除错。Vmware与此非常相似,它作为原操作系统下的一个应用程序可以为运行于其上的目标操作系统创建出一部虚拟的机器,目标操作系统就象运行在单独一台真正机器上,丝毫察觉不到自己处于Vmware的控制之下。当在Vmware中按下电源键(Power On)时,窗口里出现了机器自检画面,接着是操作系统的载入,一切都和真的一样。而WIN9X为了让多个程序共享CPU和其它硬件资源决定使用VMs(所有Win32应用程序运行在一部系统虚拟机上;而每个16位DOS程序拥有一部DOS虚拟机)。VM是一个完全由软件虚构出来的东西,以和真实电脑完全相同的方式来回应应用程序所提出的需求。从某种角度来看,你可以将一部标准的PC的结构视为一套API。这套API的元素包括硬件I/O系统,和以中断为基础的BIOS和MS-DOS。WIN9X常常以它自己的软件来代理这些传统的API元素,以便能够对珍贵的硬件多重发讯。在VM上运行的应用程序认为自己独占整个机器,它们相信自己是从真正的键盘和鼠标获得输入,并从真正的屏幕上输出。稍被加一点限制,它们甚至可以认为自己完全拥有CPU和全部内存。实现虚拟技术关键在于软件虚拟化和硬件虚拟化,下面简要介绍WIN9X下的DOS虚拟机的实现。
当Windows移往保护模式后,保护模式程序无法直接调用实模式的MS-DOS处理例程,也不能直接调用实模式的BIOS。软件虚拟化就是用来描述保护模式Windows部件是如何能够和实模式MS-DOS和BIOS彼此互动。软件虚拟化要求操作系统能够拦截企图跨越保护模式和实模式边界的调用,并且调整适当的参数寄存器后,改变CPU模式。WIN9X使用虚拟设备驱动(VXD)拦截来自保护模式的中断,通过实模式中断向量表(IVT),将之转换为实模式中断调用。做为转换的一部分,VXD必须使用置于保护模式扩展内存中的参数,生成出适当的参数,并将之放在实模式(V86)操作系统可以存取的地方。服务结束后,VXD在把结果交给扩展内存中保护模式调用端。16位DOS程序中大量的21H和13H中断调用就此解决,但其中还存在不少直接端口I/O操作,这就需要引入硬件虚拟化来解决。虚拟硬件的出现是为了在硬件中断请求线上产生中断请求,为了回应IN和OUT指令,改变特殊内存映射位置等原因。硬件虚拟化依赖于Intel 80386+的几个特性。其中一个是I/O许可掩码,使操作系统可能诱捕(Trap)对任何一个端口的所有IN/OUT指令。另一个特性是:由硬件辅助的分页机制,使操作系统能够提供虚拟内存,并拦截对内存地址的存取操作,将Video RAM虚拟化是此很好的例证。最后一个必要的特性是CPU的虚拟8086(V86)模式 ,让DOS程序象在实模式中那样地执行。
我们下面讨论用于查毒的虚拟机并不是象某些人想象的:如Vmware一样为待查可执行程序创建一个虚拟的执行环境,提供它可能用到的一切元素,包括硬盘,端口等,让它在其上自由发挥,最后根据其行为来判定是否为病毒。当然这是个不错的构想,但考虑到其设计难度过大(需模拟元素过多且行为分析要借助人工智能理论),因而只能作为以后发展的方向。我设计的虚拟机严格的说不能称之为虚拟机器,而叫做虚拟CPU,通用解密器等更为合适一些,但由于反病毒界习惯称之为虚拟机,所以在下面的讨论中我还将延续这个名称。查毒的虚拟机是一个软件模拟的CPU,它可以象真正CPU一样取指,译码,执行,它可以模拟一段代码在真正CPU上运行得到的结果。给定一组机器码序列,虚拟机会自动从中取出第一条指令操作码部分,判断操作码类型和寻址方式以确定该指令长度,然后在相应的函数中执行该指令,并根据执行后的结果确定下条指令的位置,如此循环反复直到某个特定情况发生以结束工作,这就是虚拟机的基本工作原理和简单流程。设计虚拟机查毒的目的是为了对付加密变形病毒,虚拟机首先从文件中确定并读取病毒入口处代码,然后以上述工作步骤解释执行病毒头部的解密段(decryptor),最后在执行完的结果(解密后的病毒体明文)中查找病毒的特征码。这里所谓的“虚拟”,并非是创建了什么虚拟环境,而是指染毒文件并没有实际执行,只不过是虚拟机模拟了其真实执行时的效果。这就是虚拟机查毒基本原理,具体介绍请参看后面的相关章节。
当然,虚拟执行技术使用范围远不止自动脱壳(虚拟机查毒实际上是自动跟踪病毒入口的解密子将加密的病毒体按其解密算法进行解密),它还可以应用在跨平台高级语言解释器,恶意代码分析,调试器。如刘涛涛设计的国产调试器Trdos就是完全利用虚拟技术解释执行被调试程序的每条指令,这种调试器比较起传统的断点式调试器(Debug,Softice等)具有诸多优势,如不易被被调试者察觉,断点个数没有限制等。
2.2