断链activeprocesslinks能让进程“消失”,因为ntquerysysteminformation等系统调用枚举进程时仅遍历psactiveprocesshead链表;摘除目标eprocess节点后,任务管理器、tasklist等标准工具便无法发现该进程,但其仍正常运行且不受影响。

为什么断链 ActiveProcessLinks 能让进程“消失”
因为 Windows 内核枚举进程(比如 NtQuerySystemInformation 传 SystemProcessInformation)时,底层只做一件事:从全局头指针 PsActiveProcessList 出发,顺着每个 EPROCESS->ActiveProcessLinks 的 Flink 一路遍历。只要把目标 EPROCESS 从这个双向链表里摘掉(即修改前后节点的指针),它就彻底不在标准枚举路径上——任务管理器、tasklist、EnumProcesses 全部查不到。
但要注意:这不等于进程被终止,也不影响其线程调度、内存、句柄或网络连接;只是“不可见”,不是“不存在”。EDR 若注册了 PsSetCreateProcessNotifyRoutine 回调,仍能捕获进程创建事件。
断链前必须获取目标 EPROCESS 地址
驱动不能靠 PID 直接硬算地址,必须用内核 API 安全获取。常见错误是直接读取用户传入的 PID 后调用 PsLookupProcessByProcessId,却忽略返回值检查和引用计数管理:
-
PsLookupProcessByProcessId返回的是带引用的PEPROCESS,必须配对调用ObDereferenceObject,否则对象泄漏、系统不稳定 - 若目标进程已退出,该函数返回
STATUS_INVALID_CID,不检查就断链会蓝屏(BSOD) - 64 位系统下,
EPROCESS地址是内核态有效地址,不能在用户态缓存或跨进程传递
正确做法是:在驱动中封装一个安全查找函数,内部用 ExAcquireResourceSharedLite 保护(某些 Windows 版本需),并始终校验返回状态。
摘链操作本身要原子且可逆
断链不是简单改两个指针,而是三步原子操作(以摘除 TargetEprocess 为例):
- 保存
TargetEprocess->ActiveProcessLinks.Flink和Blink - 将
Flink->Blink指向Blink,再将Blink->Flink指向Flink(即跳过当前节点) - 清空
TargetEprocess->ActiveProcessLinks.Flink/Blink,避免被误遍历
关键细节:
- 必须禁用中断(
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql))或使用自旋锁,防止遍历时链表被并发修改 - 不能只改一个方向(如只改
Flink),否则遍历到末尾时可能触发空指针解引用 - 恢复链表时,必须用保存的原始指针原路写回,不能依赖当前
PsActiveProcessList状态(它可能已被其他驱动改动)
隐藏自身进程时最易踩的三个坑
驱动想隐藏自己(即当前驱动所对应的 System 进程或注入后所在的宿主进程),难点不在断链逻辑,而在时机与上下文:
- 不能在
DriverEntry里直接断链——此时EPROCESS可能尚未完全初始化,PsActiveProcessList也未稳定,极易蓝屏 - 不能在 IRP 处理函数中反复执行断链——每次调用都需重新查找
EPROCESS,开销大且易因进程退出导致状态不一致 - 若驱动以
Win32k.sys或csrss.exe为宿主,断链后可能导致 GUI 子系统异常,因为这些进程有特殊内核契约
真正可行的做法是:在驱动完成初始化后,用工作线程(ExQueueWorkItem)延时 1–2 秒再执行一次断链,并记录原始 Flink/Blink 值供卸载时恢复。所有操作必须包裹在 __try/__except 中,任何异常都要立即放弃并记录日志。
断链不是黑魔法,它暴露在内核回调、对象表扫描、物理内存 dump 下毫无遮掩。真正难的是让断链动作不破坏调度、不触发 AV/EDR 的内核钩子检测、不留下可被 !process -a 或 dt _eprocess 手动定位的痕迹——这些细节,往往比断链本身更消耗调试时间。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











