Windows DLL名前解決の知識マップ(知識グラフ)
この記事は、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で確認する。
DLLの検索順序
Windowsが実行ファイルの起動時に限らず、LoadLibrary系APIによる動的な読み込み時も含めて、DLL redirectionやKnown DLLsなどの前段ルールを経てDLLを探す、決まった順序でフォルダーを辿る仕組み。
DLL探索順
DLLハイジャッキング(DLL preloading攻撃)
フルパスなしの動的ロードと、攻撃者が支配できる検索対象ディレクトリの組み合わせによって、悪意あるDLLをアプリの権限で実行させる攻撃。
DLLリダイレクション(.local)
実行ファイルと同じ場所に「実行ファイル名.local」という目印を置くと、ローダーが実行ファイルのフォルダを先に見るようになる仕組み。
API set
api-ms-win-...のような契約名で、実装している物理DLLを隠す仮想エイリアスの仕組み。
SxSマニフェストリダイレクション
マニフェストに記された依存関係に基づき、どのバージョンのDLLへ結び付ける(bind)かをローダーが判断する仕組み。
loaded-module list
同じモジュール名のDLLが既にそのプロセスのメモリにロードされていないかを、ファイルシステム検索より前に確認する仕組み。どのフォルダから読まれたかに関係なく、読み込み済みならそれが使われる。
ロード済みモジュール一覧
Known DLLs
Windowsがそのバージョンで既知とみなすDLLの一覧で、該当するDLLはシステム側のコピーが使われる。レジストリのKnownDLLsキーで確認できる。
package dependency graph
アプリのパッケージ本体と、パッケージマニフェストのDependenciesセクションに書かれた依存パッケージの集合。マニフェストに書かれた順に検索される。Windows 11 バージョン21H2以降、unpackaged appの検索順序にも含まれる。
パッケージ依存グラフ
current folderの検索
unpackaged appの標準検索順序に含まれる、プロセスのカレントディレクトリを対象にした検索段。safe DLL search modeの有効・無効で順位が変わる。
カレントフォルダ検索
safe DLL search mode
既定で有効な設定で、current folderを検索順序の後ろへ移す。レジストリ値SafeDllSearchModeを0にすると無効になる。
packaged app
MSIXなどのパッケージとして配布・インストールされるアプリ。package dependency graphがより前段で効くなど、unpackaged appとは異なる検索順序が定義される。
パッケージアプリ
MSIXアプリ
unpackaged app
MSIXなどのパッケージを使わず、従来どおりフォルダに実行ファイルを置く形で配布されるアプリ。
アンパッケージアプリ
従来型のデスクトップアプリ
SetDllDirectory
DLL検索順序を変更するWin32 APIで、safe DLL search modeを実質的に無効化する副作用を持つ。親プロセスで呼ぶと子プロセスの検索順序にも影響し得る。
SetDefaultDllDirectories
標準のDLL検索パスから脆弱になりやすいディレクトリを外し、プロセス既定の検索対象を限定するWin32 API。プロセス単位で効き、標準形へ戻すことはできない。
AddDllDirectory
DLLの検索対象ディレクトリを1つ追加するWin32 API。LOAD_LIBRARY_SEARCH_USER_DIRSと組み合わせて使い、複数追加時の探索順は未規定。
LoadLibraryEx
LOAD_WITH_ALTERED_SEARCH_PATHやLOAD_LIBRARY_SEARCH_*フラグでDLLの検索挙動を明示的に変更できるDLLロード用Win32 API。
DLL検索空間の絞り込み
SetDefaultDllDirectoriesやLoadLibraryExのLOAD_LIBRARY_SEARCH_*フラグ、AddDllDirectoryを組み合わせて、DLLの検索対象を明示的に絞り込む設計方針。
検索空間の明示的な限定
裸の名前によるLoadLibrary
フルパスを指定せずモジュール名だけでLoadLibraryを呼び出す実装。攻撃者が支配できる検索対象ディレクトリと組み合わさるとDLLハイジャックにつながる。
フルパスなしの動的ロード
フルパス指定でのDLLロード
LoadLibrary系APIにモジュール名だけでなく完全なパスを渡してDLLをロードする方式。裸の名前によるロードに起因するDLLハイジャックを軽減する。
フルパス指定ロード
依存DLLの解決
ロードされたDLLがさらに要求する依存DLLの名前解決。最初のDLLをフルパス指定でロードしても、依存DLLはモジュール名だけで検索される扱いになり自動的には固定されない。
依存DLL解決
DllImportSearchPath(.NET)
.NETのP/InvokeやNativeLibrary.Loadで、DLLの検索先を限定する属性・パラメーター。値はLOAD_LIBRARY_SEARCH_*フラグに対応する。
Process Monitor(procmon.exe)
ファイルI/O・レジストリ・プロセス/スレッド活動をリアルタイムに監視できるSysinternalsのツール。Advanced Outputを有効にすると、IRPやファストI/Oのカーネル側の語彙で操作を表示する。
ListDLLs
プロセス横断で、どのDLLがどこから読み込まれているかをコマンドラインで確認できるSysinternalsのツール。
tasklist /m
指定したモジュールを読み込んでいるプロセスの名前とPIDを一覧するWindowsのコマンド。
DLL名前解決は、ファイルシステム検索の前段ルールの1つとしてDLL redirectionを評価する
DLL名前解決は、ファイルシステム検索の前段ルールの1つとしてAPI setによる契約名から実装DLLへの変換を評価する
DLL名前解決は、ファイルシステム検索の前段ルールの1つとしてSxSマニフェストによるバージョンbindingを評価する
DLL名前解決は、ファイルシステム検索の前段ルールの1つとして同名モジュールが既にロード済みかを確認する
DLL名前解決は、ファイルシステム検索の前段ルールの1つとしてKnown DLLsへの該当を確認する
Windows 11 バージョン21H2以降、unpackaged appの検索順序にもpackage dependency graphが前段ルールとして含まれる
current folderが検索順序の何番目に来るかは、safe DLL search mode(レジストリ値SafeDllSearchMode)の有効・無効で変わる
packaged appとunpackaged appでは検索順序の定義自体が異なり、同じ実行形態が両方に同時に該当することはない
packaged appではpackage dependency graphがより前段で効き、unpackaged appとは検索の考え方自体が異なる
SetDllDirectoryはsafe DLL search modeを実質的に無効化する振る舞いを持つ
SetDefaultDllDirectoriesで標準検索パスから脆弱になりやすいディレクトリを外すことは、DLL preloading攻撃の軽減につながる
AddDllDirectoryで追加したディレクトリを「プロセス既定の検索」に含めるには、SetDefaultDllDirectoriesにUSER_DIRSを含めておく必要がある。ただしSetDefaultDllDirectoriesを呼ばなくても、LoadLibraryExの呼び出しごとにLOAD_LIBRARY_SEARCH_USER_DIRSフラグを指定すれば、追加したディレクトリをその呼び出しの探索対象にできる
LoadLibraryExはLOAD_LIBRARY_SEARCH_USER_DIRSフラグと組み合わせて、AddDllDirectoryで追加したディレクトリを探索範囲に含める
SetDllDirectoryは検索順序を変えるだけでなくsafe DLL search modeを実質的に無効化するため、検索空間を絞る安全側の設計に使うのは推奨されない
LOAD_LIBRARY_SEARCH_*フラグで探索範囲を明示的に絞るLoadLibraryExは、安全側の検索空間設計に推奨される
検索空間を明示的に絞り、current folderや不用意なPATH依存を避けることが、DLL preloading攻撃を避ける基本形になる
フルパス指定なしの動的ロードと、攻撃者が支配できる検索対象ディレクトリの組み合わせがDLL preloading攻撃やbinary planting攻撃につながる
DLLをフルパス指定でロードすることは、裸の名前によるロードに起因するDLLハイジャックの軽減につながる
最初のDLLをフルパス指定でロードしても依存DLLはモジュール名だけで検索される扱いになるため、依存DLLの解決先まで固定する手段としてフルパス指定だけに頼るのは推奨されない
.NETのDllImportSearchPath属性の値は、LoadLibraryExのLOAD_LIBRARY_SEARCH_*フラグに対応する
実際にどのフォルダからDLLが読まれたかは、Process MonitorのResult列(NAME NOT FOUND/SUCCESS)を追うことで確認できる
同名DLLが別フォルダから既にロード済みかどうかは、ListDLLsやProcess Explorerのモジュール一覧で確認できる
指定したモジュールを読み込んでいるプロセスは、tasklist /mで一覧できる