0%
毅种循环

模块 36 - 线程劫持 - 远程线程创建

恶意软件开发课程 - 线程劫持 - 远程线程创建


模块 36 - 线程劫持 - 远程线程创建#

[!IMPORTANT] 本知识库声明

本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示实践心得

  1. 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
  2. 版权归属:原始知识产权归原作者/官方所有。
  3. 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程
  4. 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。

线程劫持 - 远程线程创建#

简介#

上一模块演示了通过创建一个运行良性虚拟函数的挂起牺牲线程并利用其句柄执行 Payload,对本地进程进行线程劫持。本模块将演示针对远程进程而不是本地进程的相同技术。

本模块的另一个显着区别是不会在远程进程中创建牺牲线程。虽然可以使用 CreateRemoteThread WinAPI 调用来完成,但它是一个经常被滥用的函数,因此受到安全解决方案的高度监控。

更好的方法是使用 CreateProcess 创建一个处于挂起状态的牺牲进程,这将使其所有线程在挂起状态下创建,从而允许它们被劫持。

💡 初学者提示:Fork & Run

这种“创建新进程 -> 注入代码 -> 恢复运行”的技术通常被称为 Fork & Run

  • 优点:如果 Shellcode 崩溃,只会导致新进程崩溃,不会影响我们的主马(Loader)。
  • 缺点:进程创建行为本身通过内核回调(PsSetCreateProcessNotifyRoutine)很容易被监控。

远程线程劫持步骤#

本节描述了对驻留在远程进程中的线程执行线程劫持所需的步骤。

CreateProcess WinAPI#

CreateProcess 是一个强大且重要的 WinAPI,具有多种用途。为了确保用户有扎实的理解,下面解释了该函数的重要参数。

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);
c
  • lpApplicationNamelpCommandLine 参数分别代表进程名称及其命令行参数。例如,lpApplicationName 可以是 C:\Windows\System32\cmd.exelpCommandLine 可以是 /k whoami。或者,lpApplicationName 可以设置为 NULL,但 lpCommandLine 可以包含进程名称及其参数,C:\Windows\System32\cmd.exe /k whoami。这两个参数都标记为可选,意味着新创建的进程不需要有任何参数。
  • dwCreationFlags 是控制优先级类和进程创建的参数。可以在此处找到此参数的可能值。例如,使用 CREATE_SUSPENDED 标志会在挂起状态下创建进程。
  • lpStartupInfo 是指向 STARTUPINFO 的指针,其中包含与进程创建相关的详细信息。唯一需要填充的元素是 DWORD cb,即结构的大小(以字节为单位)。
  • lpProcessInformation 是一个 OUT 参数,返回一个 PROCESS_INFORMATION 结构。PROCESS_INFORMATION 结构如下所示。
typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;        // 新创建进程的句柄。
  HANDLE hThread;         // 新创建进程的主线程的句柄。
  DWORD  dwProcessId;     // 进程 ID
  DWORD  dwThreadId;      // 主线程 ID    
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
c

使用环境变量#

创建进程的最后一步是确定进程的完整路径。牺牲进程将从驻留在 System32 目录中的二进制文件创建。可以假设路径为 C:\Windows\System32 并硬编码该值,但通过编程验证路径总是更安全的。为此,将使用 GetEnvironmentVariableA WinAPI。GetEnvironmentVariableA 检索指定环境变量的值,在本例中为 “WINDIR”。

WINDIR 是指向 Windows 操作系统安装目录的环境变量。在大多数系统上,此目录是 “C:\Windows”。可以通过在命令提示符下键入 “echo %WINDIR%” 或在文件资源管理器搜索栏中只需键入 %WINDIR% 来访问 WINDIR 环境变量的值。

DWORD GetEnvironmentVariableA(
  [in, optional]  LPCSTR lpName,
  [out, optional] LPSTR  lpBuffer,
  [in]            DWORD  nSize
);
c

创建牺牲进程函数#

CreateSuspendedProcess 将用于在挂起状态下创建牺牲进程。它需要 4 个参数:

  • lpProcessName - 要创建的进程的名称。
  • dwProcessId - 指向接收进程 ID 的 DWORD 的指针。
  • hProcess - 指向接收进程句柄的 HANDLE 的指针。
  • hThread - 指向接收线程句柄的 HANDLE 的指针。

注入远程进程函数#

创建目标进程后的下一步是使用来自 Process Injection - Shellcode 初学者模块的 InjectShellcodeToRemoteProcess 函数注入 Payload。Payload 仅写入远程进程而不执行。然后存储基地址以供以后通过线程劫持使用。

远程线程劫持函数#

在创建挂起进程并将 Payload 写入远程进程后,最后一步是使用 CreateSuspendedProcess 返回的线程句柄执行线程劫持。这部分与本地线程劫持模块中演示的部分相同。

回顾一下,使用 GetThreadContext 检索线程上下文,更新 RIP 寄存器以指向写入的 Payload,调用 SetThreadContext 更新线程上下文,最后使用 ResumeThread 执行 Payload。所有这些都在下面的自定义函数 HijackThread 中演示,该函数接受两个参数:

  • hThread - 要劫持的线程。
  • pAddress - 指向要执行的 Payload 基地址的指针。

结论#

快速回顾本模块中演示的内容:

  1. 使用 CreateProcessA 在挂起状态下创建了一个新进程,也就是将其所有线程也都在挂起状态下创建。
  2. 使用 VirtualAllocExWriteProcessMemory 将 Payload 注入到新创建的进程中,但未执行。
  3. 使用 CreateProcessA 返回的线程句柄通过线程劫持执行 Payload。

演示#

本演示使用 Notepad.exe 作为牺牲进程,劫持其线程并执行 Msfvenom 计算器 Shellcode。

图片
图片


🎯 总结#

在本模块中,我们学习了:

  1. 远程线程劫持:比本地劫持更强大,因为可以在一个全新的、干净的进程中运行代码。
  2. 牺牲进程:使用 CreateProcess (挂起状态) 启动一个受害者进程,这比滥用 CreateRemoteThread 更安全。
  3. 上下文操作:跨进程修改线程上下文 (GetThreadContext / SetThreadContext) 的能力。

💡 关键要点

  • 通过 CreateProcess 获得的 hThread 句柄已经拥有完全访问权限,因此不需要像 OpenThread 那样再去请求权限。
  • 这是一个非常经典的 Loader 模式:启动 -> 注入 -> 劫持 -> 恢复
  • 注意:一些 EDR 可能会检测 SetThreadContext 调用,特别是当它指向非模块内存区域(即 VirtualAlloc 分配的堆区域)时。

📚 下一步学习

下一个模块将介绍 Thread Hijacking - Local Thread Enumeration。如果我们想劫持一个已经运行的进程中的线程,而不是自己创建的新进程,该怎么办?我们需要先枚举出它的线程!