导语:本研究的主要主题是对两个版本的DotRunpeX注入器进行深入分析,对比它们之间的相似之处,并介绍用于分析新版本的DotRunpeX的PoC技术,因为它是由自定义版本的KoiVM .NET protector.虚拟化传播的。
新版DotRunpeX完整的技术分析
为了分析dotRunpeX的新版本,使用了示例SHA256:“44a11146173db0663a23787bffbb120f3955bc33e60e73ecc798953e9b34b2f2”。这个示例是一个用.NET编写的64位可执行文件“.exe”,受KoiVM保护。版本信息与旧版本的DotRunpeX的情况相同,并且在CPR分析的所有示例中都是一致的。CPR可以再次注意到ProductName – RunpeX.Stub.Framework 。
新DotRunpeX版本的信息
在dnSpyEx中打开示例并导出入口点函数_sb()方法后,CPR可以立即确认此新版本的DotRunpeX受到KoiVM虚拟程序的保护。尽管大多数IL代码都是虚拟化的,但CPR仍然可以发现P/Invoke定义的CreateProcess方法的调用,该方法以某种方式创建一个处于挂钩状态的进程——通常用于代码注入技术“Process Hollowing”。
创建挂钩的流程作为Process Hollowing技术的一部分
在进一步研究了.NET元数据(特别是ImplMap表)中剩余的内容之后,找出了定义为P/Invoke并很可能被这个示例使用的其他方法,CPR得到了比旧版本dotRunpeX更令人兴奋的发现。显然,该示例不仅执行代码注入,还执行加载和与驱动程序通信。
ImplMap表——DotRunpeX的新版本
CPR立即注意到的下一个是使用了与旧版本相同的资源名——BIDEN_HARRIS_PERFECT_ASSHOLE——它包含要注入的加密有效负载。资源名称在CPR分析的所有样本中都是一致的。很明显,解密例程隐藏在代码虚拟化之后,但通过猜测,他们可以得到一个简单的异或解密例程,它使用了一个表示开发者秘密愿望的密码——I_LOVE_HENTAIU2。
使用密码“I_LOVE_HENTAIU2”对.NET资源进行简单异或解密
不过,由于DotRunpeX仍处于开发阶段,并添加了新功能,使用该注入器的最新示例改变了解密方案(不再是简单的XOR),从而省略了嵌入式有效负载的静态提取。
如上所述,IL代码受到KoiVM虚拟程序的保护,因此为了继续分析,CPR需要想出一些方法来处理受保护的代码,并在合理的时间内从中获得一些有意义的东西。首先,CPR想到的是使用一个名为OldRod的公开开源KoiVM去虚拟程序。这个工具完全适用于KoiVM的普通版本。它的开发方式甚至要优于KoiVM原始版本的一些简单修改(例如VMEntry类中方法的签名修改或默认#Koi流名称的修改)。
不过,CPR正在处理一个自定义版本的KoiVM,它以一种不那么容易被发现的方式修改了保护程序。KoiVM的原始实现定义了119个用于虚拟化代码的常量变量。这些常量用于定义寄存器、标志、操作码等。这些常量的指定值用于正确执行虚拟化代码,也是去虚拟化过程所需的。
KoiVM的原始实现定义了119个常量
在使用普通版本的KoiVM时,在constants类内已编译的、受保护的示例中,生成的常量以完全相同的顺序显示为字段,并带有升序标记值。在编译后的二进制文件中,常量及其对应的标记的顺序是OldRod所依赖的。
OldRod源代码——常量的自动检测
尽管OldRod工具是一个非常好用的工具,并且可以在通过配置文件(——config选项)提供自定义常量映射时处理常量的自定义顺序,但找出这些常量的正确映射并不像听起来那么简单。有时,当一个常量的顺序是手工修改时,通过分析它们在代码中的使用来正确地映射它们可能并不难。但更糟糕的是,它们以一种非常有效的方式被打乱,使得正确的映射非常困难,以至于认为这种方法无法在合理的时间内获得一些结果。
OldRod源代码——常量的自动检测
通过精确的代码分析和通过适当的处理程序映射常量期间的一些困难时刻,CPR还是能够完全去虚拟化代码。不过,就算有了完全去虚拟化的代码,但还是一个不能完全运行的.NET程序集,它仍然被ConfuserEx混淆器混淆了。
下图是与驱动程序例程相关的完全去虚拟化和去混淆的代码。
驱动程序装载/卸载:
负责加载/卸载驱动程序的虚拟化和非虚拟化代码
与procexp设备的通信:
负责与procexp设备通信的去虚拟化和去混淆的代码
为了讲解方便,本文不讨论去虚拟化和去混淆的过程。
通常,当不可能在合理的时间内对代码进行反虚拟化时,CPR仍然没有其他选择。第一个选项(处理虚拟化代码时非常常见的方法)是使用调试器、DBI(动态二进制检测)、挂钩和WIN API跟踪进行动态分析。当CPR处理dotnet代码时,另一种方法可能是使用一些来自.NET内部世界的知识进行PoC。CPR决定将这两种方法结合起来,从而开发出了一种非常有效的新工具。
为了获得更多关于代码功能的信息,CPR从使用x64dbg的动态分析方法开始。正如CPR之前指出的,包含P/Invoke定义的方法的ImplMap表似乎是在调试器中设置断点的一个很好的起点。通过自动解析P/Invoke定义的方法并将其转换为x64dbg脚本,CPR开发了第一个工具,称为“ImplMap2x64dbg”。
ImplMap2x64dbg
使用dnfile模块正确解析.NET可执行文件及其元数据的Python脚本,此工具创建一个x64dbg脚本,用于在.NET可执行文件的已定义ImplMap (P/Invoke)方法上设置断点。
使用" ImplMap2x64dbg "处理DotRunpeX示例将生成x64dbg脚本:
CPR主要关注某些WIN/NT API,如CreateProcessW、NtWriteVirtualMemory、CreateFileA、CreateFileW、NtLoadDriver、NtQuerySystemInformation和DeviceIoControl,因为它们是与驱动程序和进程注入例程相关的有趣API。
我们能看到的第一个有趣的WIN API调用是CreateFileW,它用于在路径C:\Users\XXX\AppData\Local\Temp\Иисус.sys中创建一个文件。
CreateFileW用于创建文件“Иисус.sys”
如果CPR检查创建的文件Иисус.sys(俄语翻译为“jesus.sys”),就会立即发现它是一个有效的进程资源管理器驱动程序,版本为16.43。
创建的文件“Иисус.sys”是有效的进程资源管理器驱动程序,版本16.43
CPR可以看到负责加载此驱动程序的例程NtLoadDriver,其中参数指向DriverServiceName–\Registry\Machine\System\CurrentControlSet\Services\TaskKill,它指定了驱动程序注册表项的路径。
NtLoadDriver用于通过其关联的注册表项加载procexp驱动程序
驱动程序注册表项“\registry\Machine\System\CurrentControlSet\Services\TaskKill”的内容
挂钩到进程资源管理器设备如下。
获取进程资源管理器设备的句柄
DotRunpeX 逃避杀毒软件技术之一是在进程资源管理器驱动程序(procexp.sys)的帮助下阻止一个硬编码的反恶意软件服务列表。使用进程资源管理程序驱动程序背后的原因是,反恶意软件服务通常作为受保护的进程运行,更具体地说是作为PPL,以避免由恶意活动引起的系统保护失效。有可能滥用procexp驱动程序的易受攻击版本来关闭受保护进程的对象句柄。一旦关闭了足够多的句柄,特定的受保护进程将被终止。CPR分析的所有示例都滥用了该驱动程序的16.43版本,这也是最新的易受该技术攻击的版本。
为了获得有关对象句柄的信息,DotRunpeX使用具有指定SystemInformationClass 0x10的NT API NtQuerySystemInformation,该SystemInformationClass0x10指向未记录的结构[SYSTEM_HANDLE_information]。通过这种方式,它可以找到属于受保护进程的所有句柄。
NtQuerySystemInformation用于获取未记录的结构SYSTEM_HANDLE_INFORMATION
为了处理受保护进程的对象句柄,DotRunpeX使用WIN API DeviceIoControl将IOCTL直接发送给易受攻击的procexp驱动程序。IOCTL“2201288708”(IOCTL_CLOSE_HANDLE)在RDX寄存器中,处理此请求的procexp驱动程序例程负责关闭指定进程的某些对象句柄,无论指定进程是否受到保护。一旦关闭了足够多的对象句柄,反恶意软件服务就会被终止。
DeviceIoControl用于发送IOCTL“2201288708”以关闭受保护进程的对象句柄
我们还可以看到寄存器R8 (lpInBuffer)指向关闭对象句柄所需的数据。该数据结构可以定义如下:
让我们比较一下DotRunpeX示例使用的procexp驱动程序版本(版本16.43,2021.8.17编译)和最新版本的proceexp驱动程序(版本17.02,2022.11010编译)。CPR可以立即发现添加的修复代码,该代码负责禁用关闭受保护进程的对象句柄的可能性。
16.43与17.02版本进程资源管理器驱动程序之间的比较
这种使用进程资源管理器驱动程序关闭受保护流程的对象句柄的技术可以随时上网查找到,并且是名为Backstab的开源项目的一部分,进程资源管理器驱动程序17.0以上的版本已经被修复。
在阻止特定的受保护进程后,Process Hollowing将使用WIN API CreateProcessW以挂钩状态启动进程(在本例中为C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe),并直接使用NT API NtWriteVirtualMemory将DotRunpeX的嵌入式有效负载写入新创建的远程进程。
事实证明,通过一种专注于本机层和WIN/NT API的某些使用的动态分析方法,CPR对这种可用于自动化和大规模处理的虚拟化dotnet注入器获得了一些有趣的发现:
每个DotRunpeX示例都有一个要注入的特定恶意软件家族的嵌入式有效负载;
每个DotRunpeX示例都有一个嵌入式procexp驱动程序来终止受保护的进程;
虚拟化代码背后很可能隐藏着某种配置,它指定了process Hollowing的目标进程、要阻止的受保护进程列表(反恶意软件服务),以及其他有趣的可配置内容;
除此之外,CPR可以利用.NET内部世界的知识来实现一些自动化。当谈论dotnet时,CPR可以立即想到由.NET运行时管理的代码。越来越多的事情正在被管理,其中特别重要的就是所谓的“内存管理”。dotnet中的内存类型有堆栈和.NET堆。在网络世界中,CPR不需要为内存分配/释放而烦恼,因为这些例程是由.NET运行时和垃圾收集器处理的。网络的内存管理不知何故需要知道分配什么、在哪里以及如何分配;解除分配/释放内存也是如此。一旦讨论了从System.Object(类、对象、字符串……)继承的引用类型,就会在.NET堆上进行分配。这些对象保存在.NET堆上,为了进行自动管理,它们还附带了某些元数据信息,如类型、引用和大小。另外就是不再引用的对象的自动内存释放不会立即发生,垃圾收集器会在固定时间间隔内处理这一问题,可能需要几分钟。像“静态对象”这样的特定对象会在垃圾收集中幸存下来,并一直存持续到应用程序结束。
这意味着,如果CPR可以枚举.NET堆上的对象,CPR也可以获得与它们的类型和大小相关的信息,这些信息可以用于它们的适当重建。创建这种工具可能非常耗时,但幸运的是,CPR已经创建了dotnet进程和崩溃转储自省(crash dump introspection)开源库ClrMD Microsoft.Diagnostics.Runtime,该库由微软开发,可以精确地用于从.NET堆重建对象。为什么这很重要?
是因为在dotRunpeX执行的特定时刻,嵌入的有效负载、procexp驱动程序和某种配置必须以解密状态出现。它们的内容可能会被分配到.NET堆上分配的某个对象。对于这些,CPR可以期望字节blobbyte[]或字符串。这也意味着,如果CPR能够控制DotRunpeX的执行,并将其挂钩,使其处于适合重建对象的状态,那么CPR将能够在解密状态下获得所需的一切。
挂钩和自省DotRunpeX进程的正确时机之一可能是调用用于Process Hollowing的WIN API CreateProcessW,这被认为是正确的假设,CPR为此开发了挂钩库“CProcessW_Hook”。
CProcessW_Hook
使用minhook框架的本地挂钩库(适用于Windows的Minimalistic x86/x64 API挂钩库)。下面提供的代码用于挂钩WIN API函数CreateProcessW,该函数用于DotRunpeX注入器中的进程创建,该注入器后来用作代码注入(PE Hollowing)的目标。一旦CreateProcessW函数被挂钩并在目标进程中被调用,整个进程就会被挂钩进行自省。某些进程创建会被过滤(powershell、conhost),因为它们可以根据配置为DotRunpeX的其他函数生成(例如修改Windows Defender设置)。CPR只需要在执行代码注入之前的状态下暂停进程(其中所有需要的对象都已在.NET堆上解密)。
CPR可以看到,在函数DllMain()中加载这个库时,所有的挂钩逻辑都会立即执行。另一件需要注意的重要事情是,CPR定义了导出函数Decoy(),它永远不会被执行或调用,但在以后的预注入技术中需要它。
有了挂钩库“CProcessW_Hook.dll”,CPR可以继续创建一个注入器和提取器。这就是下面提供的主要工具——DotRunpeX提取器“Invoke-DotRunpeXextract”。
Invoke-DotRunpeXextract
PowerShell模块,支持从DotRunpeX中提取有效负载、procexp驱动程序和配置。该工具是用PowerShell脚本语言编写的,使用预注入本机挂钩库“CProcessW_Hook.dll”(使用AsmResolver)和从.NET堆重建.NET对象(使用ClrMD)。它使用动态方法进行提取,因此样本以托管方式执行(仅在VM中使用)。使用PowerShell 7.3+, clrMD v2.2.343001 (net6.0), AsmResolver v5.0.0 (net6.0)。
本文提供了该工具的两个版本,其中一个是创建为多线程的PowerShell模块,以获得最佳性能和使用效果。该工具的第二个版本是一个具有相同功能的单线程脚本,可以用于简单的调试和故障排除,并且可以更容易地创建具有类似功能的多个代码段。
PowerShell模块的整个代码都以易于理解其核心功能的方式进行了注释,接下来我们将简要描述该工具的核心功能,如使用AsmResolver的挂钩库的预注入技术以及提取背后的实现逻辑。
首先,该工具使用AsmResolver修改DotRunpeX的PE结构。AsmResolver以其检查dotnet可执行文件及其相关元数据的功能而闻名,但它也允许访问PE的底层结构来修改它们。这些PE结构修改用于实现上述PoC技术,目的是将dll预注入64位的dotnet可执行文件。CPR正在讨论将本机挂钩库的新导入条目添加到.NET程序集中。由于DotRunpeX是一个64位可执行文件,而且与32位的dotnet可执行文件不同,64位的dotRunpeX甚至没有导入目录,因此可以在函数PatchBinaryWithDllInjection()中从头开始构建一个导入目录。在这个函数中,可以看到CPR正在创建新的数据部分.data和.data,新建的IDT(导入目录表)和IAT(导入地址表)就放置在其中。为了在进程启动时立即预注入挂钩库“CProcessW_Hook.dll”,并让windows加载程序运行,CPR用挂钩库中定义的导出函数Decoy()创建一个导入项。当CPR处理dotnet并添加本机导入时,.NET目录中的“IL Only ”标志就不再为真,需要进行修复。
对PE结构进行上述修改前后的DotRunpeX示例对比图如下所示。
用于dll预注入的修改前后DotRunpeX示例的PE结构
现在,CPR进入了可以执行修改后的二进制文件的状态。在挂钩库就位的情况下,DotRunpeX进程在调用WIN API CreateProcessW的过程中立即挂钩。这个确切的例程是在函数StartProcessWaitSuppended()中实现的。
进程被挂钩后,就可以进行自省了。DotRunpeX进程自省背后的整个逻辑可以在函数GetPayloadAndConfig()中看到。在该函数中,CPR使用clrMD库附加到所需的进程,并枚举当前分配在.NET堆上的所有System.Byte[]对象。为了重建要注入的有效负载,CPR实现了一些虚假逻辑,以查找大于1KB的字节blob对象,并从“MZ”标头开始。尽管听起来如此,但事实证明它足以满足研究的需求。
查找与进程资源管理器驱动程序和配置相对应的对象的逻辑略有不同。首先,procexp驱动程序和与配置相关的常量保存在同一个对象中。CPR假设这是KoiVM虚拟程序和ConfuserEx混淆器结合使用的结果,因为ConfuserEx通常将定义的常量放在一个字节blob中,并在运行时需要它们时解析它们。在逻辑找到这种字节blob之后,它将进程资源管理器驱动程序和配置程序分离,并推送配置程序进行进一步处理。
所谓的配置实际上是一堆常量,其中一些用作DotRunpeX的配置。这个配置需要在ParseConfig()函数中解析,因为它似乎处于某种结构中,每个字符串前面都有其长度,如果需要,还可以填充被4整除的长度,如下图所示。
未解析的配置结构
一旦CPR正确地解析了配置,它将与提取的有效负载和进程资源管理器驱动程序一起保存,暂停的进程将被终止,修改后的DotRunpeX示例将被删除。
“Invoke-DotRunpeXextract”的执行和大量处理视频。
如上所述,“Invoke-DotRunpeXextract”将生成一个要注入的有效负载,processxp驱动程序和解析的常量值,其中一些可以称为配置,CPR分析的DotRunpeX示例的配置文件内容示例:
CPR可以很容易地发现与持久性设置、资源名称及其解密密钥(其中.NET资源包含要注入的有效负载)、要注入的负载的目标二进制文件、要阻止的反恶意软件服务名称、UAC绕过、反VM、反沙盒、procexp驱动程序安装路径及其名称等相关的配置字符串。
CPR提供了该工具的两个版本,可以只处理一个示例或批量处理示例目录。为了获得最佳性能,建议使用多线程PowerShell模块。不过,为了进行故障排除、简单修改和简单调试,我们还提供了一个具有相同功能的单线程脚本,因为CPR预计很快就会在dotRunpeX代码中进行一些修改,需要对工具或挂钩库的代码进行适当的修改。
总结
通过对这种新威胁的数月监控,我们深入了解了它的演变、传播方法,以及它是如何被滥用来传播广泛的不同恶意软件家族的。
随着时间的推移,CPR认为DotRunpeX处于高度开发阶段,定期添加新功能,每天都越来越受欢迎和关注。由于这种注入器的使用越来越多,CPR开发并提供了几种工具来自动分析这种虚拟化的dotnet代码。
本文描述的一些开发工具引入了PoC方法,可以用于开发具有类似功能的其他工具。CPR展示了如何在现实世界的示例中使用AsmResolver和clrMD等开源库来支持研究并帮助进行受保护代码的逆向工程。
在本文中,CPR对两个版本的DotRunpeX注入器进行了深入分析,介绍了它们之间的相似之处,并描述了它们使用的主要有趣的技术,如滥用易受攻击的进程资源管理器驱动程序、使用KoiVM保护程序导致的代码虚拟化、使用诱饵系统调用补丁修改D/Invoke框架。
CPR的分析和结论是基于CPR在野外发现的数十次活动和数百个大规模处理的样本。
由于DotRunpeX的快速迭代,CPR认为所提供的工具需要尽快进行一些修改,以应对DotRunpeX的变化。尽管如此,有了所提供的源代码,其他研究人员应该可以相对容易地应对这些变化。
本文翻译自:https://research.checkpoint.com/2023/dotrunpex-demystifying-new-virtualized-net-injector-used-in-the-wild/如若转载,请注明原文地址