1   1  /  1  页   跳转

[分享] 反病毒引擎设计(作者不详)Part2

反病毒引擎设计(作者不详)Part2

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加密变形病毒
前面提到过设计虚拟机查毒的目的是为了对付加密变形病毒。这一章就重点介绍加密变形技术。

早期病毒没有使用任何复杂的反检测技术,如果拿反汇编工具打开病毒体代码看到的将是真正的机器码。因而可以由病毒体内某处一段机器代码和此处距离病毒入口(注意不是文件头)偏移值来唯一确定一种病毒。查毒时只需简单的确定病毒入口并在指定偏移处扫描特定代码串。这种静态扫描技术对付普通病毒是万无一失的。

随着病毒技术的发展,出现了一类加密病毒。这类病毒的特点是:其入口处具有解密子(decryptor),而病毒主体代码被加了密。运行时首先得到控制权的解密代码将对病毒主体进行循环解密,完成后将控制交给病毒主体运行,病毒主体感染文件时会将解密子,用随机密钥加密过的病毒主体,和保存在病毒体内或嵌入解密子中的密钥一同写入被感染文件。由于同一种病毒的不同传染实例的病毒主体是用不同的密钥进行加密,因而不可能在其中找到唯一的一段代码串和偏移来代表此病毒的特征,似乎静态扫描技术对此即将失效。但仔细想想,不同传染实例的解密子仍保持不变机器码明文(从理论上讲任何加密程序中都存在未加密的机器码,否则程序无法执行),所以将特征码选于此处虽然会冒一定的误报风险(解密子中代码缺少病毒特性,同样的特征码也会出现在正常程序中),但仍不失为一种有效的方法。

由于加密病毒还没有能够完全逃脱静态特征码扫描,所以病毒写作者在加密病毒的基础之上进行改进,使解密子的代码对不同传染实例呈现出多样性,这就出现了加密变形病毒。它和加密病毒非常类似,唯一的改进在于病毒主体在感染不同文件会构造出一个功能相同但代码不同的解密子,也就是不同传染实例的解密子具有相同的解密功能但代码却截然不同。比如原本一条指令完全可以拆成几条来完成,中间可能会被插入无用的垃圾代码。这样,由于无法找到不变的特征码,静态扫描技术就彻底失效了。下面先举两个例子说明加密变形病毒解密子构造,然后再讨论怎样用虚拟执行技术检测加密变形病毒。

著名多形病毒Marburg的变形解密子:

00401020: movsx edi,si ;病毒入口
00401023: movsx edx,bp
00401026: jmp 00408a99
......
00407400: ;病毒体入口
加密的病毒主体
00408a94: ;解密指针初始值
......
00408a99: mov dl,f7
00408a9b: movsx edx,bx
00408a9e: mov ecx,cf4b9b4f
00408aa3: call 00408ac4
......
00408ac4: pop ebx
00408ac5: jmp 00408ade
......
00408ade: mov cx,di
00408ae1: add ebx,9fdbd22d
00408ae7: jmp 00408b08
......
00408b08: add ecx,80c1fbc1
00408b0e: mov ebp,7fcdeff3 ;循环解密记数器初值
00408b13: sub cl,39
00408b16: movsx esi,si
00408b19: add dword ptr[ebx+60242dbf],9ef42073 ;解密语句,9ef42073是密钥
00408b23: mov edx,6fd1d4cf
00408b28: mov di,dx
00408b2b: inc ebp
00408b2c: xor dl,a3
00408b2f: mov cx,si
00408b32: sub ebx,00000004 ;移动解密偏移指针,逆向解密
00408b38: mov ecx,86425df9
00408b3d: cmp ebp,7fcdf599 ;判断解密结束与否
00408b43: jnz 00408b16
00408b49: jmp 00408b62
......
00408b62: mov di,bp
00408b65: jmp 00407400 ;将控制权交给解密后的病毒体入口
著名多形病毒Hps的变形解密子:

005365b8: ;解密指针初始值和病毒体入口
加密的病毒主体
......
005379cd: call 005379e2
......
005379e2: pop ebx
005379e3: sub ebx,0000141a ;设置解密指针初值
005379e9: ret
......
005379f0: dec edx ;减少循环记数值
005379f1: ret
......
00537a00: xor dword ptr[ebx],10e7ed59 ;解密语句,10e7ed59是密钥
00537a06: ret
......
00537a1a: sub ebx,ffffffff
00537a20: sub ebx,fffffffd ;移动解密指针,正向解密
00537a26: ret
......
00537a30: mov edx,74d9cb97 ;设置循环记数初值
00537a35: ret
......
00537a3f: call 005379cd ;病毒入口
00537a44: call 00537a30
00537a49: call 00537a00
00537a4e: call 00537a1a
00537a53: call 005379f0
00537a58: mov esi,edx
00537a5a: cmp esi,74d9c696 ;判断解密结束与否
00537a60: jnz 00537a49
00537a66: jmp 005365b8 ;将控制权交给解密后的病毒体入口
以上的代码看上去绝对不会是用编译器编译出来,或是编程者手工写出来的,因为其中充斥了大量的乱数和垃圾。代码中没有注释部分均可认为是垃圾代码,有用部分完成的功能仅是循环向加密过的病毒体的每个双字加上或异或一个固定值。这只是变形病毒传染实例的其中一个,别的实例的解密子和病毒体将不会如此,极度变形以至让人无法辩识。至于变形病毒的实现技术由于涉及复杂的算法和控制,因此不在我们讨论范围内。

这种加密变形病毒的检测用传统的静态特征码扫描技术显然已经不行了。为此我们采取的方法是动态特征码扫描技术,所谓“动态特征码扫描”指先在虚拟机的配合下对病毒进行解密,接着在解密后病毒体明文中寻找特征码。我们知道解密后病毒体明文是稳定不变的,只要能够得到解密后的病毒体就可以使用特征码扫描了。要得到病毒体明文首先必须利用虚拟机对病毒的解密子进行解释执行,当跟踪并确定其循环解密完成或达到规定次数后,整个病毒体明文或部分已被保存到一个内部缓冲区中了。虚拟机之所以又被称为通用解密器在于它不用事先知道病毒体的加密算法,而是通过跟踪病毒自身的解密过程来对其进行解密。至于虚拟机怎样解释指令执行,怎样确定可执行代码有无循环解密段等细节将在下一节中介绍。


用户系统信息:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; InfoPath.2)
本帖被评分 1 次
分享到:
gototop
 

回复: 反病毒引擎设计(作者不详)Part2

2.3虚拟机实现技术详解
有了前面关于加密变形病毒的介绍,现在我们知道动态特征码扫描技术的关键就在于必须得到病毒体解密后的明文,而得到明文产生的时机就是病毒自身解密代码解密的完毕。目前有两种方法可以跟踪控制病毒的每一步执行,并能够在病毒循环解密结束后从内存中读出病毒体明文。一种是单步和断点跟踪法,和目前一些程序调试器相类似;另一种方法当然就是虚拟执行法。下面分别分析单步和断点跟踪法和虚拟执行法的技术细节。

单步跟踪和断点是实现传统调试器的最根本技术。单步的工作原理很简单:当CPU在执行一条指令之前会先检查标志寄存器,如果发现其中的陷阱标志被设置则会在指令执行结束后引发一个单步陷阱INT1H。至于断点的设置有软硬之分,软件断点是指调试器用一个通常是单字节的断点指令(CC,即INT3H)替换掉欲触发指令的首字节,当程序执行至断点指令处,默认的调试异常处理代码将被调用,此时保存在栈中的段/偏移地址就是断点指令后一字节的地址;而硬件断点的设置则利用了处理器本身的调试支持,在调试寄存器(DR0--DR4)中设置触发指令的线形地址并设置调试控制寄存器(DR7)中相关的控制位,CPU会在预设指令执行时自动引发调试异常。而Windows本身又提供了一套调试API,使得调试跟踪一个程序变得非常简单:调试器本身不用接挂默认的调试异常处理代码,而只须调用WaitForDebugEvent等待系统发来的调试事件;调试器可利用GetThreadContext挂起被调试线程获取其上下文,并设置上下文中的标志寄存器中的陷阱标志位,最后通过SetThreadContext使设置生效来进行单步调试;调试器还可通过调用两个功能强大的调试API--ReadProcessMemory和WriteProcessMemory来向被调试线程的地址空间中注入断点指令。根据我逆向后的分析结果,VC++的调试器就是直接利用这套调试API写成的。使用以上的调试技术既然可以写出像VC++那样功能齐全的调试器,那么没有理由不能将之运用于病毒代码的自动解密上。最简单的最法:创建待查可执行文件为调试器的调试子进程,然后用上述方法对其进行单步跟踪,每当收到具有EXCEPTION_SINGLE_STEP异常代码的事件时就可以分析该条以单步模式执行的指令,最后当判断病毒的整个解密过程结束后即可调用ReadProcessMemory读出病毒体明文。

用单步和断点跟踪法的唯一一点好处就在于它不用处理每条指令的执行--这意味着它无需编写大量的特定指令处理函数,因为所有的解密代码都交由CPU去执行,调试器不过是在代码被单步中断的间隙得到控制权而已。但这种方法的缺点也是相当明显的:其一容易被病毒觉察到,病毒只须进行简单的堆栈检查,或直接调用IsDebugerPresent就可确定自己正处于被调试状态;其二由于没有相应的机器码分析模块,指令的译码,执行完全依赖于CPU,所以将导致无法准确地获取指令执行细节并对其进行有效的控制。;其三单步和断点跟踪法要求待查可执行文件真实执行,即其将做为系统中一个真实的进程在自己的地址空间中运行,这当然是病毒扫描所不能允许的。很显然,单步和断点跟踪法可以应用在调试器,自动脱壳等方面,但对于查毒却是不合适的。

而使用虚拟执行法的唯一一点缺点就在于它必须在内部处理所有指令的执行--这意味着它需要编写大量的特定指令处理函数来模拟每种指令的执行效果,这里根本不存在何时得到控制权的问题,因为控制权将永远掌握在虚拟机手中。用软件方法模拟CPU并非易事,需要对其机制有足够的了解,否则模拟效果将与真实执行相去甚远。举两个例子:一个是病毒常用的乘法后ASCII调整指令AAM,这条指令因为存在未公开的行为从而常常被病毒用来考验虚拟机设计的优劣。通常情况下AAM是双字节指令,操作码为D4 0A(其实0A隐含代表了操作数10);但也可作为单字节指令明确地指定第二字节除数为任意8位立即数,此时操作码仅为D4。虚拟机必需考虑到后一种指定除数的情况来保证模拟结果的正确性;还有一个例子是关于处理器响应中断的方式,即CPU在刚打开中断后将不会马上响应中断,而必须隔一个指令周期。如果虚拟机没有考虑到该机制则很可能虚拟执行流程会与真实情况不符。但虚拟执行的优点也是很明显的,同时它正好填补了单步和断点跟踪法所力不能及的方面:首先是不可能被病毒觉察到,因为虚拟机将在其内部缓冲区中为被虚拟执行代码设立专用的堆栈,所以堆栈检查结果与实际执行无二(不会向堆栈中压入单步和断点中断时的返回地址);其次由于虚拟机自身完成指令的解码和地址的计算,所以能够获取每条指令的执行细节并加以控制;最后,最为关键的一条在于虚拟执行确实做到了“虚拟”执行,系统中不会产生代表被执行者的进程,因为被执行者的寄存器组和堆栈等执行要素均在虚拟机内部实现,因而可以认为它在虚拟机地址空间中执行。鉴于虚拟执行法诸多的优点,所以将其运用于通用病毒体解密上是再好不过的了。

通常,虚拟机的设计方案可以采取以下三种之一:自含代码虚拟机(SCCE),缓冲代码虚拟机(BCE),有限代码虚拟机(LCE)。

自含代码虚拟机工作起来象一个真正的CPU。一条指令取自内存,由SCCE解码,并被传送到相应的模拟这条指令的例程,下一条指令则继续这个循环。虚拟机会包含一个例程来对内存/寄存器寻址操作数进行解码,然后还会包括一个用于模拟每个可能在CPU上执行的指令的例程集。正如你所想到的,SCCE的代码会变的无比的巨大而且速度也会很慢。然而SCCE对于一个先进的反病毒软件是很有用的。所有指令都在内部被处理,虚拟机可以对每条指令的动作做出非常详细的报告,这些报告和启发式数据以及通用清除模块将相互参照形成一个有效的反毒系统。同时,反病毒程序能够最精确地控制内存和端口的访问,因为它自己处理地址的解码和计算。

缓冲代码虚拟机是SCCE的一个缩略版,因为相对于SCCE它具有较小的尺寸和更快的执行速度。在BCE中,一条指令是从内存中取得的,并和一个特殊指令表相比较。如果不是特殊指令,则它被进行简单的解码以求得指令的长度,随后所有这样的指令会被导入到一个可以通用地模拟所有非特殊指令的小过程中。而特殊指令,只占整个指令集的一小部分,则在特定的小处理程序中进行模拟。BCE通过将所有非特殊指令用一个小的通用的处理程序模拟来减少它必须特殊处理的指令条数,这样一来它削减了自身的大小并提高了执行速度。但这意味着它将不能真正限制对某个内存区域,端口或其他类似东西的访问,同时它也不可能生成如SCCE提供的同样全面的报告。

有限代码虚拟机有点象用于通用解密的虚拟系统所处的级别。LCE实际上并非一个虚拟机,因为它并不真正的模拟指令,它只简单地跟踪一段代码的寄存器内容,也许会提供一个小的被改动的内存地址表,或是调用过的中断之类的东西。选择使用LCE而非更大更复杂的系统的原因,在于即使只对极少数指令的支持便可以在解密原始加密病毒的路上走很远,因为病毒仅仅使用了INTEL指令集的一小部分来加密其主体。使用LCE,原本处理整个INTEL指令集时的大量花费没有了,带来的是速度的巨大增长。当然,这是以不能处理复杂解密程序段为代价的。当需要进行快速文件扫描时LCE就变的有用起来,因为一个小型但象样的LCE可以用来快速检查执行文件的可疑行为,反之对每个文件都使用SCCE算法将会导致无法忍受的缓慢。当然,如果一个文件看起来可疑,LCE还可以启动某个SCCE代码对文件进行全面检查。

下面开始介绍32位自含代码虚拟机w32encode(w32encode.cpp,Tw32asm.h,Tw32asm.cpp做为查毒引擎的一部分和其它搜索清除模块联编为Rsengine.dll)的程序结构和流程。由于这是一个设计完备且复杂的大型商用虚拟机,其中不可避免地包含了对某些特定病毒的特定处理,为了使虚拟机模型的结构清晰脉络分明,分析时我将做适当的简化。

w32encode的工作原理很简单:它首先设置模拟寄存器组(用一个DWORD全局变量模拟真实CPU内部的一个寄存器,如ENEAX)的初始值,初始化执行堆栈指针(虚拟机用内部的一个数组static int STACK[0x20]来模拟堆栈)。然后进入一个循环,解释执行指令缓冲区ProgBuffer中的头256条指令,如果循环退出时仍未发现病毒的解密循环则可由此判定非加密变形病毒,若发现了解密循环则调用EncodeInst函数重复执行循环解密过程,将病毒体明文解密到DataSeg1或DataSeg2中。相关部分代码如下:

W32Encode0中总体流程控制部分代码:

for (i=0;i<0x100;i++) //首先虚拟执行256条指令试图发现病毒循环解密子
{
if (InstLoc>=0x280)
return(0);
if (InstLoc+ProgSeekOff>=ProgEndOff)
return(0); //以上两条判断语句检查指令位置的合法性
saveinstloc(); //存储当前指令在指令缓冲区中的偏移
HasAddNewInst=0;
if (!(j=parse())) //虚拟执行指令缓冲区中的一条指令
return(0); //遇到不认识的指令时退出循环
if (j==2) //返回值为2说明发现了解密循环
break;
}
if (i==0x100) //执行过256条指令后仍未发现循环则退出
return(0);
PreParse=0;
ProcessInst();
if (!EncodeInst()) //调用解密函数重复执行循环解密过程
return(0);
jmp中判定循环出现部分代码:

if ((loc>=0)&&(loc<InstLoc)) //若转移后指令指针小于当前指令指针则可能出现循环
if (!isinstloc(loc)) //在保存的指令指针数组InstLocArray中查找转移后指
...... //令指针值,如发现则可判定循环出现
else
{
......
return(2); //返回值2代表发现了解密循环
}
parse中虚拟执行每条指令的过程较复杂一些:通常parse会从取得指令缓冲区ProgBuffer中取得当前指令的头两个字节(包括了全部操作码)并根据它们的值调用相应的指令处理函数。例如当第一个字节等于0F并且第二个字节位与BE后等于BE时,可判定此指令为movszx并同时调用movszx进行处理。当执行进入特定指令的处理函数中时,首先要通过判断寻址方式(调用modregrm或modregrm1)确定指令长度并将控制权交给saveinst函数。saveinst在保存该指令的相关信息后会调用真正指令执行函数W32ExecuteInst。这个函数和parse非常相似,它从SaveInstBuf1中取得当前指令的头两个字节并根据它们的值调用相应的指令模拟函数以完成一条指令的执行。相关部分代码如下:

W32ExecuteInst中指令分遣部分代码:

if ((c&0xf0)==0x50)
{if (ExecutePushPop1(c)) //模拟push和pop
return(gotonext());
return(0);
}
if (c==0x9c)
{if (ExecutePushf()) //模拟pushf
return(gotonext());
return(0);
}
if (c==(char)0x9d)
{if (ExecutePopf()) //模拟popf
return(gotonext());
return(0);
}
if ((c==0xf)&&((c2&0xbe)==0xbe))
{if (i=ExecuteMovszx(0)) //模拟movszx
return(gotonext());
return(0);
}
  2.4虚拟机代码剖析
总体流程控制和分遣部分的相关代码,在上一章中都已分析过了。下面分析具体的特定指令模拟函数,这才是虚拟机的精华之所在。我将指令分成不依赖标志寄存器和依赖标志寄存器两大类分别介绍:

2.4.1不依赖标志寄存器指令模拟函数的分析
push和pop指令的模拟:

static int ExecutePushPop1(int c)
{
if (c<=0x57)
{if (StackP<0) //入栈前检查堆栈缓冲指针的合法性
return(0);
}
else
if (StackP>=0x40) //出栈前检查堆栈缓冲指针的合法性
return(0);
if (c<=0x57) {
StackP--;
ENESP-=4; //如果是入栈指令则在入栈前减少堆栈指针
}
switch (c)
{case 0x50:STACK[StackP]=ENEAX; //模拟push eax
break;
......
case 0x5f:ENEDI=STACK[StackP]; //模拟push edi
break;
}
if (c>=0x58) {
StackP++;
ENESP+=4; //如果是出栈指令则在出栈后增加堆栈指针
}
return(1);
gototop
 
1   1  /  1  页   跳转
页面顶部
Powered by Discuz!NT