Reactos 第 5 章 进程与线程 — 5.2 Windows 进程的用户空间

第 5 章 进程与线程 --- 5.2 Windows 进程的用户空间

5.2.0 框架图

复制代码
┌──────────────────────────────────────────────────────────────────────────────────┐
│                        Windows 用户地址空间布局                                │
│                                                                                  │
│   32 位系统(默认 2GB 用户空间):                                                  │
│                                                                                  │
│   地址范围          内容                                    说明                │
│   ────────────────  ────────────────────────────────────  ──────────────────   │
│   0x00000000-      NULL 指针保护区                       访问即触发异常         │
│   0x0000FFFF                                                                     │
│                                                                                  │
│   0x00010000-      用户代码、数据、堆、栈                   应用程序主要区域      │
│   0x7FFDFFFF                                                                     │
│       │                                                                          │
│       ├── 0x00400000 通常是可执行文件基址                                       │
│       ├── 0x00220000 通常是 NTDLL.DLL 基址                                     │
│       ├── 0x7FFDF000 PEB 位置(固定偏移)                                         │
│       └── 0x7FFDE000 TEB 位置(每个线程一个)                                      │
│                                                                                  │
│   0x7FFE0000-      KUSER_SHARED_DATA                        用户态只读内核数据    │
│   0x7FFEFFFF                                                                     │
│                                                                                  │
│   0x7FFF0000-      系统映射区域                            保留给系统使用        │
│   0x7FFFFFFF                                                                     │
│                                                                                  │
│   0x80000000-      内核空间                                3GB 开关时为 0xC0000000 │
│   0xFFFFFFFF                                                                     │
│                                                                                  │
│   64 位系统(用户空间 ~8TB):                                                      │
│                                                                                  │
│   0x00000000`00000000- 空指针保护区                                             │
│   0x00000000`0000FFFF                                                             │
│                                                                                  │
│   0x00000000`00010000- 用户代码、数据                    应用程序区域            │
│   0x00007FFF`FFFFFFFF                                                             │
│                                                                                  │
│   0xFFFFFE00`00000000- KUSER_SHARED_DATA(64 位位置)                              │
│   0xFFFFFE00`00010000                                                             │
│                                                                                  │
│   0xFFFFF780`00000000- 系统 DLL 区域(ntdll, kernel32 等)                         │
│   0xFFFFF7FF`FFFFFFFF                                                             │
│                                                                                  │
│   0xFFFF8000`00000000- 内核空间                                                │
│   0xFFFFFFFF`FFFFFFFF                                                             │
└──────────────────────────────────────────────────────────────────────────────────┘

5.2.0.1 设计意图

核心问题

用户态进程能看到什么?用户态与内核态如何通信?PEB 和 TEB 在其中扮演什么角色?

设计哲学 :「用户态可观察 + 内核态可控制

想象一下,Windows 操作系统就像一个庞大的现代化办公大楼

  • 用户态空间:这是大楼里各个公司(进程)租用的办公室。每个办公室有自己的家具、设备和员工(线程)。
  • 内核态空间:这是大楼的基础设施层,包括电力系统、供水系统、电梯、安保等。普通员工(用户态代码)不能直接进入这个区域。
  • PEB :每个办公室门口的信息公告板,上面贴着公司名称、员工名单、紧急联系人等信息。
  • TEB :每个员工(线程)随身携带的工作证,记录着个人信息、当前任务、紧急联系方式。
  • KUSER_SHARED_DATA :大楼入口处的公告栏,张贴着所有公司都能看到的公共信息(如当前时间、大楼公告等)。

本节定位

本节深入分析 Windows 进程用户空间的核心数据结构:PEB(Process Environment Block)和 TEB(Thread Environment Block)。读完本节后,读者应当能够:

  • 理解 PEB/TEB 的内存布局
  • 掌握关键字段的含义和用途
  • 理解用户态与内核态的通信机制
  • 了解安全相关的字段和机制

5.2.1 PEB(Process Environment Block)

PEB:进程的"身份证"和"信息中心"

如果把进程比作一家正在营业的商店 ,那么 PEB 就是这家商店的营业执照店内信息看板。它记录了商店的基本信息、经营范围、员工名单、库存情况等所有重要数据。

复制代码
┌──────────────────────────────────────────────────────────────────────────────────┐
│                        PEB 内存布局                                            │
│                                                                                  │
│   PEB 结构(偏移从 PEB 基址开始):                                                 │
│                                                                                  │
│   偏移      字段名                    类型                    说明               │
│   ────────  ────────────────────────  ────────────────────  ─────────────────  │
│   0x00      InheritedAddressSpace    BOOLEAN                 是否继承地址空间     │
│   0x01      ReadImageFileExecOptions BOOLEAN                 读取执行选项        │
│   0x02      BeingDebugged            BOOLEAN                 是否正在被调试      │
│   0x03      BitField                 UCHAR                   位标志              │
│   0x04      Mutant                   HANDLE                  进程互斥体          │
│   0x08      ImageBaseAddress         PVOID                   可执行文件基址      │
│   0x0C      Ldr                      PPEB_LDR_DATA           模块加载器数据      │
│   0x10      ProcessParameters        PRTL_USER_PROCESS_PARAMETERS 进程参数      │
│   0x14      SubSystemData            PVOID                   子系统数据          │
│   0x18      ProcessHeap              PVOID                   默认进程堆          │
│   0x1C      FastPEBLockRoutine       PPEBLOCKROUTINE         快速 PEB 锁定例程   │
│   0x20      FastPEBUnlockRoutine     PPEBLOCKROUTINE         快速 PEB 解锁例程   │
│   0x24      ActiveProcessAffinityMask KAFFINITY              活动 CPU 亲和性     │
│   0x28      GdiHandleBuffer          ULONG[34]               GDI 句柄缓冲区      │
│   0xAC      PostProcessInitRoutine   PPOSTPROCESSINITROUTINE 后初始化例程        │
│   0xB0      TlsExpansionSlots        PVOID                   TLS 扩展槽位        │
│   0xB4      SecureProcess            BOOLEAN                 是否安全进程        │
│   0xB5      Spare1                   UCHAR                   保留                │
│   0xB6      Spare2                   UCHAR                   保留                │
│   0xB7      Spare3                   UCHAR                   保留                │
│   0xB8      Spare4                   ULONG                   保留                │
│   0xBC      TlsBitmap                PVOID                   TLS 位图            │
│   0xC0      TlsBitmapBits            ULONG[2]                TLS 位图位          │
│   0xC8      ReadOnlySharedMemoryBase PVOID                   只读共享内存基址    │
│   0xCC      HotpatchInformation      PVOID                   热补丁信息          │
│   0xD0      ReadOnlyStaticServerData PVOID                   只读静态服务器数据  │
│   0xD4      AnsiCodePageData         PVOID                   ANSI 代码页数据     │
│   0xD8      OemCodePageData          PVOID                   OEM 代码页数据      │
│   0xDC      UnicodeCaseTableData     PVOID                   Unicode 大小写表    │
│   0xE0      NumberOfProcessors       ULONG                   处理器数量          │
│   0xE4      NtGlobalFlag             ULONG                   NT 全局标志         │
│   0xE8      CriticalSectionTimeout   LARGE_INTEGER           临界区超时时间      │
│   0xF0      HeapSegmentReserve       SIZE_T                  堆段保留大小        │
│   0xF4      HeapSegmentCommit        SIZE_T                  堆段提交大小        │
│   0xF8      HeapDeCommitTotalFreeThreshold SIZE_T           堆释放阈值          │
│   0xFC      HeapDeCommitFreeBlockThreshold SIZE_T          堆释放块阈值        │
│   0x100     NumberOfHeaps            ULONG                   堆数量              │
│   0x104     MaximumNumberOfHeaps     ULONG                   最大堆数量          │
│   0x108     ProcessHeaps             PVOID*                  堆数组指针          │
│   0x10C     GdiSharedHandleTable     PVOID                   GDI 共享句柄表      │
│   0x110     ProcessStarterHelper     PVOID                   进程启动助手        │
│   0x114     GdiDCAttributeList       ULONG                   GDI DC 属性列表     │
│   0x118     LoaderLock               PVOID                   加载器锁            │
│   0x11C     OSMajorVersion           ULONG                   OS 主版本号         │
│   0x120     OSMinorVersion           ULONG                   OS 次版本号         │
│   0x124     OSBuildNumber            USHORT                  OS 构建号           │
│   0x126     OSCSDVersion             USHORT                  OS CSD 版本         │
│   0x128     OSPlatformId             ULONG                   OS 平台 ID          │
│   0x12C     ImageSubsystem           ULONG                   镜像子系统          │
│   0x130     ImageSubsystemMajorVersion USHORT               子系统主版本        │
│   0x132     ImageSubsystemMinorVersion USHORT               子系统次版本        │
│   0x134     ImageProcessAffinityMask KAFFINITY              镜像 CPU 亲和性     │
│   0x138     GdiHandleBuffer32        PVOID                   GDI 句柄缓冲区 32   │
│   0x13C     LoaderReserve            PVOID                   加载器保留          │
│   0x140     LoaderCommit             PVOID                   加载器提交          │
│   0x144     LoaderStackCommit        PVOID                   加载器栈提交        │
│   0x148     PatchInformation         PVOID                   补丁信息            │
│   0x14C     Cookie                   ULONG                   进程 Cookie         │
│   0x150     Spare5                   ULONG[4]                保留                │
│   0x160     TlsExpansionBitmap       PVOID                   TLS 扩展位图        │
│   0x164     TlsExpansionBitmapBits   ULONG[32]               TLS 扩展位图位      │
│   0x1E4     SessionId                ULONG                   会话 ID             │
│   0x1E8     AppCompatFlags           ULONG                   兼容标志            │
│   0x1EC     AppCompatFlagsUser       ULONG                   用户兼容标志        │
│   0x1F0     pShimData               PVOID                   Shim 数据           │
│   0x1F4     AppCompatInfo            PVOID                   兼容信息            │
│   0x1F8     CSDVersion               UNICODE_STRING          CSD 版本字符串      │
│   0x204     ActivationContextData    PVOID                   激活上下文数据      │
│   0x208     ProcessAssemblyStorageMap PVOID                  程序集存储映射      │
│   0x20C     SystemDefaultActivationContextData PVOID        系统默认激活上下文  │
│   0x210     SystemAssemblyStorageMap PVOID                   系统程序集存储映射  │
│   0x214     MinimumStackCommit       SIZE_T                  最小栈提交          │
│                                                                                  │
│   PEB 在内存中的位置: 32 位: 0x7FFDF000, 64 位: 0x7FFF00000000                    │
│   获取方式: NtCurrentTeb()->ProcessEnvironmentBlock                              │
└──────────────────────────────────────────────────────────────────────────────────┘

PEB 关键字段详解

BeingDebugged

c 复制代码
// 检查是否被调试
if (NtCurrentTeb()->ProcessEnvironmentBlock->BeingDebugged) {
    // 正在被调试
}

用途:调试器附加时设置为 TRUE,常用作反调试检测。

ImageBaseAddress

c 复制代码
// 获取可执行文件基址
PVOID ImageBase = NtCurrentTeb()->ProcessEnvironmentBlock->ImageBaseAddress;

用途:指向当前进程可执行文件的加载基址。

Ldr (PEB_LDR_DATA)

c 复制代码
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;

用途:维护进程已加载的模块列表(DLL)。

ProcessParameters (RTL_USER_PROCESS_PARAMETERS)

c 复制代码
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;
    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_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

用途:存储进程启动时的参数(命令行、环境变量、工作目录等)。

ProcessHeap

用途:指向进程默认堆的指针,由 RtlCreateHeap 创建。

NtGlobalFlag

用途:调试相关标志,包含多种调试选项。


5.2.2 TEB(Thread Environment Block)

TEB:线程的"个人档案"和"工作手册"

如果说 PEB 是商店的营业执照 ,那么 TEB 就是每个员工的个人档案。想象一下,商店里的每个员工都有一个专属的档案夹,里面记录着:

  • 个人信息:姓名、工号、所属部门(ClientId)

  • 工作状态:当前任务、工作进度、上次出错记录(LastErrorValue)

  • 工作工具:计算器、笔记本、绘图工具(TlsSlots、GDI 相关字段)

  • 紧急联系人:遇到问题时该找谁(Peb 指针,指向商店信息)

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ TEB 内存布局 │
    │ │
    │ TEB 结构(偏移从 TEB 基址开始): │
    │ │
    │ 偏移 字段名 类型 说明 │
    │ ──────── ──────────────────────── ──────────────────── ───────────────── │
    │ 0x00 NtTib NT_TIB 线程信息块 │
    │ 0x1C EnvironmentPointer PVOID 环境指针 │
    │ 0x20 ClientId CLIENT_ID 客户端 ID (PID/TID) │
    │ 0x28 ActiveRpcHandle PVOID 活动 RPC 句柄 │
    │ 0x2C ThreadLocalStoragePointer PVOID TLS 指针 │
    │ 0x30 Peb PPEB 进程环境块 │
    │ 0x34 LastErrorValue NTSTATUS 最后错误值 │
    │ 0x38 CountOfOwnedCriticalSections ULONG 临界区数量 │
    │ 0x3C CsrClientThread PVOID CSR 客户端线程 │
    │ 0x40 Win32ThreadInfo PVOID Win32 线程信息 │
    │ 0x44 User32Reserved ULONG[26] User32 保留 │
    │ 0xAC UserReserved ULONG[5] 用户保留 │
    │ 0xC0 WOW32Reserved PVOID WOW32 保留 │
    │ 0xC4 CurrentLocale LCID 当前区域设置 │
    │ 0xC8 FpSoftwareStatusRegister ULONG FP 软件状态寄存器 │
    │ 0xCC SystemReserved1 PVOID[3] 系统保留 │
    │ 0xD8 ExceptionCode NTSTATUS 异常代码 │
    │ 0xDC ActivationContextStack PACTIVATION_CONTEXT_STACK 激活上下文栈 │
    │ 0x130 Instrumentation PVOID 检测数据 │
    │ 0x134 WinSockData PVOID WinSock 数据 │
    │ 0x138 GdiTebBatch PVOID GDI 批处理 │
    │ 0x13C GdiBatchCount ULONG GDI 批处理计数 │
    │ 0x140 GdiSharedHandleTable PVOID GDI 共享句柄表 │
    │ 0x144 GdiStarterBitmap PVOID GDI 启动位图 │
    │ 0x148 GdiBrushBitmap PVOID GDI 画刷位图 │
    │ 0x14C GdiPenBitmap PVOID GDI 画笔位图 │
    │ 0x150 PaletteTable PVOID 调色板表 │
    │ 0x154 RealClientId CLIENT_ID 真实客户端 ID │
    │ 0x15C GdiCachedProcessHandle HANDLE GDI 缓存进程句柄 │
    │ 0x160 GdiClientPID ULONG GDI 客户端 PID │
    │ 0x164 GdiClientTID ULONG GDI 客户端 TID │
    │ 0x168 GdiThreadLocalInfo PVOID GDI 线程本地信息 │
    │ 0x16C Win32ClientInfo ULONG Win32 客户端信息 │
    │ 0x170 glDispatchTable PVOID[233] OpenGL 分发表 │
    │ 0x4CC glReserved1 ULONG[29] OpenGL 保留 │
    │ 0x544 glReserved2 PVOID OpenGL 保留 │
    │ 0x548 glSection PVOID OpenGL 段 │
    │ 0x54C glSectionOffset ULONG OpenGL 段偏移 │
    │ 0x550 glTable PVOID OpenGL 表 │
    │ 0x554 glCurrentRC PVOID OpenGL 当前 RC │
    │ 0x558 glContext PVOID OpenGL 上下文 │
    │ 0x55C LastStatusValue NTSTATUS 最后状态值 │
    │ 0x560 StaticUnicodeString UNICODE_STRING 静态 Unicode 字符串 │
    │ 0x56C StaticUnicodeBuffer WCHAR[261] 静态 Unicode 缓冲区 │
    │ 0x770 DeallocationStack PVOID 释放栈 │
    │ 0x774 TlsSlots PVOID[TLS_MINIMUM_AVAILABLE] TLS 槽位 │
    │ 0x804 TlsExpansionSlots PVOID* TLS 扩展槽位 │
    │ │
    │ TEB 在内存中的位置: 每个线程独立,通过 FS:[0] 访问(32 位)或 GS:[0] 访问(64 位) │
    │ 获取方式: NtCurrentTeb() │
    └──────────────────────────────────────────────────────────────────────────────────┘

TEB 关键字段详解

NT_TIB (NtTib)

c 复制代码
typedef struct _NT_TIB {
    struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
    PVOID StackBase;           // 栈基址
    PVOID StackLimit;          // 栈界限
    PVOID SubSystemTib;
    union {
        PVOID FiberData;
        ULONG Version;
    };
    PVOID ArbitraryUserPointer;
    struct _NT_TIB *Self;      // 指向自身
} NT_TIB;

用途:存储线程栈信息和异常链。

ClientId

c 复制代码
typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;  // 进程 ID
    HANDLE UniqueThread;   // 线程 ID
} CLIENT_ID;

用途:标识线程所属的进程和线程本身。

Peb

用途:指向所属进程的 PEB。

LastErrorValue

用途:存储线程的最后错误码(SetLastError/GetLastError 使用)。

TlsSlots

用途:线程局部存储槽位数组,大小为 TLS_MINIMUM_AVAILABLE(64)。


5.2.3 用户态栈与内核态栈

栈:线程的"工作台"和"记事本"

想象一下,每个员工(线程)在工作时都有两张桌子:

  • 用户态栈 :这是员工在自己办公室里的办公桌。桌面上放着当前正在处理的文件、笔记本和计算器。员工可以自由地在这张桌子上工作,不需要经过任何人批准。

  • 内核态栈 :这是员工在大楼机房里的专用工作台。当员工需要使用大楼的基础设施(比如使用电梯、查看电力系统状态)时,必须到这个工作台来操作。这个工作台受到严格的安保控制。

当员工(线程)需要执行系统调用时,就像从自己的办公桌走到机房的工作台

  1. 保存工作:把当前正在处理的文件(寄存器状态)整理好放在机房工作台上

  2. 切换场地:从办公室(用户态)移动到机房(内核态)

  3. 执行操作:在机房工作台上执行需要的操作(系统调用处理)

  4. 恢复工作:回到办公室,继续之前的工作

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ 用户态栈与内核态栈 │
    │ │
    │ 用户态线程结构: │
    │ │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 用户态地址空间 │ │
    │ │ │ │
    │ │ TEB (0x7FFDE000) │ │
    │ │ ┌─────────────────────────────────────────────────────────────┐ │ │
    │ │ │ NT_TIB: │ │ │
    │ │ │ StackBase = 0x0012FFF0 (栈顶) │ │ │
    │ │ │ StackLimit = 0x00120000 (栈底) │ │ │
    │ │ └─────────────────────────────────────────────────────────────┘ │ │
    │ │ │ │ │
    │ │ ▼ │ │
    │ │ 用户态栈 │ │
    │ │ 0x0012FFF0 ───────────────────────────────────────► 0x00120000 │ │
    │ │ ↑ │ │
    │ │ 高地址 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ 系统调用 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 内核态地址空间 │ │
    │ │ │ │
    │ │ ETHREAD │ │
    │ │ ┌─────────────────────────────────────────────────────────────┐ │ │
    │ │ │ KTHREAD: │ │ │
    │ │ │ StackBase = 0x80500000 (内核栈顶) │ │ │
    │ │ │ StackLimit = 0x804FF000 (内核栈底) │ │ │
    │ │ └─────────────────────────────────────────────────────────────┘ │ │
    │ │ │ │ │
    │ │ ▼ │ │
    │ │ 内核态栈 │ │
    │ │ 0x80500000 ───────────────────────────────────────► 0x804FF000 │ │
    │ │ ↑ │ │
    │ │ 高地址 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │
    │ 系统调用时的栈切换: │
    │ │
    │ KiSystemService (系统调用入口): │
    │ 1. 保存用户态上下文到内核栈 │
    │ 2. 切换到内核栈 (SS:ESP = 内核栈段:内核栈指针) │
    │ 3. 执行系统调用处理函数 │
    │ 4. 恢复用户态上下文 │
    │ 5. 切换回用户态栈 │
    │ │
    │ 64 位系统的 IST (Interrupt Stack Table): │
    │ - IST0: 系统调用栈 │
    │ - IST1: 调试异常栈 │
    │ - IST2: NMI 栈 │
    │ - IST3-7: 保留 │
    └──────────────────────────────────────────────────────────────────────────────────┘


5.2.4 KUSER_SHARED_DATA

KUSER_SHARED_DATA:大楼里的"公告栏"

想象一下,在办公大楼的入口大厅里,有一个电子公告板,上面显示着:

  • 当前时间:所有公司的员工都能看到准确的时间
  • 大楼状态:电力系统状态、网络状态、可用停车位数量
  • 公告信息:大楼通知、维护计划、安全提醒
  • 设施信息:电梯状态、会议室预约情况

这个公告板有几个特点:

  1. 只读访问:任何人都可以看,但只有大楼管理员(内核态)可以更新

  2. 即时更新:管理员更新后,所有人立即看到最新信息

  3. 公共位置:每个人都知道公告板在哪里,不需要问路

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ KUSER_SHARED_DATA │
    │ │
    │ 地址: │
    │ - 32 位: 0x7FFE0000 │
    │ - 64 位: 0xFFFFFE0000000000 │
    │ │
    │ 关键字段: │
    │ │
    │ 偏移 字段名 类型 说明 │
    │ ──────── ──────────────────────── ──────────────────── ───────────────── │
    │ 0x000 TickCountLow ULONG 低 32 位 TickCount │
    │ 0x004 TickCountMultiplier ULONG TickCount 乘数 │
    │ 0x008 InterruptTime LARGE_INTEGER 中断时间 │
    │ 0x010 SystemTime LARGE_INTEGER 系统时间 │
    │ 0x018 TimeZoneBias LARGE_INTEGER 时区偏移 │
    │ 0x020 ImageNumberLow ULONG 镜像号(低) │
    │ 0x024 ImageNumberHigh ULONG 镜像号(高) │
    │ 0x028 NtSystemRoot UNICODE_STRING NT 系统根目录 │
    │ 0x038 MaxStackTraceDepth ULONG 最大堆栈深度 │
    │ 0x03C CryptoExponent ULONG 加密指数 │
    │ 0x040 TimeZoneId ULONG 时区 ID │
    │ 0x044 Reserved ULONG 保留 │
    │ 0x048 Reserved2 ULONG 保留 │
    │ 0x04C TimeZoneBiasStamp LARGE_INTEGER 时区偏移戳 │
    │ 0x054 Cookie ULONG 安全 Cookie │
    │ 0x058 ConsoleSessionId ULONG 控制台会话 ID │
    │ 0x05C ImageNumber ULONG 镜像号 │
    │ 0x060 NtProductType UCHAR NT 产品类型 │
    │ 0x061 ProductTypeIsValid BOOLEAN 产品类型有效 │
    │ 0x062 Reserved3 UCHAR 保留 │
    │ 0x063 Reserved4 UCHAR 保留 │
    │ 0x064 NtMajorVersion ULONG NT 主版本号 │
    │ 0x068 NtMinorVersion ULONG NT 次版本号 │
    │ 0x06C ProcessorFeatures ULONG[64] 处理器特性 │
    │ 0x16C Reserved5 ULONG[32] 保留 │
    │ 0x20C ActiveConsoleId ULONG 活动控制台 ID │
    │ 0x210 DismountCount ULONG 卸载计数 │
    │ 0x214 ComPlusPackage ULONG COM+ 包 │
    │ 0x218 LastSystemRITEventTick LARGE_INTEGER 最后系统 RIT 事件 │
    │ 0x220 NumberOfPhysicalPages ULONG 物理页数 │
    │ 0x224 LowPhysicalMemoryLimit ULONG 低物理内存限制 │
    │ 0x228 HighPhysicalMemoryLimit ULONG 高物理内存限制 │
    │ 0x22C AllocationGranularity ULONG 分配粒度 │
    │ 0x230 NonPagedSystemSize ULONG 非分页系统大小 │
    │ 0x234 PagedSystemSize ULONG 分页系统大小 │
    │ 0x238 CommitLimit ULONG 提交限制 │
    │ 0x23C CommitPeak ULONG 提交峰值 │
    │ 0x240 CommitTotal ULONG 提交总数 │
    │ 0x244 PhysicalMemorySize ULONG 物理内存大小 │
    │ 0x248 PhysicalMemoryUsed ULONG 物理内存使用量 │
    │ 0x24C SystemCacheSize ULONG 系统缓存大小 │
    │ 0x250 SystemCachePeak ULONG 系统缓存峰值 │
    │ 0x254 PoolPagedUsed ULONG 分页池使用量 │
    │ 0x258 PoolNonPagedUsed ULONG 非分页池使用量 │
    │ 0x25C SystemDriverPages ULONG 系统驱动页数 │
    │ 0x260 SystemCodePages ULONG 系统代码页数 │
    │ 0x264 TotalSystemPages ULONG 总系统页数 │
    │ 0x268 Reserved6 ULONG 保留 │
    │ 0x26C Reserved7 ULONG 保留 │
    │ 0x270 Reserved8 ULONG 保留 │
    │ 0x274 Reserved9 ULONG 保留 │
    │ 0x278 TimeAdjustment ULONGLONG 时间调整 │
    │ 0x280 BootId ULONG 启动 ID │
    │ 0x284 SystemRootPathName UNICODE_STRING 系统根路径名称 │
    │ │
    │ 访问方式: 用户态直接访问该地址,无需系统调用 │
    │ 安全性: 用户态只读,内核态可写 │
    └──────────────────────────────────────────────────────────────────────────────────┘


5.2.5 进程参数(RTL_USER_PROCESS_PARAMETERS)

RTL_USER_PROCESS_PARAMETERS:商店开业时的"开业许可证"

想象一下,当一家新商店要开业时,需要向管理部门提交一份开业申请表,上面写着:

  • 商店名称和地址:ImagePathName(可执行文件路径)
  • 经营范围:CommandLine(命令行参数)
  • 营业场所:CurrentDirectory(当前工作目录)
  • 联系方式:Environment(环境变量,包含各种配置信息)
  • 店面要求:窗口大小、位置、标题等

这份申请表在商店开业前就准备好了,商店老板(进程)可以随时查看这些信息。

复制代码
┌──────────────────────────────────────────────────────────────────────────────────┐
│                        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;           // DLL 搜索路径                          │
│       UNICODE_STRING ImagePathName;     // 可执行文件路径                        │
│       UNICODE_STRING CommandLine;       // 命令行参数                            │
│       PVOID Environment;                // 环境变量块                            │
│       ULONG StartingX;                  // 窗口起始 X 坐标                       │
│       ULONG StartingY;                  // 窗口起始 Y 坐标                       │
│       ULONG CountX;                     // 窗口宽度                              │
│       ULONG CountY;                     // 窗口高度                              │
│       ULONG CountCharsX;                // 字符宽度                              │
│       ULONG CountCharsY;                // 字符高度                              │
│       ULONG FillAttribute;              // 填充属性                              │
│       ULONG WindowFlags;                // 窗口标志                              │
│       ULONG ShowWindowFlags;            // 显示窗口标志                          │
│       UNICODE_STRING WindowTitle;       // 窗口标题                              │
│       UNICODE_STRING DesktopInfo;       // 桌面信息                              │
│       UNICODE_STRING ShellInfo;         // Shell 信息                            │
│       UNICODE_STRING RuntimeData;       // 运行时数据                            │
│   } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;                  │
│                                                                                  │
│   获取方式: PEB->ProcessParameters                                                │
│   创建时机: BasePushProcessParameters (在进程启动前)                               │
└──────────────────────────────────────────────────────────────────────────────────┘

5.2.6 模块列表(PEB_LDR_DATA)

PEB_LDR_DATA:商店的"员工花名册"

想象一下,商店开业后,需要一份员工花名册,记录所有在店里工作的员工(DLL)。这份花名册有三个版本:

  1. 招聘顺序名单(InLoadOrderModuleList):按招聘时间排序,记录谁先入职谁后入职。就像 ntdll.dll 是第一个来的,然后是 kernel32.dll,再是 user32.dll。

  2. 座位位置名单(InMemoryOrderModuleList):按员工座位位置排序,从办公室前面到后面。就像低地址的 DLL 在前面,高地址的 DLL 在后面。

  3. 培训顺序名单(InInitializationOrderModuleList):按培训顺序排序,需要先培训的先排。就像基础模块先初始化,依赖它的模块后初始化。

每个员工档案(LDR_DATA_TABLE_ENTRY)里记录着:

  • 座位号(DllBase):员工坐在哪个位置

  • 工作职责(EntryPoint):员工主要负责什么工作

  • 入职时间(TimeDateStamp):什么时候入职的

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ PEB_LDR_DATA 模块列表 │
    │ │
    │ PEB_LDR_DATA 结构: │
    │ │
    │ 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; │
    │ │
    │ LDR_DATA_TABLE_ENTRY 结构(链表节点): │
    │ │
    │ typedef struct _LDR_DATA_TABLE_ENTRY { │
    │ LIST_ENTRY InLoadOrderLinks; // 加载顺序链接 │
    │ LIST_ENTRY InMemoryOrderLinks; // 内存顺序链接 │
    │ LIST_ENTRY InInitializationOrderLinks; // 初始化顺序链接 │
    │ PVOID DllBase; // DLL 基址 │
    │ PVOID EntryPoint; // 入口点 │
    │ ULONG SizeOfImage; // 镜像大小 │
    │ UNICODE_STRING FullDllName; // 完整路径 │
    │ UNICODE_STRING BaseDllName; // 基础名称 │
    │ ULONG Flags; // 标志 │
    │ WORD LoadCount; // 加载计数 │
    │ WORD TlsIndex; // TLS 索引 │
    │ LIST_ENTRY HashLinks; // 哈希链接 │
    │ PVOID SectionPointer; // 段指针 │
    │ ULONG CheckSum; // 校验和 │
    │ ULONG TimeDateStamp; // 时间戳 │
    │ PVOID LoadedImports; // 已加载的导入 │
    │ PVOID EntryPointActivationContext; // 入口点激活上下文 │
    │ PVOID PatchInformation; // 补丁信息 │
    │ } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; │
    │ │
    │ 三个链表的用途: │
    │ │
    │ 1. InLoadOrderModuleList: │
    │ - 按照 LoadLibrary 的顺序排列 │
    │ - 通常: ntdll.dll → kernel32.dll → user32.dll → ... │
    │ │
    │ 2. InMemoryOrderModuleList: │
    │ - 按照内存地址顺序排列 │
    │ - 低地址 DLL 在前,高地址 DLL 在后 │
    │ │
    │ 3. InInitializationOrderModuleList: │
    │ - 按照 DllMain 的调用顺序排列 │
    │ - 依赖 DLL 先初始化 │
    │ │
    │ 遍历示例: │
    │ │
    │ PPEB_LDR_DATA Ldr = NtCurrentTeb()->ProcessEnvironmentBlock->Ldr; │
    │ PLIST_ENTRY ListEntry = Ldr->InLoadOrderModuleList.Flink; │
    │ while (ListEntry != &Ldr->InLoadOrderModuleList) { │
    │ PLDR_DATA_TABLE_ENTRY Entry = CONTAINING_RECORD(ListEntry, │
    │ LDR_DATA_TABLE_ENTRY, │
    │ InLoadOrderLinks); │
    │ // 处理 Entry... │
    │ ListEntry = ListEntry->Flink; │
    │ } │
    └──────────────────────────────────────────────────────────────────────────────────┘


5.2.7 关键设计决策的深度分析

5.2.7.1 PEB/TEB 放在用户态的合理性

为什么 PEB/TEB 必须放在用户态?

  1. 性能优化:用户态代码可以直接访问 PEB/TEB,不需要系统调用
  2. 调试支持:调试器可以直接读取 PEB/TEB 了解进程/线程状态
  3. 兼容性:很多用户态代码依赖 PEB/TEB 中的信息
  4. 动态链接:LDR 需要在用户态访问模块列表

5.2.7.2 NtCurrentTeb() 的实现机理

c 复制代码
// 32 位实现
__declspec(naked) PTEB NtCurrentTeb(void) {
    __asm {
        mov eax, fs:[0x18]  // FS:[0x18] 指向 TEB->Self
        ret
    }
}

// 64 位实现
__declspec(naked) PTEB NtCurrentTeb(void) {
    __asm {
        mov rax, gs:[0x30]  // GS:[0x30] 指向 TEB
        ret
    }
}

为什么使用 FS/GS 段寄存器?

  • 线程本地存储:每个线程有独立的 FS/GS 段基址
  • 快速访问:单条指令即可获取 TEB 指针
  • 历史原因:从 Windows NT 早期延续下来的设计

5.2.7.3 KUSER_SHARED_DATA 的单向只读性

为什么 KUSER_SHARED_DATA 是用户态只读?

  1. 安全性:防止用户态代码修改系统关键数据
  2. 性能优化:内核态可以直接写入,用户态可以直接读取,不需要系统调用
  3. 一致性:保证数据的一致性(内核写入后立即对用户态可见)

5.2.7.4 TLS 槽位的大小选择

c 复制代码
#define TLS_MINIMUM_AVAILABLE 64

为什么是 64?

  1. 历史原因:Windows NT 3.1 以来的传统
  2. 足够使用:大多数应用程序不需要超过 64 个 TLS 槽位
  3. 扩展支持:超过 64 个时使用 TlsExpansionSlots

5.2.8 概念解释

5.2.8.1 PEB / TEB / NT_TIB

  • PEB:进程环境块,存储进程级别的用户态信息
  • TEB:线程环境块,存储线程级别的用户态信息
  • NT_TIB:线程信息块,是 TEB 的第一个字段,存储栈信息

5.2.8.2 TLS(Thread Local Storage)

线程局部存储,允许每个线程拥有独立的数据副本。

5.2.8.3 KUSER_SHARED_DATA

内核态和用户态共享的数据区域,用户态只读,内核态可写。

栈安全机制,在函数栈帧中插入随机值,函数返回前检查是否被修改。


5.2.9 为什么要这样设计

5.2.9.1 为什么 PEB/TEB 必须放在用户态而不是只在内核态?

答案:性能和兼容性。

  • 性能:用户态代码可以直接访问,不需要系统调用
  • 兼容性:很多旧代码依赖 PEB/TEB
  • 调试:调试器需要直接访问这些结构

5.2.9.2 为什么每个线程有独立的 TEB?

答案:线程独立性。

  • 每个线程有独立的栈、寄存器、TLS
  • TEB 存储线程特定的信息
  • 支持线程安全的操作(如 GetLastError)

5.2.9.3 为什么 PEB 的 LDR 链表有三个?

答案:不同的遍历需求。

  • LoadOrder:按照加载顺序,用于依赖分析
  • MemoryOrder:按照内存地址,用于内存操作
  • InitializationOrder:按照初始化顺序,用于 DllMain 调用

5.2.9.4 KUSER_SHARED_DATA 为什么用 0x7FFE0000 这个"魔数"地址?

答案:历史原因和内存布局。

  • 32 位系统中,0x7FFE0000 是用户空间的最高地址附近
  • 这个地址不会与应用程序代码冲突
  • 是 Windows NT 早期设计的延续

5.2.9.5 为什么 LastErrorValue 在 TEB 中而不是 PEB?

答案:线程安全。

  • 每个线程应该有独立的错误码
  • 如果放在 PEB 中,多线程会互相覆盖
  • GetLastError/SetLastError 需要线程安全

5.2.10 增强子节 1:PEB/TEB 的安全视角

复制代码
┌──────────────────────────────────────────────────────────────────────────────────┐
│                        PEB/TEB 的安全视角                                        │
│                                                                                  │
│   反调试检测技术:                                                                 │
│                                                                                  │
│   1. BeingDebugged 检测:                                                        │
│                                                                                  │
│      if (PEB->BeingDebugged) {                                                  │
│          // 被调试!                                                              │
│      }                                                                           │
│                                                                                  │
│      绕过方法: 修改 PEB->BeingDebugged 为 0                                       │
│                                                                                  │
│   2. NtGlobalFlag 检测:                                                         │
│                                                                                  │
│      if (PEB->NtGlobalFlag & FLG_HEAP_ENABLE_TAIL_CHECK) {                       │
│          // 被调试!                                                              │
│      }                                                                           │
│                                                                                  │
│   3. 检查调试端口:                                                               │
│                                                                                  │
│      NtQueryInformationProcess(ProcessHandle,                                   │
│                                ProcessDebugPort,                                 │
│                                &DebugPort,                                      │
│                                sizeof(DebugPort),                               │
│                                NULL);                                           │
│      if (DebugPort != NULL) {                                                   │
│          // 被调试!                                                              │
│      }                                                                           │
│                                                                                  │
│   4. 检查父进程:                                                                 │
│                                                                                  │
│      // 获取父进程 PID                                                           │
│      DWORD ParentPid = PEB->InheritedFromUniqueProcessId;                        │
│      // 检查是否是调试器进程(如 windbg.exe, x64dbg.exe)                            │
│                                                                                  │
│   Stack Cookie (/GS):                                                             │
│                                                                                  │
│   ┌─────────────────────────────────────────────────────────────────────┐       │
│   │ 函数栈帧结构 (启用 /GS):                                           │       │
│   │                                                                     │       │
│   │   High Address                                                      │       │
│   │   ┌─────────────────────────────────────────────────────────────┐   │       │
│   │   │ 返回地址                                                     │   │       │
│   │   ├─────────────────────────────────────────────────────────────┤   │       │
│   │   │ Stack Cookie (随机值)                                       │   │       │
│   │   ├─────────────────────────────────────────────────────────────┤   │       │
│   │   │ 局部变量                                                     │   │       │
│   │   └─────────────────────────────────────────────────────────────┘   │       │
│   │   Low Address                                                      │       │
│   │                                                                     │       │
│   │   保护机制:                                                         │       │
│   │   1. 函数入口: 生成随机 Cookie 并存入栈中                            │       │
│   │   2. 函数返回前: 检查 Cookie 是否被修改                             │       │
│   │   3. 如果修改: 调用 __security_error_handler                        │       │
│   └─────────────────────────────────────────────────────────────────────┘       │
│                                                                                  │
│   Cookie 的生成位置: PEB->Cookie                                                   │
│   获取方式: RtlRandomizeCookie() 在进程启动时调用                                  │
└──────────────────────────────────────────────────────────────────────────────────┘

5.2.11 增强子节 2:用户态堆(Heap)

复制代码
┌──────────────────────────────────────────────────────────────────────────────────┐
│                        用户态堆(Heap)                                           │
│                                                                                  │
│   进程默认堆: PEB->ProcessHeap                                                   │
│                                                                                  │
│   堆结构:                                                                       │
│                                                                                  │
│   ┌─────────────────────────────────────────────────────────────────────┐       │
│   │                        HEAP 结构                                  │       │
│   │                                                                     │       │
│   │   字段:                                                             │       │
│   │   - SegmentList: 段链表                                            │       │
│   │   - FreeList: 空闲块链表                                           │       │
│   │   - LookasideList: 快速分配缓存                                    │       │
│   │   - Flags: 堆标志                                                  │       │
│   │   - Size: 堆大小                                                   │       │
│   │   - AllocationSize: 分配大小                                       │       │
│   │   - UserFlags: 用户标志                                             │       │
│   │   - VirtualMemoryThreshold: 虚拟内存阈值                           │       │
│   │   - Signature: 签名(用于验证)                                      │       │
│   │   - ForceFlags: 强制标志                                           │       │
│   │   - Reserved: 保留                                                 │       │
│   └─────────────────────────────────────────────────────────────────────┘       │
│                                                                                  │
│   LFH (Low Fragmentation Heap):                                                 │
│                                                                                  │
│   ┌─────────────────────────────────────────────────────────────────────┐       │
│   │                     LFH 工作原理                                   │       │
│   │                                                                     │       │
│   │   1. 将堆划分为多个大小类别 (4 字节, 8 字节, 16 字节, ...)          │       │
│   │   2. 每个类别维护一个空闲块链表                                    │       │
│   │   3. 分配时: 在对应类别中查找空闲块                                │       │
│   │   4. 释放时: 将块放回对应类别的空闲链表                            │       │
│   │   5. 优点: 减少碎片,提高分配速度                                   │       │
│   └─────────────────────────────────────────────────────────────────────┘       │
│                                                                                  │
│   堆分配函数:                                                                   │
│                                                                                  │
│   ┌─────────────────────────────────────────────────────────────────────┐       │
│   │   RtlAllocateHeap(HeapHandle, Flags, Size)                         │       │
│   │   RtlFreeHeap(HeapHandle, Flags, BaseAddress)                      │       │
│   │   RtlReAllocateHeap(HeapHandle, Flags, BaseAddress, NewSize)       │       │
│   │   RtlSizeHeap(HeapHandle, Flags, BaseAddress)                     │       │
│   └─────────────────────────────────────────────────────────────────────┘       │
│                                                                                  │
│   堆调试技术:                                                                   │
│                                                                                  │
│   ┌─────────────────────────────────────────────────────────────────────┐       │
│   │   PageHeap: 启用页堆检测                                           │       │
│   │   !heap: WinDbg 命令查看堆信息                                      │       │
│   │   Application Verifier: 自动检测堆错误                              │       │
│   │   GFlags: 启用堆调试标志                                           │       │
│   └─────────────────────────────────────────────────────────────────────┘       │
└──────────────────────────────────────────────────────────────────────────────────┘

5.2.12 小结

5.2.12.1 关键知识点

主题 关键点
PEB 进程环境块,存储进程级用户态信息
TEB 线程环境块,存储线程级用户态信息
NT_TIB TEB 的第一个字段,存储栈信息
KUSER_SHARED_DATA 用户态只读的内核数据
PEB_LDR_DATA 模块加载器数据,三个链表
TLS 线程局部存储,64 个槽位
Stack Cookie /GS 安全机制

5.2.12.2 设计原则

  1. 用户态可访问:PEB/TEB/KUSER_SHARED_DATA 都在用户态可访问地址
  2. 线程独立:TEB 每个线程一份,保证线程安全
  3. 快速访问:通过 FS/GS 段寄存器快速获取 TEB
  4. 分层设计:PEB(进程级) + TEB(线程级)

5.2.12.3 常见陷阱

  1. 直接访问固定地址:PEB/TEB 地址在不同系统版本可能变化
  2. 假设字段位置不变:PEB/TEB 结构可能因 Windows 版本而变化
  3. 忽略对齐:结构体字段可能有对齐填充
  4. 多线程竞争:修改 PEB 字段需要同步

5.2.12.4 后续学习路径

  • 5.3 节:系统调用 NtCreateProcess()
  • 第 6 章:线程调度
  • WinDBG 调试技巧

源码位置sdk/include/ndk/peb_teb.h(file:///d:/reactos/sdk/include/ndk/peb_teb.h)、ntoskrnl/mm/pe.c(file:///d:/reactos/ntoskrnl/mm/pe.c)

相关推荐
Warren2Lynch1 小时前
破局“伪敏捷”:UML诊断视角下的微服务转型与架构重构——以EcoStream为例
微服务·架构·uml
hz567891 小时前
实时音视频SDK发展趋势:TRTC、WebRTC与云端音视频服务融合路径
架构·音视频·webrtc·实时音视频
莫逸风1 小时前
【AgentScope】6.文件系统(Filesystem)详解
开发语言·windows·springai·agentscope·agnet
珠海西格电力2 小时前
零碳园区的竞争力体现在哪些方面?
大数据·人工智能·算法·架构·能源
该昵称用户已存在2 小时前
技术栈无关化设计:MyEMS 能源中台的兼容层架构与开源
架构·开源·能源
超级无敌zhq2 小时前
内网权限维持实战:打造持久化后门与隐蔽通道
网络·windows·安全·网络安全
Database_Cool_2 小时前
PB 级数据实时分析:阿里云 AnalyticDB MySQL Serverless 弹性架构深度解析
阿里云·架构·云计算
段一凡-华北理工大学2 小时前
工业领域的Hadoop架构学习~系列文章19:能源行业Hadoop应用实践
大数据·人工智能·hadoop·分布式·学习·架构·高炉炼铁
喜欢踢足球的老罗2 小时前
Chrome MV3 插件架构深度解析:Service Worker 生命周期与 Token 管理的三层博弈
前端·chrome·架构