文章总结: DirtyVanity是一种利用Windows进程分支机制实现EDR规避的新型攻击技术,通过将传统的分配-写入-执行攻击链分离到不同进程中,使EDR系统无法关联恶意代码的写入与执行。该技术利用RtlCreateProcessReflection等API创建克隆进程,在其中执行已在父进程中写入的恶意代码,从而绕过大多数注入检测逻辑。防御者需加强对进程分支原语的监控,跟踪克隆进程的继承状态,并关联父进程历史记录到子进程的内存状态以有效防御此类攻击。
综合评分: 85
文章分类: 渗透测试,漏洞分析,EDR规避,红队,内网渗透
windows下利用进程克隆实现 EDR 规避
原创
黑晶
黑晶
2025年11月24日 19:45
浙江
windows下利用进程克隆实现 EDR 规避
摘要 (Abstract)
“Dirty Vanity” 是一种利用 Windows 操作系统中鲜为人知的进程分支(Process Forking)机制实现的代码注入技术。该技术的核心在于将传统的“分配-写入-执行”(Allocate-Write-Execute)攻击链中的写入和执行步骤分离到不同的进程中。通过滥用如 RtlCreateProcessReflection 等进程反射 API,攻击者能够在一个新创建的子进程(克隆进程)中执行恶意代码。由于 EDR(端点检测与响应)系统通常侧重于监控和关联在同一进程中发生的操作,Dirty Vanity 使得克隆进程在 EDR 看来从未被写入或修改,从而有效规避了现有的大多数注入检测逻辑,对传统的防御模型提出了挑战。
1. 技术背景:Windows 进程分支 (Process Forking)
1.1 分支机制的起源与在 Windows 中的存在
进程分支(Forking)是指从调用进程创建一个新的进程的行为。它起源于 Unix 系统中用于进程创建的 fork 和 exec 系统调用。分支的结果(子进程)是分支调用者(父进程)的一个精确副本,除了 fork 的返回值不同。
虽然 Windows 默认的进程创建机制(如 CreateProcess)不使用 fork 和 exec 模型,但它确实通过遗留的 POSIX 子系统(包含 psxdll.dll)支持了这一机制。
在现代 Windows 中,进程分支或克隆能力主要通过以下机制实现:
- 1. 进程反射 (Process Reflection): 旨在允许对需要持续提供服务的进程进行分析。Windows 诊断基础设施(WDI)就使用了反射进程,主要通过
RtlCreateProcessReflection实现。 - 2. 进程快照 (Process Snapshotting): 通过
PssCaptureSnapshot调用来实现。 - 3. 凭证窃取: 反射和快照技术允许在逃避 EDR 检测的同时执行凭证窃取。
1.2 进程分支 API 概览
Windows 提供了用于进程克隆的底层 Native API:
| API 类型 | 核心函数 | 描述 |
| — | — | — |
| 自克隆 (Self Fork) | RtlCloneUserProcess | 克隆调用自身的进程,子进程线程返回 STATUS_PROCESS_CLONED (0x00000129)。 |
| 远程克隆 (Remote Fork) | NtCreateProcess[Ex] | 用于创建进程,也是进程快照(PssCaptureSnapshot)的底层机制。 |
| 远程执行克隆 | RtlCreateProcessReflection | 允许通过远程线程在目标进程内部触发克隆,并指定起始执行例程。 |
需要注意的是,直接使用 NtCreateUserProcess 尝试远程克隆会失败,系统会返回 STATUS_INVALID_PARAMETER (0xC000000D)。例如,Forker.exe 尝试克隆 lsass.exe 时就遇到了此错误。因此,远程代码执行依赖于 RtlCreateProcessReflection。
2. 内部机制:地址空间克隆的奥秘
Dirty Vanity 攻击的有效性深刻植根于 Windows 内核管理进程地址空间克隆的方式。
2.1 地址空间复制与写时复制 (Copy-on-Write)
克隆机制的核心在于 MiCloneProcessAddressSpace,该函数负责将父进程的内存复制给分支出的子进程。这种复制是以写时复制 (Copy-on-Write) 视图的形式进行的,这极大地提高了效率,意味着它仅在子进程或父进程尝试写入页面时才进行实际的数据复制。克隆操作会复制父进程的私有内存页面和大部分映射的内存区域。
2.2 内存继承的筛选机制
尽管进程克隆听起来像是完美的内存复制,但并非所有内存区域都会被克隆。操作系统通过一个内核对象 _MMVAD(描述进程中内存分配的内核对象)来管理每个进程的虚拟地址描述符。在克隆过程中,内核函数 MiAllocateChildVads 会迭代父进程的 VADs,并通过 MiVadShouldBeForked 函数进行筛选。
筛选关键点:
内存是否被克隆主要取决于 _MMVAD_FLAGS2 中的第 26 位,即 Inherit 标志(值为 0x4000000)。
MiVadShouldBeForked 的伪代码逻辑如下:
PSEUDO bool MiVadShouldBeForked(_MMVAD *CurrentVadNode)
{
// 对于大多数 MEM_PRIVATE VADs (私有内存)
return 1;
// 对于 MEM_MAPPED VADs (映射内存)
if ( _bittest(CurrentVadNode.u2.LongFlags2 , 0x1A)) // 26th bit
return 1; // 继承标志位设置,克隆
else
return 0; // 继承标志位未设置,不克隆
}
内存映射是通过 NtMapViewOfSection API 进行的,其中 InheritDisposition 参数控制了视图如何与子进程共享:
- • ViewShare (1): 视图将被映射到未来创建的任何子进程中(设置
Inherit标志)。 - • ViewUnmap (2): 视图将不会被映射到子进程中(不设置
Inherit标志)。
2.3 MessageBoxA 失败的启示:DLL 调用的兼容性问题
当尝试在克隆进程中执行涉及高级 DLL(如 user32.dll)的 Shellcode 时,例如调用 MessageBoxA,会导致访问冲突(Access violation – code c0000005)。
失败的原因在于 MessageBoxA 调用的内部函数(如 USER32!GetDpiForCurrentProcess)需要访问 USER32!gpsi。USER32!gpsi 指向一个核心对象 win32k!tagSHAREDINFO,该对象持有会话特定的 GUI 对象。这个对象位于一个共享的只读区段,在 user32.dll 初始化期间被映射到每个进程中。
通过逆向分析可知,win32k!InitMapSharedSection 在映射该共享区段时,使用了 ViewUnmap 继承属性:
result = NtMapViewOfSection(ghSectionShared,[snip], ViewUnmap, [snip]);
因此,由于使用了 ViewUnmap 属性,进程分支过程不会复制这些共享区段。这导致克隆进程缺少 USER32!gpsi 所需的内存映射,从而使得依赖于这些高层 DLL 状态的 Shellcode 失败。
结论: Dirty Vanity 攻击者在构建 Shellcode 时,应避免依赖于这些非继承的内存映射,或选择使用纯 Ntdll API 进行操作,例如执行 NtCreateUserProcess 来启动一个新进程。
3. Dirty Vanity 攻击原理与实现
3.1 核心原理:分离攻击链
传统的代码注入检测依赖于 EDR 关联进程内发生的以下三个步骤:
- 1. 分配 (Allocate): 在目标进程中分配可执行内存。
- 2. 写入 (Write): 将恶意 Shellcode 写入该内存区域。
- 3. 执行 (Execute): 通过创建远程线程等方式执行该 Shellcode。
Dirty Vanity 利用进程分支机制引入了两个新的原语:分支 (Fork) 和 分支与执行 (Fork & Execute)。
攻击链分离流程 (拟人化架构图描述):
| 步骤 | 原始进程(父进程,例如 explorer.exe) | 克隆进程(子进程,例如 explorer.2.exe) | EDR 视角 |
| — | — | — | — |
| 1. 分配 (Allocate Primitive) | VirtualAllocEx 在父进程中分配内存。 | N/A | EDR 记录:分配 |
| 2. 初始写入 (Initial Write Primitive) | WriteProcessMemory 将 Shellcode 写入父进程的内存。 | N/A | EDR 记录:写入 |
| 3. 分支与执行 (Fork & Execute Primitive) | 调用 RtlCreateProcessReflection。 | 克隆父进程地址空间,继承写入的 Shellcode。 | EDR 记录:克隆创建 |
| 4. 执行 (Execution) | N/A | 在克隆进程中,从克隆的 Shellcode 起始地址开始执行。 | EDR 发现:克隆进程上只有执行操作,没有写入记录。 |
EDR 规避点: EDR 观察到 Allocate 和 Write 发生在父进程,但 Execute 发生在子进程。由于子进程是父进程内存的克隆,EDR 错误地认为子进程从未被写入,从而不会被标记为注入。Dirty Vanity 通过改变操作系统监控的规则,颠覆了对注入防御的传统看法。
3.2 攻击步骤与代码示例
3.2.1 初始写入步骤 (Initial Write Step)
攻击者首先需要在目标父进程中分配内存并写入 Shellcode。可用于初始写入的常见方法包括:
- •
VirtualAllocEx和WriteProcessMemory - •
NtCreateSection和NtMapViewOfSection - •
NtSetContextThread(幽灵写入,Ghost Writing)
3.2.2 分支与执行步骤 (Fork & Execute Step)
攻击者随后使用 RtlCreateProcessReflection API 来触发克隆和执行。
先决条件:
在使用 RtlCreateProcessReflection 变体时,需要以特定的访问权限打开目标进程:
- •
PROCESS_VM_OPERATION - •
PROCESS_CREATE_THREAD - •
PROCESS_DUP_HANDLE
代码示例:通过 RtlCreateProcessReflection 实现 Dirty Vanity
以下代码片段展示了如何打开目标进程,分配和写入 Shellcode,然后利用反射机制在克隆进程中执行 Shellcode:
// 1. 定义 Shellcode 数组 (此处省略实际内容,假设为 ntdll API shellcode)
unsigned char shellcode[] = {0x40, 0x55, 0x57, ...};
// 2. 具有适当权限打开分支目标
HANDLE victimHandle = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE,
TRUE,
victimPid
);
// 3. 在目标中分配 Shellcode 大小的内存
DWORD_PTR shellcodeSize = sizeof(shellcode);
LPVOID baseAddress = VirtualAllocEx(
victimHandle,
nullptr,
shellcodeSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
// 4. 写入 Shellcode
BOOL status = WriteProcessMemory(
victimHandle,
baseAddress,
shellcode,
shellcodeSize,
&bytesWritten
);
// 5. 加载 ntdll.dll 并获取 RtlCreateProcessReflection 函数指针
#define RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES 0x00000002
HMODULE ntlib = LoadLibraryA("ntdll.dll");
Rtl_CreateProcessReflection RtlCreateProcessReflection =
(Rtl_CreateProcessReflection)GetProcAddress(ntlib, "RtlCreateProcessReflection");
// 6. 分支目标并在克隆进程中执行 Shellcode
T_RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION info = { 0 };
NTSTATUS ret = RtlCreateProcessReflection(
victimHandle,
RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES,
baseAddress, // Shellcode 的基地址作为 StartRoutine
NULL,
NULL,
&info
);
// 克隆进程 (clone) 将在 baseAddress 处开始执行
反射流程 (Process Reflection Flow) 描述:
RtlCreateProcessReflection 涉及三个进程:
- 1. 调用者 (Caller): 准备共享内存,创建同步事件,并将事件句柄复制到父进程。
- 2. 父进程 (Parent): 在父进程中创建远程线程执行
RtlpProcessReflectionStartup。该线程随后调用RtlCloneUserProcess完成克隆,并将事件句柄复制到克隆进程。 - 3. 克隆进程 (Clone): 克隆进程在启动后,等待用户提供的事件(如果存在),然后调用
StartRoutine(即 Shellcode 地址),执行完毕后终止。
通过将 Shellcode 的起始地址 (baseAddress) 作为 StartRoutine 传递给 RtlCreateProcessReflection,克隆进程便会在其地址空间中直接执行这段已写入的恶意代码。
4. EDR 规避、挑战与防御对策
4.1 EDR 规避机制
Dirty Vanity 的主要规避点在于其打破了 Allocate/Write/Execute 的单进程关联模型:
- • Allocate & Write 阶段: 发生在父进程,这本身可能看起来像是合法的进程内存操作,或者与其他内存操作混淆。
- • Execute 阶段: 发生在克隆的子进程中。由于内存是作为父进程的副本被继承的(写时复制),子进程的内存页在 EDR 看来是“干净”的,从未被写入。
4.2 攻击的可见性与局限性
虽然 Dirty Vanity 绕过了传统的注入模式识别,但实现远程代码注入所需的底层操作仍具有高度的可见性。特别是通过 RtlCreateProcessReflection 进行远程注入,共享许多与经典注入技术相似的检测向量:
- 1. 进程句柄获取: 打开目标父进程时触发 Ob- 回调,请求的访问掩码(
PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE)与 shellcode 或 DLL 注入所需权限高度相似,且受到严格监控。 - 2. 跨进程内存映射: 在父进程中映射共享内存区域(用于传递上下文),触发 EtwTi。
- 3. 跨进程线程创建: 在父进程中创建远程线程来启动反射过程,这是一个具有高风险的操作,触发相应的 Ps- 回调,本身可能足以引发警报。
- 4. 进程创建和句柄复制: 随后出现的异常进程创建和远程进程/线程句柄复制,可通过 Ob- 回调被观察到。
因此,虽然 Dirty Vanity 提供了新的注入原语,使其能够突破基于已知模式的检测,但 EDR 仍可以通过监控底层内核回调和进程交互行为来捕获这些攻击。
4.3 防御对策与检测建议
针对 Dirty Vanity 及其变种,防御者需要提升监控粒度,重点关注进程分支原语:
- 1. 监控所有分支原语: EDR 解决方案必须响应并监控所有呈现的分支原语,包括
RtlCreateProcessReflection和NtCreateProcess[Ex]。 - 2. 跟踪克隆进程的继承状态: 最关键的防御是对克隆出的子进程进行跟踪,并将其视为与父进程具有相同的“被污染”知识。如果父进程在内存中有一个被写入的区域,那么子进程继承的这一区域也应被标记为恶意。
- 3. 加强对高风险 API 的监控: 密切关注
PROCESS_CREATE_THREAD和PROCESS_VM_OPERATION等高监控权限的跨进程访问请求。 - 4. 变体预防: 攻击者可能探索更多的变体,包括:
- • 使用
NtCreateProcess[Ex]结合其他执行原语。 - • 在分支前修补父进程中的分支入口点。
- • 修复 Shellcode 中对
User32或更高层级 DLL 操作的兼容性问题。
结论 (Conclusion)
Dirty Vanity 证明了 Windows 进程克隆机制,尤其是 RtlCreateProcessReflection,为攻击者提供了强大的新工具来规避传统的代码注入检测。通过将攻击链分离,它迫使防御者重新审视对进程内存状态和活动关联的监控策略。
如果将 EDR 系统比作一个图书馆保安,Dirty Vanity 的攻击就像是有人在图书馆的一本书上写了笔记(Allocate/Write),然后将这本书送入复印室(Forking)。保安只检查复印室里的新书(Clone Process)是否在其内部被写入,由于克隆进程继承了笔记但自身未执行写入动作,它被误认为是“干净”的,从而成功逃避了检查。因此,EDR 必须学会关联父进程的历史记录到其子进程的内存状态,才能有效防御此类高级攻击。
推荐阅读
这是一个纯粹,开放,前沿的技术交流社区,成员主要有互联网大厂安全部门任职的成员,乙方红队专家,以及正在学习入门的小白等,目前主题主要以红队研发为主(有经验的都知道是什么意思),以及其他涉及到红队进攻侵入性技术,如果你想学习技术,认识不同的人或者寻求一个机会之类的,可以来看看👇👇👇****
欢迎加入交流圈
扫码获取更多精彩
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:黑晶 黑晶《windows下利用进程克隆实现 EDR 规避》