障害の調査やセキュリティのアラートを見ていると、「.dll」という拡張子のファイルがよく出てきます。「このDLLが読み込めない」「怪しいDLLが読み込まれた」「DLLハイジャック」といった具合です。
ただ、そもそもDLLが何で、OSの中でいつ使われているのかを、順序立てて説明できる人は意外と多くありません。この記事では、DLLの基本的な仕組みを図で整理し、そのうえで、DLLハイジャックがなぜ起きるのかを説明します。
先に仕組みを理解しておくと、DLLハイジャックは「特別な攻撃」ではなく、DLLの探し方の性質を突いたものだと分かります。
- 本記事は2026年10月12日時点で公開されている情報をもとにした、個人の見解を含む解説です。内容の正確性や完全性を保証するものではありません。
- OSのバージョンや設定によって、挙動が異なる場合があります。実際の設定や設計の際は、必ずマイクロソフトの最新の資料を確認してください。
- 図は理解のためのイメージです。攻撃の手順の詳細を示すものではなく、防御の理解を目的としています。
- DLLとは何か。EXEとの関係と、「共有の部品」と呼ばれる理由
- メモリ上でDLLがどう共有されるか
- DLLが読み込まれる2つの方式と、読み込みの流れ
- OSの中で、どんなDLLが、どんなときに使われているか
- DLLの探索順序と、DLLハイジャックが起きる理由、対策
DLLとは何か
DLL(Dynamic-Link Library)は、複数のプログラムで共通して使うコードやデータをまとめたファイルです。Windowsのプログラムは、必要な機能をDLLから「借りて」動いています。
共有の工具箱と考える
工具に例えると分かりやすくなります。ドライバーやレンチを、作業のたびに自分の道具箱へ買い揃えるのが「静的リンク」です。同じ工具が、作業の数だけ増えます。
共有の作業場に工具箱を1つ置いておき、必要なときに借りて使うのが「動的リンク」で、この共有の工具箱がDLLです。
| 項目 | 静的リンク | 動的リンク(DLL) |
|---|---|---|
| 部品の置き場所 | 各アプリの中にコピーされる | 別のファイル(DLL)に置かれ、共有される |
| 部品を直したとき | 使うアプリを、それぞれ作り直す | 関数の引数や戻り値が変わらなければ、アプリの作り直しは不要 |
| メモリ | アプリごとに部品が載る | 同じDLLのコードは、物理メモリ上で共有される |
| 弱点 | ファイルが大きくなる | DLLが見つからないと、アプリが動かない |
動的リンクには、メモリの節約と更新のしやすさに加えて、異なるプログラミング言語で書かれたプログラムが同じ関数を使える、という利点もあります。呼び出しの約束事(呼び出し規約)が合っていればよいためです。
一方で、アプリが自分だけで完結しない、という弱点もあります。必要なDLLがなければ、アプリは起動に失敗します。この「必要なものが見つからない/別のものが見つかる」という性質が、あとで出てくるDLLハイジャックの背景になります。
DLLの中身:エクスポートとインポート
DLLは、EXEと同じ「PE形式」のファイルです。EXEが「実行を始める入口」を持つのに対し、DLLは「他のプログラムが呼べる関数の一覧(エクスポート)」を持ちます。
一方、EXEや別のDLLは、「どのDLLのどの関数を使うか」の一覧(インポート)を持っています。OSが、この2つを突き合わせて、アプリがDLLの関数を呼べるようにします。
DLLの中身は、関数だけではありません。データやアイコン、文字列などのリソースを入れたDLLもあります。
メモリの中でDLLはどう共有されるのか
プロセスがDLLを読み込むと、DLLはそのプロセスの仮想アドレス空間に配置されます。そのうえで、同じDLLを同じアドレスで読み込んだ複数のプロセスは、物理メモリ上の1つのコピーを共有します。
共有されるのは、実行するコードの部分です。DLLの中にある変数(グローバル変数など)は、既定ではプロセスごとに別のコピーを持ちます。あるプロセスが書き換えた値が、別のプロセスに見えてしまうことはありません。
また、DLLの関数は、それを呼んだスレッドの中で動きます。DLLのコードが確保するメモリは、呼んだプロセスのメモリです。DLLは、呼び出したアプリの一部として動く、と考えてください。この点は、DLLハイジャックの影響を理解するうえで重要です。
OSは、DLLごとにプロセス単位の参照カウントを持っています。読み込むたびに1増え、プロセスが終了するか、カウントが0になると、そのプロセスの空間からDLLが外されます。
DLLが読み込まれる流れ
DLLを読み込む方法には、2つあります。
ロードタイム:起動時にまとめて読み込む
アプリのEXEに、「使うDLLの一覧(インポート)」を持たせておく方式です。アプリを起動するとき、OSのローダーが、必要なDLLを探して読み込みます。
この方式では、必要なDLLが1つでも見つからないと、アプリは起動に失敗します。「DLLが見つからない」という内容のエラーで起動に失敗するのは、この状況です。
DLLが読み込まれると、OSは、そのDLLに用意されている入口の関数(DllMain)を呼びます。初期化のための仕組みですが、裏を返すと、DLLが読み込まれた時点で、DLLの中のコードが実行されるということです。覚えておいてください。
ランタイム:必要になったときに読み込む
プログラムが、実行中に、必要になった時点でDLLを読み込む方式です。LoadLibrary という関数でDLLを読み込み、GetProcAddress で関数の場所を取得して呼びます。プラグインの仕組みや、機能を後から追加する場合に使われます。
この方式では、DLLがなくても、アプリはすぐには終了しません。そのDLLの関数が使えないだけで、アプリ側でエラー処理ができます。
| 項目 | ロードタイム | ランタイム |
|---|---|---|
| 読み込みの時期 | アプリの起動時 | 必要になったとき |
| 使うDLLの情報 | EXEのインポートの一覧 | プログラムが、DLL名を指定して読み込む |
| DLLがないと | 起動に失敗する | その機能が使えないだけ。アプリ側で対処できる |
| よく使われる場面 | OSの標準機能、通常のライブラリ | プラグイン、機能の後付け |
OSの中で使われているDLL
Windowsの標準機能の多くは、DLLを通して提供されています。アプリが画面を描く、ファイルを開く、ネットワークにつなぐといった場面では、裏でDLLが呼ばれています。
アプリは、直接カーネルを呼ぶのではなく、Windows APIのDLL群(kernel32.dll など)を呼びます。その先の処理は、ntdll.dll を経て、OSのカーネルへ渡ります。DLLは、アプリとOSの間にある、標準の窓口の役割を持っています。
代表的なDLLと、使われる場面
| DLL | 主な役割 | 使われる場面の例 |
|---|---|---|
| kernel32.dll | ファイル、プロセス、メモリなどの基本機能 | ファイルの読み書き、プロセスの作成 |
| user32.dll | ウィンドウやメッセージの管理 | 画面にウィンドウを出す、ボタン操作を受け取る |
| gdi32.dll | 画面への描画 | 文字や図形の描画 |
| advapi32.dll | レジストリ、サービス、権限など | 設定の読み書き、サービスの操作 |
| ws2_32.dll | ネットワーク通信(Winsock) | ソケット通信 |
| ntdll.dll | カーネルへの入口 | 上のDLLが、内部で呼び出す |
DLLの種類(用途別)
| 種類 | どんなもの |
|---|---|
| OS標準のDLL | System32 などにある、Windowsの機能そのもの(上の表のもの) |
| ランタイムライブラリ | C/C++のランタイムなど、アプリが共通で使う部品。アプリと一緒にインストールされることもある |
| アプリ固有のDLL | アプリのフォルダーに置かれた、そのアプリ専用の部品 |
| プラグイン・拡張 | エクスプローラーの機能拡張、ブラウザやエディタの拡張など、後から差し込まれるもの |
| リソースのDLL | アイコンや文字列など、コードを含まないデータだけのもの |
| .NETの部品 | .NETで作られたアプリの部品も、拡張子は .dll |
DLLと同じ仕組みのファイルには、拡張子が .ocx や .cpl(コントロールパネルの項目)、.drv のものもあります。いずれも、読み込まれる仕組みはDLLと共通です。
手元のPCで確かめる
実際にどのDLLが読み込まれているかは、コマンドで確認できます。例として、メモ帳(notepad.exe)を起動した状態で実行します。
コマンドプロンプト:プロセスが読み込んでいるDLLの一覧
tasklist /m /fi "IMAGENAME eq notepad.exe"
PowerShell:同じ内容を、パスつきで確認する
(Get-Process notepad).Modules | Select-Object ModuleName, FileName
Visual Studioの開発者向けコマンドプロンプト:EXEが使うDLLと、DLLが提供する関数
dumpbin /dependents C:\Windows\System32\notepad.exe dumpbin /exports C:\Windows\System32\kernel32.dll
kernel32.dll や ntdll.dll など、OSの標準のDLLが並ぶのが確認できると思います。
DLLはどこから読み込まれるのか
ここからが、DLLハイジャックを理解するための本題です。アプリがDLLを読み込むとき、DLLの名前だけを指定して、場所(フルパス)を指定しないことがあります。その場合、OSは、決まった順序でDLLを探します。
通常のデスクトップアプリで、セーフDLL検索モード(既定で有効)の場合の順序です。まず、リダイレクトやAPIセット、マニフェストでの指定、すでに読み込み済みのモジュール、KnownDLLsといった「特別扱い」が確認されます。それでも決まらない場合に、フォルダーを①から順に探します。
なお、Windows 11(バージョン21H2以降)では、フォルダーの検索の前に、パッケージの依存関係を探す段階が加わります。
KnownDLLs:OSの正規コピーを使う仕組み
KnownDLLsは、OSが「正規のコピー」を持っているDLLの一覧です。レジストリの HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs で確認できます。一覧に載っているDLLは、フォルダーを探す前に、OSが持つコピーが使われます。
コマンドプロンプト:KnownDLLsの一覧を見る
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs"
つまり、OSの根幹にかかわるDLLは、フォルダーに偽物を置かれても影響を受けにくくなっています。逆に言うと、KnownDLLsに載っていないDLLは、フォルダーの探索の対象になります。
セーフDLL検索モード:カレントフォルダーを後ろへ
カレントフォルダーは、ユーザーが今作業している場所です。ここが探索の先頭近くにあると、ダウンロードしたフォルダーなどに置かれたDLLが読まれやすくなります。そこで、既定のセーフDLL検索モードでは、カレントフォルダーを⑤に下げています。このモードは、Windows XP SP2から既定で有効です。
DLLが別のDLLを必要とするときも、同じ探索が行われます。注意したいのは、最初のDLLをフルパスで読み込んでも、そのDLLが必要とする別のDLLは、名前だけで探される点です。
DLLハイジャックとは
DLLハイジャック(DLLプリロード攻撃、バイナリプランティングとも呼ばれます)は、この探索の性質を使った攻撃です。攻撃者が、本物のDLLよりも先に探される場所に偽のDLLを置き、正規のアプリに読み込ませます。
流れを整理します。
- 攻撃者が、探索の対象になっているフォルダー(書き込める場所)に、偽のDLLを置く。
- 利用者が、そのフォルダーにある正規のアプリを起動する(または、関連するファイルを開く)。
- アプリが、DLLの名前だけで読み込みを依頼する。
- OSが探索して、本物より先に、偽のDLLを見つけて読み込む。
- 偽のDLLの DllMain が動き、アプリの権限で攻撃者のコードが実行される。
ここで、先ほどの「DLLが読み込まれた時点で、中のコードが実行される」「DLLは呼び出したアプリの一部として動く」という2点が効いてきます。偽のDLLは、アプリが持つ権限で動きます。そのアプリが管理者権限で動いていれば、権限の昇格につながります。
成立しやすくなる条件
攻撃が成立するのは、アプリがDLLをフルパスで指定せず、名前だけで探している場合です。そして、探索の対象のどこかに、攻撃者が書き込める場所があることが必要です。たとえば、ダウンロードフォルダーや、誰でも書き込める共有フォルダー、一般ユーザーが書き込めるように設定されたアプリのインストール先などです。
なお、読み込まれるDLLに署名があるかどうかは、既定では読み込みの条件になっていません。名前などが合えば、署名のないDLLでも読み込まれます。署名を条件にする設定は、後述する対策で触れます。
代表的なパターン
ひとくちにDLLハイジャックと言っても、いくつかの形があります(MITRE ATT&CKでの整理に沿っています)。
| パターン | 何が起きるか |
|---|---|
| 探索順序の乗っ取り | 本物のDLLより先に探される場所に、同じ名前の偽のDLLを置く。 |
| ファントムDLL | アプリが、実際には存在しないDLLの名前を探している。その名前の偽のDLLを置く。 |
| DLLの差し替え | 本物のDLLを、同じ名前・同じ場所で偽のものに置き換える。本物も読み込んで、正常に動いているように見せる場合がある。 |
| サイドローディング | 正規の(署名のある)アプリに、偽のDLLを読み込ませる。信頼されたアプリの中で動くため、検知を避ける目的で使われる。 |
| リモートDLL | カレントフォルダーがネットワーク共有などになっているときに、そこにあるDLLを読み込ませる。 |
被害のイメージ
- 権限昇格:管理者権限などで動くアプリを経由して、高い権限でコードが実行される。
- 永続化:アプリが起動するたびに、偽のDLLが読み込まれる。
- 検知回避:正規のアプリの中で動くため、不審なプロセスとして見えにくい。
DLLの悪用には、ディスクにファイルを置かず、メモリの上でDLLを読み込ませる手法(リフレクティブDLLインジェクションなど)もあります。これは探索順序を使うDLLハイジャックとは別の話で、ファイルの監視だけでは気づきにくい点が違います。対策としては、メモリ上の挙動を見られるEDRのような仕組みが役立ちます。
対策:DLLハイジャックを防ぐには
先ほどの3つの条件のどれかを崩せば、攻撃は難しくなります。立場ごとに、できることを整理します。
作る側:アプリの設計で防ぐ
- LoadLibrary などでDLLを読み込むときは、可能な限り、フルパスで指定する。
- SetDefaultDllDirectories と LOAD_LIBRARY_SEARCH のフラグで、探す場所を、System32やアプリのフォルダーなどに絞る。
- SetDllDirectory に空の文字列を指定して、カレントフォルダーを探索から外す。プロセスの起動の早い段階で、1回だけ呼ぶ。
- 通常の探索順序を使う場合は、セーフDLL検索モードが有効なことを確認する。
- DLLのリダイレクトやマニフェストで、使うDLLを固定する。
運用する側:OSとフォルダーの設定で防ぐ
1つ目は、書き込み権限の見直しです。アプリのインストール先や、アプリが参照する共有フォルダーに、一般ユーザーが書き込める状態になっていないかを確認します。書き込める場所がなければ、偽のDLLを置けません。
2つ目は、プロセスの保護設定(Exploit Protection)の利用です。PowerShellの Set-ProcessMitigation で、アプリごと、またはシステム全体に設定できます。DLLの読み込みに関係する主な項目は、次のとおりです。
| 設定項目 | 内容 |
|---|---|
| PreferSystem32 | DLLの探索で、System32を、アプリのフォルダーより先に探す。 |
| MicrosoftSignedOnly | マイクロソフトの署名があるバイナリだけを読み込む。 |
| BlockRemoteImageLoads | ネットワーク共有(UNC)など、リモートにあるバイナリの読み込みを拒否する。 |
これらは、アプリによっては正常な動作を妨げます。いきなり有効にせず、まず監査(Audit)のモードで影響を確認するのが安全です。たとえば PreferSystem32 には、AuditPreferSystem32 が用意されています。
PowerShell:特定のアプリで、監査モードを有効にする
Set-ProcessMitigation -Name example.exe -Enable AuditPreferSystem32
PowerShell:アプリに設定されている保護の状態を確認する
Get-ProcessMitigation -Name example.exe
3つ目は、アプリケーション制御です。許可したバイナリ以外を実行・読み込みさせない仕組みで、偽のDLLが読み込まれること自体を防げます。MITRE ATT&CKでも、緩和策の一つとして挙げられています。
確かめる側:Process Monitorで探索を観察する
自社のアプリが、DLLハイジャックの影響を受けるかどうかは、Sysinternalsの Process Monitor(Procmon)で確かめられます。マイクロソフトの資料に、手順が示されています。
- Process Monitor を起動し、フィルターで「Operation is CreateFile」「Operation is LoadImage」と、「Path contains .dll」などを指定する。
- プロセス名がprocmon、Operationが IRP_MJ_ や FASTIO_ で始まるもの、Result is SUCCESS などは除外する。
- アプリを、特定のフォルダーをカレントにして起動する。たとえば、そのアプリに関連付けられたファイルを開く。
- カレントフォルダーなど、怪しい場所からDLLを読もうとしていないかを確認する。
アプリのフォルダーなど、一般ユーザーが書き込める場所でDLLが見つからず(NAME NOT FOUND)、その後、別の場所で見つかっている場合は、その場所に偽のDLLを置かれると先に読まれる可能性があります。
おわりに
DLLは、アプリで共通して使う部品を、共有するための仕組みです。メモリを節約し、更新をしやすくする一方で、「名前でDLLを探す」という性質を持っています。DLLハイジャックは、この探し方の順序を突いたものです。
| DLLの性質 | そこから生まれる弱点 | 対策の方向 |
|---|---|---|
| 名前でDLLを探す | 本物より先に、偽のDLLが見つかる。 | フルパス指定、探索場所の限定 |
| 読み込まれると、コードが動く | 偽のDLLの処理が、アプリの権限で動く。 | 署名や、アプリケーション制御による制限 |
| 探索の対象に、書き込めるフォルダーがある | 攻撃者が、偽のDLLを置ける。 | 書き込み権限の見直し |
まずは、自社で使っているアプリのインストール先が、一般ユーザーに書き込める状態になっていないかを確認するところから始めるのが、手軽で効果の大きい一歩です。
参考URL
本記事で参照した公開情報です。内容は、2026年10月12日時点で確認したものです。
DLLの仕組み
- Microsoft Learn「About Dynamic-Link Libraries」:https://learn.microsoft.com/en-us/windows/win32/dlls/about-dynamic-link-libraries
- Microsoft Learn「Advantages of Dynamic Linking」:https://learn.microsoft.com/en-us/windows/win32/dlls/advantages-of-dynamic-linking
- Microsoft Learn「Dynamic-Link Library Data」:https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-data
- Microsoft Learn「Dynamic-Link Library Entry-Point Function」:https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-entry-point-function
- Microsoft Learn「Dynamic-Link Library Search Order」:https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order
セキュリティと対策
- Microsoft Learn「Dynamic-Link Library Security」:https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-security
- Microsoft Learn「Set-ProcessMitigation」:https://learn.microsoft.com/powershell/module/processmitigations/set-processmitigation
- Microsoft Learn「PROCESS_MITIGATION_IMAGE_LOAD_POLICY structure」:https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-process_mitigation_image_load_policy
- Microsoft Learn「Exploit protection reference」:https://learn.microsoft.com/defender-endpoint/exploit-protection-reference
- MITRE ATT&CK「Hijack Execution Flow: DLL(T1574.001)」:https://attack.mitre.org/techniques/T1574/001/
コメント