スキップしてメイン コンテンツへ

 none
LoadLibrary の成否を調べるには RRS feed

  • 質問

  • お世話になっております。

    dll インジェクションを行って shell32.dll の api の振る舞いをログ出力しているのですが、
    対象のプロセスが LoadLibrary を呼び出してshell32.dll を再ロードした場合に、
    LoadLibrary もフックしているため、その中で再度インポートテーブルの
    書き換えなどを行っています。

    その際に問題がありまして、
    Explorer でフォルダの名前変更を行った場合など、LoadLibrary がかなり大量に実行され
    (秒間数十回) shell32.dll の再読み込みが多発します。
    その都度CriticalSectionで排他しつつテーブルの書き換えを行うため、
    Explorer のパフォーマンスがかなり落ちます。

    LoadLibraryが呼ばれても、まだメモリ上に shell32.dll が残っているため
    実際は再ロードしていないようなのですが、呼び出された LoadLibrary によって
    本当に再ロードしたのかどうか?を何かで判定する方法はないでしょうか?

    宜しくお願い致します。


    2018年12月21日 7:55

回答

  • HomeCloset さんからのコメントで気づいたのですが、GetModuleHandle() は対象プロセスのユーザ モード空間にモジュールがロードされてる場合にのみ、そのハンドル (先頭アドレス) を返す仕様のようです。
    -----------------------------------------------
    GetModuleHandleA function
    https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-getmodulehandlea

    Retrieves a module handle for the specified module.
    The module must have been loaded by the calling process.
    -----------------------------------------------

    つまり、LoadLibray() の Hook Routine が呼び出されたタイミング。。。即ち LoadLibray() の処理を行う前に GetModuleHandle() でモジュール ハンドルが取得できるかをチェックすれば、既にロードされているかを確認できると思うのです。
    この方法なら、FreeLibrary() を Hook する必要もないし、対象モジュールの Reference Counter もチェックする必要もないから簡単だし、なによりも一番確実な方法だと思うのです。


    • 回答としてマーク supao 2019年4月3日 2:21
    2018年12月25日 5:26

すべての返信

  • とりあえずお手軽な方法としては。。。。
    Reference Count が 0 にならない限り Unload はされないので、Unload させたくない DLL に対して、予め「余計」に LoadLibray() コールをしておけばいいのでは?
    あるいは、FreeLibrary() と併せてコールされた回数をチェックするとか。
    もっとも、誰かが意図的に複数回 FreeLibrary() コールを行ったら、この方法でも NG ですけど。
    ----------------------------------
    LoadLibraryA function
    https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya
    Remarks
    .....
    The system maintains a per-process reference count on all loaded modules.
    Calling LoadLibrary increments the reference count.
    Calling the FreeLibrary or FreeLibraryAndExitThread function decrements the reference count.
    The system unloads a module when its reference count reaches zero
    or when the process terminates (regardless of the reference count).
    .....
    ----------------------------------

    一番確実なのはやっぱり、 FreeLibrary() も Hook してそのタイミングで Reference Count のチェック。。。。という方法かと。
    ----------------------------------
    How to check dll's reference count? How to know where the dll was loaded?
    https://stackoverflow.com/questions/3553231/how-to-check-dlls-reference-count-how-to-know-where-the-dll-was-loaded
    ----------------------------------
    2018年12月21日 8:56
  • アンロードさせないという方針であれば、GET_MODULE_HANDLE_EX_FLAG_PIN でいいかも。
    2018年12月21日 9:16
  • 正攻法で行くなら、同じDLLをロードする限りハンドル値は変化しないのでは? そして同名でも別ファイルの場合は別のハンドル値になるのでは。
    つまり、LoadLibraryの戻り値を監視して、未知の値であれば新規にロードされたと判断すればいいのでは? もちろんFreeLibraryも監視し、アンロードされたかを確認するとか。

    もちろん、お馬鹿さんやHomeClosetさんが提案されるように、故意にロードカウントを1上乗せしておくことでアンロードされ難くする手も有効だと思います。

    2018年12月21日 13:27
  • 正攻法で行くなら、同じDLLをロードする限りハンドル値は変化しないのでは? 

    Reload された場合でも同じアドレスにロードされるケースは、多々あります。


    • 編集済み お馬鹿 2018年12月21日 18:32
    2018年12月21日 18:32
  • だから「FreeLibraryも監視し」も併せて必要ですし、それは先に書いています。
    2018年12月21日 21:52
  • だから「FreeLibraryも監視し」も併せて必要ですし、それは先に書いています。

    Reload した場合でもハンドル値が同じになるケースは多々ある、といっているのです。

    だから FreeLibrary() 側で監視したとしても、ハンドル値での判断は無理です。


    • 編集済み お馬鹿 2018年12月21日 23:57
    2018年12月21日 23:56
  • Reloadの概念がよくわからないので教えてください。

    FreeLibraryされずにReloadが発生することがあるのでしょうか?
    Reloadは必ずLoadLibraryのタイミングでのみ発生するのでしょうか?(他のタイミングでは行われないことが保証されているのでしょうか?)
    Reloadが発生すると質問文のようなパッチ内容がリセットされるのでしょうか? (他のタイミングではリセットされないことが保証されているのでしょうか?)

    もっとも気になるのが、FreeLibraryされずにReloadが発生した場合、当該ライブラリが確保していたリソースはどうなるのでしょうか? 例えば質問文ではshell32.dllについて言及しています。shell32.dllに含まれるExtractIconはハンドルを返します。Reloadが発生した場合、このハンドルは有効なのでしょうか?

    2018年12月22日 1:20
  • 私の言っている Reload とは、そのまんまの意味。
    Unload された後に、LoadLibrary() が呼び出された場合の Reload。

    なぜ以下の質問をされるのか、その趣旨が私には理解できていませんが一応。。。。

    > FreeLibraryされずにReloadが発生することがあるのでしょうか?
    > Reloadは必ずLoadLibraryのタイミングでのみ発生するのでしょうか?
    > (他のタイミングでは行われないことが保証されているのでしょうか?)

    LoadLibrary() や FreeLibrary () のリファレンスでも説明されてますが、モジュールの Unload を決定する要素は "Reference Count" です。
    裏を返せば、"Reference Count" を操作し関連する Native API をコールすれば、モジュールの Unload は可能だと私は考えています。
    (試したことはありませんけど。)
    もっとも、一般的なプログラムはそんな面倒なことしないですけど、以前に 3rd ベンダー製セキュリティソフトがそれらしいことをしてるのは見たことがあります。
    ただ私が以前に調べた限りでは、"Reference Count" が 0 になったからといって即座に Unload されるわけでもないようなので、"Reference Count" 以外にも要素はあるんだと思います。

    > Reloadが発生すると質問文のようなパッチ内容がリセットされるのでしょうか?
    >(他のタイミングではリセットされないことが保証されているのでしょうか?)

    質問者さんはなんかの API Hook のために、Load されたモジュールの IAT (Import Address Table) を書き換えているんだと思いますけど、IAT は PE Header 内にあるわけですから、モジュールが Reload されたら IAT の中身も初期状態に戻ります。
    PE Header の内容は基本的にロード時に決定されますが、そのモジュールがロードされている限り、書き換えはいつでもできます。
    従って「他のタイミングではリセットされないことが保証されている」ということは、無いと思います。

    > もっとも気になるのが、FreeLibraryされずにReloadが発生した場合、
    > 当該ライブラリが確保していたリソースはどうなるのでしょうか? 
    > 例えば質問文ではshell32.dllについて言及しています。
    > shell32.dllに含まれるExtractIconはハンドルを返します。
    > Reloadが発生した場合、このハンドルは有効なのでしょうか?

    有効だと思います。
    なぜなら、そのハンドルとそれに対応するオブジェクトの実体は、関数を提供する DLL ではなく、NT カーネルにより管理されてるから。
    例えば CreateFile() API を提供する kernel32.dll/kernelbase.dll は、インターフェイスを提供してるだけであって、ハンドルやオブジェクトを管理しているわけではありません。
    それと同じ理屈です。

    釈迦に説法だとは思いますけど。。。。。

    LoadLibrary() API で返されるハンドル値の実体は、そのモジュールがユーザ モード空間にマップされた先頭仮想アドレスです。
    個々のモジュールがマップされるユーザ モード空間の仮想アドレスは、NT カーネル様の気分次第です。
    Unload 後からの Reload では、大抵の場合同じ仮想アドレスになるけど、それが保証されているわけではない。
    だから、ハンドル値での判断は無理。。。。というのが、私の見解。
    (そこら辺の状態は、デバッガつなげれば確認できる。)

    FreeLibrary() を監視すれば、ハンドル値で判別できる根拠を、もう少し詳しく教えてもらえますか?



    • 編集済み お馬鹿 2018年12月23日 3:00 補足
    2018年12月22日 2:36
  • よく考えたら、GetModuleHandleA() / GetModuleHandleW() をコールしてその結果をチェックするだけで、Reload かどうかの判断が出来そうな気がしてきた。。。(試したわけぢゃないけど。)
    2018年12月23日 13:28
  • LoadLibraryとFreeLibraryからReference Countを推測し、0になった時点でアンロードされたとみなせばいいのでは。最初からそう主張しています。
    それに対するお馬鹿さんの返信は、同じアドレスにリロードされる可能性があるからFreeLibraryを監視しても無理、とのことで何を主張されているのかわかりませんでした。

    アンロードされたとみなす以上、同じアドレスにリロードされようが新規ロードとして扱えばいいだけのことです。

    ただ私が以前に調べた限りでは、"Reference Count" が 0 になったからといって即座に Unload されるわけでもないようなので、"Reference Count" 以外にも要素はあるんだと思います。

    そうだとして、次のLoadLibraryではロードされるわけですから、新規ロードとみなせばいいだけのことです。

    裏を返せば、"Reference Count" を操作し関連する Native API をコールすれば、モジュールの Unload は可能だと私は考えています。

    もちろん可能ですが、それを言い出したらLoadLibraryを経ない読み込みも可能で、質問の前提が成立しません。

    別ツリーですが、FreeLibraryの呼び出し後にGetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT )を使うことで、ハンドルがまだ有効かどうか判定するという考え方でしょうか? 失敗→ハンドル無効→アンロードされた、と。

    2018年12月23日 21:51
  • > LoadLibraryとFreeLibraryからReference Countを推測し、
    > 0になった時点でアンロードされたとみなせばいいのでは。
    > 最初からそう主張しています。
    話が噛み合っていません。
    私は佐祐理さんの、下記返信についていっているのです。

    > 正攻法で行くなら、同じDLLをロードする限りハンドル値は変化しないのでは?
    > そして同名でも別ファイルの場合は別のハンドル値になるのでは。
    > つまり、LoadLibraryの戻り値を監視して、
    > 未知の値であれば新規にロードされたと判断すればいいのでは?
    > もちろんFreeLibraryも監視し、アンロードされたかを確認するとか。

    「LoadLibraryの戻り値」、すなわちハンドル値で判別するのが「正攻法」と主張されてたのでは?
    そもそも、佐祐理さんの返信で "Reference Count" と言う単語が出てきたのは今回が初めてなのに、「最初からそう主張しています。」と言われても、お馬鹿な私にはさっぱり分かりませんでした。

    繰り返しになりますが、LoadLibrary() API の戻り値は、そのモジュールがユーザモード空間にマップされたときの先頭アドレスです。
    モジュールの Reload ではどこにマップされるかは不定なため、ハンドル値からの判別は無理だと指摘したのです。
    (少なくとも、質問者さんは DLL Injection しているので、LoadLibrary の戻り値がモジュール先頭アドレスであることを理解しているはず。)
    また、私は「Reference Countを推測」も無理だと思っています。(ここも噛み合ってない。。。)
    私が一番初めの返信で提案したのは、「推測」ではなく、値そのものの「確認」です。
    だから "Reference Count" の値を取得するサンプルが提示されている URL を張り付けたのです。

    2018年12月23日 23:20
  • お馬鹿さま 佐祐理さま HomeClosetさま

    お世話になります。
    確認が遅くなり申し訳ございません。

    やはり FreeLibrary() もフックして Reference Count をチェックするのが確実でしょうか。。。
    GetModuleHandle で確認する方法も有効に思えますので、試してみて結果わかりましたら
    追記致します。

    2018年12月25日 4:07
  • HomeCloset さんからのコメントで気づいたのですが、GetModuleHandle() は対象プロセスのユーザ モード空間にモジュールがロードされてる場合にのみ、そのハンドル (先頭アドレス) を返す仕様のようです。
    -----------------------------------------------
    GetModuleHandleA function
    https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-getmodulehandlea

    Retrieves a module handle for the specified module.
    The module must have been loaded by the calling process.
    -----------------------------------------------

    つまり、LoadLibray() の Hook Routine が呼び出されたタイミング。。。即ち LoadLibray() の処理を行う前に GetModuleHandle() でモジュール ハンドルが取得できるかをチェックすれば、既にロードされているかを確認できると思うのです。
    この方法なら、FreeLibrary() を Hook する必要もないし、対象モジュールの Reference Counter もチェックする必要もないから簡単だし、なによりも一番確実な方法だと思うのです。


    • 回答としてマーク supao 2019年4月3日 2:21
    2018年12月25日 5:26
  • お世話になります。

    返信が遅くなり申し訳ございません。
    確認した結果、GetModuleHandle() でハンドルが返ってきた場合のみ、
    アドレスの書き換え処理を行うようにしたところ、
    劇的にパフォーマンスが改善しました。また、経過を確認していたのですが、
    書き換え漏れも起こってないように思えます。

    みなさま色々とご意見を頂きましてありがとうございました。
    クローズとさせて頂きたいと思います。今後とも宜しくお願い致します。

    2019年4月3日 2:21