恶意软件反检测技术简介:模拟器超限技术

恶意软件是一种招人恨的代码,因为它们专干坏事,如泄漏个人隐私、造成数据丢失,等等。而杀毒软件公司则不断想办法检测并阻止恶意软件。如此一来,猫和老鼠的大戏从此开演了。一般来说,杀毒软件要想防御某种恶意软件需要经过以下过程:收集到样本,分析样本,升级病毒库,之后杀毒软件才能够识别该恶意软件。而反检测技术,就是在恶意软件的分析阶段设置障碍,让分析人员无法或难以对恶意代码进行分析,这主要包括两类技术,一种是反调试技术,一种是反仿真技术,或叫做反虚拟执行技术。

无论是处于分析恶意软件的目的,还是防止软件被分析的目的,了解恶意软件常用的反检测手段都是很有必要的,而本文的目的就是在于,向读者们介绍目前的反调试和反仿真技术,并提供代码样本,以供读者在识别恶意代码时作为练手之用。

一、引言

病毒作者使用反调试与反仿真技术的目的在于为逆向分析恶意软件制造障碍,理想的情况下是使得逆向工程师无法分析恶意软件,退一步讲即使可以分析也会让分析过程更为缓慢。当病毒在一个仿真器或调试器中运行时,这些技术会使逆向工程过程变得举步维艰,他们企图以此逃避检查。恶意代码可以利用多种不同的方法来“忽悠”动态检测和其他如仿真器和调试器之类的分析机制,通常情况下每种病毒都会采用其中的一种甚至多种。

对于这些病毒作者用于使对病毒的逆向过程变缓的方法,本文将分别进行讲解,并为它们提供了示例。本文会概述各种反调试与反仿真技术,以使得读者对它们有所了解。下面我们开始介绍反仿真技术。

二、反仿真技术

仿真器为人们提供了一个受限制的环境(即安装在主机操作系统之上的操作系统的映像),我们可以在这个环境中动态地分析程序。例如,一台运行Linux操作系统的机器可以在其虚拟机上安装并运行Windows XP系统。一个仿真器也将包括对CPU和内存的仿真,以及其它硬件的仿真,还有控制器的仿真。这为我们提供了一种安全的方式来动态地分析程序,因为这种环境下,无论做什么都不会对底层的操作系统造成损害。

然而,这一系统也有其缺点。首先,花在仿真上的时间越多,在对目标程序进行实际分析之前所必须等待的时间也就相应越长。其次,仿真过程是缓慢的,因为不仅对目标程序实施监控需要开销,而且对操作系统和硬件进行仿真同样也需要开销。QEMU,一种硬件和操作系统仿真器,其仿真出来的硬件比物理硬件的速度有四分之一到十分之一之间的下降,软件在其上运行的速度只是在内存管理单元上运行时的二分之一。同时,因为QEMU不具备任何动态监控功能,这会进一步降低速度。最后一个缺点是,恶意软件可以利用各种反仿真技术来“忽悠”模拟器,这些技术分为三类:比耐力型、比智力型以及过度扩展模拟器。

三、跟模拟器比耐力

因为仿真的代价较高,所以模拟器通常只运行代码的前几百条指令来检测恶意软件,后面的指令通常不予理会。在英特尔的X86系统上,人们普遍相信,只要运行1000条指令便足以检测出恶意代码,同时还能保持较短的运行时间。如果在这段时间内没有运行被视为恶意的指令,那么模拟器就不会再将该代码作为恶意软件而继续检查。跟模拟器比耐力的方法有很多,如基于特定概率的感染技术,在运行恶意代码之前先将良性代码运行特定的时间,或通过入口点迷惑技术来达到目的。

1.概率式感染

一些恶意软件的恶意代码并不是每次都会执行,而是按照一定的概率发作。例如,某病毒在运行时执行恶意代码的概率是10%,这就意味着,模拟器将其运行若干次,才有可能检测到恶意代码。

2.运行良性代码

有的恶意软件会在每次启动时先运行良性代码,并且良性代码运行一段时间后,才开始运行恶意代码。这样做的目的是让模拟器运行指定数量的指令,让它觉得该代码在这段时间内没有做恶意的事情。等危险期过后,它就会露出本来面目:感染并危害系统。

3.入口点迷惑技术

入口点迷惑技术(EPO)是一种将附带的恶意代码放到一个文件的特定的部分的方法。恶意软件不会一上来就执行恶意代码,相反,它能查找对ExitProcess()API函数的调用,然后用一个跳至恶意代码的转移指令来覆盖这些调用。用这种方法,代码将在可执行文件的出口处,而非入口处运行。它还可以寻找一个特定的代码序列,然后用恶意代码本身或跳至恶意代码的转移指令来覆盖之。所以,入口点迷惑技术后,能够使病毒在将代码拷贝至新的位置之后、实际覆盖它之前运行恶意

四、跟模拟器比智力

为了智取模拟器,一些恶意软件使用基于时间的触发器,或者其它的条件转移,或者使用不同的解密技术(如果它是采用加密的恶意软件的话)。基于时间的触发器可以只有在下午3点或指定的日期运行恶意代码。恶意软件也可以检测某些条件是否成立,如查看自身是否正在被调试或仿真,如果条件成立,则执行良性代码。

解密技术也可以将解密循环分布到代码的各处,或者使用多轮解密技术。多轮解密技术已经被W32/Harrier、W32/Coke和W32/Zelly用过了,其中第一个解密器解密第二个解密器,第二个解密器又解密第三个解密器,以此类推。为了靠小聪明玩弄模拟器,恶意软件也可以仅仅解密所需的代码块。

RDA.Fighter病毒使用蛮力解密技术,这意味着,该病毒不会储存解密密钥,所以它必须尝试所有可能的解密密钥,以便解密本身。这是非常有用的,因为如果反病毒公司使用了不同的蛮力解密方法的话,对病毒解密是非常困难的,它需要大量的仿真指令。还有一种可能,就是只要模拟器正在运行,病毒就不对自身进行解密。下面是病毒RDA Fighter用于解密的代码。

setup:
        xor ebx,ebx

iterate:

       mov esi,[ebp + hostOffset]
       mov edi,esi
       mov ecx,[ebp + host_size]
       inc ebx

decrypt:

       lodsb
       xor al,bl
       stosb
       loop decrypt

check:

       mov esi,[ebp + hostOffset]
       push esi
       mov ecx,[ebp + host_size]
       push ecx
       mov eax,[ebp + __ADDR_CheckSum]  ; whatever this happens to be
       call eax
       test eax,eax
       jnz iterate
       mov esi,[ebp + hostOffset]
       jmp esi

在上面的例子中,我们看到setup段将ebx设为0,并且它只运行一次。然后,在循环部分将esi和edi设为被加密的代码的起点,将ecx设为被加密的代码的尺寸,ebx每次递增1。用于解密的基本块会逐字节遍历被加密的代码,并用ebx中的密钥来异或每一字节。当ecx 变成零时,循环终止。之后,由负责检查的代码段来查看当前的代码是否跟预设的校验和是否匹配,如果匹配的话,那么说明代码已经成功解密。如果不匹配,则返回迭代循环并再次尝试下一个密码。如果已经解密,那么它会跳转到新解密代码处。

W95/Fono使用非线性解密算法,即病毒的加密部分是无法使用通常的线性方式来解密的。因为病毒无法一个字节接一个字节地解密,所以它能够迷惑仿真器。W95/Fono使用一个密钥表和解密器来完成基于该表的置换。明文字母中的每个符号对应于密文中的另一个不同的符号。例如:A和L相对应,Z和F相对应,等等。因此,使用这个加密方法后,病毒的各个部分是以半随机的顺序解密的,并且每个位置仅出现一次。此外,W95/Drill和(W32,Linux)/Simile.D也使用非线性解密方法。

W95/Silcer和W95/Resure装入内存时,它们强迫Windows的加载器对受感染的程序的映像进行重定位。映像的重定位处理决定了病毒体的解密处理,因为病毒解密时必须进行特定的重定位处理。W95/Resurrel是继W95/Resure之后出现的一种病毒,该病毒会把刚感染的文件的基地址设为0xBFxxxxxx(其中XXXXXX是一个由API调用GetTickCount()返回的随机值)。然后,它为自己代码部分的每个DWORD值添加一个重定位表项,并对每个DWORD进行加密,方法是将代码部分的DWORD加上基址值,然后减去0x400000。当应用程序被执行时,会因为基地址错误,或者一个地址位于KERNEL32.DLL中而导致程序的映像无法照现在的样子装入内存。这样,系统装入程序就需要把代码重定位到一个有效地址,这实际上也是病毒的解密工作要做的事情。记住,病毒越难以解密,检测起来就越难。

五、模拟器超限技术

所谓模拟器超限技术,是指执行一组将导致仿真器崩溃或可以表明仿真器正在运行的指令。调用仿真器不支持的未公开指令就是一种导致仿真器掷出异常并停止运行的方法。W95/Vulcano就是这样一个例子,它使用了非正式的CPU指令SALC。用来检测仿真或导致正在运行的仿真器崩溃(如果有的话)的另一种方法是,尝试一次访问大量内存,大到什么程度呢?如果机器上安装了2G内存,那么你就一次访问1G以上的内存。这通常是不能有效执行的,因为大多数的操作系统,以及仿真器,将阻止程序的这种行为。

检测是否正在运行仿真器的一种方法是,将每次调用都返回不同的值的函数调用两次,例如,我们可以对任何时间函数调用两次,然后检查两个返回值之差的大小。在Windows系统下,我们可以通过kernel32!QueryPerformanceCounter(包装有ZwQueryPerformaceCounter),kernel32!GetTickCounter达到上述目的;或通过RDTSC(读时间戳计数器)指令查询自从机器启动以来目前已经执行的机器周期数也能达到目的。下面是一个例子:

push offset handler
push dword ptr fs:[0]
mov fs:[0],esp
rdtsc
push eax
xor eax, eax
div eax ;trigger exception
rdtsc
sub eax, [esp] ;ticks delta
add esp, 4
pop fs:[0]
add esp, 4
cmp eax, 10000h ;threshold
jb @not_debugged
@debugged:
...
@not_debugged:
...
handler:
mov ecx, [esp+0Ch]
add dword ptr [ecx+0B8h], 2 ;skip div
xor eax, eax
ret

上例向我们展示了如何利用RDTSC指令来检测是否存在调试器。如果存在的话,RDTSC被调用两次后返回的值会有变化。然后用这个差异跟一个阈值(在本例中它是10000h)进行比较。如果差异较大,那么就表明存在调试器,所以它会执行一个跳离恶意代码的jump指令。

虽然导入各种迷惑性的程序库不能导致仿真器停止运行,但是如果不导入这些库,恶意软件本身代码就无法运行。

恶意软件还可以寻找网页,看看它是否可以使用互联网。这样会导致当病毒的代码所在机器当前没有连接到互联网的时候,它就不执行;另外,当仿真器运行时病毒的代码也会停止运行,因为大多数仿真器不允许访问互联网。

使用协处理器浮点运算单元(FPU)指令是另一种过分扩展仿真器的方法,因为大部分仿真器都不会仿真FPU指令。Prizzy polymorphic engine (PPE)能够产生43条不同的协处理器指令供其多态解密器使用。如果不提供这些FPU指令的话,就无法对Prizzy进行解密。

同理,恶意软件也可以使用MMX指令。这个指令集为X86架构新增了了8个寄存器。恶意软件可以通过CPUID指令检查是否支持MMX。使用这种技术恶意软件的例子是W32/Legacy和W32/Thorin。

恶意软件还可以设置一个异常处理程序,执行一些无用的代码块,然后间接执行自己的处理程序,以将控制权传送到多态解密器的另一部分。这一诡计之所以得逞,是因为仿真器不能处理异常。3.7部分给出了类似于这种技术的例子。

六、小结

本文中,我们介绍了恶意软件用以阻碍对其进行逆向工程的若干技术,并着重讲解了当前恶意软件常用的反仿真技术。我们应该很好的理解这些技术,以便能够更有效地对恶意软件进行动态检测和分析。在下篇中,我们会为读者介绍恶意软件常用来的反调试技术。


你可能感兴趣的:(模拟器)