操作方法

十种进程注入技术:常用及热门进程注入技术的技术综述

编者注:Elastic 于 2019 年 10 月与 Endgame 合并,并将 Endgame 博客的部分内容迁移到了 elastic.co/cn/。请参阅 Elastic Security,以了解有关我们的集成安全解决方案的更多信息。

进程注入是一种广泛使用的防御规避技术,常用于恶意软件和无文件攻击手段中。其原理是在另一个进程的地址空间内运行自定义代码。进程注入可以提高隐蔽性,部分技术还能实现持久化。尽管进程注入技术种类繁多,但本文将介绍十种在实际攻击中常见的、以其他进程身份运行恶意代码的技术。此外,我还为其中许多技术提供了屏幕截图,以方便进行逆向工程和恶意软件分析,从而更好地检测并防范这些常见技术。

1. 通过 CREATEREMOTETHREAD 和 LOADLIBRARY 实现经典的 DLL 注入

该技术是将恶意软件注入到其他进程中最常见的方法之一。恶意软件会将恶意动态链接库 (DLL) 的路径写入目标进程的虚拟地址空间,并通过在目标进程中创建远程线程来确保该远程进程加载该 DLL。

恶意软件首先需要选择一个目标进程(例如 svchost.exe)进行注入。这通常通过调用三个应用程序编程接口 (API) 来搜索进程:CreateToolhelp32Snapshot、Process32First 和 Process32Next。CreateToolhelp32Snapshot 是一个用于枚举指定进程或所有进程的堆或模块状态的 API,它会返回一个快照。Process32First 用于获取快照中第一个进程的信息,然后使用 Process32Next 在循环中遍历这些进程。找到目标进程后,恶意软件会调用 OpenProcess 获取目标进程的句柄。

如图 1 所示,该恶意软件首先调用 VirtualAllocEx 函数来分配一段内存空间,以便写入其 DLL 的路径。随后,恶意软件调用 WriteProcessMemory 函数将该路径写入已分配的内存中。最后,为了在另一个进程中执行代码,恶意软件会调用 CreateRemoteThread、NtCreateThreadEx 或 RtlCreateUserThread 等 API。其中后两个 API 没有文档记录。但总体思路是将 LoadLibrary 函数的地址传递给这些 API 之一,从而使远程进程必须代表恶意软件执行该 DLL。

CreateRemoteThread 会被许多安全产品跟踪并标记。此外,它还需要磁盘上存在恶意 DLL 文件,而该文件很容易被检测到。考虑到攻击者通常通过注入代码来绕过防御,老练的攻击者可能不会使用这种方法。下面的屏幕截图展示了名为 Rebhip 的恶意软件正在执行此技术。

图 1:Rebhip 蠕虫执行典型的 DLL 注入
Sha256:07b8f25e7b536f5b6f686c12d04edc37e11347c8acd5c53f98a174723078c365

2. 可移植可执行文件注入(PE注入)

恶意软件无需传递 LoadLibrary 的地址,而是将自身的恶意代码复制到一个已打开的进程中并使其执行(可通过一段简短的 shellcode 或调用 CreateRemoteThread 实现)。PE 注入相对于 LoadLibrary 技术的一个优势在于,恶意软件无需在磁盘上写入恶意 DLL。与第一种技术类似,恶意软件会在宿主进程中分配内存(例如使用 VirtualAllocEx),然后通过调用 WriteProcessMemory 来写入恶意代码,而不是写入“DLL 路径”。然而,这种方法的缺点在于被复制映像的基址会发生变化。当恶意软件将其 PE 注入到另一个进程中时,它会获得一个新的、不可预测的基址,这要求它动态地重新计算其 PE 的固定地址的位置。为了解决这个问题,恶意软件需要在宿主进程中找到其重定位表的地址,并通过遍历其重定位描述符来解析已复制映像的绝对地址。

这种技术与其他一些技术(例如反射式 DLL 注入和内存模块)类似,因为它们都不会将任何文件写入磁盘。然而,内存模块和反射式 DLL 注入方法更加隐蔽。它们不依赖任何额外的 Windows API(例如 CreateRemoteThread 或 LoadLibrary),而是直接在内存中加载并执行自身。反射式 DLL 注入的工作原理是创建一个能够在执行时自行映射到内存中的 DLL 来实现,而无需依赖 Windows 的加载器。内存模块与反射式 DLL 注入类似,区别在于注入器或加载器负责将目标 DLL 映射到内存中,而不是由 DLL 自身完成映射。在之前的博文中,我们已经详细讨论了这两种内存驻留方法。

在分析 PE 注入时,我们经常会在调用 CreateRemoteThread 之前看到循环结构(通常是两个嵌套的“for”循环)。这种技术在加密器(即用于加密和混淆恶意软件的软件)中非常流行。图 2 中的示例单元测试正是利用了这种技术。代码包含两个嵌套循环,用于调整其重定位表,这两个循环位于调用 WriteProcessMemory 和 CreateRemoteThread 之前。“and 0x0fff”指令也是另一个很好的指标,它表明前 12 位用于获取包含重定位块的虚拟地址偏移量。一旦恶意软件重新计算了所有必要的地址,它只需要将起始地址传递给 CreateRemoteThread,即可执行该代码。

图 2:调用 CreateRemoteThread
之前用于 PE 注入循环的示例结构Sha256:ce8d7590182db2e51372a4a04d6a0927a65b2640739f9ec01cfd6c143b1110da

3. 进程空心化(又称进程替换和 RUNPE)

恶意软件除了向宿主程序注入代码(例如,DLL 注入)之外,还可以使用一种称为“进程空心化”的技术。进程空心化是指恶意软件从目标进程的内存中取消映射(清空)合法代码,并用恶意可执行文件覆盖目标进程(例如,svchost.exe)的内存空间。

恶意软件首先创建一个新进程,以挂起模式托管恶意代码。如图 3 所示,这是通过调用 CreateProcess 并将进程创建标志设置为 CREATE_SUSPENDED (0x00000004) 来实现的。新进程的主线程在挂起状态下创建,直到调用 ResumeThread 函数才会运行。接下来,恶意软件需要将合法文件的内容替换为其恶意有效载荷。这是通过调用 ZwUnmapViewOfSection 或 NtUnmapViewOfSection 来取消映射目标进程的内存来实现的。这两个 API 基本上会释放由某个代码段所指向的所有内存。一旦内存解除映射,加载器便使用 VirtualAllocEx 为恶意软件分配新的内存,并通过 WriteProcessMemory 将恶意软件的各个代码段写入目标进程空间。随后,恶意软件调用 SetThreadContext 将入口点指向其写入的新代码段。最后,恶意软件通过调用 ResumeThread 恢复挂起的线程,使进程脱离挂起状态。

图 3:Ransom.Cryak 执行进程空心化
Sha256:eae72d803bf67df22526f50fc7ab84d838efb2865c27aef1a61592b1c520d144

4. 线程执行劫持(又称挂起、注入和恢复 (SIR))

这种技术与之前讨论过的进程空心化技术有一些相似之处。在线程执行劫持中,恶意软件针对进程中的现有线程进行操作,从而避免了可能产生异常行为的进程或线程创建操作。因此,在分析过程中,您可能会看到调用 CreateToolhelp32Snapshot 和 Thread32First,以及随后调用 OpenThread。

恶意软件获取目标线程的句柄后,会调用 SuspendThread 函数将线程置于挂起模式,从而执行注入操作。接着,该恶意软件调用 VirtualAllocEx 和 WriteProcessMemory 来分配内存并执行代码注入。注入的代码可能包含 shellcode、恶意 DLL 的路径以及 LoadLibrary 的地址。

图 4 展示了一个使用此技术的通用木马程序。为了劫持目标线程的执行,该恶意软件通过调用 SetThreadContext 修改目标线程的 EIP 寄存器(该寄存器包含下一条指令的地址)。随后,恶意软件恢复该线程以执行其写入宿主进程中的 shellcode。从攻击者的角度来看,SIR 方法可能存在风险,因为在系统调用过程中暂停和恢复线程可能会导致系统崩溃。为了避免这种情况,更复杂的恶意软件在检测到 EIP 寄存器位于 NTDLL.dll 范围内时,会选择稍后恢复并重试。

图 4:一个通用木马正在执行线程执行劫持
Sha256:787cbc8a6d1bc58ea169e51e1ad029a637f22560660cc129ab8a099a745bd50e

5. 通过 SETWINDOWSHOOKEX 进行挂钩注入

挂钩是一种用于拦截函数调用的技术。恶意软件可以利用挂钩功能,在特定线程中触发某个事件时加载其恶意 DLL。这通常是通过调用 SetWindowsHookEx 函数将挂钩例程安装到钩子链中来实现的。SetWindowsHookEx 函数接受四个参数。第一个参数是事件类型。这些事件反映了各种挂钩类型,从键盘按键 (WH_KEYBOARD) 到鼠标输入 (WH_MOUSE)、CBT 等。第二个参数是指向恶意软件在事件触发时希望调用的函数的指针。第三个参数是包含该函数的模块。因此,在调用 SetWindowsHookEx 之前,通常会先调用 LoadLibrary 和 GetProcAddress 函数。该函数的最后一个参数是要与挂钩过程关联的线程。如果此值设置为零,则所有线程都会在事件触发时执行该操作。然而,恶意软件通常仅针对单个线程进行攻击以减少噪音,因此也可能在调用 SetWindowsHookEx 之前先调用 CreateToolhelp32Snapshot 和 Thread32Next 来查找并锁定单个线程。DLL 注入后,恶意软件就会以传递给 SetWindowsHookEx 函数的线程 ID 所对应进程的身份执行其恶意代码。图 5 展示了 Locky 勒索软件如何运用这种技术。

图 5:Locky 勒索软件使用挂钩注入
Sha256:5d6ddb8458ee5ab99f3e7d9a21490ff4e5bc9808e18b9e20b6dc2c5b27927ba1

6. 通过修改注册表进行注入和持久化(例如 APPINIT_DLLS、APPCERTDLLS、IFEO)

Appinit_DLL、AppCertDlls 和 IFEO(映像文件执行选项)都是恶意软件用于注入和持久化的注册表项。这些条目位于以下位置:

HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\Appinit_Dlls HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows\Appinit_Dlls HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls HKLM\Software\Microsoft\Windows NT\currentversion\image file execution options

AppInit_DLLs

恶意软件可以将恶意库的位置信息插入到 Appinit_Dlls 注册表项下,从而让其他进程加载该库。该注册表项下的所有库都会被加载到所有加载 User32.dll 的进程中。User32.dll 是一个非常常见的库,用于存储对话框等图形元素。因此,当恶意软件修改此子项时,大多数进程都会加载该恶意库。图 6 展示了木马程序 Ginwui 利用该方法实现注入和持久化的过程。它只需调用 RegCreateKeyEx 打开 Appinit_Dlls 注册表项,并通过调用 RegSetValueEx 修改其值即可完成该操作。

图 6:Ginwui 修改 AppIniti_DLLs 注册表项
Sha256:9f10ec2786a10971eddc919a5e87a927c652e1655ddbbae72d376856d30fa27c

AppCertDlls

这种方法与 AppInit_DLLs 方法非常相似,区别在于此注册表项下的 DLL 会被加载到所有调用 Win32 API 函数 CreateProcess、CreateProcessAsUser、CreateProcessWithLogonW、CreateProcessWithTokenW 和 WinExec 的进程中。

图像文件执行选项 (IFEO)

IFEO 通常用于调试目的。开发人员可以设置此注册表项下的“调试器值”,以将一个程序附加到另一个可执行文件上进行调试。因此,每当启动该可执行文件时,附加到该文件的程序也会随之启动。要使用此功能,只需提供调试器的路径,并将其附加到要分析的可执行文件即可。恶意软件可以修改此注册表项,从而将自身注入到目标可执行文件中。如图 7 所示,Diztakun 木马通过修改任务管理器的调试器值来实现此技术。

图 7:Diztakun 木马修改 IFEO 注册表项
Sha256:f0089056fc6a314713077273c5910f878813fa750f801dfca4ae7e9d7578a148

7. APC 注入与原子炸弹注入

恶意软件可以利用异步过程调用 (APC) 将自定义代码附加到目标线程的 APC 队列中,强制该线程执行其代码。每个线程都有一个 APC 队列,当目标线程进入可更改状态时,这些队列中的 APC 就会被执行。如果线程调用了 SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsEx、WaitForMultipleObjectsEx 或 WaitForSingleObjectEx 函数,则该线程会进入可更改状态。恶意软件通常会查找处于可更改状态的线程,然后调用 OpenThread 和 QueueUserAPC 函数将 APC 添加到该线程的队列中。QueueUserAPC 函数接收三个参数:1) 目标线程的句柄;2) 指向恶意软件想要运行的函数的指针;3) 传递给该函数指针的参数。如图 8 所示,Amanahe 恶意软件首先调用 OpenThread 获取另一个线程的句柄,然后调用 QueueUserAPC,并将 LoadLibraryA 作为函数指针,将恶意 DLL 注入到该线程中。

AtomBombing 是一种最初由 enSilo 研究团队提出,随后被 Dridex V4 所采用的攻击技术。正如我们在之前的文章中详细讨论的那样,该技术同样依赖于 APC 注入。但其通过原子表 (atom tables) 向其他进程的内存中写入数据。

图 8:Almanahe 执行 APC 注入
Sha256:f74399cc0be275376dad23151e3d0c2e2a1c966e6db6a695a05ec1a30551c0ad

8. 通过 SETWINDOWLONG 实现的额外窗口内存注入 (EWMI)。

EWMI 依赖于向资源管理器托盘窗口的额外窗口内存中注入代码,曾被 Gapz 和 PowerLoader 等恶意软件家族多次使用。应用程序在注册窗口类时可以指定一定数量的额外内存字节,称为额外窗口内存 (EWM)。然而,EWM 的空间非常有限。为了绕过这一限制,恶意软件会将代码写入 explorer.exe 的共享段中,并使用 SetWindowLong 和 SendNotifyMessage 函数,使一个函数指针指向该 shellcode,然后执行它。

恶意软件在向共享段写入数据时有两种选择。它可以创建一个共享段,并将其映射到自身和其他进程(例如 explorer.exe),或者直接打开一个已存在的共享段。前者除了需要分配堆空间和调用 NTMapViewOfSection 函数外,还需要调用一些其他 API,因此开销较大,后一种方法更为常用。恶意软件在共享段中写入 shellcode 后,会使用 GetWindowLong 和 SetWindowLong 来访问和修改“Shell_TrayWnd”窗口的额外内存。GetWindowLong 是一个用于获取窗口类对象额外内存中指定偏移处 32 位值的 API;而 SetWindowLong 则用于更改指定偏移量处的值。通过这种方式,恶意软件只需更改窗口类中某个函数指针的偏移地址,使其指向写入共享段的 shellcode 即可。

与上述大多数其他技术一样,恶意软件还需要触发其写入的代码。在之前讨论的技术中,恶意软件通过调用 CreateRemoteThread、QueueUserAPC 或 SetThreadContext 等 API 来实现这一目的。而在此方法中,恶意软件则通过调用 SendNotifyMessage 来触发注入的代码。执行 SendNotifyMessage 后,Shell_TrayWnd 会接收到消息,并将控制权转移至 SetWindowLong 先前设置的地址所指向的位置。在图 9 中,一种名为 PowerLoader 的恶意软件使用了这种技术。

图 9:PowerLoader 向 shell 托盘窗口
的额外内存中注入 Sha256:5e56a3c4d4c304ee6278df0b32afb62bd0dd01e2a9894ad007f4cc5f873ab5cf

9. 使用 Shims 进行注入

Microsoft 向开发人员提供 Shims 主要是为了向后兼容。Shims 允许开发者无需重写代码即可修复程序。通过利用 Shims,开发者可以告知操作系统如何处理其应用程序。Shims 本质上是一种挂钩技术,用于拦截 API 并针对特定可执行文件。恶意软件可以利用 Shims 来针对某个可执行文件,实现持久化和注入目的。Windows 在加载二进制文件时会运行 Shim 引擎,检查是否存在 Shims 数据库,并据此应用相应的修复措施。

有很多方法可以修复这个问题,但恶意软件最常用的还是那些与安全相关的方法(例如 DisableNX、DisableSEH、InjectDLL 等)。恶意软件可以通过多种方式安装 shim 数据库。例如,一种常见的方法是直接执行 sdbinst.exe,并将其指向恶意 sdb 文件。如图 10 所示,一款名为“通过 Conduit 搜索保护”的广告软件使用了 shim 技术来实现持久化和注入。它通过向 Google Chrome 注入“InjectDLL” shim 来加载 vc32loader.dll。虽然有一些工具可以分析 sdb 文件,但针对下面列出的 sdb 文件的分析,我使用了 python-sdb 工具。

图 10:“搜索保护”用于注入的 SDB
Sha256:6d5048baf2c3bba85adc9ac5ffd96b21c9a27d76003c4aa657157978d7437a20

10. IAT 挂钩和内联挂钩(又称用户空间 ROOTKITS)

IAT 挂钩和内联挂钩通常被称为用户空间 rootkit。IAT 挂钩是一种恶意软件用来修改导入地址表 (IAT) 的技术。当合法应用程序调用位于 DLL 中的 API 时,将执行被替换的函数而非原始函数。相比之下,内联挂钩则是恶意软件直接修改 API 函数本身。如图 11 所示,恶意软件 FinFisher 通过修改 CreateWindowEx 指向的位置来执行 IAT 挂钩。

图 11:FinFisher 通过更改 CreateWindowEx 的指向
位置来执行 IAT 挂钩。Sha256f827c92fbe832db3f09f47fe0dcaafd89b40c7064ab90833a1f418f2d1e75e8e

结论

本文介绍了恶意软件用于在其他进程中隐藏其活动的十种不同技术。总体而言,恶意软件要么直接将自身的 shellcode 注入到另一个进程中,要么强制其他进程加载其恶意库。表 1 对这些技术进行了分类,并提供了示例,可作为观察本文所涵盖每种注入技术的参考。文中包含的图表将有助于研究人员在逆向分析恶意软件时识别各种技术。

表 1:进程注入可以通过直接向另一个进程注入代码,或者强制将 DLL 加载到另一个进程中来实现。
攻击者和研究人员不断发现新的注入技术和隐蔽攻击手段。本文详细介绍了十种常见和新兴的技术,但还有其他技术,例如 COM 劫持。

由于攻击者永不停歇地创新,防御者在检测和阻止隐蔽进程注入方面永远无法“完成”任务。

在 Endgame,我们持续研究先进的隐蔽技术,并将这些保护措施融入到我们的产品中。我们采用多层防护机制,能够检测加载到持久化存储中的恶意 DLL(例如 AppInit DLL、COM 劫持等),通过我们获得专利的 shellcode 注入防护技术,实时阻止多种形式的代码注入行为,并通过我们正在申请专利的无文件攻击检测技术,检测通过上述任何技术注入的、在内存中运行的恶意有效载荷。这种综合方法使我们的平台在抵御代码注入方面比市面上任何其他产品都更有效,同时还能最大限度地抵御因新兴代码注入技术而导致的绕过攻击风险。