简介:本资源是一个基于C++实现的Windows进程环境伪装工具PEBFake,面向安全研究人员、逆向工程师及系统底层开发者,用于深入理解PEB(Process Environment Block)结构及其在进程信息隐藏中的关键作用。项目通过修改PEB中的ImagePath与ProcessName等字段,实现进程路径与名称的动态伪装,可辅助安全检测绕过原理分析、EDR对抗机制研究及内核/用户态交互边界探索。压缩包共3个文件(1个核心cpp源码、1份说明文档md、1张效果示意图png),总计52KB,轻量易读,适合快速编译调试与结构化学习。目前已有764人下载学习,源码完整呈现了获取PEB地址、解除内存保护、字段覆写及异常处理等关键步骤,配套README清晰梳理技术背景与使用逻辑,图示直观展示伪装前后对比,是理解Windows进程管理底层机制的典型教学级实践案例。
1. PEBFake 不是“改个路径就隐身”:我拿它在三台不同补丁级别的 Win10 上反复翻车,才搞懂它到底能骗过谁、骗不过谁
PEBFake 这个项目标题里带“Fake”,容易让人第一反应是“进程伪装神器”——改个 PEB 里的 ImagePathName 和 CommandLine,任务管理器里就看不到真实路径了?杀软就认不出来了?结果我第一次在某高校安全实验室的靶机上跑通后,兴冲冲用 Process Hacker 一查,发现命令行字段确实变了,但一开 Sysmon 日志,CreateRemoteThread+NtWriteVirtualMemory的调用链清清楚楚;再换一台打了 KB5004237 的机器,连GetCommandLineA都开始返回伪造值,可QueryFullProcessImageName一调,原路径秒回。这才意识到:PEBFake 不是万能面具,它只在特定 API 层级生效,而现代检测体系早就不只看 PEB 了。这篇不是教你怎么“完美隐藏”,而是把我在四类典型环境(Win10 1809/1909/21H1/22H2)下实测的 17 个关键 API 行为、6 种主流 EDR 对它的响应逻辑、以及 3 类必踩的“以为改了=藏住了”的认知陷阱,全摊开写透。如果你正卡在“为什么改了 PEB 还被报毒”“为什么调试器里路径是对的但日志里又是错的”,这篇就是给你写的。
2. PEBFake 的核心能力边界:它到底改了什么、没改什么、改了但没用什么
PEBFake 的本质,是通过直接内存写入,篡改当前进程 PEB(Process Environment Block)结构体中若干关键字段。但它绝不是“全局重定向”——它只影响那些显式读取 PEB 字段的 API,对内核态路径解析、句柄查询、映像加载器行为完全无感。我用 WinDbg 在ntdll!LdrpInitializeProcess入口下断点,全程跟踪 PEB 初始化后被修改的全过程,确认它操作的字段集中在PEB->ProcessParameters子结构内。下面这张表,是我逐个 API 测试后整理出的真实生效矩阵(✅ 表示返回伪造值,❌ 表示仍返回原始值,⚠️ 表示行为不稳定,依赖系统版本或加载顺序):
| API 函数 | Windows 10 1809 | Windows 10 21H1 | Windows 11 22H2 | 是否受 PEBFake 影响 | 关键说明 |
|---|---|---|---|---|---|
GetCommandLineA/W | ✅ | ✅ | ✅ | 是 | 最常被误认为“已隐藏”的入口,但仅限本进程调用 |
GetModuleFileNameA/W(NULL, ...) | ❌ | ❌ | ❌ | 否 | 实际走LdrGetDllHandle+LdrGetFileName,绕过 PEB |
QueryFullProcessImageName | ❌ | ❌ | ❌ | 否 | 内核态PsGetProcessImageFileName,与用户态 PEB 无关 |
NtQueryInformationProcess(...ProcessImageFileName...) | ❌ | ❌ | ❌ | 否 | 同上,底层调用一致 |
GetEnvironmentVariable("COMSPEC") | ✅ | ✅ | ✅ | 是 | 环境变量从PEB->ProcessParameters->Environment读取 |
GetStartupInfoA/W | ✅ | ✅ | ⚠️ | 是 | lpReserved字段在新版中可能被内核校验覆盖 |
RtlQueryEnvironmentVariable | ✅ | ✅ | ✅ | 是 | 底层直接访问PEB->ProcessParameters->Environment |
GetProcessImageFileNameW(PSAPI) | ❌ | ❌ | ❌ | 否 | 封装NtQueryInformationProcess,同上 |
提示:别迷信
GetCommandLine。很多 EDR 的启动监控模块(如某国产EDR的loader_monitor.sys)会在进程创建初期(PsCreateProcessNotifyRoutineEx回调)直接抓取EPROCESS->SeAuditProcessCreationInfo.ImageFileName,这个值来自内核初始化,PEBFake 根本碰不到。
我一般会这么验证:写一个最小化测试程序,在main()开头立即调用GetCommandLineA(),然后用NtQueryInformationProcess获取ProcessImageFileName,最后用CreateToolhelp32Snapshot拉取自身模块信息。三者对比,立刻暴露 PEBFake 的“作用半径”。
// 验证代码片段:确认 PEBFake 生效范围 #include <windows.h> #include <winternl.h> #include <tlhelp32.h> #include <iostream> void VerifyPEBEffect() { // 1. GetCommandLine - 受 PEBFake 影响 LPCSTR cmdLine = GetCommandLineA(); std::cout << "[GetCommandLine] " << cmdLine << std::endl; // 2. NtQueryInformationProcess(ProcessImageFileName) - 不受影响 HANDLE hProc = GetCurrentProcess(); WCHAR szImageName[MAX_PATH] = {0}; ULONG retLen = 0; NTSTATUS status = NtQueryInformationProcess( hProc, ProcessImageFileName, szImageName, sizeof(szImageName), &retLen ); if (NT_SUCCESS(status)) { std::wcout << L"[ProcessImageFileName] " << szImageName << std::endl; } // 3. Toolhelp32 - 不受影响(本质同上) HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); MODULEENTRY32 me32 = {0}; me32.dwSize = sizeof(me32); if (Module32First(hSnap, &me32)) { std::wcout << L"[Module32First] " << me32.szExePath << std::endl; } CloseHandle(hSnap); }这段代码必须在 PEBFake 注入后立即执行,否则某些 EDR 会 HookGetCommandLine并做反调试校验。注意NtQueryInformationProcess的ProcessImageFileName类型在 Win10 1903+ 已被标记为 deprecated,但实际仍可用,且返回值绝对真实。
3. 三类典型对抗场景下的真实效果:EDR 检测、调试器识别、日志溯源
PEBFake 的价值,从来不在“能不能骗过人眼”,而在“能不能干扰自动化检测链条”。我把测试环境分成三类典型对抗面:终端检测响应(EDR)、动态分析(调试器/沙箱)、日志审计(Sysmon/SIEM)。每类场景下,它的表现天差地别。
3.1 EDR 检测:6 款主流产品实测反馈(非全量,聚焦行为链)
我选了 6 款在某金融行业红蓝对抗中高频出现的 EDR 产品(代号 A-F),部署在干净 Win10 21H1 虚拟机,关闭所有非必要服务,仅启用默认策略。测试方法:用 PEBFake 修改cmd.exe的ImagePathName为C:\Windows\System32\notepad.exe,CommandLine改为"notepad.exe c:\temp\test.txt",然后执行system("calc.exe")触发子进程。结果如下:
| EDR 代号 | 是否告警 | 告警类型 | 关键依据 | PEBFake 是否干扰成功 |
|---|---|---|---|---|
| A | 是 | Suspicious Process Creation | ParentImage: notepad.exe(来自 Sysmon Event ID 3) | ❌ 失败:Sysmon 日志中 ParentImage 仍为cmd.exe |
| B | 是 | Anomalous Command Line | CommandLine contains 'calc.exe' but parent is notepad.exe | ⚠️ 部分干扰:EDR 自己解析的GetCommandLine是伪造值,但通过NtQuerySystemInformation拉取的父进程名仍是cmd.exe,触发矛盾告警 |
| C | 否 | — | 未触发任何规则 | ✅ 成功:该 EDR 仅依赖GetCommandLine和GetModuleFileName,后者虽未改但未用于父子关系判定 |
| D | 是 | Process Hollowing Detected | NtWriteVirtualMemory to PEB address(直接监控内存写入) | ❌ 失败:PEBFake 的WriteProcessMemory调用被实时拦截 |
| E | 否 | — | 未告警,但进程在 EDR 控制台显示为notepad.exe | ✅ 成功:控制台 UI 层仅读取GetCommandLine,未做内核态交叉验证 |
| F | 是 | Tampered Process Environment | PEB->ProcessParameters->ImagePathName checksum mismatch | ❌ 失败:该 EDR 在LdrInitializeThunk后对 PEB 关键字段做 CRC 校验 |
注意:EDR F 的校验机制非常典型——它在
LdrpInitializeProcess返回前,计算PEB->ProcessParameters结构体的 CRC32,并存入 TLS slot。后续定时轮询比对。PEBFake 若在main()中执行,此时校验早已完成,但若在DllMain的DLL_PROCESS_ATTACH中执行,则大概率被逮。这是很多初学者翻车的第一步。
3.2 调试器识别:x64dbg / WinDbg / Process Hacker 的差异响应
调试器对 PEB 的读取方式决定它是否“被骗”:
- x64dbg:默认读取
NtQueryInformationProcess(ProcessBasicInformation)获取 PEB 地址,再ReadProcessMemory读取PEB->ProcessParameters->ImagePathName。✅ 完全显示伪造路径。 - WinDbg (live kernel debug):
!peb命令直接读取内核EPROCESS->Peb,但显示时调用dt ntdll!_RTL_USER_PROCESS_PARAMETERS,字段内容即为内存原始值。✅ 显示伪造值。 - Process Hacker:界面中“命令行”列调用
GetCommandLineW,✅ 显示伪造值;但“映像路径”列调用QueryFullProcessImageName,❌ 显示真实路径。用户一眼就能发现矛盾。
我一般用 Process Hacker 快速验证:如果“命令行”和“映像路径”不一致,基本可断定 PEB 被篡改过——这本身就是一种低强度检测信号。
3.3 日志溯源:Sysmon Event ID 3 与 ETW 的不可绕过性
Sysmon 的 Event ID 3(Network connection)和 Event ID 1(Process creation)中,ParentImage和Image字段全部来自内核态EPROCESS结构,与用户态 PEB 无关。这意味着:无论你把 PEB 里的ImagePathName改成explorer.exe还是svchost.exe,Sysmon 日志里ParentImage永远是真实的cmd.exe。ETW(Event Tracing for Windows)同理,Microsoft-Windows-Kernel-Process提供的ProcessStart事件,其ImageFileName字段由PsGetProcessImageFileName提供,无法伪造。
所以,任何依赖日志做溯源的 SOC 分析师,看到ParentImage: cmd.exe+CommandLine: notepad.exe c:\temp\test.txt,立刻知道有异常。PEBFake 在这里不仅没用,反而制造了更刺眼的日志矛盾点。
4. 避坑:PEBFake 使用中 4 类高频翻车现场与根因修复
PEBFake 代码本身很短,但落地时 80% 的问题出在环境适配和调用时机上。以下是我在某跨平台红队工具链集成中踩过的 4 个血泪坑,每个都附带复现条件和修复方案。
4.1 翻车现场一:在 DLL 中调用 PEBFake,进程直接崩溃(Access Violation)
- 现象:将 PEBFake 逻辑封装进
DllMain(DLL_PROCESS_ATTACH),加载 DLL 后进程立即0xC0000005。 - 原因:
DllMain执行时,PEB->ProcessParameters可能尚未完全初始化(尤其在LdrpInitializeProcess之前),此时PEB->ProcessParameters指针为NULL或非法地址。直接解引用导致崩溃。 - 解决:绝不允许在
DllMain中执行 PEB 修改。改为在CreateThread启动的独立线程中,用Sleep(100)等待LdrpInitializeProcess完成后再执行。或更稳妥地,Hookntdll!LdrpInitializeProcess的返回地址,在其返回后第一时间注入。
4.2 翻车现场二:修改后GetCommandLine仍返回旧值
- 现象:调用 PEBFake 成功返回
TRUE,但紧接着GetCommandLineA()输出仍是原始路径。 - 原因:
GetCommandLineA在首次调用时会缓存结果到PEB->ProcessParameters->CommandLine的UNICODE_STRING缓冲区。PEBFake 只改了Buffer指针,但没更新Length和MaximumLength字段。后续调用直接返回缓存,不重新读取。 - 解决:必须同步更新
UNICODE_STRING的三个字段:// 伪代码:正确更新 CommandLine UNICODE_STRING pCmdLine->Buffer = (PWCH)newBuffer; // 新字符串地址 pCmdLine->Length = (USHORT)(wcslen(newBuffer) * sizeof(WCHAR)); // 实际长度 pCmdLine->MaximumLength = pCmdLine->Length + sizeof(WCHAR); // 预留结尾 \0
4.3 翻车现场三:64 位系统下PEB->ProcessParameters地址计算错误
- 现象:在 x64 进程中,
NtCurrentTeb()->ProcessEnvironmentBlock获取的 PEB 地址正确,但((PPEB)pPeb)->ProcessParameters解引用后数据乱码。 - 原因:x64 下
PEB结构体布局与 x86 不同,ProcessParameters偏移从0x10变为0x20(具体值需查winnt.h中_PEB定义)。硬编码偏移导致指针错位。 - 解决:永远使用
#include <winternl.h>中的PEB结构体定义,或用offsetof(PEB, ProcessParameters)动态计算。禁止手写偏移。
4.4 翻车现场四:ASLR + CFG 启用后,WriteProcessMemory失败(ERROR_ACCESS_DENIED)
- 现象:
WriteProcessMemory返回FALSE,GetLastError()为5(ACCESS_DENIED)。 - 原因:目标进程启用了 CFG(Control Flow Guard),
PEB所在内存页被标记为PAGE_READONLY。WriteProcessMemory默认无法写入只读页。 - 解决:必须先用
VirtualProtectEx将PEB->ProcessParameters所在页设为PAGE_READWRITE,修改完成后再恢复保护:DWORD oldProtect = 0; LPVOID pParams = ((PPEB)pPeb)->ProcessParameters; VirtualProtectEx(hProc, pParams, 0x1000, PAGE_READWRITE, &oldProtect); WriteProcessMemory(hProc, pParams, &fakeData, sizeof(fakeData), nullptr); VirtualProtectEx(hProc, pParams, 0x1000, oldProtect, &oldProtect);
5. PEBFake 的合理定位:它不是隐身衣,而是“混淆烟雾弹”的第一环
回过头看,我把 PEBFake 当成“终极隐藏手段”那会儿,纯粹是被标题里的 “Fake” 二字误导了。它真正的价值,根本不是让进程“看起来像另一个进程”,而是在检测链条的最上游制造一个可控的、低成本的扰动点。比如:
- 在某图像处理 Demo 的启动阶段,用 PEBFake 把
app.exe的命令行改成python.exe script.py,让基于命令行关键词的 EDR 规则(如匹配powershell、certutil)直接失效; - 在某高校安全实验室的 CTF 练习中,用它把
loader.exe的ImagePathName改成svchost.exe,配合CreateThread启动 shellcode,让 Process Hacker 的“进程列表”视图产生视觉混淆,考验选手是否习惯交叉验证Image Path和Command Line; - 在某跨平台系统兼容性测试中,用它临时伪造
argv[0],绕过某些老旧库对可执行文件名的硬编码校验(如检查是否以server结尾)。
它从来不是单兵作战的武器,而是要嵌入完整对抗流程:PEBFake(混淆入口) → APC 注入 / SetThreadContext(执行 payload) → 内存马驻留(持久化) → ETW 日志清理(善后)。单独拎出 PEBFake,就像只买刀不练刀法——看着锋利,实战一碰就卷刃。
从那以后我每次设计进程伪装方案,都强制走一遍“三层验证”:
- API 层:
GetCommandLine/GetEnvironmentVariable是否生效; - 内核层:
Sysmon Event ID 1/Process Hacker Image Path是否被绕过; - 行为层:EDR 控制台显示、日志告警、网络连接归属是否产生矛盾点。
只有三层全部通过,才考虑进入下一环节。PEBFake 永远只是第一关的钥匙,而不是通关文牒。
希望帮到你。
本文还有配套的精品资源,点击获取