Windowsデスクトップアプリケーションにおけるdllハイジャッキング脆弱性について考えます。その1です。
まずは、Windowsにおけるdllハイジャッキング脆弱性を理解するために、dllの読み込みの仕組みについて理解します。
アプリケーションで明示的にdllを読み込むときは、
のWin32 APIを使います。このときの引数は、ファイルパスとフラグになります。
ここで重要なのがファイルパスの指定方法です。ファイルパスには、
- 絶対パス (ex. c:\abcde\testModule.dll)
- 相対パス (ex. testModule.dll)
と二つの指定方法で指定できます。
絶対パスで指定した場合、指定の場所に該当のファイルが存在すればロードされ、存在しないときはエラーになります。
それに対し、相対パスで指定した場合は、指定されたファイル名のdllファイルを、システムが決まった順番で探し、初めに見つかった同名のファイルがロードされます。もし、すべての検索場所で見つからなかったらエラーになります。
dllファイルの検索方法
この「決まった順番」というのは、マイクロソフトのサイト(英語)に書かれているように以下の通りです。
- 自身のプログラムファイル(Exeファイル)と同じフォルダー
- システムフォルダー
GetSystemDirectory() APIを使って得られるパスのフォルダー
例) c:\Windows\System32\ - 16ビットシステムフォルダー
APIを使って取得できないフォルダーですが検索されます
例) c:\Windows\System\ - Windowsフォルダー
GetWindowsDirectory() APIを使って得られるパスのフォルダー
例) c:\Windows\ - カレントフォルダー
- PATH環境変数に列挙されている複数のフォルダー
App Pathsレジストリにアプリごとに設定されるパスはこの検索に含まれません。App Pathレジストリはdllの検索のときには使用されません。
前者の「絶対パス」の場合は、ロードされるのは明確に指定したものが、「読み込めるか」、「読み込めないか」のいずれかなのでアプリの開発者の意図したとおりの動作となります。
それに対し、後者の「相対パス」の場合は、アプリの開発者が指定するのはファイル名のみなのでどこにあるファイルが読み込まれるかは、アプリを使用するユーザーのPC環境によって異なります。
指定したファイル名のdllファイルが、どのようなPC環境においても1番もしくは2番に必ず存在するファイルであれば、ほとんどの場合、開発者の意図のファイルが読み込まれます。しかし、PC環境によっては存在しない可能性があるdllファイルの場合、開発者が意図しないファイルが読み込まれる可能性があります。
ちなみにWindows XP SP1以前では、5番にあるカレントフォルダーが2番のシステムフォルダーの前でした。そのため、システムフォルダーに存在するファイルを読み込もうとしたときでさえ、意図しないファイルが読み込まれる可能性がありました。
開発者が意図しないdllファイルの読み込み
では、本当に意図しないファイルが読み込まれる可能性があるのでしょうか?
Windowsでは、フォルダーやファイルに対してユーザーごとのアクセス権を付与することにより、ユーザーがファイルを「読み出せる」のか「書き込める」のかを制御しています。ほとんど場合、管理者権限ではどのフォルダやファイルでも読み書きできますが、一般ユーザー権限では「読み出し」や「書き込み」ができる書きできるフォルダーやファイルが制限されています。
相対パスで指定した時に検索されるフォルダーの権限を確認すると以下のようになります。
検索順 | 場所(フォルダー) | 管理者でアプリを起動 | 一般ユーザーAでアプリを起動 | 一般ユーザーBでアプリを起動 |
---|---|---|---|---|
1 | a. 管理者権限で配置(インストール)したアプリのexeファイルのあるフォルダー (C:\Program Files\配下にある) |
読み/書き | 読み/- | 読み/- |
b. 一般ユーザーA権限で配置したアプリのexeファイルのあるフォルダー | 読み/書き | 読み/書き | -/- | |
c. 一般ユーザーB権限で配置したアプリのexeファイルのあるフォルダー | 読み/書き | -/- | 読み/書き | |
2 | システムフォルダー | 読み/書き | 読み/- | 読み/- |
3 | 16bitシステムフォルダー | 読み/書き | 読み/- | 読み/- |
4 | Windowsフォルダー | 読み/書き | 読み/- | 読み/- |
5 | カレントフォルダー | 読み/書き | 読み/書き | 読み/書き |
6 | PATH環境変数に列挙されている複数のフォルダー | 読み/書き | 読み/書き | 読み/書き |
この一覧の中で一般ユーザー権限でアプリを起動したときに書き込み権限があるところが問題になります。というのは、アプリの開発者の意図しない同名のファイルが書き込まれる可能性があるからです。
その書き込まれた同名のファイルがマルウェアなど悪意のあるdllファイルの場合、その悪意のあるdllを読み込んでしまうことになります。
dllファイルが読み込まれると何が起こるのか?
dllファイルは、簡単に言ってしまうと、メインのExeファイルから呼び出される関数の塊です。これらのdllファイル内の関数は一部の例外を除き開発者が明示的にdllファイル内の関数を呼び出さない限りdllファイル内のコードは実行されません。しかし、その一部の例外というのが、悪意のあるdllファイルに利用されます。
その例外というのは、dllファイルがロードされるとアプリに制御が戻る前にdllファイル内の初期化関数(DllMain)が呼び出されます。そのため悪意のあるdllファイルでは、必ず呼ばれる初期化関数内に攻撃コードが記述されています。
結果としてdllファイルをロードした時点でdll内の攻撃コードが実行されることになります。もし、検索順の表の1bや1cのアプリが管理者権限で実行された場合、dllを読み込んだ時点で、管理者権限で攻撃コードが実行されることになり非常な危険な状態になります。
アプリの明示的なdll読み込みするときのdllハイジャッキング対策
アプリでLoadLibrary()などで明示的にdllファイルをロードするときに相対パスを使っている場合、dllハイジャッキングの脆弱性を持つことになります。
このタイプのdllハイジャッキング脆弱性は2008年頃に大きく取り上げられました。
これに対処するには、dllの検索順序に依存しないようにすればよいだけです。すなわち、相対パスは使わず、絶対パスを使うことです。そして対象のフォルダーは、管理者権限でしか書き込めない場所に限定します。これによりこの種の脆弱性は防ぐことができます。
話題になった時期が10年も前のことなので、最近のアプリでは対策が進んでおり、dllハイジャッキング脆弱性の話題はいったんは下火になりました。
対策済みのはずでしたが、ここ最近「dllハイジャッキング脆弱性の問題」があったとのことで、いろいろなアプリのインストーラーの対策版がリリースされています。この最近話題なっているdllハイジャッキング脆弱性は、上記の発生メカニズムとは異なります。
この種のdllハイジャッキング脆弱性については別の機会に考えてみたいと思います。