特邀体验者
- 帖子:2994
- 注册:
2007-06-02
- 来自:
|
发表于:
2009-06-11 15:15
|
显示全部
短消息
资料
回复: 反病毒引擎设计(作者不详)Part1
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》第三版。
|