Windows DLL 名称解析机制 - 搜索顺序与 SxS

· · Windows, DLL, 加载器, 安全, Windows开发

Windows 在处理原生 DLL 时,经常会引发下面这些困惑:

  • 写了 LoadLibrary("foo.dll"),实际会去哪里查找
  • 明明放在与可执行文件相同的文件夹里,为什么会加载别的 DLL
  • 到底是 System32 优先,还是应用文件夹优先
  • manifest、API set、Known DLLs 分别在哪个阶段起作用
  • 使用 SetDllDirectoryAddDllDirectory 会改变什么
  • 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
  • 实务中,完整路径指定、SetDefaultDllDirectoriesAddDllDirectoryLoadLibraryExLOAD_LIBRARY_SEARCH_* 标志 组合起来,明确收紧搜索范围才是安全的做法。4562

总而言之,可以这样理解:Windows 的 DLL 名称解析,不仅取决于“哪个文件夹排第几”,还取决于“有没有把名称解析到别处的前置规则”,以及“用 API 把搜索空间改成了什么样”,这才是实务上的思路。

2. DLL 的名称解析,在“文件夹搜索”之前还有前置规则

Microsoft Learn 对 DLL search order 的说明中提到,加载 DLL 时,首先会把下面这些要素当作搜索顺序的一部分来处理。1

  1. DLL redirection
  2. API sets
  3. SxS manifest redirection
  4. loaded-module list
  5. Known DLLs

之后,才会进入 app folder、System32、Windows 文件夹、PATH 等文件系统上的搜索。1

如果忽略这一点,可能会觉得“怎么会有东西比应用文件夹更先被决定”,但从 Windows 加载器的说明来看,这恰恰才是正统的行为。

要解析 DLL 名称DLL redirectionAPI setSxS manifest redirectionloaded-module listKnown DLLs文件系统上的搜索顺序确定实际加载的 DLL

3. unpackaged app 的标准搜索顺序

对于所谓普通的桌面应用,如果不用完整路径加载 DLL,Microsoft Learn 说明了 unpackaged app 的标准搜索顺序。在启用 safe DLL search mode 的默认状态下,主要流程如下。1

  1. DLL redirection
  2. API sets
  3. SxS manifest redirection
  4. loaded-module list
  5. Known DLLs
  6. Windows 11 21H2 及更高版本中还包含 package dependency graph
  7. 应用程序被加载的文件夹
  8. System32
  9. 16 位系统文件夹
  10. Windows 文件夹
  11. current folder
  12. 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 listKnown 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 在实现”

3

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 到哪个版本

89

因此,实务中需要把下面这 3 件事分开考虑:

  • 单纯把 private DLL 放进 app folder 的做法
  • 使用 .local 等 DLL redirection 的做法
  • 使用 manifest 进行 side-by-side binding 的做法

这三者虽然都“会影响 DLL 的解析”,但设计意图并不相同。

8. LoadLibraryExSetDllDirectoryAddDllDirectory 分别改变了什么

8.1 SetDllDirectory

SetDllDirectory 会改变搜索顺序,但 Microsoft Learn 明确指出,它实质上会禁用 safe DLL search mode1

也就是说,即使只是想“额外增加一个应用专用文件夹”,实际使用后也会连带改变包括 current folder 处理方式在内的整个搜索空间。

而且,如果在父进程中调用 SetDllDirectory,其影响还可能波及子进程一侧的标准搜索顺序。1

因此,实务中与其随意常态化使用 SetDllDirectory,不如转而使用:

  • SetDefaultDllDirectories
  • AddDllDirectory
  • LoadLibraryExLOAD_LIBRARY_SEARCH_*

这样更安全。456

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_PATHLOAD_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_SYSTEM32LOAD_LIBRARY_SEARCH_APPLICATION_DIRLOAD_LIBRARY_SEARCH_USER_DIRS 等明确指定搜索范围
  • 避免 current folder 或不必要的 PATH 依赖

尤其是,以管理员权限运行的进程持有含糊的搜索路径 这种情况非常危险。Microsoft Learn 也说明,一旦加载了恶意 DLL,该 DLL 就会以该进程的权限被执行。2

11. 实务判断清单

在评审 Windows 的 DLL 加载设计时,至少确认以下几点,就能减少事故。

  1. 该应用是 packaged app 还是 unpackaged app
  2. 哪些 DLL 来自静态链接,哪些是动态加载
  3. 是完整路径指定,还是只有模块名
  4. 是否使用了 SetDllDirectory
  5. 是否具备可以使用 SetDefaultDllDirectoriesLOAD_LIBRARY_SEARCH_* 的结构
  6. 是否多次使用 AddDllDirectory,并隐含地依赖了其顺序
  7. 依赖关系是通过 manifest / SxS / private DLL / redirection 中的哪一种管理的
  8. current folder 或 PATH 是否存在安全上薄弱的前提
  9. 依赖 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
  • 想偏向安全一侧时,使用 SetDefaultDllDirectoriesLoadLibraryEx 的搜索标志

DLL 名称解析,是启动故障、环境差异、安全问题很容易一起暴露出来的地方。 正因如此,不仅要理解“Windows 按什么顺序查找”,还值得理解“Windows 本身把什么当作名称解析的前提”

相关文章

参考资料

  1. Microsoft Learn: Dynamic-link library search order
  2. Microsoft Learn: Dynamic-Link Library Security
  3. Microsoft Learn: Windows API sets
  4. Microsoft Learn: SetDefaultDllDirectories function
  5. Microsoft Learn: AddDllDirectory function
  6. Microsoft Learn: LoadLibraryEx function
  7. Microsoft Learn: Dynamic-link library redirection
  8. Microsoft Learn: Manifests
  9. Microsoft Learn: About Side-by-Side Assemblies
  1. 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

  2. Microsoft Learn, Dynamic-Link Library Security,访问日期:2026年3月24日  2 3 4 5

  3. Microsoft Learn, Windows API sets,访问日期:2026年3月24日  2 3 4 5

  4. Microsoft Learn, SetDefaultDllDirectories function,访问日期:2026年3月24日  2 3 4 5 6 7

  5. Microsoft Learn, AddDllDirectory function,访问日期:2026年3月24日  2 3 4

  6. Microsoft Learn, LoadLibraryEx function,访问日期:2026年3月24日  2 3 4

  7. Microsoft Learn, Dynamic-link library redirection,访问日期:2026年3月24日  2

  8. Microsoft Learn, Manifests,访问日期:2026年3月24日  2 3

  9. Microsoft Learn, About Side-by-Side Assemblies,访问日期:2026年3月24日  2 3

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

用 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 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表