代码战争:伪装和狙杀——从“壳”到“病毒混淆器”
-
作者:火绒安全
-
发布时间:2016-08-25
-
阅读量:1068
一、前言
“壳”(Packer)——在安全领域同APT一样,是一个宽泛的概念,即指代一类对可执行代码及数据进行包装已达到隐藏、压缩或保护等目的的运行时代码。
在病毒与安全软件的代码对抗过程中,“壳”一直扮演着极为重要的角色。早期反病毒引擎仅能够扫描可执行文件最外层的特征(代码和数据)。“加壳”后的病毒由于原始病毒特征(代码和数据)被“壳”改变(压缩、加密等),则可以躲避反病毒引擎的查杀。随着这种对抗的不断发展,虽然很多病毒的核心特征变化并不大,但是“壳”的“免杀”能力在不断增强,不断地更新换代,长时间与安全软件进行着对抗。所以,在现今互联网大环境中,如果一个反病毒引擎不具备强大的“脱壳”能力,就不能对病毒进行有效查杀,最终落后于整个安全行业的发展。
早期的“壳”通常以压缩目标程序为目的,如UPX、PECompact等经典的“压缩壳”等。随着软件知识产权意识的萌发,以保护程序代码及数据为目的的“壳”应运而生,此类“加密壳”、“保护壳”往往会利用花指令、反调试等手段干扰破解者对被保护代码和数据的“窥探”(包括静态和动态分析),Asprotect、VMProtect、Themida便是其中一些经典的“保护壳”。
上述“压缩壳”、“加密壳”和“保护壳”由于加壳程序公开(免费或收费),所以安全工程师可以对”加壳“的过程和结果进行分析,从而可以通过代码识别程序是否被“加壳”、加了何种“壳”,进而“脱壳”,并可以将这种“脱壳”逻辑嵌入到反病毒引擎的扫描逻辑中,从而在一定程度上“解决”了“壳”的对抗。
然而近10年来,很多病毒作者通过“壳”和感染型病毒常用的多态、变形等技术编写非公开的“私有壳”对病毒代码进行保护,这类“壳”往往不仅包括上述公开“保护壳”的功能,并且外层的“壳”代码还会通过各种手段干扰、误导、对抗反病毒引擎的识别,比较典型的手段包括代码变形、入口伪装、高级语言包裹(High-Level Language Wrapper)等。安全领域为此类“壳”起了个更为“高大上”的名字:“病毒混淆器”(Obfuscator)。安全领域较早的关于“病毒混淆器”的详细分析可以参考2010年RECon大会上Pierre-Marc Bureau(来自ESET)和Joan Calvet对于C2Lop病毒外层的Swizzor混淆器的分析(https://recon.cx/2010/slides/Recon2010-UnderStaningSwizzorObfuscation.pdf)。
“病毒混淆器”的出现使得传统反病毒引擎“认壳”才能“脱壳”的机制完全失效。更有意思的是,在很长一段时间,很多安全厂商由于一直以来依赖对“壳”和编译器的识别,完全没有意识到“病毒混淆器”的崛起,甚至根据“大数据”的分析发现“加壳”样本的比例不升反降,思忖一定是“现在反病毒引擎都能‘脱壳’,所以病毒作者也就不再‘加壳’了”。就像曾有人对笔者说的,“‘壳’的问题是个轮回的过程,从2008年以后壳就变得很少了”。然而,根据我们对大量病毒样本的分析,我们坚定的认为“壳”的问题并不是轮回,传统的“壳”早已被“病毒混淆器”这个更为复杂的概念意义上的“后代”所取代,并持续的进化着。
病毒制造者在“病毒混淆器”的帮助下,可以在短时间内批量生成大量“伪装”(变形)过的病毒样本并迅速传播。为了应对不断变化的病毒样本,云引擎和大数据人工智能学习的方法逐渐走进了人们的视野。但长期看来,上述两种对抗策略从根本上存在着不可逾越的瓶颈和数据资源的限制。而“通用脱壳”技术相较于前两者,虽需要长期的技术投入,但却可以获得更为持久、有效的查杀回报,无疑将成为未来解决“病毒混淆器”类问题的优选方案。
下面我们针对这三种技术进行详细说明:
1. 云查杀和云引擎
如今很多安全厂商的“云引擎”,主要是希望通过缩短反病毒引擎特征更新周期来与病毒作者拼速度,从而达到快速抑制病毒扩散的效果。当然,这显然不是“云安全”的本质,但由于篇幅所限,本文并不对“云安全”这一话题进行讨论。
“云引擎”虽然可以实时同步云端的计算结果,但由于网络带宽的限制,在有限的扫描时间内,云引擎只能在本地提取高度抽象的数据特征发送到云端进行匹配,所以一般云引擎会选择哈希类特征(通常是全文哈希)。而哈希类特征的检出能力与样本基本是1:1的关系,即一条哈希特征通常只能检出一个样本。
由于“云引擎”采用哈希类特征,所以每个“伪装”过的病毒样本对于云引擎来说都是全新的,都需要进行“鉴定”。我们并不需要进行精确的计算,就可以得出如下结论:“云引擎”从采集到样本并传到云端分析,到云端分析出结果,再到客户端能够请求到结果,这个周期远远长于病毒作者通过“病毒混淆器”批量生成变形病毒样本的周期。
虽然“云引擎”能够缩短反病毒引擎特征更新周期、提高安全产品的反应速度,但是对于“病毒混淆器”批量生成的变形病毒,起到的作用只不过是“掩耳盗铃”罢了。
2. 利用大数据和“人工智能“,猜测病毒的“伪装“
近些年,国内一些安全软件发布了各自的基于大数据或“人工智能”的反病毒引擎。这些引擎本质上都是基于统计学算法,通过对海量样本以固定方法抽取特征,并对特征进行统计、分析,进而产生计算模型。依照计算模型对待扫描样本进行分类,进而预测新样本是否属于恶意分类。
在没有数据为依托的前提下,我不能对此类引擎的效果妄加揣测。但无论算法如何、样本如何选取,都逃不过一个重要条件,那就是对特征的抽取。我们不妨做如下假设:我们通过外貌、服饰、声音、举止等方面可以基本准确地判断一个人的性别,这就像扫描明文的恶意代码。如果将人关进房间,只有房间的颜色、外观等特征可见,而人本身相关的特征完全看不到,这就像是被混淆过的恶意代码。将大量的人随机安排进不同颜色,不同外观的房间,试问仅通过房间的颜色和外观,通过统计、分析,产生计算模型,并推测某一房间内人的性别是否可靠?我想答案是否定的。
无论是统计学或是人工智能算法,算法本身并没有任何问题,但如果不能戳穿病毒的“伪装”,不能抽取到有效的特征,一切都只会是徒劳。如果仅抽取样本的外层特征,“病毒混淆器”作者可以很轻易“伪装”出正常的程序结构、代码特征以及数据特征来躲过统计模型的预测,甚至可以”误导”并“污染”统计模型从而使此类反病毒引擎导致严重的误报。
3. 通过“通用脱壳”戳穿病毒的“伪装”
“通用脱壳”(Generic Unpacking)就是最典型的用于戳穿病毒“伪装”的技术。简单来说,“通用脱壳”就是不对程序进行精确的“壳识别”,而是通过某些启发式逻辑评估待扫描是否可能被“加壳”(或者完全不评估是否可能“加壳”),并在虚拟沙盒中运行待扫描样本,使其在虚拟环境中还原被保护的代码、数据、行为,从而进行查毒。关于虚拟沙盒相关介绍可以参考《动若脱兔——火绒虚拟沙盒简介》一文相关章节,此处不再赘述。后文中,如无特别说明,“虚拟机”均指代虚拟沙盒。
“通用脱壳”技术对于处理“病毒混淆器”显然具有极大的帮助,如果反病毒引擎构造的虚拟环境足够逼真、虚拟沙盒的执行效率足够高,那么对于批量产生的变形病毒样本,反病毒引擎仅需要捕获其中部分样本则可以查杀后续的全部变种。纵观国内外也只有部分反病毒引擎在不同程度上实现了“通用脱壳“技术,国外包括MSE、ESET、Kaspersky、BitDefender、VIPRE在内的多款反病毒引擎,很早就引入了“通用脱壳”技术,并且多年来从未停止过对这方面的技术投入。火绒也在2013年实现了“通用脱壳”技术,并至今仍然持续保持着火绒虚拟沙盒的高速更新。关于“通用脱壳”相关介绍可以参考《反病毒”芯“技术——火绒反病毒引擎简介》一文相关章节,此处不再赘述。
在对大量样本的分析基础上,我们将“病毒混淆器”的大致进化过程进行了简单梳理,如下表所示。后续,本文将以”病毒混淆器“的“进化”过程为主轴,回溯“病毒混淆器”与反病毒引擎的对抗过程。后文中,如无特殊说明,“混淆器”均指代“病毒混淆器”。
表1、混淆器进化过程总表
二、经典案例
1. 指令级混淆器——Conficker
Conficker的出现时间在2008年,作为最早使用混淆器的病毒家族之一,其对于整个病毒的“黑色产业链”发展有着里程碑式的意义。MS08-067这个漏洞利用门槛低、成功率高是这个病毒能够快速流行的主要原因,但能使该病毒能够长期处于“免杀”状态显然归功于其使用的变形混淆器。在该病毒流行的早期,国内外安全厂商在没有“通用脱壳”技术的状态下,只能采用批量入库的方式处理,从而很难有效通杀整个病毒家族。在Conficker“一战成名”之后,使用混淆器的病毒样本开始逐渐增多,“病毒混淆器”的“进化”也就此展开。
该病毒会尝试创建许多用于其进行病毒传播的网络连接,并禁用系统安全设置、阻止本地安全软件的正常升级。其主要传播途径为:U盘、网络共享、弱口令密码的共享计算机等。当病毒在一个局域网内部大范围传播后,会造成严重的网络阻塞。
通过火绒行为沙盒我们可以看到一些主要的病毒行为:
图1、火绒行为沙盒运行效果图
Conficker家族的样本结构极为统一。最外层为混淆器,其主要功能为解密原有数据、对抗虚拟沙盒和反调试。经过混淆器的数据解密后,再向内一层是经过UPX压缩的Conficker病毒主体。如下图所示:
图2、Conficker病毒整体结构
虽然混淆器的逻辑并不十分复杂,但是其主要逻辑代码被切分成了很多小的逻辑块,随机打乱顺序后分布在整个混淆器代码中,逻辑块与逻辑块之间用jmp指令进行连接,jmp指令的跳转位置全部被存放在混淆器的跳转表中。
图3、混淆器跳转表示意图
其混淆器的解密过程并不复杂,本样本解密流程如下(dest代表目标字节,index为当前解密位置,shl_count为可变参数,decrypt_data为加密数据):
图4、混淆器解密流程
通过对Conficker所使用的混淆器的分析,我们发现最初的混淆器只是通过对外层特征的混淆达到其与安全软件对抗的目的,对抗主要还停留在指令级。基于“通用脱壳”的反病毒引擎还是可以很从容地处理。
混淆代码运行结束后,我们可以看到内层的UPX相关代码。如下图所示:
图5、内层UPX相关代码示意图
该混淆器由于出现时间比较早,采用的反调试、反虚拟机的手段还比较原始,利用rdtsc执行检测两次系统时间戳的间隔是否过长,如果间隔时间过长,则判定是在进行调试,从而结束病毒进程。此处逻辑也都是使用jmp指令进行连接,通过简单的反混淆之后我们可以清楚地看到其反调试、反虚拟机的具体逻辑。如下图所示:
图6、混淆器反调试相关代码
Ogimant是一个国外的著名“下载器”病毒家族,主要流行时间在2014前后,其所使用的混淆器在混淆能力上较Conficker家族已经有了“长足的进步”。随着”通用脱壳“技术的成熟,混淆器为了加强自己的“免杀能力”已经逐渐的开始使用一些简单的针对虚拟沙盒的对抗和检测技巧,其对抗和检测逻辑会根据不同样本而稍有不同。
通过火绒行为沙盒,我们可以看到一些主要的病毒行为:
图7、Ogimant病毒的主要病毒行为
该混淆器的总体结构比较简单,最外层是病毒混淆器,在混淆器运行完成后,原程序主映像区中就会解密出病毒本体。虽然总体结构比较简单,但其解密过程则稍微复杂一些。
首先,混淆器会在内存中将负责解密原始病毒PE文件的解密代码在堆区中进行解密并执行。
图8、解密代码
解密出解密代码之后,再由该解密代码利用8个字节的解密密钥对整个加密的病毒PE文件进行全文解密。解密代码如下图:
图9、Ogimant病毒原始PE文件解密代码
然后,在原进程的主映像区中将病毒的PE文件进行内存映射,将映射后的病毒数据以逐字节异或的方式备份起来,使用NtUnmapViewOfSection函数将原进程的主映像区内存释放,之后用备份数据将映射后的病毒数据重新在主映像区复原。由于加密与解密代码相似,我们只列出了加密代码,代码如下:
图10、加密映射后的病毒PE文件代码
在完成了病毒文件的内存映射后,最后混淆器帮助病毒修复导入表后从病毒的映像入口点开始运行,混淆器的所有逻辑运行完毕。
相较于早期的混淆器,该混淆器使用了一些与虚拟沙盒对抗的技巧,如循环执行没有实际意义的代码试图使反病毒引擎的“通用脱壳“流程超时、检测API返回值、检测调用API出错后的LastError等,下面我们逐一举例。
图11、混淆器拖慢虚拟机代码
通过上图我们看到,该混淆器大量调用VirtualAlloc函数申请内存,并填入垃圾数据再逐一释放。这样的混淆手段很大程度上的消耗了扫描效率,如果虚拟沙盒一味的按照其逻辑进行执行,最终都会因为超时而很难对其进行查杀。
相较于拖慢虚拟机而言,检测API LastError的检测手法则更加直接。在真实操作系统中setsockopt调用时五个参数如果全部为0时,LastError的值应该为0x276d,如果虚拟沙盒中环境不够真实,那么程序将无法为解密代码分配内存空间,从而造成异常。如下图所示:
图12、混淆器检测API LastError代码
在其解密代码中,我们依然可以看到与虚拟沙盒对抗的相关代码。如下图,其代码中调用了Winscard.dll动态库中的SCardIntroduceCardTypeA函数,并检测器返回的错误号。与上文说到的检测函数LastError相近,其目的都是为了检测当前运行环境是否真实。
图13、混淆器检测函数返回值代码
该混淆器还使用了GetTickCount函数来进行反调试,在Conficker使用的混淆器中我们已经介绍过类似手法,这里不再进行过多赘述。
在该混淆器中,相对于上述几种反虚拟机的手段之外,我们还发现其使用CreateProcessW函数开启新的自身进程并添加了命令行参数,新进程会通过检验命令行参数的方法判断是否执行后续逻辑代码。我们以此可以看到,混淆器的发展从单一、简单的检测方法,逐渐向系统化检测运行环境的方向进行发展。
图14、利用进程命令参数对抗虚拟沙盒行为效果图
在我们所发现的混淆器中,经常会发现混淆器检测一些比较不常见的API,由于这些函数本身不容易被一般程序使用,所以在虚拟沙盒中通常也不会对其进行实现,这样就给了这些混淆器一个可乘之机。虽然这些的“稀奇古怪”的 API有一定的数量,但是API的数量也毕竟是有限的,经过我们对虚拟沙盒环境的不断进行完善,此类问题就都将会迎刃而解。
FakeAV的出现时间在2007年前后,该病毒主要以虚假病毒威胁进行欺骗,让用户相信当前系统已被病毒感染或存在严重问题需要付费进行修复,骗取用户钱财。在利益的驱使下,病毒制造者在保持病毒核心不变的情况下,不停地迭代混淆器来”免杀”,随之而来的变是更加“花样百出 ”的反虚拟机对抗手段。
本报告中所述样本有一定的反虚拟机能力,经过简单的对抗后,我们可以看到该病毒的运行界面。在当前环境无任何安全威胁的情况下,谎报出若干各式各样的病毒,并提示用户付费购买其所为的“安全软件”。
图15、FakeAV病毒运行界面
图16、FakeAV虚假报毒弹窗
通过火绒行为沙盒我们也可以看到该样本运行的一些细节,如下图所示:
图17、火绒行为沙盒运行效果图
FakeAV在火绒虚拟沙盒中运行后的内存特征如下:
图18、FakeAV在火绒虚拟沙盒中运行后的内存特征
该混淆器先进行了反虚拟机的相关操作。在通过检测后,其会在堆区解密出用于还原原本病毒PE文件的一段解密代码。因为其解密代码中穿插有大量花指令,在对其代码进行过反混淆之后,我们得到了如下解密逻辑:
图19、解密代码示意图
最终负责解密病毒文件的代码分为两个部分。第一部分主要用于获取后面逻辑所用的API地址,并简单检测了ReadFile函数的入口点数据。检测函数入口点是一种对抗虚拟沙盒的手段,我们初步猜测,混淆器作者发现某家安全软件的虚拟沙盒中,其仿真的ReadFile函数入口为0x8B(很多Windows API入口都是8BFF:mov edi,edi)。在上述逻辑运行之后,再将解密代码释放到另外一块堆区中,对其的第二部分进行执行。
解密代码的第一部分具体逻辑如下图所示:
图20、解密代码第一部分逻辑示意图
该解密代码的第二部分主要负责病毒PE文件的还原与加载。首先其从自身PE文件中读取被加密的病毒PE 文件,之后进行解密。经过PE文件映射、修复导入表、检测是否需要修复重定位表之后,开始执行病毒逻辑代码。
其主要流程如下:
图21、病毒PE文件解密相关代码
图22、恢复及加载病毒PE结构代码
对于FakeAV所使用的混淆器来说,其使用了更加“疯狂”的反虚拟机手法。在混淆器运行之初,调用了若干的API来检测当前环境是否为真实环境。与之前的Ogimant混淆器中所使用的混淆方法相比,FakeAV混淆器所调用的系统API检测效果更加系统化,如:FindResourceA,需要对当前进程中的所有资源进行解析;SetCurrentDirectoryA,需要在虚拟沙盒中也模拟一个完整的文件系统。
图23、FakeAV调用API检测运行环境效果图
除了上述这些监测点外,我们还发现了一些十分有意思的检测方法。该混淆器先调用GetCursorPos函数得到两次当前鼠标位置(其间会调用GetEnvironmentVariableA检测环境变量真实性),之后不断地调用 GetCursorPos函数,直到所得鼠标位置的x坐标与第一次不同的时候,才会执行后续逻辑。经过简单的反混淆处理之后,我们可以看到其基本的对抗虚拟沙盒的逻辑,如下图所示:
图24、FakeAV通过检测鼠标位置反虚拟机代码示意
随着混淆器技术的不断发展,混淆器检测虚拟沙盒的手段也不断更新换代。其所使用的检测方法要求虚拟沙盒中的运行环境与真实机中更加接近,这使得我们对于虚拟沙盒仿真的复杂程度也逐渐提升。虽然如此,但是对于虚拟沙盒的提升是对整个引擎综合能力的提升,一个良好仿真环境就如同庖丁手中的利刃,可以帮助我们剖开其表象,直达其本质。
Upatre是2013年左右开始流行的“下载器”病毒家族, 该家族主要通过将自己伪装成Email附件的形式进行传播,并附以发票、传票等欺骗性的文件名诱骗用户下载、运行,该病毒通常会将自己的图标设置为PDF图标。由于其所使用的混淆器十分复杂多变,所以如果不借助“通用脱壳”技术,仅仅使用静态特征对其进行查杀是十分困难的。这使得该家族在短时间之内大范围传播,甚至成为了很多勒索病毒的主要传播渠道,如:CryptoLocker。
混淆器整体结构与之前的混淆器相近,在此不再赘述。下面我们针对Upatre的病毒行为及其所使用的混淆器的反虚拟机的手段进行详细说明。
其主要病毒行为比较简单,只是联网下载文件。如下图所示:
图25、行为沙盒运行效果图
其混淆器使用的反虚拟机手段十分复杂,其主要使用了一些Windows图形控件的回调函数进行运行环境监测,如RichEdit、ListBox等。在本样本中其使用的是RichEdit,首先其利用SendMessage向RichEdit发送EM_SETBKGNDCOLOR消息将解密代码地址相对某区域的相对偏移设置改控件的背景颜色(由于在设置新的背景颜色时SendMessage函数会返回原来的背景色,所以在该混淆器中将其当作一个隐藏的数据存储空间),然后在其再次向该RichEdit发送消息时,获取其之前设置的数值。这一检测手段大大提高的虚拟沙盒的仿真难度,首先要对图形窗口的生命周期流程、消息的获取、派发等一系列窗口运行流程进行全面的仿真,其次还要对RichEdit、ListView等一系列控件进行仿真。
图26、混淆器利用RichEdit检测虚拟沙盒代码展示
从行为中我们也可以观察出其反虚拟机的基本逻辑:
图27、混淆器利用RichEdit检测虚拟沙盒效果图
早期混淆器的检测手法多为单一检测点的检测,比如注册表项、文件、API返回结果等,虽然在反虚拟机上确实在很短的一段时间内存在一定的效果,但对于虚拟沙盒来说,其依然可以通过添加这些运行环境有效地进行仿真。随着这种简单交锋的次数不断增多,虚拟沙盒的运行环境也越来越接近于真实的操作系统,混淆器制作者想要通过单一方法检测虚拟沙盒所要付出的成本也越来越高,这也促使了其对抗手段的又一次升级。从上述分析中,我们不难看出混淆器对运行环境的检测手法已经从单一检测点完全过渡到了系统化检测的新阶段。在这一层面进行对抗,就要求虚拟沙盒的仿真方面,在对抗初期投入大量的精力对其检测的整个系统框架进行整体仿真。因为只有进行仿真才能抓住病毒本质,从而跳出一味地使用外层特征查杀而被动挨打的局面。
Tescrypt是在2014年前后较为流行的勒索病毒,其已经给成千上万的受害者带来巨大的经济损失。其大面积爆发的主要原因也是因为其所使用的病毒混淆器。如本文中所提到的Tescrypt病毒样本,其包含有内外两层混淆逻辑,外层混淆效果较弱,主要用于加密内层数据、躲避静态查杀;内层混淆能力较强,主要用于对抗虚拟沙盒。
图28、Tescrypt样本结构图
由此我们推断,随着虚拟沙盒的越来越接近真实操作系统,混淆器在与虚拟沙盒进行对抗所需要付出的精力越来越大,最后混淆器的制作者将混淆工作进行了明确的分工。即外层混淆器使用代码级混淆、更改主要逻辑等方法与静态查杀进行对抗;内层混淆器使用系统化检测运行环境的方法与虚拟沙盒进行对抗。在这种分工模式下,致使多数安全软件很难对其所混淆后的样本进行有效查杀。
其病毒行为主要以加密本地文件为主,我们通过行为沙盒看到一些主要的行为细节:
图29、Tescrypt病毒行为示意图
Tescrypt病毒在在火绒虚拟沙盒中运行后的内存特征如下:
图30、Tescrypt病毒在火绒虚拟沙盒中运行后的内存特征
混淆器一路发展而来,混淆手段已经不断“推陈出新”升级了很多次,其主要目的是与虚拟沙盒进行对抗,从而绕过安全软件的查杀。Tescrypt家族作为一个曾大范围传播的勒索病毒家族,其所使用的混淆手段十分巧妙(因为外层混淆器与以往的高级壳类似,所以我们主要围绕其内层混淆器展开讨论)。
其内层混淆器主要使用了COM接口帮助其检测当前运行环境。其先创建了两个COM对象,FilgraphManager对象中可以调用IGraphBuilder接口中的AddFilter方法来添加Filter对象。其先添加了空的对象地址以检测错误,之后以创建出来的Filter对象以随机名调用AddFilter方法再次进行添加。添加之后在通过IGraphBuilder接口中的FindFilterByName方法查找并检测对象信息中名字和Filter对象的地址是否与之前添加的对象信息相匹配。
图31、通过COM接口检测虚拟沙盒代码示意图
与之前提到的Upatre家族所使用的混淆器相同,该混淆器也是通过系统化的检测手法对当前运行环境进行检测。根据我们以往所遇到的使用COM接口进行混淆的混淆器来说,此类混淆器多为使用两个COM对象相互配合从而达到检测运行环境的作用。用来进行反虚拟机的COM对象经常变换,Windows所提供的COM接口十分繁杂、多样,这就使得此类混淆器变化多端,从而也致使使用这种混淆器的病毒可以在短时间之内大范围进行传播。
6. 高级语言混淆器
病毒混淆器除了使用上述方法与虚拟沙盒进行对抗外,还会使用一些高级语言制作病毒混淆器,如Visual C++、Visual Basic、C#等。这不光给病毒分析人员制造了麻烦,也给虚拟沙盒带来了新的难题。
C2Lop病毒可以说开创了大规模使用高级语言混淆器的先河,其主要流行时间在2008年前后。在2010年的RECon大会上Pierre-Marc Bureau和Joan Calvet对于C2Lop病毒外层的混淆器(此前该混淆器被业内多家安全软件命名为Swizzor)的分析(https://recon.cx/2010/slides/Recon2010-UnderStaningSwizzorObfuscation.pdf)已十分详细,此处不再赘述分析过程。
图32、Swizzor混淆器 “伪装“的Visual C++入口
图33、Swizzor混淆器的WinMian函数
Swizzor混淆器与以前的加密不同的地方是其解密后的代码及数据全部都在堆区,所以在其运行后在主映像区提取到的数据与C2Lop病毒本体并无直接关系。Swizzor混淆器运行后,我们即可看到内层的C2Lop病毒本体。如下图所示:
图34、C2Lop病毒的真正入口
图35、C2Lop病毒WinMain函数
在火绒虚拟沙盒运行后,我们可以看到C2Lop病毒的内存特征。如下图所示:
图36、C2Lop病毒在火绒虚拟沙盒中运行后的内存特征
Visual Basic是一种高级语言,其主要特点是程序主要运行在Visual Basic虚拟机中,所以为Visual Basic程序的调试造成了很大的困难。我们可以通过行为沙盒查看其主要病毒行为,如下图所示:
图37、使用Visual Basic混淆器的Zbot病毒运行效果图
图38、使用Visual Basic混淆器的Zbot病毒主要行为一览
通过上图我们可以看出,虽然此类混淆器使用了高级语言进行混淆,但是因为Visual Basic程序也基于x86指令集,所以只要针对Visual Basic程序中使用的关键函数进行仿真该类程序依然可以在虚拟沙盒中正确运行。
与Visual Basic程序相比,.Net程序的运行需要依托于IL指令和.Net运行时框架,这使得基于x86指令集的虚拟沙盒很难对其进行完整还原。就以我们所遇到的.Net混淆器——mpress进行具体说明。
该混淆器中的大部分代码都是对LZMAT的实现,该混淆器把用LZMAT算法压缩后的病毒数据存放在PE文件的一个指定位置上,通过解压缩将原本病毒文件进行还原。mpress混淆器中的类结构图:
图39、mpress混淆器中的类机构图
其混淆器的主要逻辑就是使用LZMAT算法将原样本进行还原,并释放到指定目录进行执行。如下图:
图40、混淆器解密代码
随着病毒混淆器与安全软件对抗的不断升级,我们发现了一些利用AutoIt脚本程序制作病毒混淆器。通常,这类混淆器用脚本作为病毒程序的加载器,其加载的病毒程序可以任意更替。再加之脚本混淆可以通过变量名替换、常量拆分等简单却十分有效的方法实现,所以即使安全软件在查杀过程中能够获取到脚本文件也很难对这类混淆器进行通杀。Zbot,作为一个在互联网中活跃多年的病毒家族,也曾在2012前后使用过AutoIt脚本所编写的病毒混淆器。在病毒的运行过程中,其不会直接释放出病毒文件,而是直接在内存中进行还原。
图41、混淆器脚本示意图
我们可以直观的看到,使用了AutoIt混淆器的Zbot病毒在火绒行为沙盒中的主要病毒行为。
图42、火绒行为沙盒运行效果图
通过对该样本的分析,我们对该混淆器的结构有了进一步的了解。Zbot病毒的二进制数据以字符串的形式存放在文件的尾部,数据的字符串之前是混淆器用来定位病毒数据的标记字符串。
图43、AutoIt混淆器结构概览
我们对图34中所示病毒脚本进行了反混淆之后,得到了原始的脚本代码。
图44、整理后的混淆器脚本
如上图所示,混淆器脚本可以通过标记字符串“delimitador”找到Zbot病毒的二进制数据,virus_loader中所存放的就是混淆器用来加载Zbot病毒的汇编代码,之后将上述两段数据进行注册,最后通过AutoIt中的DllCall函数调用WindowsAPI(CallWindowProcW)将两块数据的地址以参数的形式传入,virus_loader为回调函数。
图45、混淆器回调函数调用示意
如上图,virus_loader主要负责将Zbot病毒的二进制数据注入到新进程中。首先,混淆器根据自己的使用需求分配内存,将这些内存地址添入提前准备好的结构中(如下图)。
图46、混淆器调用数据结构示意图
之后,其将NT头复制到之前开辟的对应内存中,以挂起方式再次启动自身进程,利用NtUnmapViewOfSection卸载掉主模块之后重新在镜像基址位置开辟新空间。最后,通过NT头中的信息遍历节表,并将病毒的PE结构以每个节所对应的映射位置在内存中进行还原,在设置进程映像基址和EIP之后恢复主线程。
图47、在内存中映射代码示意
图48、恢复线程环境代码示意
我们还注意到,在其申请内存的时候,其真实使用的内存大小总比调用VirtualAlloc申请内存时大4个字节。
图49、混淆器使用内存代码举例
图50、混淆器申请内存代码举例
根据MSDN上微软给出VirtualAlloc函数说明,我们了解到该函数实际申请的空间大小为系统内存页大小的整数倍。根据混淆器传入的参数0x3C、0xF4,其实际申请大小为64K,这样频繁进行块分配试图对虚拟沙盒的内存占用造成负担。
Cerber家族是在自2016年3月以来较为流行的勒索病毒家族,由于其所使用的混淆器复杂多变,其所使用混淆手段也多种多样。在该家族的大量样本中,我们发现很多使用NSIS混淆器进行混淆的Cerber样本。其主要特点是:加密的病毒以文件的方式被NSIS安装包释放到指定的安装目录,之后其用于还原病毒的Loader动态库会将Cerber病毒以注入的方式在新进程中进行还原。加密病毒源文件所用的加密密钥可以随意更改,加密后的数据也会随之进行变化;用来还原病毒的Loader动态库也可以任意变化还原流程或者进行代码级免杀。所以使得对于这类混淆器来讲,最有效的方法就是对其整个还原病毒流程进行完整还原。
我们可以在行为沙盒看到其病毒还原的完整流程。首先其释放用于病毒还原的所有文件,如下图所示:
图51、NSIS混淆器释放文件效果图
释放文件后,混淆器会调用Services.dll中的Orchil函数以注入的方式将Cerber病毒进行还原,如下图所示:
图52、NSIS混淆器以注入方式还原病毒效果图
最后我们可以得到其还原后的病毒文件,虽然外层混淆器千变万化,但是内层病毒行为完全相同,我们可以清楚看到Cerber运行的一些基本行为特征。如下图所示:
图53、混淆器解密后的Cerber病毒运行效果图
s混淆器整体是一个NSIS安装包,主体逻辑有NSIS脚本进行控制。在NSIS安装包中包括三个主要部分:用来恢复病毒程序的Loader动态库、混淆器所调用API名字的加密数据和病毒加密数据。如下图所示:
图54、NSIS混淆器的基本结构图
在本报告所提到的样本中,NSIS释放的文件如下:
图55、NSIS混淆器释放的文件一览
如上图,NardooDeposal.W文件中存放着还原病毒程序时需要使用系统函数名,我们下文将称其为函数数据;ShopDemise.RkT文件中存放着加密后的Cerber病毒数据,暂且称其病毒数据;Services.dll为用来还原病毒程序的Loader动态库。
Loader动态库首先会将函数数据进行解密,解密时所用的密钥即是函数数据的文件名“NardooDeposal.W”,通过解密之后我们可以看到其所使用的函数列表。由于在如下图所示:
图56、函数数据解密过程
在恢复函数数据之后,Loader动态库再根据提前设定好的解密密钥“CaratSerinIntermittency”对加密的病毒数据进行解密。解密之后,再将病毒PE文件进行映射等操作,最后运行病毒代码。如下图所示:
图57、病毒数据解密示意图
根据我们对这类样本经验,在病毒混淆器中出现解密密钥、文件名和Loader动态库都会不断的进行变化,数据存放的结构也会进行不断变化。虽然混淆器中的这些关键点都会不断地改变、不断地进行免杀,但是该类混淆器的整体还原逻辑是不会改变的。不论中间环节如何改变,虚拟沙盒都将会原原本本的还原病毒行为,进而可以通过火绒的行为沙盒评估病毒行为并查杀。
虽然此类混淆器已经将自己裹上了一层NSIS安装包的外衣,但是其所使用的对抗手段却一点也没有比以往混淆器要少。与以往的混淆器不同的是,NSIS混淆器所使用的对抗手段是在NSIS脚本中进行实现的,这就不单需要虚拟沙盒具有可以正常运行NSIS安装包能力,还需要与其脚本中对抗手法进行博弈。脚本中反虚拟机代码如下图所示:
图58、NSIS脚本反虚拟机代码
其脚本中首先其欲从comdlg32动态库中调用Chippewa函数,在真实系统中的comdlg32动态库中并不存在Chippewa函数,如果调用成功则无法正常运行,从而达到了检测虚拟沙盒的效果。
之后,检测一初值为0的变量是否大于预先设定好的一个非常大的数值,如果不满足条件则每次将变量加2,试图以此来拖慢在虚拟沙盒中的运行速度,导致扫描超时。与之前提到拖慢虚拟沙盒速度的方法相比,其整体逻辑都会由NSIS脚本的解释器进行执行,使得虚拟沙盒对于这种对抗手段更加复杂。
三、总结
“特征”的时代永远不会过去,我们需要的只是发现“特征”的“眼睛”。
通过前文的分析,我们可以清晰地了解到,静态特征已不再能够有效地解决日益严峻的代码对抗问题。只有通过动态还原病毒本来的代码、数据以及行为,才能戳穿病毒的“伪装”,进而有效地解决此类问题。虚拟沙盒和动态防御,这两项技术则天然具备了上述特质,相信未来这两项技术将成为终端安全领域的制高点。
泡沫终将散去,安全必然回归本质。在这个病毒技术日新月异、不断推陈出新的年代,安全核心技术的迭代将是决定安全软件整体能力的基石。
附录
文中涉及样本SHA1:
与文中类似样本举例: