HOME IP Messenger FastCopy Tech-memo Diary Twitter [English]

Windows DLLプリロード攻撃の新しいパターンと防御法

白水啓章
作成: 2017/08/01
更新: 2017/08/06

概要

Windows DLLプリロード攻撃と呼ばれる脆弱性攻撃がありますが、原因や対応方法は広く知られています。(*1)
しかし、「特定のCOM I/F」や「WinAPI」の内部で「パス指定なしでDLLがロードされる」という、あまり知られていない経路があるのでご注意、というお話。

技術詳細

手元のWindows10Pro(x64)上で、FastCopyインストーラ(x64)で確認した範囲では、下記のようなAPIで「不正なDLLの読み込みとそのDllMainが呼ばれること」を確認しています。(*2)

  1. IShellLink/IPersistFile COM I/F(ショートカット作成)のメンバ関数を呼び出すと、パス指定なしで"linkinfo.dll"等がロードされる。
  2. ShellExecute API(ファイル/フォルダを開く)を呼び出すと、パス指定なしで"edputil.dll"等がロードされる。
    (注意: 第3引数がフルパスかどうかは無関係)
  3. SHBrowseForFolder(フォルダ選択ダイアログ)を呼び出すと、パス指定なしで"WindowsCodecs.dll"等がロードされる。
  4. その他、IPropertyStore, GetOpenFileName, GetSaveFileNameなどもパス指定なしDLLロードする。 簡単に調べても次々に見つかるので、他にも沢山存在(特にシェル関連API?)している可能性が高そうです。(*3)

従来の対策で考慮が必要な点

これらの資料にあるように、多くの注意喚起が行われており、およその要点は下記の通り。

  1. パス指定なしでDLLをロード(LoadLibrary)すると、exeフォルダ、カレントフォルダの不正DLLを読む危険あり
  2. パス指定でDLLをロードすることで防御可能
  3. カレントの不正DLLロードはSetDllDirectory("")で防御可能
  4. exeフォルダの不正DLLロードはSetDefaultDllDirectoriesで防御可能(ただし、Win8以降 or KB2533623が入ったWin7/Vistaのみ)
  5. その他、SeachPathを使わない、SetSearchPathModeを使う等

これらは正しいのですが、これだけでOKではなく、

  1. 「SetDefaultDllDirectories API が利用できない環境(もしくはそれを実行していないバイナリ)」と、
  2. 「特定のWinAPIやCOM I/Fが、パス指定なしDLLロードを発生させる」

の2つが組み合わさった場合に、SetDefaultDllDirectories以外の対策を全て行っても、DLLプリロード攻撃が可能になります。
(特にインストーラexeの場合、ブラウザのダウンロードフォルダという不正DLLが存在しうる場所での実行になるため危険度が高い)

今回の問題を考慮した対策方法

一番良い方法は、

(これが発行できれば、WinAPIやCOM I/F によって、パス指定なしDLLのロードが発生しても、exeフォルダのDLLがロードされることはなくなります)

しかし、KB2533623が入っていないWin7やVistaではこのAPIがありません。
その場合、少し面倒ですが下記の方法が無難です。
(いくつかの汎用インストーラはこの形)

上記の方法以外に、予め何をロードするかが判っていれば、

により、その後、パス指定なしのLoadLibraryが発生しても、すでにロードされたモジュールが使われるようになります。
(しかし、3の方法ではどのDLL群をロードするか、事前に確定することが難しい(WinAPI等の内部実装に依存)という点を考慮する必要があります)
.

補足(2017/08/06追記)

  1. 上記 2-1 でテンポラリ名を乱数生成して作成する場合、下記に注意します。
    STL の std::random_device()を利用すると、内部で"cryptbase.dll"がパスなしでロードされます。 従い、std::random_device()を利用する前に、事前にパス指定でcryptbase.dllをロードしておきます。 (なお、CryptGenRandom APIを使った場合も、類似問題があるのでご注意)
  2. 起動からWinMainに到達するまでに、exeフォルダDLLが優先してロードされるDLLに注意します。
    リンカやpragmaに指定しており、KnownDLLでないDLL全般が該当します。(手元で確認している例では、winmm.dll, mpr.dll など) 解決には***「DLL遅延読み込み」***を使います。 リンカの「DLL遅延読み込み(/DELAYLOAD)」に winmm.dll; mpr.dll 等を指定した上で、WinMain直後に上記の「今回の問題を考慮した対策方法」の 1. or 2. を行います。
  3. いずれの場合でも、デバッガで起動から終了までのDLLロード状況を確認して、対策が正しく機能しているか確かめることをお勧めします。(*4)

動作例

FastCopyインストーラをVS2015から実行したときのログ

  1. 不正DLL群を置いた状態での実行例
  2. 正常な実行例

備考

(*1) これまでのDLLプリロード攻撃に対する、原因や対処に関する資料

(*2) MS署名がないDLLの多くは、読み込んでもすぐにアンロードされ、DLL側関数であるDllMainは呼ばれないようですが、上記の例にある "linkinfo.dll", "edputil.dll", "WindowsCodecs.dll" 等ではそういった機構は働かないようです。
(それ以外にも "urlmon.dll", "propsys.dll", "secur32.dll", "sspicli.dll" 等多々あるようです)

(*3) Microsoftさんへ: APIやMS提供のCOM I/Fの内部DLLロードに関しては、パス指定DLLロードにすべきと思います。
これをそのままに放置しているのは、DLL乗っ取りを前提としたソフトが存在している等でしょうか?
(あまり合理性のある理由が思いつかないのですが…)

(*4) デバッガでモニタすると、DLLによっては、パス指定でSystem32からロードしたのち、exeフォルダの同名DLLを再度ロードする、不可思議な挙動を示すDLLがあったりします。
(この挙動の原因をご存知の方はご連絡ください。なお、LoadLibraryEx(LOAD_LIBRARY_SEARCH_SYSTEM32)の場合は、この挙動が起きませんが、このフラグが使える環境=SetDefaultDllDirectoriesが使える環境なので、この方法はあまり助けになりません)

おまけ(今回の問題を見つけた経緯)

7月下旬に、公的機関から、IP Messengerインストーラに対する DLL Preload攻撃が成功した旨の報告があったことに端を発します。
しかし、私自身は
これらの資料にあるような確認はすでに行っており、IP Messengerインストーラでは、パス指定なしDLLロードを行っていないことを確認済みだったため、この報告にはかなり驚きました。

そこでデバッガ等でインストーラの挙動をトレースしたところ、ソースコードでは LoadLibrary していないにもかかわらず、ショートカット作成(IShellLink)内で、パス指定なしDLLロードが行われていることを発見し、ぐぬぬとなった…という流れになります。

関連リンク

JPCERT 戸田さん講演 「DLL読み込みの問題を読み解く」(オープンソース・カンファレンス2017京都)

免責

この内容は無保証とします。
可能な限り、実際のログ等を掲載して正確性を期したつもりですが、勘違いや事実誤認があれば随時訂正しますので、ご連絡ください。

履歴

2017/08/06 対策方法に「補足」を追記
2017/08/06 「関連リンク」を追記
2017/08/01 初版作成