导语:在研究过程中,我们以这一.NET恶意软件Dropper为例,详细讲解了逆向工程的方法及所需要的工具,并发现了该恶意软件的一个重要特性。

概述

作为安全专家,我们更喜欢对新型恶意软件进行分析。每当有安全团队发布新型攻击分析报告时,我们都会第一时间阅读。从这些报告中,我们发现了一个规律,即使是能力最强的攻击者,也会倾向于使用各种OS工具来执行攻击,而不会倾向选择使用某个API。

针对攻击者,他们不一定需要掌握太多的开发技术,也不一定需要亲自编写恶意软件才能实现恶意的目的。一个比较恰当的例子就是Fauxpersky恶意软件,该恶意软件是用AutoHotKey编写而成的,这是一个用于自动执行任务的合法工具,攻击者利用了该工具的特点,将其改装成了一个能够有效窃取用户凭据的恶意软件。

可能与大多数人的认知相反,安全研究人员并没有将大量的事件投入到复杂的APT(高级持续威胁)的研究之中。实际上,安全研究人员更多会研究比较常见、没有那么高技术含量、却能产生较大影响的威胁。

接下来,让我们言归正传,首先简要分析一下.NET。.NET是微软在21世纪初推出的一个编程框架,其目标是希望让编程变得更容易。使用.NET时,即使开发者在编写一个简单的程序,也可以使用C语言分配和释放内存,或者使用C++编写非常复杂的代码。在.NET语言中,最受欢迎的是C#语言,这是一个面向对象的现代通用语言,并且已经被广泛使用。从底层来看,C#对Windows、Linux、macOS的支持都非常好,开发过程便捷,语法较为流畅,再借助Visual Studio的自动完成功能和Windows API调用功能,该语言的编写能变得更加轻松。此外,在编译项目时,C#将会被编译为EXE或DLL。

针对.NET的逆向工程

在听了上面的这些介绍后,你可能会提出疑问,“如果使用.NET编程比使用C或C++更容易,那么如果我想要在编译后得到二进制的PE文件,会有什么问题吗?”问题在于,得到的文件不是真正的原始可执行文件,如果我们查看该文件,将无法找到X86汇编代码,原因在于.NET的工作方式不同。

1.jpg

图片来源:http://www.developingthefuture.net/compilation-process-and-jit-compiler/

在编译.NET项目时,它实际上被编译成MSIL语言(Microsoft Intermediate Language )。而我们在使用即时编译器(JIT)时,实际上就是对代码进行了编译。如果大家有兴趣了解更多关于.NET编译的信息,建议参考Microsoft的相关文档。在这里,我们可以将MSIL类比成汇编语言,只不过它是在更高的层次上。

那么,为什么使用.NET进行编译会让分析工作变得如此繁琐呢?我们来看看使用C/C++编写的可执行文件与使用.NET编写的可执行文件二者汇编语言的差异。当我们对一个“普通”(比如用C语言编写的)可执行文件进行逆向时,反汇编程序将会展示出x86/64汇编代码。但是,对于使用.NET编译的可执行文件,“汇编代码”也能展现,但它却是截然不同的。将代码编译成MSIL,意味着代码中会包含很多方便反编译的元数据。事实上,如果想要成功对.NET的代码进行逆向,我们需要的只是一个.NET反编译器和一点点耐心。

最近,我在用于测试恶意软件样本的主机上发现了一些奇怪的自动运行文件。我很好奇,这些自动运行文件是如何产生的。在追溯这台主机上的所有文件活动时,我注意到Patient Zero是在我发现Autoruns前几分钟在这台机器上执行的恶意软件样本。因此,我查看了该样本的原始可执行文件,并发现这个文件是从.NET项目编译而成的。这就意味着我们需要使用一组完全不同的工具来对它进行分析。我们不能再使用IDA Pro这样的反汇编程序,而是需要一个.NET反汇编程序。我最喜欢的工具是dnSpy,这是一个很棒的调试器,并且具有良好的用户界面,这个工具是基于另一个优秀的项目ILSpy开发而成的。

通过使用dnSpy反编译器,我们能看到明确的代码,这一代码非常接近于恶意软件的源代码。尽管一些变量、对象和类可能与实际名称不同,但显示的代码依然非常清晰。

反混淆处理

但是,当我们查看反编译代码以及类和函数的名称时,我们发现了一些奇怪的地方,看上去该恶意软件进行了混淆:

2.png

混淆后的命名空间:

3.png

混淆并加壳的代码,看上去没有任何实际意义:

4.png

由于.NET程序可以很容易的反编译,因此许多开发人员(和恶意软件作者)会使用各种混淆方法,使逆向工程变得更加复杂。幸运的是,有一些工具能帮助我们进行反混淆处理。

为了简化反混淆的过程,我们使用了一个名为“Detect It Easy”的工具。我们将文件拖动到相应的窗口,就能够看到关于该文件的一些重要信息,其中包括其使用的混淆器的信息。经过分析,我们所研究的恶意软件使用了SmartAssembly混淆工具

5.png

在我们知道所使用的混淆工具之后,我们就开始寻找针对它的反混淆方法。在这里,我建议使用de4dot,这是一个开源的.NET反混淆器和去壳器。

在运行de4dot之后,我们看到恶意文件已经被去壳,并进行了反混淆:

6.png

现在,我们终于得到了这个神秘的恶意软件样本。

使用dnSpy打开样本后,我们发现这回命名空间变得不一样了,现在是有意义的:

7.png

动态分析

首先,这些并不是原始的命名空间名称。为了保证易读性,反混淆器自动生成了这些命名空间和其他对象、符号的通用名称,我们无从得知作者指定的原始名称。

这个Dropper最初是使用Visual Basic编写的,但dnSpy允许我们将MSIL代码转回VB或C#,我为了让语法更容易理解,所以选择了C#。在后续深入分析的过程中,我们看到的所有代码截图都是由MSIL转换而来的C#,尽管该MSIL最初是由VB编写的代码编译而成。

当查看“-”命名空间时,我们可以看到一个名为“Class 14”的类(由反混淆器命名),以及其下面的许多类方法:

8.png

通过查看Class14中的代码,我们可以清楚地看到一些代码尝试使用Interaction.Environ调用枚举环境变量:

我们可以清楚的看到,环境变量实际上是两个经Base64编码的字符串连接。当我试图进行Base64解码时,我们得到一些无法理解的模糊内容:

9.png

在查看代码的其他部分时,我们发现它调用了一些加密库和函数:

10.png

11.png

该样本中包含着许多面条代码(Spaghetti Code,指控制结构复杂、混乱、难以理解的代码),这些代码大量引用其他类,使得研究人员难以对其进行静态分析。在这时,很容易发现Base64解码后的内容也是加密的,因此我们有两种方案可以选择:

1、静态分析:快速编写一些代码,并对代码中使用的所有密钥进行解密。

2、动态分析:使用调试器逐步执行程序,并观察该程序如何对值进行解密和反模糊处理。

我果断选择了第二种方案,主要是因为其效率更高,因为代码已经非常清晰,而且我们逐步执行的并不是讨厌的汇编代码。

dnSpy也可以作为调试器使用,只要点击相应的行并按下F9,就能轻松在代码中放置断点。

13.png

我在第1081行放置了一个断点,这是smethod_56()函数的开始位置。如图中所示,该函数接受一个参数string_0(同样,字符串变量的名称也是由反编译器生成)。

string_0是一个Base64编码的值。

13.png

当我们按F10跳过当前子函数(Step Over)时,可以看到各种对象中都填充着数据。

14.png

这样一来,理解smethod_56()的功能就变得很简单。该函数从3个不同的变量中创建3字节的数组:

S2 (1B2c3D4e5F6g7H8)
S (cffffffffffffffffff)
String_0 (Sk1N1W/kLlYPS5rz2GRFew==)

然后,会实例化rfc2898DeriveBytes类,并将这三个参数传递给它:

Password:上面截图中的“nia”,作为密码;
Bytes2:由s变量产生的字节数组,作为加盐(Salt);
2:派生密钥的迭代次数。

然后,该函数创建另一个字节数组bytes3,其中包含刚刚生成的密钥。

该函数将继续实例化RijndaelManaged类,并调用CreateDecryptor函数,使用bytes3和bytes作为其参数,这两个参数分别是密钥和IV。

当我们继续单步执行(Step Over)时,可以发现有几个经过混淆和加密的字符串是通过调用smethod_0()来实现解密的,其值需要反混淆和解密:

15.png

对每个值进行反模糊处理和解密后,我们可以在下面的变量窗格中看到它的解密值:

16.png

在这里,text2包含可执行文件(当前正在运行)自身的完整路径。

我们进一步深入分析代码,发现其他的一些字符串也被反混淆和解密,这些字符串能够表明该恶意软件的更多行为特征。

正如我们在这段代码中所看到的,几个混淆/加密的字符串由一个不同的(但非常相似的)函数负责反混淆和解密,也就是smethod_76()。请注意显示为中文的不同加密密钥:

17.png

18.png

如果我们再次查看变量窗格,可以发现有更多路径被解密。我们可以清楚地看到:sourceFileName代表msbuild.exe的原始路径,而destFileName包含用户临时目录中名为svhost.exe的文件的路径:

19.png

如果回到代码,可以看到实际上有一个文件复制的操作:

20.png

这意味着,临时目录中的svhost.exe实际上就是MSBuild.exe。在分析过程中,我们实际上可以访问该路径,并对这个文件进行分析:

21.png

Svchost(实际上是msbuild)将用于创建一个非常小的二进制文件,它会修改注册表值,以创建持久性。

恶意软件特性:通过注入终止进程

这个Dropper的一个特性是能够终止研究工具。我在运行Procmon的过程中发现了这一点。当调试器在class_14中执行第2094行时,我的所有研究工具都停止了工作。这样一来,我开始好奇:这里究竟发生了什么?恶意软件是如何使这些工具关闭的?

通常,当恶意软件想要关闭特定目标程序时,它会根据一个应用程序列表(列表中可能会有Wireshark、sysinternals等工具),每隔n秒轮询一次进程列表,如果存在正在运行的特定进程,就会调用TerminateProcess()实现对进程的关闭。

而在这里,这个恶意软件Dropper则截然不同。我没有在代码中发现任何对TerminateProcess()的直接或间接调用,这里所说的间接调用是通过GetProcAddress()实现。

在调试恶意软件的构成中,我注意到ProcessHacker退出,并且在恶意可执行文件终止之前无法再次打开。当然,这似乎是恶意软件的某种机制,用于防止安全研究人员对其进行动态分析。

其常用的方法是使用Windows API的CreateToolHelp32Snapshot()函数。这个函数将获取进程的“快照”,以及堆、模块和其他信息。

对于我们的样本,它会获取所有正在运行进程的列表,并在每个进程上调用CreateToolHelp32Snapshot(),然后将结果存储在数组中。这个数组的每个成员都存储着正在运行的进程的相关信息。

22.png

一旦检测到安全研究的相关进程,Dropper就会解密下面的代码,将其注入到相应进程并执行,通过调用NtTerminateProcess()来阻止安全研究工作。

23.png

总结

在研究过程中,我们以这一.NET恶意软件Dropper为例,详细讲解了逆向工程的方法及所需要的工具,并发现了该恶意软件的一个重要特性。

针对.NET恶意软件,只需要突破反编译的瓶颈,随后的分析过程也就变得顺理成章了。

此后,我们还将对这一Dropper投放的恶意软件进行详细分析,并尝试通过静态分析得到该恶意软件的全部功能。

源链接

Hacking more

...