首页
社区
课程
招聘
[原创]Windows 下 KnownDlls 机制的 Section 对象劫持,实现系统 DLL 的加载接管(Ring0 / 内核层)· 上(下篇有空再写)
发表于: 2026-8-16 05:33 167

[原创]Windows 下 KnownDlls 机制的 Section 对象劫持,实现系统 DLL 的加载接管(Ring0 / 内核层)· 上(下篇有空再写)

2026-8-16 05:33
167

前言


记录一次对 KnownDlls 机制的内核层实践:通过驱动关闭系统预建的 Section 对象、重建一个同名的 Section 来接管指定系统 DLL 的加载。上篇把整条链路在普通进程上跑通;系统进程 / 受保护进程为什么即便设置了 WinTCB 信任级别仍然加载失败,留到下篇专门验证


一、先了解 KnownDlls 机制


KnownDlls 是 Windows NT(及更早的 9x)系统引入的一种缓存机制,最初目的是通过预加载常用系统 DLL 来提升应用程序的启动速度。同时它也具备安全意义:能够防止利用应用程序目录权限弱点、通过在程序目录植入木马化系统 DLL 发起的攻击(即 DLL 搜索顺序劫持的一种缓解)。


工作原理大致如下:


系统通过注册表预先定义高频使用的系统 DLL 清单。启动时读取 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\KnownDLLs 下的列表。

系统在启动早期,为这些 DLL 各自创建一个 SEC_IMAGE 类型的 Section 对象,放在对象管理器命名空间的 \KnownDlls\ 目录下。

当加载器在可执行文件的导入表中发现某个 DLL 时,会先到 \KnownDlls\ 下查找是否存在同名的现有 Section。如果存在,加载器直接用这个已存在的 Section 建立映射,省去了重新打开文件、创建 Section 的开销。


我个人对该机制的理解:把 Kernel32.dll、Ntdll.dll 这类常用系统文件当作 KnownDll,系统在启动时为它们各建一个 SEC_IMAGE 的 Section 对象放进 \KnownDlls\。之后进程加载这些 DLL 时,加载器先在 \KnownDlls\ 里找对应的 Section,找到就直接拿这个已存在的 Section 来映射,而不必每次都去读本地文件、重新建 Section。这样既提升了效率,也让关键系统 DLL 的来源相对固定、不易被目录层面的手段替换。


补充一点表述上的精确性:这些 Section 是内核对象(存在于内核的对象管理器命名空间里),但它映射出来的镜像数据,是在进程加载时映射进各个用户态进程的地址空间的,并不是常驻在内核地址空间。下文提到"从 Section 加载"都是这个意思。


下面主要讨论"能否篡改 / 接管"这部分


用 WinObjEx 可以看到,64 位的 Section 目录是 \KnownDlls,32 位的是 \KnownDlls32



二、思路:关掉原 Section,然后建一个同名的,让原本从系统的Section加载Kernel32.dll变成从我们自建的Section加载Kernel32.dll


既然每个KnownDll对应一个SEC_IMAGE的Section,那么可以尝试写一个内核驱动,把某个Section关掉,然后在\KnownDlls(或 \KnownDlls32)目录下创建一个同名Section从而实现 DLL 加载的中转/接管,下面以64 位的Kernel32.dll的Section为例


代码如图:

直接用 ZwClose 去关

跑一下,dbgview输出了 [Remove Object] Open Section: 0x0,0x0 即 STATUS_SUCCESS。回到 WinObjEx 看一下:

被骗了,根本没关掉,Kernel32.dll 的Section还好好的


原因在于:\KnownDlls里的Section是永久对象(相对永久——具体来说是相对"当前这次开机"而言,重启后系统会重建)


这里需要区分对象管理器里的两个计数:


句柄计数(handle count):有多少个打开的句柄指向该对象。ZwClose减的是这个

引用计数/指针计数(pointer count):内核里有多少处引用该对象


永久对象是用OBJ_PERMANENT属性创建的,它被创建时就带着一个额外的指针引用,因此即使句柄计数归零,指针计数也不会归零,对象不会被删除,所以上面ZwClose成功了,Section却纹丝不动——我减掉的只是句柄计数,永久属性顶着的那个引用还在


三、先降级为临时对象,再ZwClose


明确这点之后,思路就清楚了:先把它从永久对象降级成临时对象,再关,降级用 ZwMakeTemporaryObject


函数声明:


c

NTSYSAPI NTSTATUS ZwMakeTemporaryObject(

  [in] HANDLE Handle

);


微软的说明:


ZwMakeTemporaryObject 是通用例程,可对任意类型的对象操作。

如果对象是用 OBJ_PERMANENT 属性创建的,它就是永久对象;永久对象创建时引用计数为 1,因此驱动取消引用时不会被删除。

如果对象不是永久的,就是临时对象。ZwMakeTemporaryObject 会把指定对象转成临时对象;如果它已经是临时的,则什么都不做。

临时对象只有在句柄计数大于零时才有名称。当句柄计数归零,系统会删除对象名称,并相应调整指针计数。

用户态调用时应使用 NtMakeTemporaryObject。



传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

最后于 2026-8-16 11:48 被Gmcixy2531编辑 ,原因: 笔误
收藏
点赞 ・5
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回