简介:这份资源聚焦Windows驱动安全与反调试领域,面向驱动开发者、逆向工程人员及IT安全研究者,围绕驱动隐藏与断链技术展开,重点解决驱动在TP(Taint Parser)等动态调试工具下易被检测、跟踪与逆向的问题。压缩包共43个文件,约5.88MB,包含C源码、头文件、inf安装信息、vcxproj工程与sln解决方案等工程文件,以及pdb调试符号、tlog与log构建日志、obj中间产物等,完整保留了HideDriver项目的编译与调试痕迹,便于还原开发环境。内容涉及驱动断链隐藏、注册回调隐藏、TP双击调试规避、自定义I/O端口通信、加密通信与多层混淆等思路,并配有GET_MIPROCESSLOADERENTRY.h、SYSTEM_MODULE_STRUCT.h等关键结构定义,可帮助读者理解驱动加载链修改与模块枚举对抗的实现细节。目前已有1541人学习下载,适合具备一定内核基础、希望提升驱动隐蔽性与抗逆向能力的技术人员参考。
1. 驱动隐藏到底在藏什么:从任务管理器看不见的那块内存说起
很多人第一次接触驱动隐藏,是因为发现自己写的驱动加载之后,任务管理器、驱动列表工具里干干净净,但功能确实在跑。这种“看不见但活着”的状态,就是驱动隐藏要解决的问题。它的核心不是让驱动不存在,而是让操作系统里那些负责枚举驱动的数据结构,不再指向你的驱动对象。常见做法分两类:一类是断链,把驱动从内核的模块链表中摘出去;另一类是抹掉对象和签名痕迹,让枚举路径查不到。标题里的 HideDriver、驱动断链隐藏,说的就是前者。这套东西适合谁?适合做内核安全研究、EDR 对抗验证、驱动加载器调试的从业者。它不解决驱动加载本身,只解决加载之后“怎么不被常规枚举发现”。理解这一点,后面所有操作才有边界。
2. 断链隐藏的底层逻辑:驱动对象、模块链表与枚举路径
2.1 驱动加载后,系统到底把它挂在哪几条链上
Windows 内核里,一个驱动加载完成,至少会出现在两条关键链上。第一条是 PsLoadedModuleList,这是一个双向链表,记录所有已加载的内核模块,任务管理器、驱动枚举工具、部分安全软件都靠它拿列表。第二条是驱动对象自身的 DriverObject,它挂在对象管理器里,通过对象目录可以查到。断链隐藏主要动的是 PsLoadedModuleList,把对应节点从链表里摘掉,让遍历这条链的人看不到你。但要注意,摘链不等于卸载,驱动的代码段、回调、设备对象都还在,功能照常跑。常见做法是拿到驱动对象的 DriverSection 指针,它指向 _LDR_DATA_TABLE_ENTRY,里面 InLoadOrderLinks、InMemoryOrderLinks、InInitializationOrderLinks 三个 LIST_ENTRY 都要处理,只摘一个,别的枚举路径照样能翻出来。
2.2 用 DriverSection 定位链表节点的最小代码路径
下面这段是内核层断链的核心逻辑,跑在驱动入口里,加载成功后立刻执行。注意它只处理 InLoadOrderLinks,实际落地时三个链表都要摘,这里为了讲清楚先给最小版本。
// 假设 DriverObject 是当前驱动对象 PLDR_DATA_TABLE_ENTRY ldr = (PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection; // 摘 InLoadOrderLinks if (ldr && ldr->InLoadOrderLinks.Flink && ldr->InLoadOrderLinks.Blink) { // 把前一个节点的 Flink 指向后一个节点 ldr->InLoadOrderLinks.Blink->Flink = ldr->InLoadOrderLinks.Flink; // 把后一个节点的 Blink 指向前一个节点 ldr->InLoadOrderLinks.Flink->Blink = ldr->InLoadOrderLinks.Blink; // 把自己的指针指向自己,防止残留引用被遍历到 ldr->InLoadOrderLinks.Flink = &ldr->InLoadOrderLinks; ldr->InLoadOrderLinks.Blink = &ldr->InLoadOrderLinks; }逻辑说明:LIST_ENTRY 是双向链表节点,摘链就是把前后节点的指针互相指,然后把自己的 Flink/Blink 指向自身。参数上,DriverObject->DriverSection 在驱动加载后由内核填好,直接强转成 PLDR_DATA_TABLE_ENTRY 即可。注意这段代码必须在驱动入口的 DriverEntry 里、且加载已经完成之后执行,太早拿不到有效 DriverSection。三个链表 InLoadOrderLinks、InMemoryOrderLinks、InInitializationOrderLinks 都要做同样处理,漏一个,用对应顺序枚举的工具就能看到你。
2.3 为什么只摘链不够:对象目录和回调的残留
摘完链表,任务管理器确实看不到了,但对象管理器里 DriverObject 还在,通过 ObOpenObjectByName 或者遍历 \Driver 目录,还是能摸到你的驱动对象。常见做法是配合对象目录隐藏,把 DriverObject 从对象目录里摘掉,或者改对象头让枚举跳过。另外,如果你注册了进程回调、线程回调、注册表回调,这些回调本身不依赖链表,摘链不影响它们工作,但安全软件可以通过回调反查是哪个驱动注册的。所以断链隐藏通常和回调清理、对象目录处理一起做,单独摘链只能骗过最基础的枚举。我一般会先确认目标环境用哪几条枚举路径,再决定摘哪几条链,不做无差别全摘,避免留下不一致的痕迹。
3. 从加载到隐藏的完整落地:驱动入口、摘链时机与验证
3.1 驱动入口里做隐藏的时机选择
隐藏代码放的位置很关键。放 DriverEntry 最前面,DriverSection 可能还没初始化完;放最后面,加载已经完成,链表节点有效,但中间可能被别的枚举抓到。常见做法是在 DriverEntry 里先完成设备创建、符号链接、回调注册,最后一步再摘链。这样驱动功能已经就绪,摘链只是收尾。下面是一个带时机判断的入口骨架。
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status = STATUS_SUCCESS; // 1. 先做正常初始化:创建设备、符号链接 status = CreateMyDevice(DriverObject); if (!NT_SUCCESS(status)) return status; // 2. 注册需要的回调 status = RegisterMyCallbacks(); if (!NT_SUCCESS(status)) return status; // 3. 最后再摘链隐藏,此时 DriverSection 已有效 HideDriverByUnlink(DriverObject); return STATUS_SUCCESS; }逻辑说明:CreateMyDevice 和 RegisterMyCallbacks 是占位函数,实际替换成你的设备创建和回调注册逻辑。HideDriverByUnlink 里做三个链表的摘除。参数上,DriverObject 由内核传入,RegistryPath 是注册表路径,隐藏逻辑不需要它。注意摘链之后不要再调用任何依赖模块链表的系统函数,比如 PsGetModuleInfo 之类,否则可能拿到不完整列表导致异常。
3.2 三个链表都要摘:InLoadOrder、InMemoryOrder、InInitializationOrder
只摘 InLoadOrderLinks 是最常见的翻车点。用 EnumDeviceDrivers 或者遍历 InMemoryOrderLinks 的工具照样能看到你。下面把三个链表一起处理。
void HideDriverByUnlink(PDRIVER_OBJECT DriverObject) { PLDR_DATA_TABLE_ENTRY ldr = (PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection; if (!ldr) return; // 摘 InLoadOrderLinks if (ldr->InLoadOrderLinks.Flink && ldr->InLoadOrderLinks.Blink) { ldr->InLoadOrderLinks.Blink->Flink = ldr->InLoadOrderLinks.Flink; ldr->InLoadOrderLinks.Flink->Blink = ldr->InLoadOrderLinks.Blink; ldr->InLoadOrderLinks.Flink = &ldr->InLoadOrderLinks; ldr->InLoadOrderLinks.Blink = &ldr->InLoadOrderLinks; } // 摘 InMemoryOrderLinks if (ldr->InMemoryOrderLinks.Flink && ldr->InMemoryOrderLinks.Blink) { ldr->InMemoryOrderLinks.Blink->Flink = ldr->InMemoryOrderLinks.Flink; ldr->InMemoryOrderLinks.Flink->Blink = ldr->InMemoryOrderLinks.Blink; ldr->InMemoryOrderLinks.Flink = &ldr->InMemoryOrderLinks; ldr->InMemoryOrderLinks.Blink = &ldr->InMemoryOrderLinks; } // 摘 InInitializationOrderLinks if (ldr->InInitializationOrderLinks.Flink && ldr->InInitializationOrderLinks.Blink) { ldr->InInitializationOrderLinks.Blink->Flink = ldr->InInitializationOrderLinks.Flink; ldr->InInitializationOrderLinks.Flink->Blink = ldr->InInitializationOrderLinks.Blink; ldr->InInitializationOrderLinks.Flink = &ldr->InInitializationOrderLinks; ldr->InInitializationOrderLinks.Blink = &ldr->InInitializationOrderLinks; } }逻辑说明:三个链表结构一样,处理方式一致。参数上,ldr 来自 DriverObject->DriverSection,强转后直接访问三个 LIST_ENTRY 字段。注意每个链表摘之前都要判空,有些精简系统或早期加载阶段某些链表可能没挂上。摘完之后,用 WinObj 或者驱动枚举工具验证,三个枚举路径都应该看不到你的驱动名。
3.3 验证隐藏效果:用哪几个工具交叉确认
隐藏做完不能只看任务管理器。常见验证组合:第一,任务管理器看驱动列表,确认没有你的驱动名;第二,用内核模块枚举工具按加载顺序、内存顺序、初始化顺序分别列,确认三个都看不到;第三,用对象管理器工具看 \Driver 目录,确认 DriverObject 是否还在,如果还在,说明对象目录没处理,需要补。第四,跑一遍功能,确认设备、回调都正常。我一般会写一个简单的用户态枚举程序,分别调 EnumDeviceDrivers 和遍历 \Driver 目录,两个结果都干净才算过。注意验证要在摘链之后、且不要重启,重启后驱动重新加载,隐藏逻辑要重新执行。
4. 避坑与排查:断链隐藏最常见的五类翻车
4.1 摘链后蓝屏:链表指针被二次遍历
现象:驱动加载成功,摘链也执行了,但过几秒系统蓝屏,错误码常见 IRQL_NOT_LESS_OR_EQUAL 或 PAGE_FAULT_IN_NONPAGED_AREA。原因:摘链时只改了前后节点指针,没有把自己的 Flink/Blink 指向自身,导致后续某个遍历逻辑走到你的节点,拿到已经失效的指针继续访问。解决:摘链后必须把自身节点的 Flink 和 Blink 都指向自己,形成自环,这样即使被遍历到也不会跳到非法地址。上面代码里已经做了这一步,漏掉就是蓝屏。
4.2 任务管理器看不到但工具能看到:只摘了一条链
现象:任务管理器驱动列表干净,但用某个内核工具按内存顺序枚举,你的驱动还在。原因:只摘了 InLoadOrderLinks,InMemoryOrderLinks 和 InInitializationOrderLinks 没动。解决:三个链表一起摘,不要只处理一个。验证时也要用支持多顺序枚举的工具交叉确认,不能只看任务管理器。
4.3 摘链后功能失效:依赖模块链表的系统调用被破坏
现象:隐藏之后,驱动自己的某些功能不工作了,比如动态获取其他模块地址失败。原因:摘链之后,系统里依赖 PsLoadedModuleList 的查询函数拿不到你的驱动,如果你自己的代码又通过这条链去查别人,可能拿到不完整列表。解决:隐藏逻辑和功能逻辑解耦,功能需要的模块地址在摘链之前先解析好缓存起来,摘链之后不再走模块链表查询。
4.4 对象目录残留:DriverObject 还能被打开
现象:链表摘干净了,但用对象管理器工具还能看到 \Driver\YourDriverName。原因:只处理了模块链表,没有处理对象目录。解决:在摘链之后,把 DriverObject 从对象目录里摘掉,或者修改对象头让枚举跳过。注意对象目录操作涉及对象管理器锁,要在 PASSIVE_LEVEL 做,别在 DISPATCH_LEVEL 硬上。
4.5 回调反查暴露:安全软件通过回调找到驱动
现象:枚举路径都干净,但安全软件还是能定位到你的驱动。原因:你注册的回调函数地址落在你的驱动代码段,安全软件通过回调反查模块基址,再比对内存里的模块信息,就能找到你。解决:回调注册和隐藏要一起考虑,要么把回调逻辑放到别的模块,要么在隐藏时一并处理回调记录。这块没有银弹,取决于目标环境的安全软件能力,我一般会先摸清对方用哪些回调,再决定隐藏深度。
5. 进阶技巧:断链隐藏的稳定性边界与自检习惯
断链隐藏做到最后,拼的不是摘链代码,而是稳定性边界。我自己的习惯是,任何隐藏操作之前,先在一个干净环境里把驱动正常加载、功能跑通、枚举可见,确认基线没问题,再上隐藏。隐藏之后,第一件事不是看功能,而是跑一轮压力测试,让系统频繁枚举模块、频繁创建销毁进程,观察有没有蓝屏或卡顿。因为摘链破坏的是系统全局数据结构,任何依赖这条链的组件都可能受影响,压力测试能提前暴露不一致。
第二个习惯是记录摘链前后的链表快照。在摘链之前,把三个链表的前后节点地址打印出来,摘链之后再打印一次,确认指针变化符合预期。这个快照在出问题时是唯一的后悔药,能快速判断是摘链逻辑错了,还是别的组件动了链表。下面是一个简单的快照打印片段。
void DumpLinks(PLDR_DATA_TABLE_ENTRY ldr) { DbgPrint("InLoadOrder: Flink=%p Blink=%p\n", ldr->InLoadOrderLinks.Flink, ldr->InLoadOrderLinks.Blink); DbgPrint("InMemoryOrder: Flink=%p Blink=%p\n", ldr->InMemoryOrderLinks.Flink, ldr->InMemoryOrderLinks.Blink); DbgPrint("InInitOrder: Flink=%p Blink=%p\n", ldr->InInitializationOrderLinks.Flink, ldr->InInitializationOrderLinks.Blink); }逻辑说明:在摘链前后各调一次,对比输出。参数上,ldr 还是 DriverSection。注意 DbgPrint 在正式环境可能被禁用,调试阶段用 DebugView 看,正式环境换成自己的日志机制。
第三个边界是版本差异。不同系统版本里 _LDR_DATA_TABLE_ENTRY 的字段偏移可能不同,硬编码偏移是玄学翻车的重灾区。常见做法是用符号信息或者动态解析,别写死偏移。如果目标环境版本固定,也要在代码里加版本判断,不匹配就跳过隐藏,宁可暴露也别蓝屏。
最后一个技巧是隐藏后的自检。写一个自检函数,在隐藏完成后主动遍历三个链表,确认自己的驱动名不在里面,同时确认链表本身没有断裂,前后节点能正常走通。这个自检可以放在驱动里定时跑,也可以做成用户态工具。我一般会在隐藏后 10 秒跑一次自检,确认稳定再交付。这套东西没有一劳永逸,环境一变,枚举路径一变,隐藏策略就要跟着调。希望帮到你。
本文还有配套的精品资源,点击获取