免杀专题

1、R3进程伪装

1. 课程概述

1.1 学习目标

通过本课时的学习,你将掌握:

  • 理解Windows进程内存结构(PEB/TEB)
  • 掌握进程伪装的核心技术
  • 学会使用相关工具进行分析和验证
  • 理解免杀中进程伪装的作用

1.2 前置知识

  • C/C++基础语法
  • Windows API基本使用
  • 指针与内存结构
  • 对PE文件格式有基本了解

2. 名词解释

2.1 核心术语

术语英文全称说明
R3Ring 3用户态,最低特权级别,普通应用程序运行的环境
R0Ring 0内核态,最高特权级别,操作系统内核运行的环境
PEBProcess Environment Block进程环境块,包含进程的关键信息
TEBThread Environment Block线程环境块,包含线程的关键信息
LdrLoader 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/
WinDbgWindows调试器,查看内存结构https://docs.microsoft.com/windows-hardware/drivers/debugger/
x64dbg用户态调试器,分析程序运行https://x64dbg.com/
PE-bearPE文件分析器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
  • 绕过现代安全机制的技巧