网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 局域网DIY—专业局域网网站 >> 局域网教程 >> 网络安全 >> [安全攻防] >> 文章正文
  反病毒引擎设计          【字体:
反病毒引擎设计
作者:互联网    文章来源:www.98pc.com    点击数:    更新时间:2006-4-11    
过解析的各文件类型过滤块指针将保存在_gaFileNameFilterArra数组中,同时更新过滤项个数_gNumOfFilters 变量的值。

83003C23:保存guidll中等待查杀打开文件的APC函数地址和当前线程KTHREAD指针。

83003C13:安装系统文件钩子,启动拦截文件操作的钩子函数FilemonHookProc的工作。

83003C27:保存guidll中等待查杀关闭文件的APC函数地址和当前线程KTHREAD指针。

83003C17:卸载系统文件钩子,停止拦截文件操作的钩子函数FilemonHookProc的工作。

以上列出的IO控制代码的发出是固定,而当钩子函数启动后,还会发出一些随机的控制代码:

83003C07:驱动将打开文件链表的头元素即最先的请求打开的文件删除并插入到等待链表尾部,同时将元素的用户空间地址传送至ring3级等待查杀打开文件的APC函数中处理。

83003C0B:驱动将关闭文件链表的头元素即最先的请求关闭的文件删除并插入到备用链表尾部,同时将元素中的文件名串传送至ring3级等待查杀关闭文件的APC函数中处理

83003C1F:当查得关闭文件是病毒时,更新历史记录链表。

下面介绍钩子函数和guidll中等待查杀打开文件的APC函数协同工作流程,写文件和关闭文件的处理与之类似:

当文件请求进入钩子函数FilemonHookProc后,它先从入口参数中取得被执行的函数的代号并判断其是否为打开操作(IFSFN_OPEN 24H),若非则马上将这个IRQ向下传递,即构造入口参数并调用保存在PrevIFSHookProc中前一个钩子函数;若是则程序流程转向打开文件请求的处理分支。分支入口处首先要判断当前进程是否是我们自己,若是则必须放过去,因为查毒模块中要频繁的进行文件操作,所以拦截来自自身的文件请求将导致严重的系统死锁。接下来是从堆栈参数中取得完整的文件路径名并通过保存的文件类型过滤阵列检查其是否在拦截类型之列,如通过则进一步检查文件是否是以下几个须放过的文件之一:SYSTEM.DAT,USER.DAT,\PIPE\。然后查找历史记录链表以确定该文件是否最近曾被检查并记录过,若在历史记录链表中找到关于该文件的记录并且记录未失效即其时间戳和当前系统时间之差不得大于1F4h,则可直接从记录中读取查毒结果。至此才进入真正的检查打开文件函数_RAVCheckOpenFile,此函数入口处先从备用,等待或关闭链表头部摘得一空闲元素(_GetFreeEntry)并填充之(文件路径名域等)。接着通过一内核未公开的数据结构中的值(ring3tcb->Flags)判断可否对该文件请求排队APC。如可则将空闲元素加入打开文件链表尾部并排队一个ring3级检查打开文件函数的APC。然后调用_VWIN32_WaitSingleObject在空闲元素中保存的一个事件对象上等待ring3查毒的完成。当钩子函数挂起不久后,ring3的APC函数得到执行:它会向驱动发出一IO控制码为83003C07的请求以取得打开文件链表头元素即保存最先提交而未决的文件请求,驱动可以将内核空间中元素的虚拟地址直接传给它而不必考虑将之重新映射。实际上由于WIN9X内核空间没有页保护因而ring3级程序可以直接读写之。接着它调用RsEngine.dll中的fnScanOneFile函数进行查毒并在元素中设置查毒结果位,完毕后再对元素中保存的事件对象调用SetEvent唤醒在此事件上等待的钩子函数。被唤醒的钩子函数检查被ring3查毒代码设置的结果位以此决定该文件请求是被采纳即继续向下传递还是被取消即在EAX中放入-1后直接返回,同时增加历史记录。

以上只是钩子函数与APC函数流程的一个简单介绍,其中省略了诸如判断固定驱动器,超时等内容,具体细节请参看guidll.dll和hooksys.vxd的反汇编代码注释。

3.当VXD收到来自VMM的ON_SYS_DYNAMIC_DEVICE_EXIT消息时,它释放初始化时分配的堆内存(HeapFree),并清除5个用于互斥的信号量(Destroy_Semaphore)。

3.3.3HOOKSYS.VXD逆向工程代码剖析
在剖析代码之前有必要介绍一下逆向工程的概念。逆向工程(Reverse Engineering)是指在没有源代码的情况下对可执行文件进行反汇编试图理解机器码本身的含义。逆向工程的用途很多,如摘掉软件保护,窥视其设计和编写技术,发掘操作系统内部奥秘等。本文中我们用到的不少未公开数据结构和服务就是利用逆向的方法得到的。逆向工程的难度可想而知:一个1K大小的exe文件反汇编后就有1000行左右,而我们要逆向的3个文件加起来有80多K,总代码量是8万多行。所以必须掌握一定的逆向技巧,否则工作起来将是非常困难的。

首先要完成逆向工作,必须选择优秀的反汇编及调试跟踪工具。IDA(The Interactive Disassembler)是一款功能强大的反汇编工具:它以交互能力强而著称,允许使用者增加标签,注释及定义变量,函数名称;另外不少反汇编工具对于特殊处理的反逆向文件,如导入节损坏等显得无能为力,但IDA仍可胜任之。当文件被加过壳或插入了干扰指令时 就需要使用调试工具进行动态跟踪。Numega公司的Softice是调试工具中的佼佼者:它支持所有类型的可执行文件,包括vxd和sys驱动程序,能够用热键实时呼出,可对代码执行,内存和端口访问设置断点,总之功能非常之强大以至于连微软总裁比尔盖茨对此都惊叹不已。

其次需要对编译器常用的编译结构有一定了解,这样有助于我们理解代码的含义。

如下代码是MS编译器常用的一种编译高级语言函数的形式:

  0001224A push ebp ;保存基址寄存器
 0001224B mov ebp, esp
 0001224D sub esp, 5Ch ;在堆栈留出局部变量空间
 00012250 push ebx
 00012251 push esi
 00012252 push edi
 ......
 0001225B lea edi, [ebp-34h] ;引用局部变量
 ......
 0001238D mov esi, [ebp+08h] ;引用参数
 ......
 00012424 pop edi
 00012425 pop esi
 00012426 pop ebx
 00012427 leave
 00012428 retn 8 ;函数返回
 如下代码是MS编译器常用的一种编译高级语言取串长度的形式:

 0001170D lea edi, [eax+1Ch] ;串首地址指针
 00011710 or ecx, 0FFFFFFFFh ;将ecx置为-1
 00011713 xor eax, eax ;扫描串结束符号(NULL)
 00011715 push offset 00012C04h ;编译器优化
 0001171A repne scasb ;扫描串结束符号位置
 0001171C not ecx ;取反后得到串长度
 0001171E sub edi, ecx ;恢复串首地址指针
最后一点是必须要有坚忍的毅力和清晰的头脑。逆向工程本身是件痛苦的工作:高级语言源代码中使用的变量和函数名字在这里仅是一个地址,需要反复调试琢磨才能确定其含义;另外编译器优化更为我们理解代码增加了不少障碍,如上例中那句压栈指令是将后面函数调用时参数入栈提前放置。所以毅力和头脑二者缺一不可。

以下进入hooksys.vxd代码剖析,由于代码过于庞大,我只选择有代表性且精彩的部分进行介绍。代码中的变量和函数及标签名是我分析后自己添加的,可能会与原作者的意图有些出入。

3.3.3.1钩子函数入口代码
 C00012E0 push ebp
 C00012E1 mov ebp, esp
 C00012E3 sub esp, 11Ch
 C00012E9 push ebx
 C00012EA push esi
 C00012EB push edi
 C00012EC mov eax, [ebp+arg_4] ; 被执行的函数的代号
 C00012EF mov [ebp+var_11C], eax
 C00012F5 cmp [ebp+var_11C], 1 ; IFSFN_WRITE
 C00012FC jz writefile
 C0001302 cmp [ebp+var_11C], 0Bh ; IFSFN_CLOSE
 C0001309 jz closefile
 C000130F cmp [ebp+var_11C], 24h ; IFSFN_OPEN
 C0001316 jz short openfile
 C0001318 jmp irqpassdown
 钩子函数入口处,堆栈参数分布如下:

 ebp+00h -> 保存的EBP值.
 ebp+04h -> 返回地址.
 ebp+08h -> 提供这个API要调用的FSD函数的的地址
 ebp+0Ch -> 提供被执行的函数的代号
 ebp+10h -> 提供了操作在其上执行的以1为基准的驱动器代号(如果UNC为-1)
 ebp+14h -> 提供了操作在其上执行的资源的种类。
 ebp+18h -> 提供了用户串传递其上的代码页
 ebp+1Ch -> 提供IOREQ结构的指针。
钩子函数利用[ebp+0Ch]中保存的被执行的函数的代号来判断该请求的类型。同时它利用[ebp+0Ch]中保存的IOREQ结构的指针从该结构中偏移0ch处path_t ir_ppath域取得完整的文件路径名称。

3.3.3.2取得当前进程名称代码
 C0000870 push ebx
 C0000871 push esi
 C0000872 push edi
 C0000873 call VWIN32_GetCurrentProcessHandle ;在eax中返回ring0 PDB(进程数据库)
 C0000878 mov eax, [eax+38h] ;HTASK W16TDB
 ;偏移38h处是Win16任务数据库选择子
 C000087B push 0 ;DWORD Flags
 C000087D or al,
 C000087F push eax ;DWORD Selector
 C0000880 call Get_Sys_VM_Handle@0
 C0000885 push eax ;取得系统VM的句柄 VMHANDLE hVM
 C0000886 call _SelectorMapFlat ;将选择子基址映射为平坦模式的线形地址
 C000088B add esp, 0Ch
 C000088E cmp eax, 0FFFFFFFFh ;映射错误
 C0000891 jnz short loc_C0000899
 ......
 C0000899 lea edi, [eax+0F2h] ;从偏移0F2h取得模块名称
 ;char TDB_ModName[8]
 3.3.3.3通信部分代码
hooksys.vxd中代码:

C00011BC push ecx ;客户程序的ring0线程句柄
 C00011BD push ebx ;传入APC的参数
 C00011BE push edx ;ring3级APC函数的平坦模式地址
 C00011BF call _VWIN32_QueueUserApc ;排队APC
 C00011C4 mov eax, [ebp+0Ch] ;事件对象的ring0句柄
 C00011C7 push eax
 C00011C8 call _VWIN32_ResetWin32Event;设置事件对象为无信号态
 ......
 C00011E7 mov eax, [ebp+0Ch]
 C00011EA push 3E8h ;超时设置
 C00011EF push eax ;事件对象的ring0句柄
 C00011F0 call _VWIN32_WaitSingleObject ;等待ring3查毒的完成
 guidll.dll中代码:

 APC函数入口:
 10001AD1 mov eax, hDevice ;取得设备句柄
 10001AD6 lea ecx, [esp+4]
 10001ADA push 0
 10001ADC push ecx ;返回字节数
 10001ADD lea edx, [esp+8]
 10001AE1 push 4 ;输出缓冲区大小
 10001AE3 push edx ;输出缓冲区指针
 10001AE4 push 0 ;输入缓冲区大小
 10001AE6 push 0 ;输入缓冲区指针
 10001AE8 push 83003C07h ;IO控制代码
 10001AED push eax ;设备句柄
 10001AEE call ds:DeviceIoControl
 10001AF4 test eax, eax
 10001AF6 jz short loc_10001B05
 10001AF8 mov ecx, [esp+0] ;得到打开文件链表头元素
 10001AFC push ecx
 10001AFD call ScanOpenFile ;调用查毒函数
 ScanOpenFile函数中:

 1000185D call ds:fnScanOneFile ;调用真正查毒库导出函数
 10001863 mov edx, hMutex
 10001869 add esp, 8
 1000186C mov esi, eax ;查毒结果
 1000186E push edx
 1000186F call ds:ReleaseMutex
 10001875 test esi, esi ;检查结果
 10001877 jnz short OpenFileIsVirus ;如发现病毒则跳到OpenFileIsViru进一步处理
 10001879 mov eax, [ebp+10h] ;事件对象的ring3句柄
 1000187C mov byte ptr [ebp+16h], 0 ;设置元素中的结果位为无病毒
 10001880 push eax
 10001881 call ds:SetEvent ;设置事件对象为有信号态唤醒钩子函数
  3.4WINNT/2000下的病毒实时监控
3.4.1实现技术详解
WINNT/2000下病毒实时监控的实现主要依赖于NT内核模式驱动编程,拦截IRP,驱动与ring3下客户程序的通信(命名的事件与信号量对象)三项技术。程序的设计思路和大体流程与前面介绍的WIN9X下病毒实时监控非常相似,只是在实现技术由于运行环境的不同将呈现很大的区别。

WINNT/2000下不再支持VXD,我将在后面剖析的hooksys.sys其实是一种称为NT内核模式设备驱动的驱动程序。这种驱动程序无论从其结构还是工作方式都与VXD有很大不同。比较而言,NT内核模式设备驱动的编写比VXD难度更大:因为它要求编程者熟悉WINNT/2000的整体架构和运行机制,NT/2000是纯32位微内核操作系统,与WIN9X有很大区别;能灵活使用内核数据结构,如驱动程序对象,设备对象,文件对象,IO请求包,执行体进程/线程块,系统服务调度表等。另外编程者在编程时还需注意许多重要事项,如当前系统运行的IO请求级,分页/非分页内存等。

这里首先介绍几个重要的内核数据结构,它们在NT内核模式设备驱动的编程中经常被用到,包括文件对象,驱动程序对象,设备对象,IO请求包(IRP),IO堆栈单元(IO_STACK_LOCATION):

文件明显符合NT中的对象标准:它们是两个或两个以上用户态进程的线程可以共享的系统资源;它们可以有名称;它们被基于对象的安全性所保护;并且它们支持同步。对于用户态受保护的子系统,文件对象通常代表一个文件,设备目录,或卷的打开实例;而对于设备和中间型驱动,文件对象通常代表一个设备。文件对象结构中的域大部分是透明的驱动可以访问的域包括:

PDEVICE_OBJECT DeviceObject:指向文件于其上被打开的设备对象的指针。

UNICODE_STRING FileName:在设备上被打开的文件的名字,如果当由DeviceObject代表的设备被打开时此串长度(FileName.Length)为0。

驱动程序对象代表可装载的内核模式驱动的映象,当驱动被加载至系统中时,有I/O管理器负责创建。指向驱动程序对象的指针将作为一个输入参数传送到驱动的初始化例程(DriverEntry),再初始化例程(Reinitialize routines)和卸载例程(Unload routine)。驱动程序对象结构中的域大部分是透明的,驱动可以访问的域包括:

PDEVICE_OBJECT DeviceObject:指向驱动创建的设备对象的指针。当在初始化例程中成功调用IoCreateDevice后这个域将被自动更新。当驱动卸载时,它的卸载例程将使用此域和设备对象中NextDevice域调用IoDeleteDevice来清除驱动创建的每个设备对象。

PDRIVER_INITIALIZE DriverInit:由I/O管理器设置的初始化例程(DriverEntry)入口地址。该例程负责创建驱动程序操作的每个设备的设备对象,需要的话还可以在设备名称和设备对用户态可见名称间创建符号链接。同时它还把驱动程序各例程入口点填入驱动程序对象相应的域中。

PDRIVER_UNLOAD DriverUnload:驱动程序的卸载例程入口地址。

PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION+1]:一个或多个驱动程序调度例程入口地址数组。每个驱动必须在此数组中为驱动处理的IRP_MJ_XXX请求集设置至少一个调度入口,这样所有的IRP_MJ_XXX请求都会 被I/O管理器导入同一个调度例程。当然,驱动程序也可以为每个IRP_MJ_XXX请求设置独立的调度入口。

当然,驱动程序中可能包含的例程将远不止以上列出的。比如启动I/O例程,中断服务例程(ISR),中断服务DPC例程,一个或多个完成例程,取消I/O例程,系统关闭通知例程,错误记录例程。只不过我们将要剖析的hooksys.sys中只用到例程中很少一部分,故其余的不予详细介绍。

设备对象代表已装载的驱动程序为之处理I/O请求的一个逻辑,虚拟或物理设备。每个NT内核模式驱动程序必须在它的初始化例程中一次或多次调用IoCreateDevice来创建它支持的设备对象。例如tcpip.sys在其DriverEntry中就创建了3个共用此驱动的设备对象:Tcp,Udp,Ip。目前有一种比较流行的称为WDM(Windows Driver Model)的驱动程序,在大多数情况下,其二进制映像可以兼容WIN98和WIN2000(32位版本)。WDM与NT内核模式驱动程序的主要区别在于如何创建设备:在WDM驱动程序中,即插即用(PnP)管理器告知何时向系统中添加一个设备,或者从系统中删除设备。WDM驱动程序有一个特殊的AddDevice例程,PnP管理器为共用该驱动的每个设备实例调用该函数;而NT内核模式驱动程序需要做大量额外的工作,它们必须探测自己的硬件,为硬件创建设备对象(通常在DriverEntry中),配置并初始化硬件使其正常工作。设备程序对象结构中的域大部分是透明的,驱动可以访问的域包括:

PDRIVER_OBJECT DriverObject:指向代表驱动程序装载映象的驱动程序对象的指针。

所有I/O都是通过I/O请求包(IRP)驱动的。所谓IRP驱动,是指I/O管理器负责在系统的非分页内存中分配一定的空间,当接受用户发出的命令或由事件引发后,将工作指令按一定的数据结构置于其中并传递到驱动程序的服务例程。换言之,IRP中包含了驱动程序的服务例程所需的信息指令。IRP有两部分组成:固定部分(称为标题)和一个或多个堆栈单元。固定部分信息包括:请求的类型和大小,是同步请求还是异步请求,用于缓冲I/O的指向缓冲区的指针和由于请求的进展而变化的状态信息。

PMDL MdlAddress:指向一个内存描述符表(MDL),该表描述了一个与该请求关联的用户模式缓冲区。如果顶级设备对象的Flags域为DO_DIRECT_IO,则I/O管理器为IRP_MJ_READ或IRP_MJ_WRITE请求创建这个MDL。如果一个IRP_MJ_DEVICE_CONTROL请求的控制代码指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操作方式,则I/O管理

上一页  [1] [2] [3] [4] [5] [6] [7] 下一页

文章录入:wuwq    责任编辑:wuwq 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
    时刻保持警觉!教你去如何防
    堵住病毒发作源头!E-mail地
    防火墙日志记录让蠕虫病毒无
    VBS脚本病毒原理分析与防范
    最顽固的恶意网页病毒?
    基础知识共享:防范病毒11个
    教您遭遇计算机教您遭遇计算
    “QQ尾巴”病毒预防和清除
    病毒应急处理中心提醒防范高
    北京公安局、瑞星发布6月13日
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    局域网DIY】.版权所有 客服QQ:5820031 站长:晨光
    Copyright © 2000-2020 www.LANDIY.net All Rights Reserved
     


    粤ICP备05009256号