免杀专题
1、R3进程伪装
1. 课程概述
1.1 学习目标
通过本课时的学习,你将掌握:
- 理解Windows进程内存结构(PEB/TEB)
- 掌握进程伪装的核心技术
- 学会使用相关工具进行分析和验证
- 理解免杀中进程伪装的作用
1.2 前置知识
- C/C++基础语法
- Windows API基本使用
- 指针与内存结构
- 对PE文件格式有基本了解
2. 名词解释
2.1 核心术语
| 术语 | 英文全称 | 说明 |
|---|---|---|
| R3 | Ring 3 | 用户态,最低特权级别,普通应用程序运行的环境 |
| R0 | Ring 0 | 内核态,最高特权级别,操作系统内核运行的环境 |
| PEB | Process Environment Block | 进程环境块,包含进程的关键信息 |
| TEB | Thread Environment Block | 线程环境块,包含线程的关键信息 |
| Ldr | Loader Data | 加载器数据,包含已加载模块的链表 |
| 进程伪装 | Process Masquerading | 修改进程显示信息,使其看起来像另一个进程 |
| 免杀 | Evasion/AV Bypass | 绕过安全软件检测的技术 |
2.2 Windows内存结构图
+------------------+
| 应用程序 (R3) | ← 用户态
+------------------+
| ntdll.dll | ← 系统调用接口
+------------------+
| kernel32.dll | ← Windows API
+------------------+
↓
+------------------+
| Windows内核 | ← 内核态 (R0)
+------------------+
| 硬件层 |
+------------------+
2.3 什么是进程伪装
进程伪装是指修改进程的PEB结构,使得在任务管理器、进程查看器等工具中显示为其他进程的名称或路径。
应用场景:
- 绕过基于进程名的白名单检测
- 伪装成系统进程规避用户怀疑
- 混淆安全分析人员的视线
2.4 技术原理图解
修改前: 修改后:
+------------------+ +------------------+
| PEB | | PEB |
| ImagePath: | | ImagePath: |
| C:\malware.exe | =====> | C:\Windows\ |
| CommandLine: | | explorer.exe |
| malware.exe | | CommandLine: |
+------------------+ | explorer.exe |
+------------------+
3. 必备工具
3.1 开发环境
| 工具 | 用途 | 下载地址 |
|---|---|---|
| Visual Studio 2019/2022 | 编译C/C++代码 | https://visualstudio.microsoft.com/ |
| MinGW-w64 | 轻量级GCC编译器 | https://www.mingw-w64.org/ |
3.2 分析工具
| 工具 | 用途 | 下载地址 |
|---|---|---|
| Process Explorer | 查看进程详细信息,验证伪装效果 | https://docs.microsoft.com/sysinternals/downloads/process-explorer |
| Process Hacker | 开源进程管理器,可查看PEB结构 | https://processhacker.sourceforge.io/ |
| WinDbg | Windows调试器,查看内存结构 | https://docs.microsoft.com/windows-hardware/drivers/debugger/ |
| x64dbg | 用户态调试器,分析程序运行 | https://x64dbg.com/ |
| PE-bear | PE文件分析器 | https://github.com/hasherezade/pe-bear |
3.3 工具使用演示
3.3.1 使用Process Explorer查看进程信息
- 下载并运行Process Explorer
- 找到目标进程,双击打开属性
- 查看 Image 选项卡:
- Path: 显示的是PEB中的ImagePathName
- Command Line: 显示的是PEB中的CommandLine
- 伪装成功后,这些字段会显示伪造的内容
3.3.2 使用WinDbg查看PEB结构
# 附加到进程
windbg -p <PID>
# 查看PEB地址
!peb
# 查看PEB结构详情
dt ntdll!_PEB @$peb
# 查看ProcessParameters
dt ntdll!_RTL_USER_PROCESS_PARAMETERS poi(@$peb+0x20)
# 查看ImagePathName
du poi(poi(@$peb+0x20)+0x60)
4. PEB结构详解
4.1 TEB与PEB的关系
+------------------+
| TEB | ← 线程环境块 (fs:[0] / gs:[0])
| +0x30: PEB* | ← 指向PEB的指针 (32位)
| +0x60: PEB* | ← 指向PEB的指针 (64位)
+------------------+
↓
+------------------+
| PEB | ← 进程环境块
| +0x10: Params* | ← ProcessParameters
| +0x0C: Ldr* | ← 模块链表 (32位)
| +0x18: Ldr* | ← 模块链表 (64位)
+------------------+
4.2 PEB结构定义
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged; // +0x002 调试标志
BOOLEAN SpareBool;
HANDLE Mutant;
PVOID ImageBaseAddress; // +0x008 模块基址
PPEB_LDR_DATA Ldr; // +0x00C 模块链表
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // +0x010 进程参数
// ... 更多字段
} PEB, *PPEB;
4.3 RTL_USER_PROCESS_PARAMETERS结构
typedef struct _RTL_USER_PROCESS_PARAMETERS {
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
PVOID ConsoleHandle;
ULONG ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory;
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName; // 我们要修改的字段
UNICODE_STRING CommandLine; // 命令行参数
// ...
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
4.4 UNICODE_STRING结构
// UNICODE_STRING是Windows内核中常用的字符串结构
typedef struct _UNICODE_STRING {
USHORT Length; // 字符串长度(字节),不包含结束符
USHORT MaximumLength; // 缓冲区最大长度
PWSTR Buffer; // 字符串缓冲区指针
} UNICODE_STRING, *PUNICODE_STRING;
// 注意:
// 1. Length是字节数,不是字符数
// 2. Buffer可能不是NULL结尾的
// 3. 修改时要同时更新Length和Buffer
5. 实现代码
5.1 完整实现(详细注释版)
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
// 定义未导出的结构体
typedef struct _MY_PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} 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;
// 获取PEB地址
PPEB GetPEB() {
#ifdef _WIN64
return (PPEB)__readgsqword(0x60);
#else
return (PPEB)__readfsdword(0x30);
#endif
}
// 修改UNICODE_STRING
void SetUnicodeString(PUNICODE_STRING dest, LPCWSTR src) {
DWORD len = (DWORD)wcslen(src) * sizeof(WCHAR);
dest->Length = (USHORT)len;
dest->MaximumLength = (USHORT)(len + sizeof(WCHAR));
memcpy(dest->Buffer, src, len + sizeof(WCHAR));
}
// 进程伪装主函数
BOOL MasqueradeProcess(LPCWSTR szFakePath, LPCWSTR szFakeCommandLine) {
PPEB pPeb = GetPEB();
if (!pPeb) {
printf("[-] Failed to get PEB\n");
return FALSE;
}
PRTL_USER_PROCESS_PARAMETERS pParams = pPeb->ProcessParameters;
if (!pParams) {
printf("[-] Failed to get ProcessParameters\n");
return FALSE;
}
printf("[*] Original ImagePathName: %ws\n", pParams->ImagePathName.Buffer);
printf("[*] Original CommandLine: %ws\n", pParams->CommandLine.Buffer);
// 修改ImagePathName
if (szFakePath) {
SetUnicodeString(&pParams->ImagePathName, szFakePath);
printf("[+] Modified ImagePathName: %ws\n", pParams->ImagePathName.Buffer);
}
// 修改CommandLine
if (szFakeCommandLine) {
SetUnicodeString(&pParams->CommandLine, szFakeCommandLine);
printf("[+] Modified CommandLine: %ws\n", pParams->CommandLine.Buffer);
}
// 修改Ldr中的模块名称
PMY_PEB_LDR_DATA pLdr = (PMY_PEB_LDR_DATA)pPeb->Ldr;
PLIST_ENTRY pListHead = &pLdr->InLoadOrderModuleList;
PLIST_ENTRY pListEntry = pListHead->Flink;
// 第一个条目是主模块
PMY_LDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(
pListEntry, MY_LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (szFakePath) {
SetUnicodeString(&pEntry->FullDllName, szFakePath);
// 提取文件名部分
LPCWSTR pFileName = wcsrchr(szFakePath, L'\\');
if (pFileName) {
SetUnicodeString(&pEntry->BaseDllName, pFileName + 1);
}
}
printf("[+] Process masquerade completed!\n");
return TRUE;
}
int main() {
printf("========== R3 Process Masquerade ==========\n");
printf("[*] Current PID: %lu\n", GetCurrentProcessId());
// 伪装为explorer.exe
MasqueradeProcess(
L"C:\\Windows\\explorer.exe",
L"C:\\Windows\\explorer.exe"
);
printf("\n[*] Press Enter to exit...\n");
getchar();
return 0;
}
5.2 使用内联汇编获取PEB(32位)
// 32位版本
PPEB GetPEB32() {
PPEB pPeb = NULL;
__asm {
mov eax, fs:[0x30] // TEB->PEB
mov pPeb, eax
}
return pPeb;
}
// 展示完整汇编方式
__declspec(naked) PPEB GetPEBAsm() {
__asm {
mov eax, fs:[0x30]
ret
}
}
5.3 64位版本PEB获取
// 64位版本使用内置函数
PPEB GetPEB64() {
return (PPEB)__readgsqword(0x60); // TEB+0x60 = PEB
}
6. 高级技术:修改模块列表
6.1 三个模块链表说明
PEB中的Ldr包含三个双向链表,分别按不同顺序组织模块:
| 链表 | 说明 |
|---|---|
| InLoadOrderModuleList | 按加载顺序排列 |
| InMemoryOrderModuleList | 按内存地址顺序排列 |
| InInitializationOrderModuleList | 按初始化顺序排列 |
注意: 完美的进程伪装需要同时修改三个链表中的模块信息。
6.2 修改InLoadOrderModuleList
void ModifyModuleList(LPCWSTR szFakeModule) {
PPEB pPeb = GetPEB();
PMY_PEB_LDR_DATA pLdr = (PMY_PEB_LDR_DATA)pPeb->Ldr;
// 遍历模块列表
PLIST_ENTRY pHead = &pLdr->InLoadOrderModuleList;
PLIST_ENTRY pEntry = pHead->Flink;
while (pEntry != pHead) {
PMY_LDR_DATA_TABLE_ENTRY pModule = CONTAINING_RECORD(
pEntry, MY_LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
// 修改第一个模块(主程序)
if (pEntry == pHead->Flink) {
SetUnicodeString(&pModule->FullDllName, szFakeModule);
LPCWSTR pFileName = wcsrchr(szFakeModule, L'\\');
if (pFileName) {
SetUnicodeString(&pModule->BaseDllName, pFileName + 1);
}
break;
}
pEntry = pEntry->Flink;
}
}
7. 检测与绕过
7.1 常见检测方式
| 检测方式 | 说明 | 是否可绕过 |
|---|---|---|
| 任务管理器 | 读取PEB | ✔ 可绕过 |
| Process Explorer | 读取PEB | ✔ 可绕过 |
| QueryFullProcessImageName | 内核查询 | ✘ 无法绕过 |
| GetModuleFileName | 读取PEB | ✔ 可绕过 |
| WMI查询 | 混合 | 部分可绕过 |
7.2 检测方式详解
7.2.1 R3层检测(可绕过)
// 任务管理器、Process Explorer等使用的API
// 这些都从用户态PEB读取,可以被伪装
GetModuleFileName() // 读取PEB中的ImagePathName
GetCommandLine() // 读取PEB中的CommandLine
7.2.2 R0层检测(无法绕过)
// 内核级检测会查询内核中的EPROCESS结构
// R3的进程伪装无法绕过
// QueryFullProcessImageName 内部调用内核
wchar_t szPath[MAX_PATH];
DWORD dwSize = MAX_PATH;
QueryFullProcessImageNameW(hProcess, 0, szPath, &dwSize);
// 返回的是真实路径,不受PEB影响
7.3 内核级检测绕过思路
// 内核使用ZwQueryInformationProcess获取真实路径
// R3无法绕过,需要R0配合
NTSTATUS NTAPI ZwQueryInformationProcess(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
8. 实战案例
8.1 伪装为系统进程
int main() {
// 伪装为svchost.exe
MasqueradeProcess(
L"C:\\Windows\\System32\\svchost.exe",
L"C:\\Windows\\System32\\svchost.exe -k netsvcs"
);
// 保持运行,让用户观察
printf("[*] Check Task Manager, our process looks like svchost.exe\n");
// 执行恶意操作...
Sleep(60000);
return 0;
}
8.2 伪装为安全软件
// 伪装为Windows Defender
MasqueradeProcess(
L"C:\\Program Files\\Windows Defender\\MsMpEng.exe",
L"C:\\Program Files\\Windows Defender\\MsMpEng.exe"
);
9. 编译与测试
9.1 项目配置
9.1.1、Visual Studio配置
- 创建新项目:控制台应用 → C++
- 项目属性设置:
- C/C++ → 预处理器 → 添加
_CRT_SECURE_NO_WARNINGS - 链接器 → 输入 → 附加依赖项 → 添加
ntdll.lib
- C/C++ → 预处理器 → 添加
- 确保平台与目标架构匹配(x86/x64)
9.2 编译命令
# MSVC
cl.exe /EHsc /Fe:masquerade.exe masquerade.cpp
# MinGW
g++ -o masquerade.exe masquerade.cpp -lntdll
# 禁用优化以便调试
g++ -O0 -g -o masquerade.exe masquerade.cpp
9.3 测试步骤
- 运行程序
- 打开任务管理器
- 观察进程名称显示
- 使用Process Explorer查看详细信息
10. 常见问题与解决方案
10.1 常见错误
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 程序崩溃 | 结构体偏移不正确 | 检查系统版本和位数,使用WinDbg确认偏移 |
| 伪装失败 | Buffer空间不足 | 检查MaximumLength,必要时重新分配 |
| 编译错误 | 缺少头文件 | 包含 winternl.h 或自定义结构 |
| 64位下失败 | 使用了32位偏移 | 检查并使用正确的64位偏移 |
10.2 调试技巧
// 添加调试输出
printf("[DEBUG] PEB Address: 0x%p\n", pPeb);
printf("[DEBUG] ProcessParameters: 0x%p\n", pPeb->ProcessParameters);
printf("[DEBUG] ImagePathName Buffer: 0x%p\n",
pPeb->ProcessParameters->ImagePathName.Buffer);
printf("[DEBUG] ImagePathName Length: %d\n",
pPeb->ProcessParameters->ImagePathName.Length);
11. 课后作业
11.1 基础练习
-
进程伪装实现:将当前进程伪装为
notepad.exe,并使用Process Explorer验证 -
代码框架(补充完整):
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
int main() {
// TODO: 获取PEB
// TODO: 修改ImagePathName为 "C:\\Windows\\System32\\notepad.exe"
// TODO: 修改CommandLine
// TODO: 输出修改前后的值
printf("Press Enter to exit...\n");
getchar();
return 0;
}
11.2 进阶练习
- 多链表修改:同时修改三个模块链表中的模块名称
- 动态伪装:实现根据配置文件动态指定伪装目标
11.3 高级练习
-
检测绕过研究:
- 分析Process Explorer如何检测进程信息
- 研究哪些字段需要修改才能完美伪装
-
实现检测程序:编写能检测进程伪装的工具
11.4 思考题
- 为什么R3层面的进程伪装无法绕过内核检测?
- 安全软件通常使用什么方式检测进程信息?
- 如何设计一个更难被检测的进程伪装方案?
12. 参考资料
12.1 推荐阅读
- Windows Internals 第7版 - 第3章 进程和作业
- MSDN: Process Environment Block
- ReactOS源码 - PEB结构定义
12.2 在线资源
- https://docs.microsoft.com/en-us/windows/win32/api/winternl/
- https://www.vergiliusproject.com/ (Windows结构体查询)
13. 下一课预告
下一课我们将学习**“栈溢出调用CALL”**,内容包括:
- 函数调用栈结构分析
- 栈溢出漏洞原理
- 利用栈溢出执行ShellCode
- 绕过现代安全机制的技巧