无需分配,照样注入:利用程序入口点实现进程注入

admin 2025-12-22 00:39:38 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了两种无需显式内存分配或直接创建线程的进程注入技术:AddressOfEntryPoint注入和ThreadQuerySetWin32StartAddress注入。前者通过修改目标进程入口点地址注入shellcode,后者利用NtQueryInformationThread获取线程起始地址进行注入。文章提供了详细的实现步骤和代码示例,分析了ThreadQuerySetWin32StartAddress的工作原理,并给出了防御考量和Yara规则用于检测此类攻击。这些技术对红队和渗透测试人员具有实用价值,同时也为防御者提供了检测思路。 综合评分: 96 文章分类: 渗透测试,红队,内网渗透,漏洞分析,漏洞POC


cover_image

无需分配,照样注入:利用程序入口点实现进程注入

securitainment

2025年12月21日 17:45 中国香港

引言

进程注入是红队和威胁行为者常用的技术,广泛应用于防御规避、权限提升及其他有趣的用例。截至本文发布时,MITRE ATT&CK 框架已收录 12 种 (远程) 进程注入子技术。当然,还有众多其他示例以及各种各样的衍生变体。

最近,我在研究远程进程注入时,试图寻找一些低调的技术——要么文档记录不完善,要么功能实现所需的核心要素极为精简。尽管经典的 VirtualAllocEx()-> WriteProcessMemory()-> CreateRemoteThread()组合是一个稳定可靠的选择,但由于 EDR 产品对其审查过于严格,想以精简方式有效使用这种组合已变得相当困难。

在本文中,我们将探讨几种入口点进程注入技术,这些技术不需要 显式内存分配,也不需要 直接使用创建线程或操纵线程上下文的方法。

AddressOfEntryPoint 进程注入

请跟我重复:有疑问时,去 Red Team Notes 找答案。我就是在那里发现了 @spotheplanet 撰写的这篇精彩文章,展示了如何利用 AddressOfEntryPoint相对虚拟地址进行代码注入。

当可移植可执行文件 (PE) 被加载到内存中时,AddressOfEntryPoint 是相对于映像基址的入口点地址 (Microsoft Learn)。在 PE exe 文件/映像中,AddressOfEntryPoint 字段位于 Optional Header中:

滥用 AddressOfEntryPoint 字段并非全新概念。虽然实现上并不总是可行,但可以在任意 PE 文件中覆写 AddressOfEntryPoint 字段并植入 shellcode,使注入的 shellcode 在程序启动时加载 (如此处所示)。有趣的是,这种技术在远程进程的上下文中同样可以实现。

当进程被创建时,首先加载到内存中的两个模块是程序映像和 ntdll.dll。当进程以 挂起状态创建时,加载的 唯一两个模块就是程序映像和 ntdll.dll:

本质上,操作系统仅完成足够的引导工作来加载最基本的组件,但此时 AddressOfEntryPoint 尚未被调用以开始正式的程序执行。那么,你可能会问……如何在挂起的进程中找到 AddressOfEntryPoint 来注入代码呢?

参照 Red Team Notes 文章,流程总结如下:

  • 通过 NtQueryInformationProcess()获取远程进程的目标映像 PEB 地址和指向映像基址的指针。
  • 通过 ReadProcessMemory()从 PEB 偏移量中获取目标进程映像基址。
  • 通过 ReadProcessMemory()读取并捕获目标进程映像头。
  • 在目标进程可选头中获取指向 AddressOfEntryPoint 地址的指针。
  • 通过 WriteProcessMemory()用所需的 shellcode 覆写 AddressOfEntryPoint。
  • 通过 ResumeThread()从挂起状态恢复进程 (主线程)。

使用提供的示例代码,我们的 shellcode 成功注入并在远程进程中执行:

注:关于此技术的 64 位代码示例,请查看 Tim White 的 GitHub 项目。

‘ThreadQuery’ 进程注入

NtQueryInformationThread()可能不如 NtQueryInformationProcess()那么广为人知,但它是从 ntdll.dll 导出的一个名称相似的方法:

在阅读 Microsoft 关于此函数的文档时,_ThreadInformationClass_ 参数部分的一段描述引起了我的注意:

“如果此参数是 THREADINFOCLASS 枚举的 ThreadQuerySetWin32StartAddress 值,则该函数返回线程的起始地址”

Microsoft Docs

尽管这非常有趣,但关于 THREADINFOCLASS枚举的信息在 Microsoft 网站上并不容易找到。不过,通过简单的 Google 搜索,我们可以找到 ProcessHacker GitHub 仓库页面,其中包含该枚举的定义:

如上图所示,可以从 THREADINFOCLASS 中提取大量信息。对于我们的目的而言,我们最感兴趣的是获取指向 ThreadQuerySetWin32StartAddress的指针。根据我们对挂起状态进程的了解,程序入口点地址尚未被调用。因此,在查询 进程线程时从 ThreadQuerySetWin32StartAddress 获取的任何进程线程地址信息,很可能就是程序入口点的地址。让我们验证这一假设……

首先,我们需要弄清楚如何获取主进程线程的句柄。幸运的是,这非常简单,因为我们使用 CreateProcess()启动进程。该信息作为指向 PROCESS_INFORMATION结构的指针随时可用。Microsoft 方便地说明:

[PROCESS_INFORMATION] 包含关于新创建进程及其主线程的信息。它与 CreateProcess、CreateProcessAsUser、CreateProcessWithLogonW 或 CreateProcessWithTokenW 函数一起使用。

Microsoft Docs

因此,我们使用 NtQueryInformationThread() 获取指向 ThreadQuerySetWin32StartAddress 的函数指针 (它在 THREADINFOCLASS 枚举中也表示为数值 0x09)。

接下来,我们使用 WriteProcessMemory() 将 shellcode 写入 ThreadQuerySetWin32StartAddress 的地址,并利用 ResumeThread() 恢复线程以启动 shellcode。

将所有内容整合在一起,这个简单的 C++ 程序 应该能完成任务 (目标为 notepad.exe):

#include&nbsp;<stdio.h>
#include&nbsp;<windows.h>
#include&nbsp;<winternl.h>
#pragma&nbsp;comment(lib, "ntdll")

int main()
{
&nbsp; &nbsp; // Embed our shellcode bytes
&nbsp; &nbsp; unsigned char shellcode[]{ 0x56,0x48,0x89, ... };

&nbsp; &nbsp; // Start target process
&nbsp; &nbsp; STARTUPINFOA si;
&nbsp; &nbsp; PROCESS_INFORMATION pi;
&nbsp; &nbsp; CreateProcessA(0, (LPSTR)"c:\\windows\\system32\\notepad.exe", 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi);

&nbsp; &nbsp; // Get memory address of primary thread
&nbsp; &nbsp; ULONG64 threadAddr = 0;
&nbsp; &nbsp; ULONG retlen = 0;
&nbsp; &nbsp; NtQueryInformationThread(pi.hThread, (THREADINFOCLASS)9, &threadAddr, sizeof(PVOID), &retlen);
&nbsp; &nbsp; printf("Found primary thread start address: %I64x\n", threadAddr);

&nbsp; &nbsp; // Overwrite memory address of thread with our shellcode
&nbsp; &nbsp; WriteProcessMemory(pi.hProcess, (LPVOID)threadAddr, shellcode, sizeof(shellcode), NULL);

&nbsp; &nbsp; // Resume primary thread to execute shellcode
&nbsp; &nbsp; ResumeThread(pi.hThread);

&nbsp; &nbsp;return 0;
}

编译并运行程序后,一切似乎按预期工作。

在宣布胜利之前,让我们稍微修改代码并分析程序运行情况,以验证 (或推翻) 我们最初的假设……

ThreadQuerySetWin32StartAddress 分析

首先,我们在程序中注释掉 ResumeThread() 调用,重新编译并运行。这当然会使目标进程 (notepad.exe) 处于挂起状态。我们将在需要时手动恢复进程。

在程序输出中,NtQueryInformationThread() 查询 ThreadQuerySetWin32StartAddress 时返回内存地址 0x_7ff6a0ff3f40_:

在 ProcessHacker 中分析挂起的进程,我们看到单个线程指向起始地址 _0x7ffdaf6a2680_。

一旦我们将 x64dbg 调试器附加到挂起的程序,程序状态恢复,但单个线程仍保持挂起。指令指针当前指向单个线程的起始地址,用于执行 ntdll:RtlUserThreadStart()函数。

需要澄清的是,当前挂起的线程 不是主程序线程。此外,对 RtlUserThreadStart()的调用实际上是初始进程启动和初始化例程的一部分。

继续前进,我们手动恢复挂起的线程以完成剩余的进程初始化,然后在调试器中为 ThreadQuerySetWin32StartAddress 返回的内存地址 (0x7ff6a0ff3f40) 添加断点。当我们运行程序时,断点命中 解析后的程序入口点地址:

逐步执行程序的其余部分,shellcode 成功执行:

*注:覆写入口点可能导致程序功能不稳定 (例如,当 shellcode 较大时)。

防御考量

  • 在查看堆栈线程时,我注意到一个有趣的方法调用 __report_securityfailure_。这是 VTGuard 的一项功能,它”检测无效的虚函数表,这种情况可能发生在漏洞利用程序试图通过内存中受控的 C++ 对象来控制执行流时”。

跟踪此类堆栈事件并与系统/应用程序/安全缓解事件日志错误相关联,可能提供一个有趣的检测机会 (如果你有更多相关信息,请联系我!)

  • 以下 POC Yara 规则可能有助于识别利用与入口点进程注入相关方法的可疑 PE 文件:
import "pe"

rule Identify_EntryPoint_Process_Injection
{
&nbsp; &nbsp; meta:
&nbsp; &nbsp; &nbsp; &nbsp; author = "@bohops"
&nbsp; &nbsp; &nbsp; &nbsp; description = "Identify suspicious methods in PE files that may be used for entry point process injection"
&nbsp; &nbsp; strings:
&nbsp; &nbsp; &nbsp; &nbsp; $a = "CreateProcess"
&nbsp; &nbsp; &nbsp; &nbsp; $b = "WriteProcessMemory"
&nbsp; &nbsp; &nbsp; &nbsp; $c = "NtWriteVirtualMemory"
&nbsp; &nbsp; &nbsp; &nbsp; $d = "ResumeThread"
&nbsp; &nbsp; &nbsp; &nbsp; $e = "NtQueryInformationThread"
&nbsp; &nbsp; &nbsp; &nbsp; $f = "NtQueryInformationProcess"

&nbsp; &nbsp; condition:
&nbsp; &nbsp; &nbsp; &nbsp; pe.is_pe and $a and ($b or $c) and $d and ($e or $f)
}

结论

一如既往,感谢你花时间阅读这篇文章。

-bohops


NO ALLOC, NO PROBLEM LEVERAGING PROGRAM ENTRY POINTS FOR

免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:securitainment 《

评论:0   参与:  3