Windows DLL 名称解析机制 - 搜索顺序与 SxS
· 小村 豪 · Windows, DLL, 加载器, 安全, Windows开发
Windows 在处理原生 DLL 时,经常会引发下面这些困惑:
- 写了
LoadLibrary("foo.dll"),实际会去哪里查找 - 明明放在与可执行文件相同的文件夹里,为什么会加载别的 DLL
- 到底是
System32优先,还是应用文件夹优先 - manifest、API set、Known DLLs 分别在哪个阶段起作用
- 使用
SetDllDirectory或AddDllDirectory会改变什么 - DLL 植入攻击(DLL hijacking)在什么情况下容易发生
这个话题,单靠“背一行搜索顺序”是没法在实务中派上用场的。 实际上,Windows 的加载器会先评估几条特殊规则,然后才按顺序搜索文件系统。
本文将 Windows 的 DLL 名称解析,连同 unpackaged app 与 packaged app 的区别、Known DLLs、loaded-module list、API set、side-by-side manifest、LoadLibraryEx 系列 API 的影响 一起,从实务角度加以整理。
内容以 2026 年 3 月时点 Microsoft Learn 公开的信息为依据。123456789
1. 先说结论
先把面向实务的结论列出来。
- Windows 的 DLL 名称解析,并不是“先搜索文件系统”。DLL redirection、API set、SxS manifest、loaded-module list、Known DLLs 等要素,会排在搜索顺序的前段。1
- 在 unpackaged app 且启用 safe DLL search mode 的标准形态下,应用程序文件夹的顺位靠前,但在此之前,上述特殊规则会先被评估。1
- 即使用完整路径加载 DLL,该 DLL 的依赖 DLL 并不会自动被固定到同一个完整路径。依赖 DLL 会按模块名单独搜索,因此可能从别的位置解析。1
Known DLLs是操作系统把特定的已知 DLL 绑定到系统侧副本的机制,通常不是靠应用侧的常规部署就能覆盖的对象。1- API set 不是“实体 DLL 名称本身”,而是用来隐藏实现 DLL 的虚拟别名。看到
api-ms-win-...这样的名字,如果按普通 DLL 搜索的思路去理解,很容易产生误解。3 SetDllDirectory不仅会改变搜索顺序,还带有实质上禁用 safe DLL search mode 的行为,随意使用可能在安全性上适得其反。1- 实务中,完整路径指定、
SetDefaultDllDirectories、AddDllDirectory、LoadLibraryEx的LOAD_LIBRARY_SEARCH_*标志 组合起来,明确收紧搜索范围才是安全的做法。4562
总而言之,可以这样理解:Windows 的 DLL 名称解析,不仅取决于“哪个文件夹排第几”,还取决于“有没有把名称解析到别处的前置规则”,以及“用 API 把搜索空间改成了什么样”,这才是实务上的思路。
2. DLL 的名称解析,在“文件夹搜索”之前还有前置规则
Microsoft Learn 对 DLL search order 的说明中提到,加载 DLL 时,首先会把下面这些要素当作搜索顺序的一部分来处理。1
- DLL redirection
- API sets
- SxS manifest redirection
- loaded-module list
- Known DLLs
之后,才会进入 app folder、System32、Windows 文件夹、PATH 等文件系统上的搜索。1
如果忽略这一点,可能会觉得“怎么会有东西比应用文件夹更先被决定”,但从 Windows 加载器的说明来看,这恰恰才是正统的行为。
flowchart TD
A["要解析 DLL 名称"] --> B["DLL redirection"]
B --> C["API set"]
C --> D["SxS manifest redirection"]
D --> E["loaded-module list"]
E --> F["Known DLLs"]
F --> G["文件系统上的搜索顺序"]
G --> H["确定实际加载的 DLL"]
3. unpackaged app 的标准搜索顺序
对于所谓普通的桌面应用,如果不用完整路径加载 DLL,Microsoft Learn 说明了 unpackaged app 的标准搜索顺序。在启用 safe DLL search mode 的默认状态下,主要流程如下。1
- DLL redirection
- API sets
- SxS manifest redirection
- loaded-module list
- Known DLLs
- Windows 11 21H2 及更高版本中还包含 package dependency graph
- 应用程序被加载的文件夹
System32- 16 位系统文件夹
- Windows 文件夹
- current folder
PATH
在实务中,尤其要留意以下 3 点。
- current folder 默认情况下排在相当靠后的位置。safe DLL search mode 让 current folder 难以被提前。1
- 不过,排在后面并不代表安全。只要攻击者能控制的目录仍留在搜索对象中,DLL preloading 的空间就依然存在。2
- 在 Windows 11 21H2 及更高版本中,unpackaged app 的搜索说明里也加入了 package dependency graph。如果只记住旧版说明,容易漏掉这个差异。1
4. packaged app 与 unpackaged app 并不相同
Microsoft Learn 为 packaged app 定义了另一套搜索顺序。在 packaged app 中,package dependency graph 会更早发挥作用,搜索思路本身也略有不同。1
忽略这个差异,在引入 MSIX 化或 Windows App SDK 之后,会引发这类混乱:
- 开发阶段以 unpackaged 方式运行时能找到的 DLL,在正式的 package 中却找不到
- package manifest 定义的依赖关系,与老式的
PATH依赖混在一起,导致复现条件发生变化 - 只用一张表来说明“Windows 的 DLL 搜索顺序是这样”,从而漏掉了 packaged app 的行为差异
在写文章或做设计评审时,先分清 “讨论的是 packaged app,还是 unpackaged app”,会更安全。1
5. loaded-module list 与 Known DLLs 到底在做什么
在 DLL 解析中,容易与直觉相悖的是 loaded-module list 和 Known DLLs。
5.1 loaded-module list
Microsoft Learn 说明,系统能够确认同名模块的 DLL 是否已经加载到内存中。1
也就是说,在文件系统搜索之前,会先做出这样的判断:
- 该 DLL 名称是否已经加载过
- 因此,现在是否真的还需要去查找
所以,如果在排查问题时忽略了“这个进程里,别的文件夹里的同名 DLL 已经先被加载了”这个事实,就会误判复现条件。
5.2 Known DLLs
Known DLLs 是 Windows 针对该版本认定为已知的 DLL 清单,可以在 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs 中查看。如果命中该清单,系统就会使用该已知 DLL 的系统副本。1
这里重要的是,Known DLLs 并不是“普通应用只要把同名 DLL 放进应用文件夹就能胜出”那种类型的机制。
如果按与 System32 单纯抢先的逻辑去理解,就会误解其行为。
6. API set 不是“实体 DLL 名称”,而是契约名称
看到 api-ms-win-core-... 这样的名字,很容易联想到“该去哪里找这个 DLL 文件”。但 Microsoft Learn 说明,API set 是指向物理 DLL 的虚拟别名,是把实现与契约分离的机制。3
也就是说,下面这种理解并不准确:
- API set 名称 = 物理 DLL 文件名本身
- API set 的解析 = 与普通 DLL 相同的文件搜索
引入 API set 的思路之后,就能更容易说明这两点:
- 即使不同 Windows 版本或设备类型的实现 DLL 名称不同,也能保持一致
- 调用方不需要固定知道“到底是哪个宿主 DLL 在实现”
7. manifest 与 side-by-side(SxS)是应对 DLL versioning 问题的另一种方案
DLL redirection 与 SxS manifest 并不只是搜索顺序上的小技巧,而是被说明为用来避免 DLL versioning 冲突的机制。789
Microsoft Learn 的整理是:
- manifest 是描述 side-by-side assembly 或 isolated application 的 XML
- side-by-side assembly 是命名、binding、versioning、deployment 的单位
- 加载器会依据 manifest 中记载的依赖关系,判断要 bind 到哪个版本
因此,实务中需要把下面这 3 件事分开考虑:
- 单纯把 private DLL 放进 app folder 的做法
- 使用
.local等 DLL redirection 的做法 - 使用 manifest 进行 side-by-side binding 的做法
这三者虽然都“会影响 DLL 的解析”,但设计意图并不相同。
8. LoadLibraryEx、SetDllDirectory、AddDllDirectory 分别改变了什么
8.1 SetDllDirectory
SetDllDirectory 会改变搜索顺序,但 Microsoft Learn 明确指出,它实质上会禁用 safe DLL search mode。1
也就是说,即使只是想“额外增加一个应用专用文件夹”,实际使用后也会连带改变包括 current folder 处理方式在内的整个搜索空间。
而且,如果在父进程中调用 SetDllDirectory,其影响还可能波及子进程一侧的标准搜索顺序。1
因此,实务中与其随意常态化使用 SetDllDirectory,不如转而使用:
SetDefaultDllDirectoriesAddDllDirectoryLoadLibraryEx的LOAD_LIBRARY_SEARCH_*
8.2 AddDllDirectory
用 AddDllDirectory 添加的路径,需要与 LOAD_LIBRARY_SEARCH_USER_DIRS 搭配使用。
Microsoft Learn 说明,添加多个路径时的搜索顺序是未规定的。15
所以,如果设计中:
- 添加了多个目录
- 并严格依赖其搜索顺序
这样的设计应尽量避免。
8.3 SetDefaultDllDirectories
SetDefaultDllDirectories 被说明为一个 API,用于从标准 DLL search path 中剔除容易带来风险的目录,从而限定搜索对象。4
需要特别掌握的是以下 3 个特性:
- 按进程生效
- 调用之后会在进程的生命周期内持续
- 一旦设置,就无法把标准搜索路径直接恢复到原始的标准形态
从安全性角度考虑,这是一个便于在“启动后立即转向偏安全的搜索空间”这种设计的 API。4
8.4 LoadLibraryEx
LoadLibraryEx 可以通过 LOAD_WITH_ALTERED_SEARCH_PATH 或 LOAD_LIBRARY_SEARCH_* 标志来改变搜索行为。61
在实务中,这个 API 便于应对下面这些需求:
- 想把加载源 DLL 所在的文件夹也纳入搜索对象,包括依赖 DLL
- 只想把范围收紧到应用文件夹、
System32,以及显式添加的用户目录
9. 即使用完整路径加载,依赖 DLL 也不会被固定
在这个主题里,有一点在实务上相当重要却容易被忽略。 Microsoft Learn 说明,即使第一个 DLL 用完整路径加载,该 DLL 的依赖 DLL 仍会按模块名单独搜索。14
也就是说:
- 明确用完整路径加载了
C:\MyApp\plugins\foo.dll - 并不意味着
foo.dll所依赖的bar.dll也一定会从同一个文件夹中取得
如果对此有误解,就会出现:
- 在开发环境中能正常运行
- 在发布环境中却解析到另一个
bar.dll - 依赖 DLL 的冲突变成与环境相关的问题
这类相当棘手的故障。
10. 如何避免 DLL preloading / hijacking
Microsoft Learn 的 DLL security 说明中提到,不带完整路径的动态加载,与攻击者可控制的搜索目标目录相结合,会导致 DLL preloading attack 或 binary planting attack。2
在实务中,可以参照下面这个基本模式来思考:
- 减少像
LoadLibrary("foo.dll")这样使用裸名称加载 - 必要时使用完整路径指定
- 用
SetDefaultDllDirectories收紧进程默认的搜索路径 - 只用
AddDllDirectory添加明确允许的目录 - 用
LOAD_LIBRARY_SEARCH_SYSTEM32、LOAD_LIBRARY_SEARCH_APPLICATION_DIR、LOAD_LIBRARY_SEARCH_USER_DIRS等明确指定搜索范围 - 避免 current folder 或不必要的
PATH依赖
尤其是,以管理员权限运行的进程持有含糊的搜索路径 这种情况非常危险。Microsoft Learn 也说明,一旦加载了恶意 DLL,该 DLL 就会以该进程的权限被执行。2
11. 实务判断清单
在评审 Windows 的 DLL 加载设计时,至少确认以下几点,就能减少事故。
- 该应用是 packaged app 还是 unpackaged app
- 哪些 DLL 来自静态链接,哪些是动态加载
- 是完整路径指定,还是只有模块名
- 是否使用了
SetDllDirectory - 是否具备可以使用
SetDefaultDllDirectories与LOAD_LIBRARY_SEARCH_*的结构 - 是否多次使用
AddDllDirectory,并隐含地依赖了其顺序 - 依赖关系是通过 manifest / SxS / private DLL / redirection 中的哪一种管理的
- current folder 或
PATH是否存在安全上薄弱的前提 - 依赖 DLL 是否会在别的环境中从别的位置被解析
分别确认这 9 点,就能在相当靠前的阶段消灭“找不到 DLL”“加载了不同的 DLL”“只有正式环境启动不了”“在漏洞评审中被卡住”这类问题。
12. 总结
Windows 的 DLL 名称解析,并不只是单纯的“文件夹搜索顺序”。 实际上,是由 DLL redirection、API set、SxS manifest、loaded-module list、Known DLLs,以及通过 API 调用改变的搜索空间 叠加决定的。134
在实务中最重要的,归结起来就是这 5 点:
- 不要把搜索顺序当成一张表死记硬背
- 区分 packaged / unpackaged
- 理解即使用完整路径指定,依赖 DLL 也可能被区别对待
- 不要随意使用
SetDllDirectory - 想偏向安全一侧时,使用
SetDefaultDllDirectories与LoadLibraryEx的搜索标志
DLL 名称解析,是启动故障、环境差异、安全问题很容易一起暴露出来的地方。 正因如此,不仅要理解“Windows 按什么顺序查找”,还值得理解“Windows 本身把什么当作名称解析的前提”。
相关文章
参考资料
- Microsoft Learn: Dynamic-link library search order
- Microsoft Learn: Dynamic-Link Library Security
- Microsoft Learn: Windows API sets
- Microsoft Learn: SetDefaultDllDirectories function
- Microsoft Learn: AddDllDirectory function
- Microsoft Learn: LoadLibraryEx function
- Microsoft Learn: Dynamic-link library redirection
- Microsoft Learn: Manifests
- Microsoft Learn: About Side-by-Side Assemblies
-
Microsoft Learn, Dynamic-link library search order,访问日期:2026年3月24日 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21
-
Microsoft Learn, Dynamic-Link Library Security,访问日期:2026年3月24日 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Windows API sets,访问日期:2026年3月24日 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, SetDefaultDllDirectories function,访问日期:2026年3月24日 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, AddDllDirectory function,访问日期:2026年3月24日 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, LoadLibraryEx function,访问日期:2026年3月24日 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Dynamic-link library redirection,访问日期:2026年3月24日 ↩ ↩2
-
Microsoft Learn, About Side-by-Side Assemblies,访问日期:2026年3月24日 ↩ ↩2 ↩3
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows出现“Windows 已保护你的电脑”提示的原因
整理Windows应用分发时出现SmartScreen警告的原因,从代码签名、EV/OV证书、Azure Artifact Signing、MSIX、Microsoft Store、ClickOnce、企业内部分发到App Control,以实务角度梳理应对方法。
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
ClickOnce 是什么 - 从实务角度整理其原理、更新机制以及适用与不适用的场景
本文围绕用于 .NET Windows 桌面应用发布的 ClickOnce 技术,结合 Mermaid 图整理其清单(manifest)、更新、缓存、签名机制,以及适用与不适用的项目类型。
用 Windows Sandbox 加快应用验证的方法
本文整理如何利用 Windows Sandbox 提升管理员权限问题排查、干净环境复现、权限不足与资源不足复现的效率,并说明 .wsb 与 CLI 的分工使用方式。
自动更新的安全设计——为什么仅靠 HTTPS 还不够
把自动更新当作信任边界来处理,从实务角度整理带签名的 metadata、客户端验证、密钥分离、防回滚措施与 fail-closed 策略。
常见问题
汇总了咨询这一主题时常见的问题。
- 用 LoadLibrary 指定 DLL 名称时,Windows 会按什么顺序查找?
- 在按顺序搜索文件系统之前,会先评估一批前置规则。具体来说,DLL redirection、API set、SxS manifest redirection、loaded-module list、Known DLLs 会先生效,之后才会进入应用程序文件夹、System32、Windows 文件夹、current folder、PATH 等文件系统上的搜索。在默认启用 safe DLL search mode 的情况下,current folder 会被排在相当靠后的位置。此外,packaged app 与 unpackaged app 的搜索顺序思路本身也不一样。
- 用完整路径加载 DLL,其依赖 DLL 也会从同一文件夹读取吗?
- 不一定。即使把第一个 DLL 用完整路径加载,该 DLL 所依赖的 DLL 仍会按模块名单独搜索,因此可能从别的位置解析。如果不了解这一点,就会出现开发环境能跑、但发布环境却解析到另一个依赖 DLL 的、与环境相关的棘手故障。如果想把依赖 DLL 也纳入控制,需要用 LoadLibraryEx 的 LOAD_LIBRARY_SEARCH_* 标志等方式显式指定搜索范围。
- 为什么不应该使用 SetDllDirectory?
- 因为它不仅会改变搜索顺序,还会实质上禁用 safe DLL search mode。哪怕只是想额外增加一个应用专用文件夹,也会连带改变包括 current folder 处理方式在内的整个搜索空间,在安全性上可能适得其反。而且,如果在父进程中调用,还可能影响子进程的搜索顺序。更安全的做法是转而使用 SetDefaultDllDirectories、AddDllDirectory,以及 LoadLibraryEx 的搜索标志。
- 如何防止 DLL 劫持(DLL preloading 攻击)?
- 由于不带完整路径的动态加载,与攻击者可控制的搜索目标目录相结合才会导致攻击,因此要同时收紧这两方面。具体来说,减少使用裸名称的 LoadLibrary,必要时使用完整路径指定,用 SetDefaultDllDirectories 收紧进程默认搜索路径,只用 AddDllDirectory 添加已允许的目录,并避免 current folder 或不必要的 PATH 依赖。尤其是以管理员权限运行的进程如果搜索路径含糊,风险更高,因为恶意 DLL 会以该进程的权限被执行。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。