整理了一下当初学习Windows内核源码的要点(七)
WRK编译调试 · EPROCESS/ETHREAD · KPCR · VAD · 进程遍历
一、WRK 与内核源码概览
微软开放了 WRK(Windows Research Kernel),可以查看、编译、调试内核。它包含Windows内核部分源码,大部分核心代码都在,其余以 .obj 文件链接进来。适用于 Windows Server 2003 和 Windows XP x64 的内核研究。

读源码推荐 Source Insight 4.0:建立工程、加载 WRK 目录、创建符号数据库,就能全文搜索、Ctrl+点击跳转到定义。NT 开头的函数属于内核,进去就是内核路径。
若需要 0~3 环完整操作系统源码,可参考 ReactOS(老版本在 GitHub 上更完整)。WRK 侧重内核机制,ReactOS 侧重全栈对照。

1.1 执行层与核心层
_EPROCESS 是给执行层用的进程对象,_KPROCESS 是给核心层用的;_ETHREAD / _KTHREAD 同理。EPROCESS 与 KPROCESS 首地址重合,拿到一个就能当两个用。Ki* 函数是更底层的实现入口。

二、EPROCESS 进程对象
WinDbg 里用 dt _EPROCESS 查看定义。进程对象里藏着调试、权限、内存布局的关键信息。

2.1 身份与链表
UniqueProcessId:进程 PID。
ActiveProcessLinks:系统全局进程双向循环链表节点。LIST_ENTRY 指向的是链表中间位置,遍历时需要减去偏移回到 _EPROCESS 首地址。
ImageFileName[16]:进程名称(最多15字符+结束符)。
ActiveThreads:当前活动线程数。
2.2 调试端口
DebugPort:调试器附加进程时,内核创建的通信端口;为 0 表示未被调试。反调试常把这里清 0,调试器就收不到事件。对策之一:对 DebugPort 下内存断点,等对方改写时定位代码并 NOP。
ExceptionPort:接收异常的端口。

2.3 句柄表、令牌与 PEB
ObjectTable:进程句柄表,记录打开的所有内核对象(进程、线程、文件等)。
Token:权限令牌,不同进程的权限靠它区分。漏洞利用常见手法是把系统进程 Token 拷贝到目标进程,实现提权。偏移因系统版本而异,需用 dt 确认。

Peb:指向 3 环进程环境块(PEB),内核通过它关联用户态进程信息。
三、KPROCESS 与页目录表
页目录表是进程的属性之一,存在 _KPROCESS.DirectoryTableBase 里。切换进程本质上就是切换页目录表(CR3)。


DirectoryTableBase[0]:本进程页目录表物理基址。
DirectoryTableBase[1]:超空间(Hyperspace)页目录表地址。
ThreadListHead:本进程所有线程的链表头,可遍历进程内全部线程。
QuantumReset:线程时间片相关。
只要拿到一个进程的 EPROCESS,就能通过 ActiveProcessLinks 遍历全部进程,进而拿到各自的页目录表,再配合页表遍历操作物理内存。

典型路径:EPROCESS → DirectoryTableBase → 遍历 PDE/PTE 算物理地址 → MmMapIoSpace 映射到当前进程可访问的虚拟地址。
四、ETHREAD 与 KTHREAD
4.1 _ETHREAD(执行层)

Cid:线程 ID(CLIENT_ID)。
ThreadsProcess:所属进程的 EPROCESS。
StartAddress:系统 DLL 启动地址,来自 Context 的 EIP。
Win32StartAddress:线程回调函数地址,来自 Context 的 EAX。
BreakOnTermination、SkipCreationMsg、SkipTerminationMsg 等标志位可用于向调试器隐藏线程的创建/终止通知。
4.2 _KTHREAD(核心层)

ApcState:APC 信息,链表记录注册的每个 _KAPC 异步过程调用。

Teb:指向 3 环线程环境块(TEB)。
TrapFrame:线程切换时保存寄存器环境。若修改 TrapFrame 里的 EIP,切换回来就会跳到指定代码——这是注入类技术的内核视角。
ServiceTable:该线程使用的 SSDT 表。
4.3 线程切换

调度器切换线程时保存 TrapFrame,同时切换 DirectoryTableBase(CR3),于是每个线程回到自己进程的地址空间。
五、KPCR 与获取当前进程
KPCR(CPU 控制块)通过 FS 段寄存器访问:3 环 FS 指向 TEB,0 环 FS 指向 KPCR。典型内核地址 ffdff000(可用 dg fs:30 等验证)。

KPCR 第一个成员是 _NT_TIB,记录内核栈信息。
KPRCB 里有 CurrentThread、NextThread、IdleThread。
CurrentThread 偏移约 +0x124,指向 _KTHREAD,再关联到 _ETHREAD 和 _EPROCESS。

PsGetCurrentProcess() 的汇编实现就是从 FS 读到 KPCR,再沿 KPRCB → CurrentThread → EPROCESS。NT 开头的 API 进入内核后走这条路径。
六、遍历进程与 VAD 树
6.1 进程链表遍历

从 PsActiveProcessHead 出发,沿 ActiveProcessLinks.Flink 走。每个节点减去 ActiveProcessLinks 在 _EPROCESS 中的偏移,得到结构体首地址,即可读 UniqueProcessId、ImageFileName、DirectoryTableBase 等。
偏移量必须用 dt _EPROCESS 在当前系统版本上确认,不同 Service Pack 和 x86/x64 都可能不同;WinDbg 的 !process 命令已经帮你做了这一步。

6.2 VAD 虚拟地址描述符
VadRoot 是一棵平衡二叉树(MM_AVL_TABLE),管理进程已申请的虚拟地址。每个节点记录父/左/右、起始页、结束页。VirtualAlloc 先查这棵树确定哪些页已占用,申请后再插入节点。

Private:VirtualAlloc 私有提交;Mapped:文件映射。
3 环常用 LDR 断链隐藏模块,但内存仍在 VAD 树里——遍历 VAD 可以找出被隐藏的映射。WinDbg 用 !vad
6.3 WinDbg 实战命令

!process 0 0:列出所有进程。
dt _EPROCESS
!vad
!thread:查看线程信息。
七、WRK 编译与源码级调试
WRK 可以编译出自定义内核,替换到 Windows Server 2003 / XP x64 的 System32 目录,配合 boot.ini 选择启动项,再用 WinDbg 双机调试做源码级跟踪。

7.1 编译步骤
1. 设置 WRK 工具链目录为环境变量。
2. 在 WRK 目录执行 build -cZ 编译。
3. 生成 ntkrnlmp.exe 和对应 PDB。
4. 拷贝内核和 HAL DLL 到目标机 System32。
5. 修改 boot.ini,重启选择 WRK 内核。
7.2 WinDbg 源码调试
配置符号路径加载 PDB。
.lsrcpath 添加 WRK 源码目录。
在目标 API 下断点,断下后打开源文件单步跟踪。
建议配合《Windows 内核原理与实现》《Windows 内核编程》等书对照阅读,把结构体字段和 WRK 源码里的注释一一对应,比死记偏移有效得多。
八、总结
1. WRK:微软开放的内核源码,核心代码可编译调试,其余以 obj 链接
2. _EPROCESS/_KPROCESS 首地址重合,执行层与核心层各取所需
3. ActiveProcessLinks 可遍历系统全部进程
4. DebugPort/ExceptionPort 是调试器通信关键;清 0 即反调试
5. Token 决定权限;拷贝系统 Token 可提权
6. VadRoot 管理虚拟地址;可发现 LDR 断链隐藏的模块
7. DirectoryTableBase 即页目录表,切换进程=切换 CR3
8. _ETHREAD/_KTHREAD:线程 ID、所属进程、TrapFrame、APC、TEB
9. KPCR@fs:[0] → KPRCB → CurrentThread → EPROCESS
10. PsGetCurrentProcess 走 FS→KPCR 链路
11. 遍历进程链表需减去 ActiveProcessLinks 偏移
12. WRK 编译替换内核 + WinDbg PDB + .lsrcpath 源码调试
13. !process / !vad / dt _EPROCESS 是日常三件套
14. ReactOS 可作 0~3 环完整对照参考
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
如果你觉得这篇文章有帮助,欢迎点赞、在看、转发!
关注本公众号,持续输出逆向工程与调试器开发技术干货。
#WRK #EPROCESS #ETHREAD #KPCR #VAD #内核调试
其他文章
Windows内核驱动完整实现:创建设备、注册派遣函数与完成IRP请求
Windows内核驱动进阶:安装卸载、内核API分类与安全防御实战
夜雨聆风