第 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

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

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

ImageBaseAddress

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

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

Ldr (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;

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

ProcessParameters (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;
    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)

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

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() 的实现机理

// 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 槽位的大小选择

#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.8.4 Stack Cookie (/GS)

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


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)

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐