利用Speakeasy仿真执行内核态Rootkit

匿名者  1188天前

2020年8月,我们发布了一篇文章,详细介绍了如何利用仿真框架Speakeasy来模拟执行用户模式下的恶意软件,如shellcode等。如果您还没有读过的话,不妨先看一遍。

实际上,除了模拟执行用户模式代码之外,Speakeasy还支持模拟执行内核模式下的Windows二进制代码。当恶意软件作者使用内核模式恶意软件时,它们通常是以设备驱动程序的形式出现的,其最终目标就是完全控制被入侵的系统。一般来说,恶意软件是不会直接与硬件打交道的,而是利用内核模式来完全控制系统并保持隐身状态。

动态分析内核恶意软件时所面临的挑战

理想情况下,内核模式的恶意样本可以使用反汇编器等工具进行静态逆向分析。然而,借助于二进制代码加壳软件,可以像处理用户模式软件那样轻松地对内核恶意软件进行混淆处理。此外,静态分析还有一个缺点:它们通常既昂贵又耗时。如果我们的目标是自动分析同一恶意软件家族的各种变体,那么,动态分析恶意驱动程序样本是更加可行的一种方法。

对内核模式恶意软件进行动态分析时,可能要比分析用户模式样本的过程要更复杂一些。这是因为,为了调试内核恶意软件,需要创建一个合适的分析环境。为此,我们通常需要创建两个独立的虚拟机,分别用于debugger和debugee。然后,可以将恶意软件作为按需内核服务加载,这样的话,就可以使用Windbg之类的工具远程调试驱动程序了。

除此之外,还有许多基于hooking或其他监控技术的沙箱式的应用程序可利用,不过,它们通常只能用于用户模式的代码。如果要对内核模式的代码进行类似的沙箱式监控,就需要更加底层的系统级hook,但是这很可能会产生大量的噪音。

模拟运行(仿真)驱动程序

事实证明,仿真技术是一种有效的恶意驱动程序分析技术:无需亲自定义相关的设置,就可以对驱动程序进行大规模的仿真。此外,与沙箱环境相比,该方法更容易将代码覆盖率最大化。通常情况下,rootkit可能会通过I/O请求包(IRP)处理程序(或其他回调函数)来暴露恶意功能。在正常的Windows系统中,当其他应用程序或设备向驱动程序发送输入/输出请求时,这些例程就会被执行。这包括一些常见的任务,如读、写或向驱动程序发送设备I/O控制(IOCTL)以执行某种类型的功能。

利用仿真技术,可以直接用掺杂的(doped)IRP数据包调用这些入口点,以便尽可能多地识别出rootkit的功能。正如我们在第一篇关于Speakeasy的文章中所讨论的那样,当发现其他入口点时,Speakeasy就会对其进行仿真。通常情况下,驱动程序的DriverMain入口点负责初始化一个函数调度表,该表被用来处理I/O请求。在主入口点结束后,Speakeasy会通过提供一个虚拟的IRP来尝试模拟这些函数。此外,任何被创建的系统线程或工作项都会被依次模拟,以获得尽可能高的代码覆盖率。

模拟运行内核模式Implant

在这篇文章中,我们将通过仿真名为Winnti的内核模式Implant家族,来为读者展示Speakeasy在分析内核模式代码方面的有效性。尽管这个样本年代久远,但由于它透明地实现了一些经典的rootkit功能,所以,我们还是选择它为我们的分析对象——这里的重点是在仿真过程中捕获的事件。

我们要分析的Winnti样本的SHA256哈希值为c465238c9da9c5ea5994fe9faf1b5835767210132db0ce9a79cb1195851a36fb,原始文件名为tcprelay.sys。在本文的大部分时间里,我们将研究Speakeasy生成的仿真报告。注意:由于内核补丁保护(PatchGuard)机制的作用,这个32位的rootkit所采用的许多技术将无法在现代64位版本的Windows系统上工作,因为该机制会阻止它修改关键的内核数据结构。

首先,我们将让Speakeasy使用图1所示的命令行来模拟运行内核驱动程序。同时,我们还让Speakeasy创建一个完整的内存转储(使用“-d”标志),这样,我们就可以获取内存中的内容。此外,我们还提供了内存跟踪标志“-m”,以记录恶意软件执行的所有内存读写操作。这对于检测hook和直接内核对象操作(DKOM)之类的东西非常有用。

 1.png

图1 用于模拟运行恶意驱动程序的命令行

然后,Speakeasy将开始模拟执行恶意软件的DriverEntry函数。驱动程序的入口点负责设置被动的回调例程,这些例程将服务于用户模式的I/O请求以及用于添加、删除和卸载设备的回调。通过查看该恶意软件的DriverEntry函数(在JSON报告中,其ep_type为entry_point)的仿真报告,可以发现该恶意软件找到了Windows内核的基地址。实际上,该恶意软件是通过ZwQuerySystemInformationAPI来定位所有内核模块的基地址,然后查找名为“ntoskrnl.exe”的模块来实现上述任务的。接着,该恶意软件会手动查找PsCreateSystemThread API的地址,然后,将其用于启动系统线程,以执行其实际功能。图2显示了从该恶意软件入口点调用的API。

 1.png

图2  tcprelay.sys入口点的主要功能

隐藏驱动程序对象

实际上,该恶意软件试图在执行其主系统线程之前隐藏自己。该恶意软件首先在自己的DRIVER_object结构体中查找“DriverSection”字段。该字段包含一个存放所有加载的内核模块的链表,恶意软件试图将自己从该链表中剔除出去,以绕过检查已加载驱动程序的API。在图3所示的Speakeasy报告中的“mem_access”字段中,我们可以看到在DriverSection条目之前和之后的两次内存写入操作,就是用于从链表中删除它自己。

 1.png

图3 内存写入事件表明tcprelay.sys恶意软件试图从链表中抽离出来,以实现隐身的目的

正如前面的文章中提到的,当线程或其他动态入口点在运行时被创建时,该框架会跟踪它们,以进行仿真。在本例中,由于恶意软件创建了一个系统线程,所以Speakeasy就会自动对其进行仿真。

转到新创建的线程(其ep_type为system_thread),我们可以看到恶意软件开始执行其真正的功能。恶意软件首先枚举主机上所有正在运行的进程,寻找名为services.exe的服务控制器进程。需要注意的是,返回给模拟样本的进程列表是可以通过运行时提供的JSON配置文件进行配置的。关于这些配置选项的更多信息,请参见GitHub仓库中的Speakeasy README。在图4中,我们给出了一个可配置进程列表的例子。

 1.png

图4 提供给Speakeasy的进程列表配置字段

进入用户模式

一旦恶意软件找到services.exe进程,它就会附加到它的进程上下文中,并开始检查用户模式内存,以找到导出的用户模式函数的地址。该恶意软件之所以这样做,是为了以后能将加壳的常驻型DLL注入到services.exe进程中。图5显示了rootkit用来解析其用户模式导出函数的API。

 1.png

图5  tcprelay.sys rootkit用于解析其用户模式implant的导出函数的API

一旦导出的函数被解析,rootkit就可以注入用户模式的DLL组件。接下来,该恶意软件会手动将内存中的DLL复制到services.exe进程的地址空间。这些内存写入事件将被捕获,具体如图6所示。

 1.png

图6  将用户模式implant复制到services.exe时捕获到的内存写入事件

Rootkit用于执行用户模式代码的常见技术,涉及一种称为“异步过程调用(APC)”的Windows特性。APC是在提供的线程的上下文中异步执行的函数。通过APC,内核模式应用程序可以对代码进行排队,以便在线程的用户模式上下文中运行。恶意软件通常希望注入用户模式,这样的话,可以更容易地访问Windows内的各种常用功能(例如网络通信)。此外,通过在用户模式下运行,在检查整台机器的错误代码时被检测到的风险较小。

为了在用户模式下排队触发APC,恶意软件必须找到处于可报警状态的线程。当线程将其执行量(execution quantum)交予内核线程调度程序并通知内核它们能够分派APC时,它们被称为处于可警报状态。恶意软件会在services.exe进程中搜索线程,一旦检测到处于可报警状态的线程,就会为DLL分配内存以供注入,然后将APC排队执行。

Speakeasy能够模拟该过程涉及到的所有内核结构体,特别是为Windows系统上的每个线程分配的执行线程对象(ETHREAD)结构体。恶意软件可能会尝试遍历这种不透明的结构体,以识别何时设置了线程的警报标志(因此是APC的有效候选对象)。图7显示了Winnti恶意软件在services.exe进程中手动解析ETHREAD结构体以确认其处于可报警状态时记录的内存读取事件。在撰写本文时,默认情况下,仿真器中的所有线程都将自己标识为可报警的。

 1.png

图7  tcprelay.sys恶意软件确认线程处于可警报状态时记录的事件

这样的话,恶意软件就可以使用这个线程对象执行任何的用户模式代码了。官方文档未记录的函数KeInitializeApc和KeInsertQueueApc将分别用于初始化和执行用户模式APC。图8显示了恶意软件用来向services.exe进程注入用户模式模块的API集。此外,该恶意软件将会执行一个shellcode stub,并将其作为APC的目标,然后执行注入的DLL的加载器。所有这些都可以从内存转储包中恢复,供将来进行分析之用。

 1.png

图8  tcprelay.sys rootkit通过APC注入用户模式所使用的API

网络Hook

注入用户模式后,内核组件会尝试安装网络混淆hook(大概是为了隐藏用户模式implant)。实际上,Speakeasy会跟踪和标记仿真空间内的所有内存。在内核模式仿真的上下文中,将记录所有的内核对象(如Driver和Device对象,以及内核模块本身)。在我们观察到恶意软件注入其用户模式implant后,我们立即看到它开始尝试hooking内核组件。通过静态分析,确认这是用于隐藏网络活动的。

仿真报告的内存访问部分显示,该恶意软件修改了netio.sys驱动,特别是名为NsiEnumerateob jectsAllParametersEx的导出函数内的代码。当系统中的用户运行“netstat”命令时,该函数最终会被调用,该恶意软件很可能是为了隐藏被感染系统中的连接网络端口而hooking这个函数的。这个内联hook是通过图9中捕获的事件识别出来的。

 1.png

图9  恶意软件为隐藏网络连接而设置的内联函数hook

此外,该恶意软件还hook了Tcpip驱动对象,以实现其他的网络隐身功能。具体来说,该恶意软件会hook Tcpip驱动的IRP_MJ_DEVICE_CONTROL处理程序。用户模式代码在查询活动连接时,会向该函数发送IOCTL代码。对于这种类型的hook,可以在Speakeasy中通过寻找关键内核对象的内存写入事件来轻松识别,具体如图10所示。

 1.png

图10  用于hook Tcpip网络驱动的内存写入事件

系统服务调度表Hook

最后,该rootkit会试图使用近乎古老的系统服务调度表(SSDT)补丁技术来隐藏自己。Speakeasy会分配一个伪造的SSDT,这样恶意软件就可以与之交互了。SSDT是一个向用户模式代码暴露内核功能的函数表。图11中的事件表明,SSDT结构在运行时被修改了。

 1.png

图11  Speakeasy检测到的SSDThook

如果我们使用IDA Pro考察该恶意软件,就可以确认该恶意软件的确对ZwQueryDirectoryFile和ZwEnumerateKey API的SSDT条目进行了修改,并使用这些API来隐藏自己,以逃避文件系统和注册表分析。SSDT补丁功能对应的代码如图12所示。

 1.png

图12  用于实现文件隐藏的SSDT补丁函数(通过IDA Pro查看)

设置完这些hook后,系统线程将退出。对于驱动程序中的其他入口点(如IRP处理程序和DriverUnload例程)来说,我们的兴趣不是太大,因为其中主要是模板驱动代码。

获取注入的用户模式Implant

现在,我们已经搞清楚了驱动程序是如何在系统中隐藏自己的,并且可以使用Speakeasy创建的内存转储来获取前面讨论的注入DLL。打开在仿真时创建的zip文件,我们可以找到图6中引用的内存标签。我们可以看出,内存块中有一个有效的PE头部,并且被成功地加载到了IDA Pro中,具体如图13所示。

 1.png

图13  从Speakeasy内存转储中恢复的注入的用户模式DLL

小结

在这文章中,我们讨论了如何通过Speakeasy有效地从内核模式二进制文件中自动识别rootkit的各种活动。Speakeasy可以用来快速找出那些可能难以动态分析的内核二进制文件。想了解更多的信息并查看相关代码,可以访问我们的GitHub仓库。

 

本文由secM整理并翻译,不代表白帽汇任何观点和立场
来源:https://sec.today/pulses/2ac4993f-ed3b-4dbf-abfc-419e1ae64efe/

最新评论

昵称
邮箱
提交评论