网络安全 频道

针对新型进程注入技术Ctrl-Inject原理分析

  概述

  在本文中,我们将主要介绍一种新型的进程注入方法,我们称之为“Ctrl-Inject”,它利用控制台应用程序中处理Ctrl信号的机制实现注入。在研究的过程中,我们在浏览MSDN时发现有一条关于Ctrl信号处理的相关评论:“这是一个与SetConsoleCtrlHandler函数( https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler )一起使用的函数,由应用程序定义。控制台进程使用此函数来处理进程收到的控制信号。当收到信号后,系统会在进程中启动一个新的线程来执行该函数。”这也就意味着,每次我们触发一个信号到一个基于控制台的进程时,系统都会调用一个在新线程中调用的处理函数。正因如此,我们可以借助这一特点,来实现一个不同于以往的进程注入。

  控制信号处理

  当用户或进程向基于控制台的进程(例如cmd.exe或powershell.exe)发送Ctrl + C(或Break)信号时,系统进程csrss.exe将会在目标进程中创建一个新的线程来调用函数CtrlRoutine。CtrlRoutine函数负责包装使用SetConsoleCtrlHandler的处理程序。接下来,我们深入研究一下CtrlRoutine,首先注意到了下面这段代码:

  该函数使用名为HandlerList的全局变量来存储回调函数列表,在该函数中会循环执行,直到其中一个处理程序返回TRUE(通知该信号已被处理)为止。为了使处理程序成功执行,它必须满足以下条件:1、函数指针必须正确编码。处理程序列表中的每个指针都使用RtlEncodePointer进行编码,并在执行之前使用RtlDecodePointer API进行解码。因此,未经编码的指针很有可能会导致程序崩溃。2、指向有效的CFG(Control Flow Guard,控制流防护)目标。CFG通过验证间接调用的目标是否为有效函数,来尝试对间接调用进行保护。我们来看一下SetConsoleCtrlHandle,看看它如何设置一个Ctrl处理程序,以便我们以后可以模仿其方式。在下图中,我们可以看到各个指针在添加到HandlerList之前是如何编码的。

  接下来,我们看到了一个名为SetCtrlHandler的内部函数调用。该函数更新了两个变量:一个是HandlerList,用于添加一个新的指针;另一个全局变量是HandlerListLength,增加了它的长度以适应新的列表大小。

  现在,由于HandlerList和HandlerListLength变量驻留在kernelbase.dll模块中,并且该模块会映射到所有进程的相同地址,所以我们可以在进程中找到它们的地址,然后使用WriteProcessMemory在远程进程中更新它们的值。我们的工作还没有完成,考虑到CFG和指针编码的存在,我们需要找到一种方法来绕过它们。

  绕过指针编码

  在Windows 10之前的版本中,我们需要理解指针编码、解码的工作原理,从而应对指针编码保护。接下来,我们一起深入了解一下EncodePointer的工作原理。

  开始,存在一个对NtQueryInformationProcess的调用,其定义如下:

  NTSTATUS WINAPI NtQueryInformationProcess(

  _In_HANDLE ProcessHandle,

  _In_PROCESSINFOCLASS ProcessInformationClass,

  _Out_ PVOIDProcessInformation,

  _In_ULONGProcessInformationLength,

  _Out_opt_ PULONG ReturnLength

  );

  根据上述定义,我们可以做出以下假设:1、ProcessHandle:当传递-1的值时,它代表引用调用进程的函数。2、ProcessInformationClass:该参数的值为0x24,这是一个未公开的值,要求内核检索进程加密Cookie。Cookie本身驻留在EPROCESS结构中。在检索加密Cookie后,我们可以看到几个涉及输入指针和加密Cookie的操作。具体为:

  EncodedPointer = (OriginalPointer ^ SecretCookie) >> (SecretCookie & 0x1F)

  一种绕过的方法,是使用CreateRemoteThread执行RtlEncodePointer,并将NULL作为参数传递给它,如下所示:1) EncodedPointer = (0 ^ SecretCookie) >> (SecretCookie & 0x1F)2) EncodedPointer = SecretCookie >> (SecretCookie & 0x1F)这样一来,返回值将被Cookie旋转的值增加到31倍(在64位Windows 10环境上该值为63,即0x3f)。如果我们在目标进程上使用已知的编码地址,就能够暴力猜测出原始Cookie值。以下代码展示了如何对Cookie进行暴力猜测:

  在Windows 10及以上版本中,微软非常慷慨地为我们提供了一组新的API,称为RtlEncodeRemotePointer和RtlDecodeRemotePointer。顾名思义,我们传递一个进程句柄和一个指针,该API将会为目标进程返回一个有效的编码后指针。此外,还有另一种提取Cookie的技术,请参考: https://github.com/changeofpace/Remote-Process-Cookie-for-Windows-7/blob/master/Remote%20Process%20Cookie%20for%20Windows%207/main.cpp 。

  绕过CFG

  到目前为止,我们已经将我们的代码注入到目标进程,并修改了HandlerList和HandlerListLength的值。如果我们现在尝试发送Ctrl+C信号来触发代码,该进程会引发异常,最终自行终止。其原因在于,CFG会注意到我们正在尝试跳转到一个非有效调用目标的指针。幸运的是,微软对我们一直非常友善,他们发布了另外一个有用的API,名为SetProcessValidCallTargets。

  WINAPI SetProcessValidCallTargets(

  _In_HANDLEhProcess,

  _In_PVOID VirtualAddress,

  _In_SIZE_TRegionSize,

  _In_ULONG NumberOfOffsets,

  _Inout_ PCFG_CALL_TARGET_INFO OffsetInformation

  );

  简而言之,我们传递进程句柄和指针后,该API会将其设置为有效的调用目标。此外,如果使用我们此前介绍过的( https://blog.ensilo.com/documenting-the-undocumented-adding-cfg-exceptions )未记录的API也可以实现这一点。

  触发Ctrl+C事件

  现在一切准备就绪,我们需要做的就是在目标进程上触发Ctrl + C,以调用我们的代码。有几种方法可以触发它。在这种情况下,我们可以使用SendInput的组合,来触发系统范围的Ctrl键按键,以及用于发送C键的PostMessage。同样,也适用于隐藏或不可见的控制台窗口。以下是触发Ctrl-C信号的函数:

  揭秘底层

  从实质上来说,在这个进程注入技术中,我们将代码注入到目标进程中,但是我们从不直接调用它。也就是说,我们从来没有自己调用CreateRemoteThread或使用SetThreadContext改变执行流。相反,我们正在让csrss.exe为我们调用它,这样一来就显得是一个正常的行为,不会被怀疑。其原因在于,每次将Ctrl + C信号发送到基于控制台的应用程序时,conhost.exe会调用类似于调用堆栈的内容,如下所示:

  其中,CsrClientCallServer会传递一个唯一索引标识符(0x30401),然后将其传递给csrss.exe服务。在其中,会从调度表中调用一个名为SrvEndTask的函数。调用链具体如下:

  在这个调用链的最后,我们看到了RtlCreateUserThread,它负责在目标进程上执行我们的线程。注意:尽管Ctrl-Inject技术仅针对于控制台应用程序,但也可能会在很多控制台应用程序上被滥用,最值得注意的就是cmd.exe。

  总结

  现在,我们已经了解了这个新型的进程注入方法,掌握了该方法的工作原理以及其背后到底发生了什么。在最后,我们可以总结一下Ctrl-Inject技术。这种技术与传统线程注入技术相比,主要优点是远程线程是由可信的Windows进程csrss.exe创建,这使得它得隐蔽性更强。但同样存在缺点,就是这种方法仅适用于控制台应用程序。

  要进行这种进程注入技术,所需的步骤如下:1、将OpenProcess附加到控制台进程。2、通过调用VirtualAllocEx,为恶意负载分配一个新的缓冲区。3、使用WriteProcessMemory将数据写入分配的缓冲区。4、使用目标进程cookie将指针指向指定的缓冲区。通过调用带有空指针的RtlEncodePointer并手动编码指针或通过调用RtlEncodeRemotePointer来实现。5、通知远程进程,新指针是可以使用SetProcessValidCallTargets的有效指针。6、最后,使用PostMessage和SendInput的组合触发Ctrl + C信号。7、恢复原始处理程序列表。

0
相关文章