APP下载

基于沙箱技术的恶意代码行为检测方法

2018-12-26牛博威

西安邮电大学学报 2018年5期
关键词:沙箱端口虚拟化

童 瀛, 牛博威, 周 宇, 张 旗

(江苏省公安厅, 江苏 南京 210000)

恶意代码具有互联网中快速传播的能力,任何连入互联网的计算机都谈不上绝对的安全。在频繁发生的互联网安全事件中,最常见的是因恶意代码引起的,而且其造成的损失也是最为严重的。

目前安全厂商的沙箱产品主要包括以下3个类别,第一类是以对外提供在线服务为主的沙箱环境,包括腾讯哈勃分析系统、金山火眼在线分析系统、VirusTotal等;第二类以对内提供服务为主的沙箱环境,如赛门铁克等很多公司内部的分析系统,主要用于云查杀技术支撑;第三类是包含在安全软件中的沙箱环境,如卡巴斯基杀毒软件的启发式扫描。严格来讲主动防御技术和沙箱技术也有很多重叠。考虑到目前大多数的攻击与防御主要集中在Windows操作系统,本文主要对Windows下的恶意代码与沙箱技术进行研究。与常见的基于操作系统层面的检测不同,本文对恶意代码还开展了虚拟执行研究,同时实现了针对Windows系统特性的模拟,以期可以实现不依赖操作系统而确定代码的执行行为。

1 恶意代码攻击与检测技术

1.1 恶意代码攻击技术

在Windows平台下,不同种类的恶意代码使用的攻击技术相互交叉融合。病毒为躲避杀毒软件和反病毒分析,采取很多对抗分析技术,比较典型的包括:(1)使用多态变形或加壳技术对自身代码做扰乱,如著名的SMEG变形引擎[1];(2)使用反检测技术规避杀毒软件、虚拟机等检测[2];(3)恶意软件体过大,导致难以全面分析,如著名的震网病毒;(4)使用解释性编程语言增加分析难度等[3]。这些恶意代码的对抗技术,极大地增加恶意代码分析工作量。

目前,病毒的制作越来越流行使用ShellCode技术[4],这种技术的应用使得病毒样本体内极少含有传统意义上的恶意代码,因此,对ShellCode代码的检测也越来越成为恶意代码检测的关键。恶意ShellCode代码的检测难度主要在于:(1)体积小。这种代码一般都是汇编语言编写,体积很小,容易隐藏在正常文件中;(2)存放形式多样。既可以存储在正常可执行文件中,也可以存放在带有漏洞的文档中,或者直接在运行时候通过网络传递,并不在受害系统磁盘上存留;(3)容易加密变形。这种编码的执行不受文件格式的限制,因此可以任意加密、自加密,分析时候面临解密问题;(4)传统代码检测手段失效。这种恶意编码被加密后,除非监测动态执行过程,否则基本不可能识别出其作用。

1.2 恶意代码分析方法

在Windows系统中可执行文件是二进制形式文件,恶意代码主要也是以这种形式存在。有很多种恶意代码的分析方法,常用的为静态分析方法和动态分析方法[5],分类依据是分析过程是否需要执行恶意代码。

静态分析方法的优点是针对恶意代码本身进行分析,从代码的具体内容中得到其行为特性,从而确定其实现的功能。因此,该方法不受具体的系统环境约束,也可以分析不能运行的二进制代码,不会对操作系统造成任何影响。静态分析方法可以对代码进行细粒度分析,且具有高精度[6]。

目前,很多恶意代码常用变形、多态、加壳[7]、模糊转换等方法隐藏自己,使得静态分析方法难以识别它们。此外,该方法不能检测未知的恶意代码及其变种,只能识别已知的恶意代码。

动态分析方法的优点是基于恶意代码的行为进行分析,而不是分析代码本身,该方法需要实际执行恶意代码。因此,动态分析方法能够较好地检测未知的恶意代码。同时,动态分析还有分析速度快,人工参与少的特点,可以批量自动化分析大量样本。

动态分析的技术难点是如何确保恶意代码的执行不会对系统造成真实破坏,通常要在虚拟机中执行恶意代码,会造成系统资源的过度消耗。虚拟机的环境不能保证与真实的系统环境完全相同,这将使分析结果或多或少地具有不确定性,降低了准确性。此外,在虚拟机环境本身存在漏洞的情况下,被执行的恶意代码可能会感染到宿主机。

2 沙箱技术简介

2.1 沙箱安全模型

沙箱是一个安全软件,它为程序的执行提供一个虚拟的环境[8]。在沙箱中有专门制定的安全策略,对程序行为进行监控,当程序的执行违反安全策略时,沙箱会限制其行为。为了确保系统环境不会遭到破坏,沙箱对文件、注册表等进行虚拟化重定向,这样恶意代码只能操作虚拟的文件和注册表,而系统真正的文件和注册表不会受到影响[9]。沙箱技术的核心是创建一个对程序操作进行限制的执行环境,在沙箱中运行不受信任和未知目的的程序,可以避免对系统可能造成的破坏。

沙箱的运行过程如下:在沙箱中运行不信任文件,并记录其可疑行为,当确认程序有恶意目的时,沙箱将终止其操作,并且删除恶意程序执行痕迹,将系统恢复到原始状态。

2.2 Windows API挂钩技术

沙箱的具体实现非常复杂,需要接管很多系统操作,文件、注册表的重定向接管只是最基本的,Windows下还有窗口、类名、消息、服务、rpc、Token、COM等等。对这些接口的接管和重定向既包括驱动层,也包括应用层API、消息协议等,本文介绍最常见的API挂钩方法。

API是Windows系统对用户开放的编程接口,可在用户态下对操作系统进行控制。恶意代码要实现其功能最终会调用API函数,通过API Hook技术可以监控系统中的API函数调用,其原理是监控进程通过dll注入方式,修改API函数的原入口地址到一个自定义的函数,然后在一个自定义的函数内调用原函数,并返回结果。在注入成功后对目标API进行挂钩,根据挂钩方式的不同可以分为两种,在函数的调用点劫持和在函数体内劫持[5]。

2.3 虚拟化技术

虚拟化技术根据虚拟化程度的不同可以将该技术分为全虚拟化、半虚拟化、硬件辅助虚拟化等[10]。不同的虚拟化程度决定了在分析恶意软件的时候,样本是否能够完全运行,以及虚拟环境是否被恶意软件识别。

完全虚拟化,敏感指令在操作系统和硬件之间被捕捉处理,客户操作系统无需修改,所有软件都能在虚拟机中运行,例如IBM CP/CMS,VirtualBox,VMware Workstation。

半虚拟化,针对软件运行时的一部分环境进行虚拟化。比如对文件系统、注册表等进行虚拟化。

硬件辅助虚拟化,利用硬件(主要是CPU)辅助处理敏感指令以实现完全虚拟化的功能,客户操作系统无需修改,例如VMware Workstation,Xen,KVM等。

3 基于应用层的沙箱监控技术

前面分析了恶意代码的攻击技术和攻击行为,这些行为主要体现在恶意代码对于文件、注册表、网络端口、进程线程和内核加载的操作上,本部分将针对恶意代码在这些方面的行为进行检测,并分析通过监控哪些API函数才能实现对恶意行为的有效监控。

3.1 文件操作行为监控

恶意软件在运行的时候。为了权限维持,安全软件绕过等目的,通常都会有一系列的文件操作行为。恶意软件在成功运行后,一般会在临时文件夹或系统文件夹下,创建一个服务的备份用作后门,用以权限的维持。在该过程中需要CreateFile函数来创建文件,WriteFile函数对其写入,结束后一般会调用CloseHandle来关闭文件句柄。

通过上述分析,不难发现,可以通过监视表1中的API函数监视涉及文件的操作。

表1 文件操作监控的关键API函数表

3.2 网络行为监控

像间谍软件和木马这样的恶意代码还会对网络造成影响,攻击的一方利用它们主动连接网络来获得窃取到的重要信息。通常情况下,恶意代码网络行为主要表现在端口复用、开放本地监听端口、端口反向连接,可疑报文收发等4个方式。

1) 端口复用

端口重用技术意味着恶意代码与操作系统已经打开的网络端口通信。公共端口是39、135、80等等,其目的是在少开甚至不开新端口的情况下进行网络通信,使得防病毒软件更难发现恶意行为[11]。

端口复用并不影响端口的正常服务,因此,其行为十分隐蔽,具有很强的欺骗性。在程序中调用系统函数setsockopt和通过so_reuseaddr参数值的函数,从而实现端口复用。

2) 开设本地监听端口

恶意代码为了将窃取的信息通过网络传输,通常在被攻击主机的系统中设立一个监听端口。通过监听80等常用端口,以避免被杀毒软件发现。设立监听端口后,黑客就可以远程操作请求网络连接,专门用于传输数据。

当被监控程序在执行过程中,有上述行为可以作为判断建立本地端口监视器的尝试的依据。

3) 端口反向连接

端口反向连接是指被控端(服务端)主动向控制端(客户端)发起连接,是木马攻击常用的技术。大多数防火墙软件都具有检测开设本地监听端口的功能,然而,防火墙软件有一个弱点,即当主机用户通过Internet访问网页时,浏览器的每一页都将与服务器建立多个网络连接,这被认为是合法的,不会被防火墙软件拦截。为了避免被检测到,恶意代码利用这一弱点主动向攻击一方发起网络连接,在这个过程中需要调用connect函数[12]。

4) 可疑报文收发

可疑报文主要包括恶意程序向控制端发送被感染者敏感信息、被感染者心跳包、控制端对恶意程序的控制指令等。这些报文一般都具有固定的特征,例如定时发送心跳包告知控制端被感染者存活、通信数据经过简单的加密处理等。将可疑报文模型化后,可以通过对send、recv等函数进行监控,一旦发现样本报文符合可疑模型,立即终止该进程并警告用户。

上述分析表明,可以通过监控表2的API函数实现对网络行为的监控,同时也可以在网卡虚拟化的基础上对通信行为与流量进行嗅探,也可以达到同样的网络行为监控的目的。

表2 网络操作监控的关键API函数表

3.3 终止和创建进程监控

恶意代码执行前期,经常会终止反病毒软件,同时在病毒更新、或者调用第三方软件时候会有进程创建操作。常见的可能被关闭的反病毒软件进程有360tray.exe、kavstare.exe、Navapw32.exe、KAVsvc.exe、Rav.exe等等,基于办公文档漏洞的攻击过程中,病毒一般会在代码释放后执行Office进程或者pdf等办公软件进程。

如果要监控对进程的创建和关闭,可以监控表3中的API函数。

表3 终止和创建进程操作监控的关键API函数表

3.4 线程操作监控

在操作系统中,程序执行是包含一个或多个线程的过程,多个线程共享进程占用的系统资源,如内存和CPU。

在Window操作系统中有很多DLL文件,其功能是扩展为一个功能模块。恶意代码往往被伪装或替换为DLL文件的系统,从而隐藏自己的痕迹。这些恶意代码经常会感染系统的关键进程,影响操作系统的正常运行。而这些进程往往不能人为终止,给恶意代码的检测和清除带来了困难。

木马通常以DLL形式,采用远程线程注入[13]封装主部件。窗口系统加载和调用Dll文件,而DLL文件作为函数模块只包含函数形式,不能直接调用,因此,需要被其他进程加载才能运行,通过这种方式实现自身的隐藏。

通过分析可知,可以将线程注入操作和线程创建操作调用系统功能链接起来,实现线程运行监控。远程线程创建函数的CreateVirtualAllocEx是用户态下的形式,内核态的API函数应该是NtCreateVirtualAllocEx,所以,通过挂钩NtSetifyRoutine常规功能可以实现内核级线程注入操作监控。同时,该NtSetifyRoutine功能也与实现线程的创建核心级监控。表4中是监视需要钩子的线程操作的API函数。

表4 线程操作监控的关键API函数表

4 基于虚拟化的沙箱监控

基于API拦截的沙箱监控方式是通过接管系统接口获取恶意文件行为,阻断对真实系统的破坏。这种技术的缺点是不能跨平台、部署复杂,运行过程系统资源消耗大,而且对ShellCode恶意代码检测效果较差。基于虚拟化的沙箱监控技术,能够克服上述缺点。该技术是利用一种纯解释执行的虚拟化检测方式,通过完全模拟x86汇编指令和Windows系统环境,监控恶意文件行为。

4.1 沙箱流程

虚拟化沙箱的设计思想,是模拟运行疑似为可执行代码的输入的数据流,当模拟执行过程中有尝试调用敏感系统函数的行为,或者模拟执行步数达到一定程度(如200步)时,则将该模拟执行过程输出报警。为此,纯虚拟化沙箱技术通过对x86汇编指令、Windows系统特性、内存布局等进行全面模拟,以期达到预期效果。

4.2 x86寄存器模拟

模拟执行x86汇编代码,先要模拟CPU基础架构,下面数据结构用于模拟x86框架下的寄存器。

32位寄存器模拟的关键数据结构如下。

SRegisterAddr_T RegisterAddr[MAX_REG_SYMBOL] =

{

{ "eax", &m_eax, 4 },

{ "ecx", &m_ecx, 4 },

{ "edx", &m_edx, 4 },

{ "ebx", &m_ebx, 4 },

{ "esp", &m_esp, 4 },

{ "bl", &m_bl, 1 },

{ "eip", &m_eip, 4 },

{ "eflags", &m_eflags, 4 },

{ "cs", &m_cs, 2 },

{ "ds", &m_ds, 2 },

{ "ss", &m_ss, 2 },

{ "es", &m_es, 2 },

{ "fs", &m_fs, 2 },

{ "gs", &m_gs, 2 },

};

完成了上述模拟寄存器结构后,涉及到对寄存器进行操作时,采用汇编语句执行时将会比较方便,如对寄存器进行读写操作,则较方便操作如下。

switch (m_RegAddr[i].nLen){

case sizeof(u32):

*(u32 *) m_RegAddr[i].pAddr = nValue;

break;

case sizeof(u16):

*(u16 *) m_RegAddr[i].pAddr = (u16) nValue;

break;

default:

*(u8 *) m_RegAddr[i].pAddr = (u8) nValue;

break;

}

完成上述32位寄存器模拟后,再配合反汇编引擎的解析,就可以实现对基本的汇编指令进行解释模拟运行。

4.3 内存地址映射关键代码

Windows对每个进程都开辟了单独的虚拟内存供该进程使用,为保证目标代码可以正常虚拟执行下去,解释器必须建立自己的虚拟内存空间。

为目标指令提供虚拟的内存环境,供寻址指令调用,采用代码模拟虚拟地址映射的方式,其内存地址映射关键代码如下。

void *SMemSpace::GetAddressAt(SCpuCore *pCpuCore,

u32 nSegment, u32 nOffset, int nExpectedLen)

{

__asm

{

// Convert nSegment::nOffset to liner address

// fetch segment information

// eax = (nSegment[15..2]) * 16 ==

SELECTOR * 4

// pCpuCore->m_Segments + eax +

4 (offset of liner address field)

mov eax, nSegment

and eax, (SELECTOR_FLAG | LDT_FLAG)

mov esi, pCpuCore

lea eax, [esi]pCpuCore.

m_Segments.m_Table[eax * 4 + 4]

// esi = liner address + offset

mov esi, [eax]

add esi, nOffset

// Check segment length

mov eax, [eax + 4] // nLen

sub eax, nOffset

jb exception

// Compare offset + nExpectedLen with nLen + 1,

inc eax

// so inc eax.

// Don’t inc eax before sub eax, nOffset since...

jz maxsize

// eax == 0xFFFFFFFF,

don’t compare with nExpectedLen

sub eax, nExpectedLen

jb exception

maxsize:;

// p = m_pImage

mov ecx, [ecx]this.m_pImage

// if p == null, raise exception

jcxz exception

// eax = esi = liner address

mov eax, esi

while_p_not_null:

// if nOffset < p->m_nVirtualAddress, skip

cmp esi, [ecx]SImageList.m_nVirtualAddress

jb next

// if nOffset > p->m_nVirtualEnd, skip

cmp esi, [ecx]SImageList.m_nVirtualEnd

ja next

// Found

// if nOffset + nExpectedLen - 1 > p->

m_nVirtualEnd, raise exception

add esi, nExpectedLen

dec esi

cmp esi, [ecx]SImageList.m_nVirtualEnd

ja exception

// return (u8 *) p->m_pImage +

nOffset - p->m_nVirtualAddress

add eax, [ecx]SImageList.m_pImage

sub eax, [ecx]SImageList.m_nVirtualAddress

jmp quit

next:

// p = p->m_pNext

mov ecx, [ecx]SImageList.m_pNext

or ecx, ecx

jnz while_p_not_null

}

}

4.4 反汇编获取指令

考虑到设计原理是将未知数据传入虚拟机,由虚拟机尝试将其视为恶意代码开展模拟执行,所以,在尝试执行这些未知输入数据时,首先利用反汇编技术,获取当前数据的汇编指令,然后才能对解析到的汇编指令,开展模拟执行。对输入数据流开展反汇编过程是先根据当前读取的字节判定可能的汇编指令归类,然后,再根据不同的指令类型获取该指令的操作数等细节。

获取指令分类关键代码如下。

m_nCode1 = GetCodeU8();

if (m_nCode1 == 0x0F)

{

// Get from two bytes opcode map

m_nCode2 = GetCodeU8();

pOper = &gOperMapB20F[m_nCode2];

} else

// Get from one byte opcode map

pOper = &gOperMapB1[m_nCode1];

然后,根据指令类型,获取该条汇编指令的详细内容。对整个数据流进行反汇编是个不断前进持续的过程,所以其循环执行的代码流程如下。

do

{

// Get code & opermap

pOper = GetCodeOper();

// Prepare operand for this instruction

PrepareOperandDesc(pOper);

// Get instruction

szDesc = pOper->pInstruction->

Deassemble(this, pOper);

} while (szDesc == NULL);

本部分反汇编技术的输出结果,是后续指令模拟执行的基础,为后续模拟执行提供汇编指令支撑。

4.5 汇编指令模拟

当进行汇编指令模拟时,对于常见的汇编指令,可以通过简单方法进行模拟,其方法可参考4.2部分寄存器操作模拟;对于不常见指令,或者较难模拟的指令,将其交给真实的CPU去执行,再获取返回到的结果,其相关代码如下。

__asm{

push edx

mov ecx, this

lea ebx, [ecx]this.m_pCodes

mov ecx, pCpu

call dword ptr [ebx]

pop edx

}

上述处理方法的优势是既充分发挥真实环境的优点,又降低沙盒环境的开发难度。

4.6 反汇编执行流程

反汇编执行流程是对输入数据流进行解释执行的主流程。通过解释分析获取当前汇编指令,然后,模拟正常汇编指令解析过程进行不断前行解释,其相关代码为

// Initialize before execute this instruction

PreExecute();

try

{

// Execute this instruction

do

{

// Get opcode & its opermap

pOper = GetCodeOper();

// Prepare operand for this instruction

PrepareOperandAddr(pOper);

// Get instruction

bDone = pOper->pInstruction->Execute(this);

// If to be continued, got NULL in szDesc

} while (! bDone);

}

确定了上述内存映射方式以后,就可以为虚拟环境开辟单独的内存空间。当目标代码尝试操作虚拟内存时,通过上述映射方式对虚拟内存进行操作。

4.7 内存结构

为了使沙箱虚拟机的沙箱环境区全面支持Windows环境,即能实现对x86汇编指令环境的模拟,还应实现对Windows系统特性如内存结构的支持。考虑到ShellCode编码的特点,运行时会预先查找当前所在进程内存空间,从中搜索到自己所需要的系统函数地址,因此,沙箱虚拟机需要模拟Windows进程内存,以便ShellCode可以在该虚拟环境中正常运行。对虚拟内存的模拟设置如下。

//用于设置32位系统下的4G内存空间

g_pMainCpu->CreateSegment(g_pMainCpu->

m_fs, 0xFFDF0000, 0x0FFF);

//从正常进程中Dump出一份FS段内容,填充到虚拟空间中

LoadData_1(0x38, "FSImage.dat",0,1024 * 4);

LoadData_1(IMAGE_BASE_ADDR, "Image.dat",

0, 0x1f80);

其中LoadData_1实现过程,先读入预先Dump出的正常进程数据,然后覆盖到设定的虚拟空间内,即

LoadData_1(0x38, "FSImage.dat",0,1024 * 4);

LoadData_1(IMAGE_BASE_ADDR, "Image.dat",

0, 0x1f80);

建立上述虚拟内存空间后,当目标代码虚拟执行时,就可以模仿正常运行时的内存读取和写入操作。如果需要解释器,也可以随时将内存状况进行保存或者监控。

4.8 代码执行

完成上述工作后,便可以对用户输入的数据流进行模拟执行检测。主要检测代码如下。

InitCpu();//CPU环境初始化

CreateImage(g_EntryPoint, nCodeSize);

//创建虚拟内存,供模拟执行时候调用

PutMemAt(g_EntryPoint, pCodeBuf, nCodeSize);

//将输入数据填充到设定的虚拟内存中

SetFSImage();//模拟Windows系统特性,初始化FS区段

//模拟其他进程区段,供ShellCode

Cpu.PutU32At(Cpu.m_fs, 0x30, 0x7FFDF000);

LoadData_(0x08, "Image.dat", IMAGE_BASE_ADDR,

IMAGE_BUF_SIZE);

LoadData_(0x08, "Kernel32IAT.dat",

KERNEL32IAT_BASE_ADDR,

KERNEL32IAT_BUF_SIZE);

LoadData_(0x30, "FSImage.dat", 0, 200);

LoadData_(0x38, "FSImage.dat", 0, 1024 * 4);

LoadData_(0x08, "SearchKern.dat",

DEFAULT_SEH_HANDLE_ADDR,1024 * 20);

//模拟执行输入数据

unsigned long nProcessCount = 0;

int nRet = CallNear(g_EntryPoint,

0, 0, 0, &nProcessCount, 1);

上述代码是目标代码虚拟执行的主要流程,具体包括创建虚拟CPU环境;为目标代码创建虚拟内存环境;模拟Windows特性,在虚拟内存里初始化内核表等数据,以便后续目标代码可能使用。

4.9 其他Windows性能模拟

通常情况下,欲获取较全的病毒运行流程,除了模拟32位汇编指令运行环境是远远不够的,还需要模拟其他病毒可能会使用的系统性能,以便支撑恶意文件的运行。为此,设计除对Windows内存分布,还对恶意代码技术涉及的API函数和异常处理进行了深入研究,并实现对常见API函数的运行结果模拟,其实现代码如下。

static WINAPI_TABLE_T Kernel32Table[] =

{

{ "CreateThread", PROC_CREATETHREAD,

(SubFunc_T) Kernel32_CreateThread, 6 },

{ "CreateEventA", PROC_CREATEEVENT,

(SubFunc_T) Kernel32_CreateEvent, 4 },

{ "LoadLibrary", PROC_LOADLIBRARY,

(SubFunc_T) Kernel32_LoadLibrary, 1 },

{ "LoadLibraryA", PROC_LOADLIBRARY,

(SubFunc_T) Kernel32_LoadLibrary, 1 },

{ "GetLocalTime", PROC_GETLOCALTIME,

(SubFunc_T) Kernel32_GetLocalTime, 1 },

{ "GetModuleHandle", PROC_GETMODULEHANDLE,

(SubFunc_T) Kernel32_GetModuleHandle, 1 },

{ "GetModuleHandleA", PROC_GETMODULEHANDLE,

(SubFunc_T) Kernel32_GetModuleHandle, 1 },

{ "GetProcAddress", PROC_GETPROCADDRESS,

(SubFunc_T) Kernel32_GetProcAddress, 2 },

{ "GetTickCount", PROC_GETTICKCOUNT,

(SubFunc_T) Kernel32_GetTickCount, 0 },

{ "GlobalFree", PROC_GLOBALFREE,

(SubFunc_T) Kernel32_GlobalFree, 1 },

{ "GlobalAlloc", PROC_GLOBALALLOC,

(SubFunc_T) Kernel32_GlobalAlloc, 2 },

{ "SetEvent", PROC_SETEVENT,

(SubFunc_T) Kernel32_SetEvent, 1 },

{ "SetUnhandledExceptionFilter",

PROC_SETUNHANDLEDEXCEPTOINFILTER,

(SubFunc_T)

Kernel32_SetUnhandledExceptionFilter, 1 },

{ "WaitForSingleObject",

PROC_WAITFORSINGLEOBJECT, (SubFunc_T)

Kernel32_WaitForSingleObject, 2 },

// { "Sleep", PROC_SLEEP,

(SubFunc_T) Kernel32_Sleep, 1 },

// { "CreateToolhelp32Snapshot",

PROC_CREATETOOLHELP32SNAPSHOT,

(SubFunc_T)

Kernel32_CreateToolhelp32Snapshot, 2 },

};

static WINAPI_TABLE_T User32Table[] =

{

{ "FindWindowA", PROC_FINDWINDOW,

(SubFunc_T) User32_FindWindow, 2 },

{ "FindWindow", PROC_FINDWINDOW,

(SubFunc_T) User32_FindWindow, 2 },

{ "MessageBoxA", PROC_MESSAGEBOX,

(SubFunc_T) User32_MessageBox, 4 },

{ "MessageBox", PROC_MESSAGEBOX,

(SubFunc_T) User32_MessageBox, 4 },

};

在对上述API的模拟过程中,对于较简单情况,可以调用真实的API函数,将结果返回;对于较为复杂的情况,需要利用Windows系统特性,如对用于设置当前进程异常处理SetUnhandledExceptionFilter函数的支持,该函数的模拟需要采用Windows系统异常处理机制的实现,其中SHE链处理关键代码如下。

// Try SEH first

// Get first seh pointer @ fs:[0]

u32 nEip;

u32 nSEH, nProcAddress;

nEip = pCpu->m_eip;

nSEH = *(u32 *) pCpu->GetAddressAt(pCpu->

m_fs, 0, sizeof(u32));

while (nSEH != 0xFFFFFFFF &&

nSEH != DEFAULT_SEH_HANDLE_ADDR)

{

// Get handler

nProcAddress = *(u32 *) pCpu->

GetAddressAt(pCpu->m_ss, nSEH + 4,

sizeof(u32));

if (nProcAddress == KERNEL_SEH_PROC_ADDR)

{

// Enter kernal exception process, do nothing

nSEH = 0xFFFFFFFF;

break;

}

// Invoke process

// arguments:

// lpExceptionRecord

// lpSEH

// lpContext

// lpDispatcherContext

args[0] = (u32) s.ExceptionInfo.ExceptionRecord;

args[1] = nSEH;

args[2] = (u32) s.ExceptionInfo.ContextRecord;

args[3] = (u32) s.ExceptionInfo.ContextRecord;

if (! pCpu->CallNear(pCpu, nProcAddress,

0x7C9037BF, /* Copied from WinXP */ &args, 4,

&nRet, STOP_WHEN_EXCEPTION))

{

// Failed to execution, exception occurred

// Stop simulation

// Restore stack

pCpu->m_esp = nEsp;

return 0;

}

if (pCpu->m_esp >= nEsp)

{

// Stack is restored by exception handler,

return immediately

// return 1 means continue excution

return 1;

}

// Drop arguments

pCpu->m_esp += 0x10;

// Restore EIP (since it has been changed by CallNear

pCpu->m_eip = nEip;

if (nRet == 0)

{

// Return 0 means continue excution

nRet = EXCEPTION_CONTINUE_EXECUTION;

break;

}

switch (nRet)

{

case 1:

// Don’t care this exception

break;

case 2:

// Nexsed excpetion

break;

case 3:

// Collided unwind

break;

}

// Try next seh pointer

nSEH = *(u32 *) pCpu->GetAddressAt(pCpu->

m_ds, nSEH, sizeof(u32));

}

通过对这些Windows系统的API、异常处理等关键特性的模拟,使该检测系统具备了掌握目标代码执行流程的能力,这是区别于传统检测手段的主要部分。

5 虚拟沙盒测试

为了测试所提出虚拟化的沙箱对恶意代码行为监控效果,为此,采用了使用编号为CVE-2012-0158的Microsoft Office安全漏洞文件进行检测,检测结果如图1和图2所示。从图1可见,所提出的虚拟沙盒技术方案成功地检测到该文件低12291处存在疑似ShellCode恶意代码,图2显示了该疑似ShellCode恶意代码实际位置为0x3005。因此,所提出的虚拟沙盒技术方案能够检测相关的恶意代码。

图1 检测结果

图2 文件16进制显示

6 结语

在破坏与反破坏的过程中,恶意代码为了躲避各类安全软件的查杀,使用越来越复杂的技术来隐藏自己,这给恶意代码的检测和防治带来了很多困难,计算机系统和网络安全面临的威胁和破坏日渐复杂。本文探讨了恶意代码的攻击技术和恶意行为分析方法,并针对现有沙箱技术不能跨平台,系统部署复杂的问题,提出了使用纯虚拟化解释执行的手段检测恶意代码的一种方法。从实验结果可以看出,沙箱能够有效地检测恶意代码对文件操作和网络行为,并对其进行限制。但是,沙箱技术对样本实例的检测结果还不够全面,需要更进一步地研究。

猜你喜欢

沙箱端口虚拟化
一种有源二端口网络参数计算方法
一种端口故障的解决方案
多按键情况下,单片机端口不足的解决方法
基于OpenStack虚拟化网络管理平台的设计与实现
Removing a stone
对基于Docker的虚拟化技术的几点探讨
巧用沙箱检测文件安全
浅析虚拟化技术的安全保障
文件检测方法及沙箱
H3C CAS 云计算管理平台上虚拟化安全防护的实现