现在的位置: 首页 > 综合 > 正文

(转载)反病毒引擎设计 — 实时监控篇

2013年05月04日 ⁄ 综合 ⁄ 共 5494字 ⁄ 字号 评论关闭

反病毒引擎设计之实时监控篇

作者:NJUE   

文章来源:安全焦点  20040115

      

编者按:

绪论《反病毒引擎设计之虚拟机查毒篇》我们重点对虚拟机查毒进行了阐述。下面看看如何对病毒实时监控。

 

   

3.1实时监控概论

3.2病毒实时监控实现技术概论

3.3 WIN9X下的病毒实时监控

  3.3.1实现技术详解

  3.3.2程序结构与流程

  3.3.3 HOOKSYS.VXD逆向工程代码剖析

    3.3.3.1钩子函数入口代码

    3.3.3.2取得当前进程名称代码

3.3.3.3通信部分代码

3.4 Windows NT/2000下的病毒实时监控

  3.4.1实现技术详解

  3.4.2程序结构与流程

  3.4.3 HOOKSYS.SYS逆向工程代码剖析

    3.4.3.1取得当前进程名称代码

    3.4.3.2启动钩子函数工作代码

    3.4.3.3映射系统内存至用户空间代码

 

 

3.病毒实时监控

3.1 实时监控概论

实时监控技术其实并非什么新技术,早在DOS编程时代就有之。只不过那时人们没有给这项技术冠以这样专业的名字而已。早期在各大专院校机房中普遍使用的硬盘写保护软件正是利用了实时监控技术。硬盘写保护软件一般会将自身写入硬盘零磁头开始的几个扇区(由0磁头0柱面1扇最开始的64个扇区是保留的,DOS访问不到)并修改原来的主引导记录以使启动时硬盘写保护程序可以取得控制权。引导时取得控制权的硬盘写保护程序会修改INT13H的中断向量指向自身已驻留于内存中的钩子代码以便随时拦截所有对磁盘的操作。

    钩子代码的作用当然是很明显的,它主要负责由判断中断入口参数,包括功能号,磁盘目标地址等来决定该类型操作是否被允许,这样就可以实现对某一特定区域的写操作保护。后来又诞生了在此基础之上进行改进了的磁盘恢复卡之类的产品,其利用将写操作重定向至目标区域外的临时分区并保存磁盘先前状态等技术来实现允许写入并可随时恢复之功能。不管怎么改进,这类产品的核心技术还是对磁盘操作的实时监控。对此有兴趣的朋友可参看高云庆著《硬盘保护技术手册》。

        DOS
下还有许多通过驻留并截获一些有用的中断来实现某种特定目的的程序,我们通常称之为TSR(终止并等待驻留terminate-and-stay-resident,此种程序不容易编好,需要大量的关于硬件和Dos中断的知识,还要解决Dos重入,tsr程序重入等问题,搞不好就会当机)。在WINDOWS下要实现实时监控决非易事,普通用户态程序是不可能监控系统的活动的,这也是出于系统安全的考虑。HPS病毒能在用户态下直接监控系统中的文件操作其实是由于WIN9X在设计上存在漏洞。而我们下面要讨论的两个病毒实时监控(For Win9x & Windows NT/2000)都使用了驱动编程技术,让工作于系统核心态的驱动程序去拦截所有的文件访问。当然由于工作系统的不同,这两个驱动程序无论从结构还是工作原理都不尽相同的,当然程序写法和编译环境更是千差万别了,所以我们决定将其各自分成独立的一节来详细地加以讨论。

   上面提到的病毒实时监控其实就是对文件的监控,说成是文件监控应该更为合理一些。除了文件监控外,还有各种各样的实时监控工具,它们也都具有各自不同的特点和功用。这里向大家推荐一个关于Windows系统内核编程的站点:www.sysinternals.com。在其上可以找到很多实时监控小工具,比如能够监视注册表访问的Regmon(通过修改系统调用表中注册表相关服务入口),可以实时地观察TCPUDP活动的Tdimon(通过hook系统协议驱动Tcpip.sys中的dispatch函数来截获tdi clinet向其发送的请求),这些工具对于了解系统内部运作细节是很有裨益的。介绍完有关的背景情况后,我们来看看关于病毒 实时监控的具体实现技术的情况。

 

3.2病毒实时监控实现技术概论

正如上面提到的病毒实时监控其实就是一个文件监视器,它会在文件打开,关闭,清除,写入等操作时检查文件是否是病毒携带者,如果是则根据用户的决定选择不同的处理方案,如清除病毒,禁止访问该文件,删除该文件或简单地忽略。这样就可以有效地避免病毒在本地机器上的感染传播,因为可执行文件装入器在装入一个文件执行时首先会要求打开该文件,而这个请求又一定会被实时监控在第一时间截获到,它确保了每次执行的都是干净的不带毒的文件从而不给病毒以任何执行和发作的机会。以上说的仅是病毒实时监控一个粗略的工作过程,详细的说明将留到后面相应的章节中。病毒实时监控的设计主要存在以下几个难点:

 

其一 是驱动程序的编写不同于普通用户态程序的写作,其难度很大。写用户态程序时你需要的仅仅就是调用一些熟知的API函数来完成特定的目的,比如打开文件你只需调用CreateFile就可以了;但在驱动程序中你将无法使用熟悉的CreateFile。在NT/2000下你可以使用ZwCreateFileNtCreateFilenative API),但这些函数通常会要求运行在某个IRQL(中断请求级)上,如果你对如中断请求级,延迟/异步过程调用,非分页/分页内存等概念不是特别清楚,那么你写的驱动将很容易导致蓝屏死机(BSOD),Ring0下的异常将往往导致系统崩溃,因为它对于系统总是被信任的,所以没有相应处理代码去捕获这个异常。在NT下对KeBugCheckEx的调用将导致蓝屏的出现,接着系统将进行转储并随后重启。另外驱动程序的调试不如用户态程序那样方便,用象VC++那样的调试器是不行的,你必须使用系统级调试器,如softice,kd,trw等。

 

其二 是驱动程序与ring3下客户程序的通信问题。这个问题的提出是很自然的,试想当驱动程序截获到某个文件打开请求时,它必须通知位于ring3下的查毒模块检查被打开的文件,随后查毒模块还需将查毒的结果通过某种方式传给ring0下的监控程序,最后驱动程序根据返回的结果决定请求是否被允许。这里面显然存在一个双向的通信过程。写过驱动程序的人都知道一个可以用来向驱动程序发送设备I/O控制信息的API调用DeviceIoControl,它的接口在MSDN中可以找到,但它是单向的,即ring3下客户程序可以通过调用DeviceIoControl将某些信息传给ring0下的监控程序但反过来不行。既然无法找到一个现成的函数实现从ring0下的监控程序到ring3下客户程序的通信,则我们必须采用迂回的办法来间接做到这一点。为此我们必须引入异步过程调用(APC)和事件对象的概念,它们就是实现特权级间唤醒的关键所在。现在先简单介绍一下这两个概念,具体的用法请参看后面的每子章中的技术实现细节。异步过程调用是一种系统用来当条件合适时在某个特定线程的上下文中执行一个过程的机制。当向一个线程的APC队列排队一个APC时,系统将发出一个软件中断,当下一次线程被调度时,APC函数将得以运行。APC分成两种:系统创建的APC称为内核模式APC,由应用程序创建的APC称为用户模式APC。另外只有当线程处于可报警(alertable)状态时才能运行一个APC。比如调用一个异步模式的ReadFileEx时可以指定一个用户自定义的回调函数FileIOCompletionRoutine,当异步的I/O操作完成或被取消并且线程处于可报警状态时函数被调用,这就是APC的典型用法。Kernel32.dll中导出的QueueUserAPC函数可以向指定线程的队列中增加一个APC对象,因为我们写的是驱动程序,这并不是我们要的那个函数。很幸运的是在Vwin32.vxd中导出了一个同名函数QueueUserAPC,监控程序拦截到一个文件打开请求后,它马上调用这个服务排队一个ring3下客户程序中需要被唤醒的函数的APC,这个函数将在不久客户程序被调度时被调用。这种APC唤醒法适用于WIN9X,在WINNT/2000下我们将使用全局共享的事件和信号量对象来解决互相唤醒问题。有关WINNT/2000下的对象组织结构我将在3.4.2节中详细说明。NT/2000版监控程序中我们将利用KeReleaseSemaphore来唤醒一个在ring3下客户程序中等待的线程。目前不少反病毒软件已将驱动使用的查毒模块移到ring0,即如其所宣传的“主动与操作系统无缝连接”,这样做省却了通信的消耗,但把查毒模块写成驱动形式也同时会带来一些麻烦,如不能调用大量熟知的API,不能与用户实时交互,所以我们还是选择剖析传统的反病毒软件的监控程序。

 

其三 是驱动程序所占用资源问题。如果由于监控程序频繁地拦截文件操作而使系统性能下降过多,则这样的程序是没有其存在的价值的。本论文将对一个成功的反病毒软件的监控程序做彻底的剖析,其中就包含有分析其用以提高自身性能的技巧的部分,如设置历史记录,内置文件类型过滤,设置等待超时等。

 

3.3  WIN9X下的病毒实时监控

 

3.3.1 实现技术详解

WIN9X下病毒实时监控的实现主要依赖于虚拟设备驱动(VXD)编程,可安装文件系统钩挂(IFSHook),VXDring3下客户程序的通信(APC/EVENT)三项技术。

 

我们曾经提到过只有工作于系统核心态的驱动程序才具有有效地完成拦截系统范围文件操作的能力,VXD就是适用于WIN9X下的虚拟设备驱动程序,所以正可当此重任。当然,VXD的功能远不止由IFSMGR.vxd提供的拦截文件操作这一项,系统的VXDs几乎提供了所有的底层操作的接口--可以把VXD看成ring0下的DLL。虚拟机管理器本身就是一个VXD,它导出的底层操作接口一般称为VMM服务,而其他VXD的调用接口则称为VXD服务。

 

二者ring0调用方法均相同,即在INT20CD 20)后面紧跟着一个服务识别码,VMM会利用服务识别码的前半部分设备标识--Device Id找到对应的VXD,然后再利用服务识别码的后半部分在VXD的服务表(Service Table)中定位服务函数的指针并调用之:

CD 20 INT 20H

01 00 0D 00 DD VKD_Define_HotKey

 

这条指令第一次执行后,VMM将以一个同样6字节间接调用指令替换之(并不都是修正为CALL指令,有时会利用JMP指令),从而省却了查询服务表的工作:

FF 15 XX XX XX XX CALL [$VKD_Define_HotKey]

 

必须注意,上述调用方法只适用于ring0,即只是一个从VXD中调用VXD/VMM服务的ring0接口。VXD还提供了V86(虚拟8086模式),Win16保护模式,Win32保护模式调用接口。其中V86Win16保护模式的调用接口比较奇怪:

XOR DI DI

  MOV ES,DI

  MOV AX,1684 INT 2FH,AX = 1684H-->取得设备入口

  MOV BX,002A 002AH = VWIN32.VXD的设备标识

  INT 2F

  MOV AX,ES ;现在ES:DI中应该包含着入口

  OR AX,AX

  JE failure

MOV AH,00 VWIN32 服务 0 = VWIN32_Get_Version

  PUSH DS

  MOV DS,WORD PTR CS:[0002]

 

  MOV WORD PTR [lpfnVMIN32],DI

  MOV WORD PTR [lpfnVMIN32+2],ES ;保存ESDI

  CALL FAR [lpfnVMIN32] call gate(调用门)

  ES:DI指向了3B段的一个保护模式回调:

003B000003D0 INT 30 #0028:C025DB52 VWIN3204+0742

 

INT30强迫CPUring3提升到ring0,然后WIN95INT30处理函数先检查调用是否发自3B段,如是则利用引发回调的CS:IP索引一个保护模式回调表以求得一个ring0地址。本例中是0028:C025DB52 ,即所需服务VWIN32_Get_Version的入口地址。

 

VXDWin32保护模式调用接口我们在前面已经提到过。一个是DeviceIoControl,我们的ring3客户程序利用它来和监控驱动进行单向通信;另一个是VxdCall,它是Kernel32.dll的一个未公开的调用,被系统频繁使用,对我们则没有多大用处。

 

你可以参看WIN95DDK的帮助,其中对每个系统VXD提供的调用接口均有详细说明,可按照需要选择相应的服务。

 

可安装文件系统钩挂(IFSHook)就源自IFSMGR.VXD提供的一个服务IFSMgr_InstallFileSystemApiHook,利用这个服务驱动程序可以向系统注册一个钩子函数。以后系统中所有文件操作都会经过这个钩子的过滤,WIN9X下文件读写具体流程如下:

 

在读写操作进行时,首先通过未公开函数EnterMustComplete来增加MUSTCOMPLETECOUNT变量的记数,告诉操作系统本操作必须完成。该函数设置了KERNEL32模块里的内部变量来显示现在有个关键操作正在进行。有句题外话,在VMM里同样有个函数,函数名也是EnterMustComplete。那个函数同样告诉VMM,有个关键操作正在进行。防止线程被杀掉或者被挂起。

 

接下来,WIN9X进行了一个_MapHandleWithContext(又是一个未公开函数)操作。该操作本身的具体意义尚不清楚,但是其操作却是得到HANDLE所指对象的指针,并且增加了引用计数。

 

抱歉!评论已关闭.