📄 反病毒引擎设计.htm
字号:
;产生一个异常<BR>当然,后面还有恢复原来中断入口地址和异常处理帧的代码。<BR><BR><BR>刚才所讨论的技术仅限于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特权),但多数用户在大多时候登录时会选择管理员身份,否则连病毒实时监控驱动也同样无法加载,所以留给病毒的机会还是很多的。
<BR><BR>1.2.2驻留病毒<BR>驻留病毒是指那些在内存中寻找合适的页面并将病毒自身拷贝到其中且在系统运行期间能够始终保持病毒代码的存在。驻留病毒比那些直接感染(Direct-action)型病毒更具隐蔽性,它通常要截获某些系统操作来达到感染传播的目的。进入了核心态的病毒可以利用系统服务来达到此目的,如CIH病毒通过调用一个由VMM导出的服务VMMCALL
_PageAllocate在大于0xC0000000的地址上分配一块页面空间。而处于用户态的程序要想在程序退出后仍驻留代码的部分于内存中似乎是不可能的,因为无论用户程序分配何种内存都将作为进程占用资源的一部分,一旦进程结束,所占资源将立即被释放。所以我们要做的是分配一块进程退出后仍可保持的内存。<BR><BR>病毒写作小组29A的成员GriYo
运用的一个技术很有创意:他通过CreateFileMappingA
和MapViewOfFile创建了一个区域对象并映射它的一个视口到自己的地址空间中去,并把病毒体搬到那里,由于文件映射所在的虚拟地址处于共享区域(能够被所有进程看到,即所有进程用于映射共享区内虚拟地址的页表项全都指向相同的物理页面),所以下一步他通过向Explorer.exe中注入一段代码(利用WriteProcessMemory来向其它进程的地址空间写入数据),而这段代码会从Explorer.exe的地址空间中再次申请打开这个文件映射。如此一来,即便病毒退出,但由于Explorer.exe还对映射页面保持引用,所以一份病毒体代码就一直保持在可以影响所有进程的内存页面中直至Explorer.exe退出。
<BR><BR>另外还可以通过修改系统动态连接模块(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 )。
<BR><BR>1.2.3截获系统操作<BR>截获系统操作是病毒惯用的伎俩。DOS时代如此,WINDOWS时代也不例外。在DOS下,病毒通过在中断向量表中修改INT21H的入口地址来截获DOS系统服务(DOS利用INT21H来提供系统调用,其中包括大量的文件操作)。而大部分引导区病毒会接挂INT13H(提供磁盘操作服务的BIOS中断)从而取得对磁盘访问的控制。WINDOWS下的病毒同样找到了钩挂系统服务的办法。比较典型的如CIH病毒就是利用了IFSMGR.VXD(可安装文件系统)提供的一个系统级文件钩子来截获系统中所有文件操作,我会在相关章节中详细讨论这个问题,因为WIN9X下的实时监控也主要利用这个服务。除此之外,还有别的方法。但效果没有这个系统级文件钩子好,主要是不够底层,会丢失一些文件操作。<BR><BR>其中一个方法是利用APIHOOK,钩挂API函数。其实系统中并没有现成的这种服务,有一个SetWindowsHookEx可以钩住鼠标消息,但对截获API函数则无能为力。我们能做的是自己构造这样的HOOK。方法其实很简单:比如你要截获Kernel32.dll导出的函数CreateFile,只须在其函数代码的开头(BFF7XXXX)加入一个跳转指令到你的钩子函数的入口,在你的函数执行完后再跳回来。如下图所示:
<BR><BR>;; Target
Function(要截获的目标函数)<BR> ……<BR> TargetFunction:(要截获的目标函数入口)<BR> jmp
DetourFunction(跳到钩子函数,5个字节长的跳转指令)<BR> TargetFunction+5:<BR> push
edi<BR> ……<BR> ;;
Trampoline(你的钩子函数)<BR> ……<BR> TrampolineFunction:(你的钩子函数执行完后要返回原函数的地方)<BR> push
ebp<BR> mov ebp,esp<BR> push ebx<BR> push
esi(以上几行是原函数入口处的几条指令,共5个字节)<BR> jmp
TargetFunction+5(跳回原函数)<BR> ……<BR> 但这种方法截获的仅仅是很小一部分文件打开操作。<BR><BR>在WIN9X下还有一个鲜为人知的截获文件操作的办法,说起来这应该算是WIN9X的一大后门。它就是Kernel32.dll中一个未公开的叫做VxdCall0的API函数。反汇编这个函数的代码如下:<BR><BR>mov
eax,dword ptr [esp+00000004h] ;取得服务代号<BR><BR>pop dword ptr [esp]
;堆栈修正<BR><BR>call fword ptr cs:[BFFC9004]
;通过一个调用门调用3B段某处的代码<BR><BR>如果我们继续跟踪下去,则会看到:<BR><BR>003B:XXXXXXXX int 30h
;这是个用以陷入VWIN32.VXD的保护模式回调<BR><BR>有关VxdCall的详细内容,请参看Matt Pietrek的《Windows
95 System Programming
Secrets》。<BR><BR>当服务代号为0X002A0010时,保护模式回调会陷入VWIN32.VXD中一个叫做VWIN32_Int21Dispatch的服务。这正说明了WIN9X还在依赖于MSDos,尽管微软声称WIN9X不再依赖于MSDos。调用规范如下:<BR><BR> my_int21h:push
ecx<BR> push eax ;类似DOS下INT21H的AX中传入的功能号<BR> push
002A0010h<BR> call dword ptr
[ebp+a_VxDCall]<BR> ret<BR> 我们可以将上面VxdCall0函数的入口处第三条远调用指令访问的Kernel32.dll数据段中用户态可写地址BFFC9004Υ娲⒌?FWORD'六个字节改为指向我们自己钩子函数的地址,并在钩子中检查传入服务号和功能号来确定是否是请求VWIN32_Int21Dispatch中的某个文件服务。著名的HPS病毒就利用了这个技术在用户态下直接截获系统中的文件操作,但这种方法截获的也仅仅是一小部分文件操作。<BR><BR>1.2.4加密变形病毒<BR>加密变形病毒是虚拟机一章的重点内容,将放到相关章节中介绍。<BR><BR>1.2.5反跟踪/反虚拟执行病毒<BR>反跟踪/反虚拟执行病毒和虚拟机联系密切,所以也将放到相应的章节中介绍。<BR><BR>1.2.6直接API调用<BR>直接API调用是当今WIN32病毒常用的手段,它指的是病毒在运行时直接定位API函数在内存中的入口地址然后调用之的一种技术。普通程序进行API调用时,编译器会将一个API调用语句编译为几个参数压栈指令后跟一条间接调用语句(这是指Microsoft编译器,Borland编译器使用JMP
<BR><BR>DWORD PTR [XXXXXXXXh])形式如下:<BR><BR> push
arg1<BR> push arg2<BR> ……<BR> call dword
ptr[XXXXXXXXh]<BR>地址XXXXXXXXh在程序映象的导入(Import
Section)段中,当程序被加载运行时,由装入器负责向里面添入API函数的地址,这就是所谓的动态链接机制。病毒由于为了避免感染一个可执行文件时在文件的导入段中构造病毒体代码中用到的API的链接信息,它选择运用自己在运行时直接定位API函数地址的代码。其实这些函数地址对于操作系统的某个版本是相对固定的,但病毒不能依赖于此。现在较为流行的做法是先定位包含API函数的动态连接库的装入基址,然后在其导出段(Export
Section)中寻找到需要的API地址。后面一步几乎没有难度,只要你熟悉导出段结构即可。关键在于第一步--确定DLL装入地址。其实系统DLL装入基址对于操作系统的某个版本也是固定的,但病毒为确保其稳定性仍不能依赖这一点。目前病毒大都利用一个叫做结构化异常处理的技术来捕获病毒体引发的异常。这样一来病毒就可以在一定内存范围内搜索指定的DLL(DLL使用PE格式,头部有固定标志),而不必担心会因引发页面错误而被操作系统杀掉。<BR><BR>由于异常处理和后面的反虚拟执行技术密切相关,所以特将结构化异常处理简单解释如下:<BR><BR>共有两类异常处理:最终异常处理和每线程异常处理。<BR><BR>其一:最终异常处理<BR><BR>当你的进程中无论哪个线程发生了异常,操作系统将调用你在主线程中调用SetUnhandledExceptionFilter建立的异常处理函数。你也无须在退出时拆去你安装的处理代码,系统会为你自动清除。<BR><BR> PUSH
OFFSET FINAL_HANDLER <BR> CALL SetUnhandledExceptionFilter
<BR> ……<BR> CALL ExitProcess
<BR> ;************************************
<BR> FINAL_HANDLER: <BR> …… <BR> ;(eax=-1
reload context and continue) <BR> MOV EAX,1 <BR> RET
;program entry point <BR> ……<BR> ;code covered by
final handler <BR> ……<BR> ;code to provide a polite
exit <BR> ……<BR> ;eax=1 stops display of closure box
<BR> ;eax=0 enables display of the box
<BR> 其二:每线程异常处理<BR><BR>FS中的值是一个十六位的选择子,它指向包含线程重要信息的数据结构TIB,线程信息块。其的首双字节指向我们称为ERR的结构:<BR><BR>1st
dword +0 pointer to next err structure<BR><BR>(下一个err结构的指针) <BR><BR>2nd
dword +4 pointer to own exception
handler<BR><BR>(当前一级的异常处理函数的地址)<BR><BR>所以异常处理是呈练状的,如果你自己的处理函数捕捉并处理了这个异常,那么当你的程序发生了异常时,操作系统就不会调用它缺省的处理函数了,也就不会出现一个讨厌的执行了非法操作的红叉。<BR><BR>下面是cih的异常段:<BR><BR>MyVirusStart:<BR> push
ebp<BR> lea eax, [esp-04h*2]<BR> xor ebx,
ebx<BR> xchg eax, fs:[ebx]
;交换现在的err结构和前一个结构的地址<BR> ; eax=前一个结构的地址<BR> ;
fs:[0]=现在的err结构指针(在堆栈上)<BR> call
@0<BR> @0:<BR> pop ebx<BR> lea ecx,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -