本文会分析野外发现的两个rootkit示例:Husky rootkit和Mingloa/CopperStealer rootkit。
驱动入口函数DriverEntry
让我们从二进制的驱动入口函数DriverEntry开始,在Windows内核驱动程序中,它是DriverEntry。
DriverEntry通常包括以下代码块:
调用IoCreateDevice和IoCreateSymbolicLink;
初始化主要函数数组,其中包含指向各种处理函数的函数指针;
为DriverUnload例程分配一个指向处理程序函数的函数指针。
以下片段(代码段1)展示了如何用C语言实现简单Windows内核驱动程序的DriverEntry。
C语言中DriverEntry实现的一个示例
下一个代码段(代码段2)展示了同一个DriverEntry的反汇编过程。
代码段2:DriverEntry的反汇编
DriverUnload函数
DriverUnload是一个在卸载驱动程序时调用的函数。
此处理程序函数的目的是清除驱动程序在初始化和执行过程中创建的任何资源,例如,删除在DriverEntry中创建的设备和符号链接。
调用ExFreePoolWithTag来取消分配在DriverEntry函数中分配的任何池内存也是一个很好的策略函数。
代码段3:C语言中DriverUnload实现的一个示例
Windows内核结构
为了充分理解Windows内核驱动程序的反汇编,我们还应该熟悉对象管理器和内核中其他组件使用的一些内核结构。
例如,以下结构是DRIVER_OBECT(代码段4)。
代码段4:分解DRIVER_OBECT结构
当对IRP进行逆向工程时,绘制出驱动程序使用的IRP主要函数是很有用的。例如,通过查看结构偏移(代码段4)和反汇编(代码段2),我们可以确定sub_1400014B0是DriverUnload。
我们还可以使用wdm.h/ntddk.h中描述的IRP主要函数代码值,通过检查代码的主要函数,得出sub_140001280(在Snippet 2中)是IRP_MJ_CREATE的函数处理程序的结论,该代码将从DRIVER_OBECT结构中的MajorFunction(0x70)的偏移量得出0x70的结果。这显然是0x00*PointerSize(x64体系结构中为8);因此,正在处理的是IRP_MJ_CREATE。
可以用同样的方式,确定IRP_J_CLOSE、IRP_J_READ、IRP_MJ_WRITE和IRP_J_DEVICE_CONTROL的函数处理程序是什么。
代码段5:摘自wdm.h,它定义了所有IRP主要函数的常数值
在进行分析时,我们熟悉的其他一些内核结构是IRP和IO_STACK_LOCATION结构。
IRP,也称为I/O请求包,是一种在创建I/O请求期间,在设备堆栈中的不同驱动程序之间移动,直到请求完成的结构。
当在用户获取的设备对象的句柄上从用户模式调用具有特定IOCTL操作的DeviceIoControl时,会创建IRP。
代码段6:IRP结构的分解
此外,IO_STACK_LOCATION表示IRP在设备堆栈中的当前位置(因此IRP结构中的CurrentLocation字段是指向IO_STACK-LOCATION的指针)。
IO_STACK_LOCATION结构包含一个联合类型的参数字段,该字段指定驱动程序中不同主函数要使用的不同参数。
例如,如果当前操作是IRP_MJ_DEVICE_CONTROL,则将使用DeviceIoControl类型的参数,包括OutputBufferLength、InputBufferLength、IoControlCode和Type3InputBuffer。
代码段7:IO_STACK_LOCATION结构的分解
有了我们对Windows内核驱动程序的新理解,以及如何在Windows驱动程序中找到关键函数,让我们看看一些真实的示例。
示例1:Brute Ratel C4(BRC4)攻击释放 “Husky” Rootkit
这项研究来源于Palo Alto Networks Unit 42在一篇关于Brute Ratel C4的博客https://unit42.paloaltonetworks.com/brute-ratel-c4-tool/中提到的与活动相关的示例。不过,他们没有提供这个示例的技术分析。
示例详细信息
示例概述
该示例是一个内核驱动程序,使用LAP$US组织窃取的NVIDIA证书进行签名。它使用zerosum0x0(图1)找到的Heresy's Gate方法https://zerosum0x0.blogspot.com/2020/06/heresys-gate-kernel-zwntdll-scraping.html,这是一种用于从内核模式驱动程序绕过SMEP向用户模式注入代码的技术。
通过zerosum0x0使用Heresy‘s Gate方法对签名驱动程序进行分解
注入的shellcode使用经典技术,如遍历InLoadOrderModuleList以查找库句柄,以及解析API函数(如LoadLibraryA和GetProcAddress),这些函数可用于解析任何其他API。
注入的shellcode分析起来也很长(图2),看起来与前面提到的Unit 42博客中描述的shellcode非常相似,因为它使用多个推送指令在堆栈上存储数据。存储在堆栈中的数据包括:
Brute Ratel C4的Base64编码配置数据;
Brute Ratel C4有效负载;
可移植可执行文件(PE)64二进制文件,是VMProtect打包的内核驱动程序,稍后加载。
图2:摘自shellcode,将许多值推送到堆栈并形成Base64 blob
Brute Ratel C4配置可以使用以下短脚本(代码段8)进行解密:
代码段8:用于解码和解密从堆栈中提取的Base64 blob的配置的代码段
解密配置后,我们得到以下输出:
代码段9:解密配置的示例
解密的配置数据(Snippet 9)包括Brute Ratel C4有效负载的一些基本配置,包括C2服务器地址和开始通信的端口,对C2的请求应该是什么样子的Base64编码模板,以及C2上用于各种功能和选项的不同路径。
攻击场景
我们发现x64 rootkit与Brute Ratel C4示例一起安装在受攻击的设备上更有趣,因为它被覆盖相同示例的其他供应商完全忽略了。
Husky Rootkit
x64 rootkit,我们称之为Husky Rootkit,与Brute Ratel有效负载一起被释放。
内核驱动程序由VMProtect打包,并使用颁发给“SHANGMAO CHEN”的证书进行签名(图4)。
rootkit使用的证书
驱动入口函数DriverEntry
由于这个DriverEntry(图5)函数被打包并混淆了,因此很难从中收集任何信息。它从一系列无条件分支指令(jmp)开始,基本上指向VMProtect解包存根。
VMProtected DriverEntry显示了一个无条件分支指令作为它的第一条指令
但是在解包之后,我们发现像GsDriverEntry这样的函数包含了更多的信息,以及我们可以在分析中使用的重要字符串(图6)。
从GsDriverEntry中反汇编一个分支,该分支包含以thpt(HTTP的混合版本)作为其URL协议的URL字符串
C2通信
rootkit直接与\\Device\Tcp进行交互以进行通信。因此,在受攻击的设备上运行的用户模式工具(如netstat和tcpview)会隐藏连接。
另一种方法是在VM主机上使用Wireshark进入客户机的共享网络接口,以监控受攻击虚拟机的所有通信流量(图7和图8)。
Wireshark网络捕获的由rootkit启动的流量
该恶意软件与多个域以及每个域的相对路径进行通信。
从服务器到URL中的/xccdd路径的Web请求和响应显示了响应有效负载
隐写术
引起我们注意的特定HTTP流量是从以下URL下载的一些图像(JPEG–JFIF标头):http://pic.rmb.bdstatic.com//bjh/.jpeg.
JPEG文件(图9)是一张看起来很无辜的狗的图片,因此研究人员将这些图片命名rootkit为“Husky”。
该图片含有有效负载
每个JPEG也有一个隐写有效负载,其形式是在多个0的分隔符之后,将数据连接到偏移量为0x1769的图片末尾(图10)。
jpg文件中带有狗的图片末尾和负载的开始之间的分隔符的Hexview
通过查看数据,我们可以看到前32个字节与前一个请求对hxxp://rxeva6w.com:10100/xccdd的十六进制格式的服务器响应相同(代码段10)。
代码段10:有效负载的前32个字节在不同的有效负载中相似
讽刺是,域rxeva6w.com在88次检测中一次也未被检测到(图11)。
VirusTotal显示了在rveva6w.com域上的检测结果
加密
HTTP有效负载使用的加密/解密算法是一种稍微修改过的DES算法,密钥为“j_k*a-vb”。
将解密密钥传递给DES解密函数
附加功能
除了通过HTTP进行通信和隐藏连接外,这个rootkit还能够加载从不同URL下载的新模块。
显然,这个rootkit包含了本文未提及的额外功能。
示例2:Mingloa(CopperStealer)Rootkit
2019年,ESET首次发现并命名了Mingloa恶意软件。该恶意软件后来也被Proofpoint发现了,也被称为CopperStealer。
正如Proofpoint研究中所指出的,该恶意软件具有查找和窃取保存的浏览器密码的能力。除了保存的浏览器密码外,该恶意软件还使用存储的cookie从Facebook检索用户访问令牌。
这是CyberArk Endpoint Privilege Manager中包含的一系列凭据和安全令牌保护技术,这可以显著限制CopperStealer等凭据窃取程序的影响。如果使用这些技术,CopperStealer将无法从受攻击的设备中抓取数据。
示例概述
此恶意内核模块是针对x86和x64体系结构编译的。
恶意软件攻击场景的分解
该驱动程序使用颁发给“大连龙梦网络技术有限公司”或“大连晨星网络技术”的证书进行签名。此证书可能是从受攻击的设备上窃取的,或者是由员工泄露的。
颁发给“大连龙梦网络科技”的证书,用于签名驱动程序
通过用户模式安装
让我们首先看看应该部署驱动程序的用户模式恶意软件攻击例程。
分解用户模式组件执行流以安装驱动程序
通过查看这个片段,我们可以看到InstallDriver函数接收到一个参数,并且首次调用时参数值为0。第二次调用时,参数值为1。
如果我们仔细观察InstallDriver,会发现它首先尝试创建一个semaphore,然后检查Windows版本。如果这些调用中的任何一个失败,它将退出而不执行任何操作。
二进制文件中InstallDriver函数开头的反汇编,它调用CreateSemaphoreWrapper
如果之前的检查成功,则恶意软件将继续,停止并删除任何具有相同名称的服务,最后将shouldInstallDriver参数与0进行比较。
CreateSemphoreWrapper函数的反汇编
如果shouldInstallDriver的值等于0,则函数将返回,不再执行任何指令。否则,它将根据系统架构,继续安装嵌入二进制文件中的适当驱动程序。
对InstallDriver函数的分解,它描述在系统上安装驱动程序的流程
这部分代码还包含一个逻辑错误,它会阻止加载此驱动程序。
第一次调用InstallDriver(应该只删除任何现有的驱动程序)也会创建一个信号量。第二个调用也应该安装驱动程序,但在安装驱动程序之前会提前退出,因为semaphore已经存在。
这个逻辑错误有些神秘,因为恶意软件通常会针对这些类型的错误进行测试。在这种情况下,它要么是在没有任何测试的情况下匆忙部署的,要么还不打算部署到任何受攻击的设备上。
驱动入口函数DriverEntry
该恶意软件的内核模式组件是传统文件系统过滤器驱动程序,与更现代的迷你过滤器驱动程序不同,它可以在不使用回调过滤函数(如操作前回调例程或操作后回调例程)的情况下修改系统行为。
传统的文件系统过滤器驱动程序可以直接修改文件系统行为,并且在每个I/O操作(如CREATE, READ和WRITE)中都被调用。
通过查看DriverEntry,我们可以看到两个主要的函数例程被分配了IRP_MJ_READ和IRP_MJ_SET_INFORMATION。此外,它还注册了两个回调函数——一个使用CmRegisterCallback,另一个使用IoRegisterFsRegistrationChange。
分解Mingloa rootkit驱动程序的DriverEntry
当IoRegisterFsRegistrationChange被调用时,一个指向DriverNotificationRoutine的函数指针被传递给它,它的目的是根据文件系统是否活动来附加或分离过滤器驱动程序。
DriverNotificationRoutine函数的反汇编
恶意软件开发者已经创建了基于过滤器驱动程序的以下功能的驱动程序:
自卫:防止移动;
防止注册表项删除(Windows服务);
禁止文件列表的读取阻止(进程的allowlist除外);
自卫:防止移动
通过将驱动程序作为过滤器驱动程序附加到文件系统并实现IRP_MJ_SET_INFORMATION,开发者可以检查要在拒绝列表中删除的文件名。
IrpMjSetInformationHandler函数的反汇编
如果文件名被拒绝,则处理程序将返回STATUS_ACCESS_DENIED并停止IRP的处理。否则,它将把它传递给设备堆栈中的底层驱动程序。
IrpMjGenericHandler函数的反汇编
防止注册表项删除
注册表项删除阻止功能防止删除与内核驱动程序的Windows服务相关的注册表项和值。此功能的工作方式是注册每次注册表更改都会触发的RegistryCallback例程,并将注册表路径与服务的路径进行比较。
防止读取禁止列出的文件(允许列出的进程除外)
该功能使用与IRP_MJ_READ的自删除预防中描述的相同的文件系统过滤器驱动机制。
IrpMjReadHandler函数的反汇编
基本上,它首先检查正在访问的文件的名称,然后检查它是否包含或以以下拒绝列出的字符串之一结尾:
如果字符串没有被拒绝列出,那么过滤器函数将把IRP转发到设备堆栈中的底层驱动程序。但是,如果字符串被拒绝列出,它将首先检查试图访问该文件的进程是否来自以下列表中的允许列出的进程:
如果进程名再次被允许列出,过滤器函数将把IRP转发给设备堆栈中的底层驱动程序。但是如果不是,它将通过返回STATUS_ACCESS_DENIED来阻止请求,从而导致读请求失败。
加载rootkit时尝试输出cookies.db文件内容的示例
字符串混淆
在多个实例中,rootkit通过以下模糊处理隐藏重要字符串,如文件名拒绝或进程名允许,它使用REGISTRY\MACHINE\SOFTWARE初始化一个字符串,并使用不同的逐位运算操作来揭示多个字符串,例如:
字符串反混淆技术的反汇编视图
虽然我们希望创建一个脚本来发现这些混淆的字符串,但不幸的是,开发者通过随机化每个字符串的逐位操作和值,使我们很难做到这一点。
寻找Rootkit
与主要从kernel32.dll和ntdll.dll等库导入的用户模式恶意软件不同,内核模式rootkit几乎完全从ntoskrnl.exe(内核本身)导入其API函数。在VirusTotal (VT)中寻找rootkit时,这一事实很有用,因为它可以很容易地找到带有恶意意图的驱动程序。例如,我们可以使用以下查询(代码段11):
代码段11:查找恶意驱动程序的VirusTotal查询示例
该查询将查找未签名或未受信任的PE格式文件,并从ntoskrnl.exe导入它们。另一种选择是在查找更具体的文件集时使用Yara规则。
我们还可以使用带有附加二进制模式或字符串的独特API来查找恶意驱动程序或rootkit的新示例。
就像我们已经分析了一个示例并想找到类似的文件(旧的或新的)一样,我们可以使用代码的一些属性,例如ExAllocatePoolWithTag和.pdb符号中使用的标记,来找到与初始二进制文件相关的文件。
这种规则的一个示例如下(代码段12)所示:
代码段12:一个搜索恶意驱动程序(也就是rootkits)的Yara规则示例
参考及来源:https://www.cyberark.com/resources/threat-research-blog/fantastic-rootkits-and-where-to-find-them-part-2