判断ActiveProcessLinks被篡改需手动遍历EPROCESS双向链表,校验Flink/Blink双向指针合法性及内存有效性,不可依赖PsGetNextProcess等容错API,同时需扩展检测线程链表与系统兼容性约束。

怎么判断 ActiveProcessLinks 被篡改
DKOM 进程隐藏最典型的痕迹,就是 EPROCESS.ActiveProcessLinks 双向链表出现断链或指针非法。系统正常遍历时会逐个跳转 Flink 和 Blink,一旦某个节点的 Flink->Blink 不等于自身地址,或指针落在非内核内存页(比如用户空间、空页、已释放池),基本可判定被摘链。
实操建议:
- 用
MmIsAddressValid检查Flink/Blink是否指向合法内核地址(注意:该函数在某些 Windows 版本中不可靠,需配合MmIsNonPagedSystemAddressValid补充验证) - 遍历链表时缓存上一个节点地址,校验当前节点
Blink == 上一节点地址,不等即异常 - 避免只检查单向链——攻击者可能只改
Flink,留Blink正常用于绕过简单检测 - 不要依赖固定偏移硬编码:Windows 10/11 各版本
ActiveProcessLinks偏移不同,必须运行时用RtlFindExportedRoutineByName或符号解析获取
为什么 PsGetNextProcess 不能直接用于检测
PsGetNextProcess 是微软提供的安全遍历接口,它内部做了链表完整性校验,遇到断链会自动跳过或返回 NULL。这意味着:你用它枚举出来的进程永远“看起来正常”,根本看不到被隐藏的节点——它不是检测工具,而是容错遍历器。
所以想发现 DKOM,必须绕过该 API,直接读取原始链表结构:
立即学习“C++免费学习笔记(深入)”;
- 用
PsGetCurrentProcess获取当前进程EPROCESS,再手动解引用ActiveProcessLinks.Flink - 自己实现链表遍历循环,每步都做双向指针校验
- 记录所有遍历到的
UniqueProcessId,再与ZwQuerySystemInformation(SystemProcessInformation)返回的 PID 列表比对,缺漏即可疑
检测线程级 DKOM 的关键点
只查进程链表不够——高级 DKOM 攻击常同步摘掉 EPROCESS.ThreadListHead 或篡改 ETHREAD.ThreadListEntry,导致线程不可见,进而让进程看似“无活动线程”而被误判为僵死。
需要额外验证:
- 对每个遍历到的
EPROCESS,检查ActiveThreads字段是否为 0,但ThreadListHead.Flink != &ThreadListHead(说明链表非空却计数为 0) - 遍历
ThreadListHead,确认每个ETHREAD的ThreadListEntry双向指针合法,且StackBase/StackLimit在有效内存范围内 - 注意
PsGetNextThread同样有容错逻辑,不能替代手动链表扫描
性能和兼容性必须考虑的三个现实约束
在真实驱动中高频扫描全链表,极易引发系统卡顿甚至蓝屏,尤其在多 CPU、高负载场景下。这不是理论风险,是实际部署时反复踩过的坑。
推荐做法:
- 检测频率控制在每 5–10 秒一次,避免在 DPC 或中断上下文里执行完整遍历
- 使用
ExAcquireResourceSharedLite锁住PspCidTable资源前先判断是否已被占用,超时立即放弃,防止死锁 - Windows 11 22H2+ 引入了
Kernel Patch Protection (KPP)对EPROCESS关键字段写保护,盲目 patch 内存会触发CRITICAL_STRUCTURE_CORRUPTION蓝屏,检测代码本身绝不能尝试写内存
最易被忽略的一点:DKOM 检测不是“找到断链就报警”那么简单。攻击者可以伪造链表节点、填充假进程、甚至劫持 ObReferenceObjectByHandle 返回伪造对象——真正的对抗,发生在检测逻辑是否被反检测的层面。



















