Windows DLL名前解決の仕組み - 検索順序とSxS

· 更新日: · · Windows, DLL, ローダー, セキュリティ, Windows開発

更新履歴(6件・最終更新 2026年08月23日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

DLL名前解決の前段ルール、検索空間を絞るAPIの使い方、読み込み元の確認手順を図でも追えるように、Mermaid図を23点追加しました(地の文500〜750字につき1図の規約に合わせたものです)。既存の前段ルール評価の図にもキャプションを付け、図番号を通しで振り直しました。本文の文章は変えていません。
記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
C#のプラグイン読み込みの例で、専用フォルダに置いたDLLを`ApplicationDirectory`と`System32`の検索フラグで拾おうとしていたのを直しました。前者はexeのあるフォルダ、後者はOSのフォルダを指すので、プラグインフォルダは見ません。フルパスで指定する形にし、`DllImportSearchPath`には任意のフォルダを指す値が無いこと、依存DLLまでは固定されないことを補足しました。
最小のコード例をC/C++とC#で追加しました(`SetDefaultDllDirectories`から`RemoveDllDirectory`までを通し、C#は属性の宣言だけでなく実際の呼び出しまで示しています)。どのDLLがどこから読まれたかを確認する章を新設し、冒頭に用語表、検索順序を12行の表として整理しました。
日本語記事なのに英語のままだった「関連記事」「参考リンク」の見出しを日本語に直し、関連記事リンクの文言をリンク先の現在のタイトルに揃えました。
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589732)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「Windows DLL名前解決の仕組み - 検索順序とSxS」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589732 https://comcomponent.com/blog/2026/03/24/002-windows-dll-name-resolution/

DOI(最新版)
10.5281/zenodo.21589732
DOI(この版)
10.5281/zenodo.22064725

Windows でネイティブ DLL を扱う話になると、かなりの頻度で次のような混乱が起きます。

  • LoadLibrary("foo.dll") と書いたら、実際にはどこを見に行くのか
  • 実行ファイルと同じフォルダに置いたのに、なぜ別の DLL が読み込まれるのか
  • System32 が優先されるのか、アプリフォルダが優先されるのか
  • manifest や API set や Known DLLs は、どの段階で効くのか
  • SetDllDirectoryAddDllDirectory を使うと、何が変わるのか
  • DLL 植え込み攻撃や DLL hijacking は、何をすると起きやすいのか

この話は、単に「検索順序を 1 行で覚える」だけでは実務で役に立ちません。 実際には、Windows のローダーは、ファイルシステムを順番に舐める前に、いくつかの特別ルールを先に評価します。

検索順序の暗記だけでは足りない理由DLLの名前解決は検索順序を1行で覚えるだけでは実務で役に立たず、Windowsのローダーはファイルシステムを順番に探す前に、いくつかの特別ルールを先に評価することを示す図。検索順序を1行で覚える実務では役に立たないWindowsローダーの実際まず特別ルールを 先に評価するそのあとでファイル システムを順に探す

図1: 名前解決は「フォルダ探索」の前に、前段の特別ルールから始まる。

この記事では、Windows での DLL の名前解決を、unpackaged app と packaged app の違い、Known DLLs、loaded-module list、API set、side-by-side manifest、LoadLibraryEx 系 API の影響 まで含めて、実務向けに整理します。 内容は 2026 年 3 月時点 で Microsoft Learn の公開情報を前提にしています。123456789

この記事で使う用語

以降で説明なしに出てくる言葉を、先に一行ずつ整理しておきます。詳しい話は本文の各章で扱います。

用語 ひとことで言うと
packaged app / unpackaged app MSIX などのパッケージとして配布・インストールされるアプリか、従来どおりフォルダに実行ファイルを置く形のアプリか。検索順序の定義そのものが別です (3 章・4 章)
safe DLL search mode 既定で有効な設定で、current folder を検索順序の後ろへ移します。レジストリ値 SafeDllSearchMode を 0 にすると無効になります1
DLL redirection 実行ファイルと同じ場所に アプリ名.exe.local という目印を置くと、ローダーが実行ファイルのフォルダを先に見るようになる仕組み。.local、DotLocal とも呼ばれます (7 章)7
SxS (side-by-side) 同じ DLL の複数バージョンを共存させ、どのバージョンへ結び付けるかを manifest に書いて指定する仕組み。side-by-side の略で、日本語では「並置」と説明されることもあります (7 章)9
loaded-module list 同じモジュール名の DLL がすでにそのプロセスのメモリに読み込まれていないか、をシステムが確認する仕組み。どのフォルダから読まれたかに関係なく、読み込み済みならそれが使われます (5.1)1
Known DLLs Windows がそのバージョンで既知とみなす DLL の一覧。該当する DLL は、システム側のコピーが使われます (5.2)1
API set api-ms-win-... のような契約名。実装している物理 DLL を隠す仮想エイリアスです (6 章)3
package dependency graph アプリのパッケージ本体と、パッケージ マニフェストの Dependencies セクションに PackageDependency として書かれた依存パッケージの集合。マニフェストに書かれた順に検索されます1

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 は、OS が既知の特定 DLL をシステム側のコピーへ結び付ける仕組みで、通常のアプリ側配置で上書きする話ではありません。1
  • API set は「実体 DLL 名そのもの」ではなく、実装 DLL を隠蔽する仮想エイリアス です。api-ms-win-... のような名前を見て、通常の DLL 検索と同じ感覚で考えると誤解しやすいです。3
  • SetDllDirectory は検索順序を変えるだけでなく、safe DLL search mode を実質的に無効化する 振る舞いを持つため、安易に使うとセキュリティ面で逆効果になることがあります。1
  • 実務では、フルパス指定、SetDefaultDllDirectoriesAddDllDirectoryLoadLibraryExLOAD_LIBRARY_SEARCH_* フラグ を組み合わせて、検索範囲を明示的に狭めるのが安全です。4562

要するに、Windows の DLL 名前解決は「どのフォルダが何番目か」だけではなく、「名前を何に解決する前段ルールがあるか」と「API で検索空間をどう変えたか」で決まる と考えるのが実務的です。

DLL名前解決を決める3つの要素WindowsのDLL名前解決は、どのフォルダが何番目かというフォルダ順だけでなく、名前を何に解決するかという前段ルールと、APIで検索空間をどう変えたかという3つの要素の重なりで決まることを示す図。名前の前段ルール実際にロードされるDLLフォルダの検索順APIで変えた検索空間redirection・Known DLLsLoadLibraryExのフラグ

図2: 解決結果は、前段ルール・フォルダ順・APIの3つの重なりで決まる。

この記事の知識マップ

この記事は、WindowsのDLL名前解決が単なるフォルダ探索順ではなく、DLL redirection・API set・SxSマニフェスト・loaded-module list・Known DLLsという前段ルールがファイルシステム探索より先に評価される仕組みであることを整理する。packaged appとunpackaged appでは検索順序の定義自体が異なり、current folderの位置はsafe DLL search modeの有効・無効で変わる。SetDllDirectoryはsafe DLL search modeを実質的に無効化するため、SetDefaultDllDirectoriesとLoadLibraryExの検索フラグ、AddDllDirectoryを組み合わせて検索空間を絞ることがDLLハイジャックの軽減につながる。フルパス指定でも依存DLLまでは自動固定されず、実際の読み込み元はProcess MonitorやListDLLsで確認する。

Windows DLL名前解決の知識マップDLL redirection・API set・SxSマニフェスト・loaded-module list・Known DLLs・package dependency graphがファイルシステム探索より前段で評価されること、packaged appとunpackaged appで検索順序の定義自体が異なること、SetDllDirectoryがsafe DLL search modeを弱める一方でSetDefaultDllDirectoriesとLoadLibraryExの検索フラグが検索空間を絞りDLLハイジャックを軽減すること、実際の読み込み元をProcess Monitor等で確認する手段の関係を示す図利用する利用する利用する利用する利用する利用するで構成できる両立しない利用する両立しない軽減する前提とする利用する用いるのは非推奨推奨される対応軽減する原因になり得る軽減する用いるのは非推奨利用するで確認できるで確認できるで確認できるDLLの検索順序DLLハイジャッキング(DLL preloading攻撃)DLLリダイレクション(.local)API setSxSマニフェストリダイレクションloaded-module listKnown DLLspackage dependency graphcurrent folderの検索safe DLL search modepackaged appunpackaged appSetDllDirectorySetDefaultDllDirectoriesAddDllDirectoryLoadLibraryExDLL検索空間の絞り込み裸の名前によるLoadLibraryフルパス指定でのDLLロード依存DLLの解決DllImportSearchPath(.NET)Process Monitor(procmon.exe)ListDLLstasklist /m

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全23件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

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: 前段ルールが順に評価され、どれにも当たらなかったときだけファイルシステム検索に入る。

3. unpackaged app の標準検索順序

いわゆる普通のデスクトップアプリで、DLL をフルパス指定せずにロードする場合、Microsoft Learn では unpackaged app の標準検索順序が説明されています。safe DLL search mode が有効な既定状態では、次の順です。1

# 探す場所 種類 補足
1 DLL redirection 前段ルール アプリ名.exe.local があるかどうか (7 章)
2 API sets 前段ルール 契約名から実装 DLL へ (6 章)
3 SxS manifest redirection 前段ルール manifest による binding (7 章)
4 loaded-module list 前段ルール 同名モジュールがロード済みかどうか (5.1)
5 Known DLLs 前段ルール 既知 DLL ならシステム側のコピー (5.2)
6 package dependency graph 前段ルール Windows 11 バージョン 21H2 以降。マニフェストに書かれた順に検索されます
7 アプリケーションがロードされたフォルダ ファイルシステム ここからが実際のフォルダ探索です
8 システムフォルダ ファイルシステム 通常は %SystemRoot%\System32GetSystemDirectory で取得できる場所です
9 16-bit system folder ファイルシステム 16 ビット時代の System フォルダ。パスを取得する関数は存在しませんが、検索はされます。現代のアプリで意識する場面はまずなく、「順序の項目としては残っている」とだけ知っていれば十分です
10 Windows フォルダ ファイルシステム GetWindowsDirectory で取得できる場所です
11 current folder ファイルシステム safe DLL search mode が無効だと、ここが 8 の位置まで繰り上がります
12 PATH に並んだディレクトリ ファイルシステム App Paths レジストリキーのアプリ別パスは含まれません

この表は暗記するためのものではありません。 自分のケースがどの段で決まっているのかを当てるための表です。1〜6 で決まっている問題を、7 以降のフォルダ配置で直そうとすると何をやっても変わりません。

検索順序の表の正しい使い方検索順序の表は暗記するためのものではなく、自分のケースがどの段で決まっているのかを当てるためのものであり、前段ルールで決まっている問題をフォルダ配置で直そうとしても何も変わらないことを示す図。前段ルール(1〜6)ファイルシステム(7〜12)症状: 意図しないDLLが 読まれるどの段で 決まっているかフォルダ配置を変えても 何も変わらない配置やパスの整理が 効く領域

図4: 直す前に、その問題がどの段で決まっているかを当てる。

実務で特に効いてくるのはこの 3 点です。

  • current folder は既定ではかなり後ろ です。safe DLL search mode により、current folder を前に出しにくくしています。1
  • ただし 後ろにあるから安全という意味ではありません。攻撃者が支配できるディレクトリが検索対象に残っている時点で、DLL preloading の余地は残ります。2
  • Windows 11 21H2 以降では、unpackaged app の検索説明にも package dependency graph が入っています。古い説明だけを覚えていると見落としやすい差分です。1
current folderの位置と安全性safe DLL search modeが有効な既定ではcurrent folderは検索順序のかなり後ろに置かれるが、後ろにあるから安全という意味ではなく、攻撃者が支配できるディレクトリが検索対象に残っている時点でDLL preloadingの余地は残ることを示す図。safe DLL search mode が有効(既定)current folderは かなり後ろに置かれる後ろにあるから安全 という意味ではない攻撃者が支配できる場所が 検索対象に残れば余地は残る

図5: current folderの位置は下げられているが、それだけでは守りにならない。

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

packagedとunpackagedを最初に分けるpackaged appには別の検索順序が定義されておりpackage dependency graphがより前段で効くため、開発中のunpackaged実行では見つかるDLLが本番packageでは見つからないといった混乱が起き、設計レビューではどちらの話かを最初に分けるのが安全であることを示す図。unpackaged apppackaged appどちらのアプリの話か3章の標準検索順序別の検索順序 (dependency graphが前段)MSIX化後に開発時と 挙動が変わる混乱のもと

図6: 検索順序は1枚の表ではなく、packagedかunpackagedかでまず分かれる。

5. Known DLLs と loaded-module list は何をしているのか

DLL の解決で直感に反しやすいのが、loaded-module listKnown DLLs です。

5.1 loaded-module list

Microsoft Learn では、同じモジュール名の DLL がすでにメモリにロードされているか をシステムが確認できると説明されています。1

つまり、ファイルシステム検索の前に、

  • その DLL 名は、すでにロード済みではないか
  • その結果として、今から探しに行く必要自体があるのか

という判定が入ります。

そのため、調査中に「このプロセスでは別フォルダの同名 DLL が先にロード済みだった」という事実を落とすと、再現条件を読み違えます。

loaded-module listの判定ファイルシステム検索の前に、同じモジュール名のDLLがすでにそのプロセスのメモリにロードされていないかが確認され、ロード済みならどのフォルダから読まれたかに関係なくそれが使われるため、探しに行く必要自体がなくなることを示す図。ロード済み未ロードDLL名の解決要求同名モジュールが ロード済みかそのモジュールを使う (フォルダは関係ない)続きの検索へ進む別フォルダの同名DLLが 先に読まれていると迷子になる

図7: ロード済みの同名モジュールがあれば、新しく探しには行かない。

5.2 Known DLLs

Known DLLs は、Windows がそのバージョンで既知とみなす DLL の一覧で、HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\KnownDLLs で確認できます。該当する DLL なら、システムはその既知 DLL のコピーを使います。1

ここで大事なのは、Known DLLs は「一般アプリが同名 DLL をアプリフォルダへ置けば勝てる」種類の話ではない という点です。 System32 との単純な先着争いとして理解すると、挙動を誤解します。

Known DLLsの働きKnown DLLsはWindowsがそのバージョンで既知とみなすDLLの一覧で、該当するDLLならシステム側のコピーが使われるため、一般アプリが同名DLLをアプリフォルダへ置けば勝てる種類の話ではなく、System32との単純な先着争いとして理解すると誤解することを示す図。該当する該当しない解決したいDLL名Known DLLsに 該当するかシステム側のコピーが 使われる続きの検索へ進むアプリフォルダ配置で 上書きできる話ではない

図8: 既知DLLはシステム側のコピーに結び付き、先着争いにならない。

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

API setは契約名api-ms-win-のような名前は物理DLLファイル名ではなく、実装DLLを隠す仮想エイリアスであり、契約と実装を分離することでWindowsのバージョンやデバイス種別で実装DLL名が違っても整合し、呼び出し側はどのホストDLLが実装しているかを固定で知らなくてよいことを示す図。api-ms-win-…という 契約名仮想エイリアスとして 解決される実装している物理DLLは 隠されているバージョンやデバイスで 実装が違っても整合する通常のファイル探索と 同じ感覚は不正確

図9: API setは「探すファイル名」ではなく、実装を隠す契約名。

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

manifestとside-by-sideの関係manifestはside-by-side assemblyやisolated applicationを記述するXMLで、side-by-side assemblyは命名・binding・versioning・deploymentの単位であり、manifestに記された依存関係でどのバージョンへbindするかをローダーが判断するという、DLL versioning衝突を避ける仕組みであることを示す図。manifest(XML)依存するside-by-side assemblyとバージョンを記述ローダーがどのバージョン へbindするか判断同じDLLの複数バージョン を共存させられる

図10: SxSは検索順序の小技ではなく、versioning衝突への仕組みとしての別解。

そのため、実務ではこの 3 つを分けて考える必要があります。

  • 単に private DLL を app folder に置く話
  • .local などの DLL redirection を使う話
  • manifest による side-by-side binding を使う話

どれも「DLL の解決に影響する」点では近いですが、設計意図は同じではありません。 Microsoft Learn は、既存のアプリに手を入れずに解決したいなら DLL redirection、新しく作るアプリなら side-by-side コンポーネント、という使い分けを案内しています。7

3つの手段の使い分けprivate DLLをapp folderへ置く話、.localなどのDLL redirectionを使う話、manifestによるside-by-side bindingを使う話はどれもDLLの解決に影響するが設計意図は同じではなく、既存のアプリに手を入れずに解決したいならDLL redirection、新しく作るアプリならside-by-sideという使い分けが案内されていることを示す図。どの手段の話かprivate DLLを app folderへ置くDLL redirection (.local)SxS manifest binding既存アプリに手を 入れずに直したいとき新しく作るアプリの 依存管理

図11: 似て見える3つの手段は、設計意図で使い分ける。

7.1 .local は具体的に何をしているのか

1 行で流されがちなので、unpackaged app での挙動を分けて書いておきます。7

  • 置くもの: リダイレクトファイルの名前は 実行ファイル名.local です。Editor.exe なら Editor.exe.local を、実行ファイルと同じフォルダに置きます。読み込ませたい DLL も同じフォルダに置きます
  • 中身: ファイルの中身は無視されます。 存在すること自体が、DLL をロードするときに実行ファイルのフォルダを先に見させる合図になります
  • 効く範囲: フルパス指定のロードにも、モジュール名だけのロードにも効きます。LoadLibraryLoadLibraryEx に渡したパスに関係なく、実行ファイルのフォルダに同名 DLL があればそちらが読まれます。COM のように登録先が 1 つしかない場面を救うための仕様です
  • 見つからなかったとき: 実行ファイルのフォルダに無ければ、通常の検索順序に戻ります
  • フォルダ形式: Editor.exe.local というフォルダを作り、その中に DLL を置く形でも動きます
  • マシン全体で有効にする: HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution OptionsDevOverrideEnable という DWORD 値を作って 1 にし、再起動します。これを設定すると、アプリが application manifest を持っていても .local によるリダイレクトが効くようになります
  • packaged app の場合: 置き場所が変わり、<パッケージのインストール先>\microsoft.system.package.metadata\application.local\ を見にいきます

ここで 1 つ、見落とすと調査が迷子になる副作用があります。 DLL redirection を使っていて、かつアプリが検索順序上のドライブやディレクトリすべてにアクセスできない場合、LoadLibrary はアクセスを拒否された時点で検索を打ち切ります。 DLL redirection を使っていなければ、アクセスできないディレクトリは飛ばして検索が続きます。7 .local を置いた環境だけ「その先にあるはずの DLL が見つからない」と言われたら、この違いを疑ってください。

.localの挙動実行ファイル名.localという目印が実行ファイルと同じフォルダにあると、その中身に関係なく存在自体が合図となり、フルパス指定のロードにも効いて実行ファイルのフォルダが先に見られ、無ければ通常の検索順序に戻ることと、アクセス拒否で検索が打ち切られる副作用があることを示す図。あるない実行ファイル名.localを置く実行ファイル側を先に見る同名DLLはあるかフルパスでもそちらを読む通常の検索順序に戻る副作用: 拒否で検索打ち切り

図12: .localは存在自体が合図で、フルパス指定のロードにまで効く。

8. LoadLibraryExSetDllDirectoryAddDllDirectory で何が変わるか

8.1 SetDllDirectory

SetDllDirectory は検索順序を変えますが、Microsoft Learn では safe DLL search mode を実質的に無効化する と明記されています。1

つまり、「アプリ専用フォルダを 1 つ足したいだけ」のつもりで使っても、結果として current folder の扱いなどを含めた検索空間が変わります。

さらに、親プロセスで SetDllDirectory を呼ぶと、その影響が子プロセス側の標準検索順序にも及ぶことがあります。1

このため、実務では SetDllDirectory を雑に常用するより、

  • SetDefaultDllDirectories
  • AddDllDirectory
  • LoadLibraryExLOAD_LIBRARY_SEARCH_*

に寄せる方が安全です。456

SetDllDirectoryの落とし穴SetDllDirectoryはアプリ専用フォルダを1つ足したいだけのつもりでも、safe DLL search modeを実質的に無効化して検索空間全体を変え、親プロセスで呼ぶと子プロセス側の検索順序にも影響が及ぶことがあるため、SetDefaultDllDirectoriesやAddDllDirectoryやLoadLibraryExの検索フラグに寄せるほうが安全であることを示す図。SetDllDirectoryで フォルダを1つ足すsafe DLL search modeが 実質的に無効化されるcurrent folderを含む 検索空間全体が変わる子プロセスの検索順序に 影響が及ぶことがある代わりにSetDefaultDllDirectories 系のAPIへ寄せる

図13: 「1フォルダ足すだけ」のつもりが、検索空間全体を弱める。

8.2 AddDllDirectory

AddDllDirectory で追加したパスは、LOAD_LIBRARY_SEARCH_USER_DIRS と組み合わせて使います。 Microsoft Learn では、複数追加した場合の検索順序は 未規定 です。15

なので、

  • 複数ディレクトリを追加した
  • その探索順まで厳密に期待した

という設計は避けた方がよいです。

AddDllDirectoryの使い方と注意AddDllDirectoryで追加したパスはLOAD_LIBRARY_SEARCH_USER_DIRSと組み合わせて使うものであり、複数追加した場合の検索順序は未規定なので、複数ディレクトリを追加してその探索順まで厳密に期待する設計は避けるべきであることを示す図。AddDllDirectoryで パスを追加LOAD_LIBRARY_SEARCH_ USER_DIRSとセットで効く複数追加時の順序は 未規定探索順に依存する 設計は避ける

図14: 追加したユーザーディレクトリ間の順序は、仕様上あてにできない。

8.3 SetDefaultDllDirectories

SetDefaultDllDirectories は、標準の DLL search path から脆弱になりやすいディレクトリを外し、検索対象を限定するための API として説明されています。4

特に押さえておきたい性質はこの 3 つです。

  • プロセス単位で効く
  • 呼び出し後はプロセスの寿命中継続する
  • 一度設定した標準検索パスを、そのまま元の標準形へ戻すことはできない

セキュリティ面を考えるなら、「起動直後に安全寄りの検索空間へ寄せる」設計が取りやすい API です。4

SetDefaultDllDirectoriesの性質SetDefaultDllDirectoriesは標準のDLL検索パスから脆弱になりやすいディレクトリを外して検索対象を限定するAPIで、プロセス単位で効き、呼び出し後はプロセスの寿命中継続し、一度設定した検索パスを元の標準形へ戻すことはできないため、起動直後に安全寄りの検索空間へ寄せる設計が取りやすいことを示す図。起動直後に SetDefaultDllDirectories脆弱になりやすい場所を 検索対象から外すプロセス単位で 寿命中ずっと効く元の標準形へ 戻すことはできない

図15: 起動直後に一度呼んで、プロセス全体を安全側へ寄せるためのAPI。

8.4 LoadLibraryEx

LoadLibraryEx は、LOAD_WITH_ALTERED_SEARCH_PATHLOAD_LIBRARY_SEARCH_* フラグで検索挙動を変えられます。61

実務的には、

  • 依存 DLL を含めて、ロード元 DLL のフォルダも探索対象にしたい
  • アプリフォルダ、System32、明示追加したユーザーディレクトリだけに絞りたい

といった要求に対応しやすい API です。

書く前に押さえておきたい制約が 4 つあります。6

制約 内容
第 2 引数 hFile は将来のための予約で、必ず NULL を渡します
併用不可 LOAD_WITH_ALTERED_SEARCH_PATH は、どの LOAD_LIBRARY_SEARCH_* フラグとも組み合わせられません
フルパス必須 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR を使うなら、第 1 引数はフルパスでなければなりません
複数指定時の順序 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIRLOAD_LIBRARY_SEARCH_APPLICATION_DIRLOAD_LIBRARY_SEARCH_USER_DIRSLOAD_LIBRARY_SEARCH_SYSTEM32 の順に探されます。ただし USER_DIRS の中での順序は未規定です

LOAD_LIBRARY_SEARCH_* フラグを 1 つでも指定すると、標準検索パスは一切使われません。 つまり LOAD_LIBRARY_SEARCH_SYSTEM32 だけを渡した場合、アプリフォルダは探されません。「絞る」とはそういう意味です。

検索フラグを指定すると標準パスは使われないLOAD_LIBRARY_SEARCH_系のフラグを1つでも指定すると標準検索パスは一切使われず、たとえばLOAD_LIBRARY_SEARCH_SYSTEM32だけを渡した場合はアプリフォルダは探されないという、「絞る」ことの意味を示す図。指定しない1つでも指定LOAD_LIBRARY_SEARCH_系 フラグを指定したか標準の検索パスが使われる標準検索パスは 一切使われない指定したフラグの範囲 だけが探索されるSYSTEM32だけなら アプリフォルダは探されない

図16: フラグは「足す」のではなく「その範囲だけに置き換える」指定。

8.5 最小のコード例 (C/C++)

ここまでの API を 1 つにまとめると、次の形になります。 SetDefaultDllDirectoriesLOAD_LIBRARY_SEARCH_*Windows 8 以降の API なので、ヘッダーの対象バージョンを明示する必要があります。46

/* cl /W4 loader.c  (Visual Studio 2022 + Windows SDK 10)
 * SetDefaultDllDirectories / AddDllDirectory / LOAD_LIBRARY_SEARCH_* は
 * Windows 8 以降。Windows 7 も対象にするなら KB2533623 が前提になり、
 * GetProcAddress で Kernel32.dll から実行時に取得する形が必要です。 */
#define _WIN32_WINNT 0x0602   /* Windows 8 */
#include <windows.h>
#include <stdio.h>

int wmain(void)
{
    /* 1. プロセス既定の検索空間を安全側へ寄せる。
     *    current folder と PATH を検索対象から外すのが目的です。
     *    LOAD_LIBRARY_SEARCH_DEFAULT_DIRS は
     *    APPLICATION_DIR + SYSTEM32 + USER_DIRS の組み合わせです。
     *    USER_DIRS を含めておかないと、手順 2 の AddDllDirectory は
     *    プロセス既定には反映されません。 */
    if (!SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)) {
        wprintf(L"SetDefaultDllDirectories failed: %lu\n", GetLastError());
        return 1;
    }

    /* 2. 自前のプラグインフォルダだけを明示的に足す。
     *    AddDllDirectory に渡すのは絶対パスでなければなりません。 */
    const wchar_t *pluginDir = L"C:\\MyApp\\plugins";
    DLL_DIRECTORY_COOKIE cookie = AddDllDirectory(pluginDir);
    if (cookie == NULL) {
        wprintf(L"AddDllDirectory failed: %lu\n", GetLastError());
        return 1;
    }

    /* 3. ロードする。
     *    第 2 引数 hFile は予約済みなので必ず NULL。
     *    ここでフラグを渡すと、手順 1 のプロセス既定ではなく
     *    このフラグの組み合わせだけが使われます。
     *    APPLICATION_DIR を外しているので、アプリフォルダは探されません。 */
    HMODULE h = LoadLibraryExW(
        L"foo.dll",
        NULL,
        LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32);
    if (h == NULL) {
        wprintf(L"LoadLibraryExW failed: %lu\n", GetLastError());
        RemoveDllDirectory(cookie);
        return 1;
    }

    /* 4. どこから読まれたかを必ず確認する。
     *    調査では「読めたかどうか」より「どこから読めたか」が重要です。 */
    {
        wchar_t loadedPath[MAX_PATH];
        DWORD cap = (DWORD)(sizeof(loadedPath) / sizeof(loadedPath[0]));
        DWORD len = GetModuleFileNameW(h, loadedPath, cap);
        if (len > 0 && len < cap) {
            wprintf(L"loaded from: %s\n", loadedPath);
        } else {
            wprintf(L"GetModuleFileNameW failed: %lu\n", GetLastError());
        }
    }

    FreeLibrary(h);
    RemoveDllDirectory(cookie);
    return 0;
}

このコードで押さえている点は 4 つです。

  1. SetDefaultDllDirectories を先に呼ぶ。 一度呼ぶとプロセスの寿命中ずっと効き、標準の検索パスへ戻すことはできません4
  2. AddDllDirectoryLOAD_LIBRARY_SEARCH_USER_DIRS とセットで初めて意味を持つ。 SetDefaultDllDirectoriesUSER_DIRS を含めていない場合、追加したディレクトリは LOAD_LIBRARY_SEARCH_USER_DIRS を指定した LoadLibraryEx の呼び出しでしか使われません5
  3. 後始末をする。 AddDllDirectory が返した cookie は RemoveDllDirectory に渡して外せます5
  4. 読めた場所を出力する。 GetModuleFileNameW の 1 行があるかどうかで、障害時の調査時間が変わります

なお foo.dll が依存する DLL も、この LOAD_LIBRARY_SEARCH_* の範囲で探されます。 foo.dll 自身のフォルダを依存 DLL の探索先にしたい場合は、第 1 引数をフルパスにしたうえで LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR を足します。

最小コード例の流れSetDefaultDllDirectoriesでプロセス既定の検索空間を安全側へ寄せ、AddDllDirectoryでプラグインフォルダを明示的に足し、LoadLibraryExで検索フラグを指定してロードし、GetModuleFileNameWでどこから読まれたかを確認し、最後にRemoveDllDirectoryで後始末するという流れを示す図。1. SetDefaultDllDirectories で既定を安全側へ2. AddDllDirectoryで 許可フォルダを足す3. LoadLibraryExで フラグ指定ロード4. GetModuleFileNameWで 読み込み元を確認5. RemoveDllDirectory で後始末

図17: コード例の骨格は、絞る→足す→ロード→確認→後始末の5手。

8.6 C# から使う場合

.NET でも同じ考え方が使えます。P/Invoke の検索パスは DefaultDllImportSearchPaths 属性で、明示ロードは NativeLibrary.Load で制御します。

// .NET 8 / C# 12
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

// アセンブリ内のすべての P/Invoke について、既定の検索先を System32 に限定する。
// 制約: 絶対パスを指定した P/Invoke には、この属性は適用されません。
//       また Windows 以外のプラットフォームでは効果がありません。
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]

internal static class Program
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint GetTickCount();

    private static void Main()
    {
        // 属性は宣言しただけでは何も起きません。呼び出して初めて効きます。
        Console.WriteLine($"GetTickCount = {GetTickCount()}");

        // プラグインは専用フォルダに置いてある。ここで
        //   NativeLibrary.Load("foo.dll", asm, ApplicationDirectory | System32)
        // と書いてはいけません。ApplicationDirectory が指すのは exe のあるフォルダ、
        // System32 が指すのは OS のフォルダで、どちらもプラグインフォルダを見ません。
        // 読み込みに失敗するか、たまたま exe の隣にあった別の foo.dll を掴みます。
        // 置き場所が分かっているものは、フルパスで指定するのが確実です。
        string pluginDir = Path.Combine(AppContext.BaseDirectory, "plugins");
        string pluginPath = Path.GetFullPath(Path.Combine(pluginDir, "foo.dll"));
        if (!File.Exists(pluginPath))
        {
            throw new FileNotFoundException("プラグインが見つかりません。", pluginPath);
        }

        // パスを渡すオーバーロードは、そのファイルを直接読みます(検索しません)。
        IntPtr handle = NativeLibrary.Load(pluginPath);

        try
        {
            // どこから読まれたかを確認する。
            foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
            {
                if (string.Equals(module.ModuleName, "foo.dll", StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine($"loaded from: {module.FileName}");
                }
            }
        }
        finally
        {
            NativeLibrary.Free(handle);
        }
    }
}

DllImportSearchPath の値は、LOAD_LIBRARY_SEARCH_* フラグと対応しています。 そのため、C# 側で「System32 だけ」と書いた場合も、8.4 の制約がそのまま当てはまります。アプリケーションディレクトリは探されません。

ここで注意したいのが、DllImportSearchPath には「任意のフォルダ」を指す値がないことです。ApplicationDirectory は exe のあるフォルダ、System32 は OS のフォルダを指すもので、プラグイン用に自分で決めたフォルダを表す値はありません。だから専用フォルダに置いたものを検索フラグで拾おうとしても届かず、上のようにフルパスで指定することになります。

なお、フルパスで読んでも 9 章のとおり foo.dll が依存する DLL までは固定されません。プラグインが独自の依存 DLL を連れてくるなら、AddDllDirectory でそのフォルダを足したうえで LOAD_LIBRARY_SEARCH_USER_DIRS を効かせるか、依存関係ごと 1 つのフォルダに収めて LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR を使う、という C 側の作法がそのまま必要になります。

C#で専用フォルダのDLLを読む考え方DllImportSearchPathには任意のフォルダを指す値がなく、ApplicationDirectoryはexeのあるフォルダ、System32はOSのフォルダを指すため、専用フォルダに置いたプラグインは検索フラグでは届かず、フルパスで指定して直接読むことになり、依存DLLの制御はC側の作法がそのまま必要になることを示す図。任意フォルダを指す 値は存在しない確実な方法プラグインを専用 フォルダに置く検索フラグで拾えるかフラグでは届かないフルパスを組み立てて NativeLibrary.Loadで直接読む依存DLLの制御は C側の作法が必要

図18: .NETでも、自分で決めたフォルダはフルパス指定で読むのが確実。

9. フルパス指定しても依存 DLL までは固定されない

このテーマで実務上かなり重要なのに見落とされやすいのがここです。 Microsoft Learn では、最初の DLL をフルパス指定でロードしても、その DLL の依存 DLL はモジュール名だけで検索される と説明されています。14

つまり、

  • C:\\MyApp\\plugins\\foo.dll を明示ロードした
  • だから foo.dll が依存する bar.dll も同じフォルダから必ず取られる

とは限りません。

この誤解があると、

  • 開発環境では動く
  • 配布先では別の bar.dll が解決される
  • 依存 DLL の衝突が再現環境依存になる

という、少し厄介な障害になります。

フルパス指定でも依存DLLは固定されない最初のDLLをフルパス指定でロードしても、そのDLLが依存するDLLはモジュール名だけで検索される扱いになるため、別の場所から解決されることがあり、開発環境では動くのに配布先では別の依存DLLが解決される環境依存の障害になることを示す図。foo.dllをフルパスで ロードするfoo.dll自体は 指定どおり読まれる依存するbar.dllは モジュール名だけで検索環境によって別の場所 から解決されうる開発では動くのに配布先で 壊れる環境依存障害

図19: フルパスで固定できるのは最初の1つだけで、依存は別扱い。

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

DLL preloading攻撃が成立する組み合わせフルパスなしの動的ロードと、攻撃者が支配できる検索対象ディレクトリの組み合わせがDLL preloading attackにつながり、悪意あるDLLがロードされるとそのプロセスの権限で実行されるため、裸の名前ロードを減らして検索空間を絞るのが対策であることを示す図。フルパスなしの 動的ロードDLL preloadingの 成立条件がそろう攻撃者が支配できる 検索対象ディレクトリ悪意あるDLLがプロセスの 権限で実行される対策: 裸の名前を減らし 検索空間を絞る

図20: 攻撃は「曖昧な名前」と「支配できる場所」の掛け算で成立する。

11. どの DLL がどこから読まれたかを確認する

ここまでは仕様の話でした。実際の調査では、推測せずに確認するほうが早いです。 目的別に 4 つ挙げます。

知りたいこと 使うもの 見る場所
どこを探して、どこで見つけたか Process Monitor10 ファイルアクセスの記録
いま何がロードされているか Process Explorer、tasklist /m11 プロセスのモジュール一覧
ある DLL を、どのプロセスが掴んでいるか ListDLLs12 プロセス横断の一覧
デバッグ中に実際に読まれたパス Visual Studio のモジュールウィンドウ 「デバッグ」>「ウィンドウ」>「モジュール」7

11.1 Process Monitor で探索の足跡を追う

いちばん情報量が多いのがこれです。手順はこうなります。10

  1. Process Monitor を管理者として起動し、記録を開始する
  2. 「Filter」>「Filter…」を開き、Process Name is に対象の exe 名を入れて追加する
  3. 同じ画面で Path ends with .dll を追加する
  4. 対象のアプリを起動し、問題が起きたところで記録を停止する

このとき見るのは Result 列 です。 ローダーは検索順序に沿って上から順に開こうとするので、見つからなかった場所には NAME NOT FOUND、実際に開けた場所には SUCCESS が並びます。 つまり、NAME NOT FOUND が並んだ最後の次の行が、実際に採用されたパスになります。

3 章の表と突き合わせると、そのケースがどの段で決まったのかが分かります。 前段ルール (表の 1〜6) で決まっている場合は、そもそもファイルを探しに行った記録が出ません。これも重要な手がかりです。

Process Monitorの記録の読み方ローダーは検索順序に沿って上から順に開こうとするため、見つからなかった場所にはNAME NOT FOUND、実際に開けた場所にはSUCCESSが並び、NAME NOT FOUNDが並んだ最後の次の行が実際に採用されたパスになり、前段ルールで決まった場合はファイルを探しに行った記録自体が出ないことを示す図。Result列を上から読むNAME NOT FOUNDの並び =探して無かった場所その次のSUCCESSが 実際に採用されたパス記録が出ない場合は 前段ルールで決まっている

図21: 足跡はResult列に残り、記録が無いこと自体も手がかりになる。

11.2 ロード済みモジュールを一覧する

すでに起動しているプロセスについて、何がどこから読まれているかを見るだけなら、追加ツールなしでも確認できます。

tasklist /m foo.dll

このコマンドは、指定したモジュールを読み込んでいるプロセスの名前と PID を一覧します。11 どのプロセスが対象かを絞ってから、Process Explorer の下部ペインを DLL 表示に切り替えると、そのプロセスが読んでいる DLL のフルパスまで確認できます。

Sysinternals の ListDLLs を使うと、同じことをコマンドラインで、しかもプロセス横断で確認できます。12

5.1 の loaded-module list が効いているかどうかは、この方法でしか分かりません。 同名の DLL が別フォルダから先にロード済みなら、そのプロセスでは新しく探しに行かないためです。

ロード済みモジュールの確認手順tasklist /mで指定モジュールを読み込んでいるプロセスを一覧して対象を絞り、Process ExplorerのDLL表示やListDLLsで読み込まれているDLLのフルパスまで確認するという手順で、loaded-module listが効いているかどうかはこの方法でしか分からないことを示す図。tasklist /m foo.dllで 掴んでいるプロセスを絞るProcess Explorerや ListDLLsでフルパスを見るどこから読まれたかが 事実として確定するloaded-module listの 影響はここでしか見えない

図22: いま読まれているものは、推測ではなくモジュール一覧で確定させる。

12. 実務での判断チェックリスト

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 が別環境で別の場所から解決されないか
  10. 症状が出ている環境で、実際にどこから読まれたかを確認したか (11 章)

この 10 点を分けて確認すると、「DLL が見つからない」「違う DLL が読まれた」「本番だけ起動しない」「脆弱性レビューで止まる」といった問題を、かなり前段で潰せます。

レビューで確認する流れDLLロード設計のレビューでは、packagedかunpackagedかというアプリの形、フルパスかモジュール名かというロードのしかた、SetDllDirectoryの有無や検索フラグなどAPIの使い方、manifestや依存DLLの管理、そして症状が出ている環境で実際にどこから読まれたかの確認、という流れで見ると事故が減ることを示す図。アプリの形を確認 (packaged / unpackaged)ロードのしかたを確認 (フルパスか名前だけか)APIの使い方を確認 (SetDllDirectory・フラグ)依存管理を確認 (manifest / SxS / PATH)実地で読み込み元を 確認する(11章)

図23: チェックリストは、形→ロード→API→依存→実地確認の順で消化できる。

13. まとめ

Windows での DLL の名前解決は、単なる「フォルダの探索順」ではありません。 実際には、DLL redirection、API set、SxS manifest、loaded-module list、Known DLLs、そして API 呼び出しで変更される検索空間 が重なって決まります。134

実務で一番大事なのは、この 6 つに尽きます。

  • 検索順序を 1 枚の表として暗記しない。表は、自分のケースがどの段で決まったかを当てるために使う
  • packaged / unpackaged を分ける
  • フルパス指定でも依存 DLL は別扱いになりうると理解する
  • SetDllDirectory を安易に使わない
  • 安全側へ寄せるなら SetDefaultDllDirectoriesLoadLibraryEx の検索フラグを使う
  • 推測で終わらせず、Process Monitor などで実際に読まれたパスを確認する

DLL 名の解決は、起動障害、環境差異、セキュリティ問題がまとめて表面化しやすい場所です。 だからこそ、「Windows がどの順番で探すか」だけでなく、「Windows がそもそも何を名前解決の前提として扱っているか」まで理解しておく価値があります。

この記事の結論DLL名の解決は起動障害・環境差異・セキュリティ問題がまとめて表面化しやすい場所であり、どの順番で探すかだけでなく、Windowsがそもそも何を名前解決の前提として扱っているかまで理解しておく価値があることを示す図。どの順番で探すか (フォルダの順序)両方を理解して はじめて説明がつく何を前提として扱うか (前段ルールと検索空間)起動障害・環境差異・ セキュリティをまとめて潰せる

図24: 順序の暗記ではなく、前提の理解までがDLL名前解決の実務知識。

関連記事

参考リンク

  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
  10. Microsoft Learn: Process Monitor
  11. Microsoft Learn: ListDLLs
  12. Microsoft Learn: tasklist
  1. Microsoft Learn, Dynamic-link library search order, accessed March 24, 2026  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

  2. Microsoft Learn, Dynamic-Link Library Security, accessed March 24, 2026  2 3 4 5

  3. Microsoft Learn, Windows API sets, accessed March 24, 2026  2 3 4 5 6

  4. Microsoft Learn, SetDefaultDllDirectories function, accessed March 24, 2026  2 3 4 5 6 7 8 9

  5. Microsoft Learn, AddDllDirectory function, accessed March 24, 2026  2 3 4 5 6

  6. Microsoft Learn, LoadLibraryEx function, accessed March 24, 2026  2 3 4 5 6

  7. Microsoft Learn, Dynamic-link library redirection, accessed March 24, 2026  2 3 4 5 6 7

  8. Microsoft Learn, Manifests, accessed March 24, 2026  2 3

  9. Microsoft Learn, About Side-by-Side Assemblies, accessed March 24, 2026  2 3 4

  10. Microsoft Learn, Process Monitor  2

  11. Microsoft Learn, tasklist  2

  12. Microsoft Learn, ListDLLs  2

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

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を実質的に無効化する振る舞いを持つためです。アプリ専用フォルダを1つ足したいだけのつもりでも、current folderの扱いを含めた検索空間全体が変わり、セキュリティ面で逆効果になることがあります。さらに、親プロセスで呼ぶと子プロセス側の検索順序にも影響が及ぶことがあります。代わりにSetDefaultDllDirectories、AddDllDirectory、LoadLibraryExの検索フラグに寄せるほうが安全です。
DLLハイジャック(DLL preloading攻撃)を防ぐにはどうすればよいですか?
フルパスなしの動的ロードと、攻撃者が支配できる検索対象ディレクトリの組み合わせが攻撃につながるため、両方を絞ります。具体的には、裸の名前によるLoadLibraryを減らし、必要ならフルパス指定を使い、SetDefaultDllDirectoriesでプロセス既定の検索パスを絞り、許可したディレクトリだけAddDllDirectoryで追加し、current folderや不用意なPATH依存を避けます。特に管理者権限で動くプロセスが曖昧な検索パスを持つ状況は危険で、悪意あるDLLがそのプロセスの権限で実行されます。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る