トップ回答者
LoadLibrary の成否を調べるには

質問
-
お世話になっております。
dll インジェクションを行って shell32.dll の api の振る舞いをログ出力しているのですが、
対象のプロセスが LoadLibrary を呼び出してshell32.dll を再ロードした場合に、
LoadLibrary もフックしているため、その中で再度インポートテーブルの
書き換えなどを行っています。その際に問題がありまして、
Explorer でフォルダの名前変更を行った場合など、LoadLibrary がかなり大量に実行され
(秒間数十回) shell32.dll の再読み込みが多発します。
その都度CriticalSectionで排他しつつテーブルの書き換えを行うため、
Explorer のパフォーマンスがかなり落ちます。LoadLibraryが呼ばれても、まだメモリ上に shell32.dll が残っているため
実際は再ロードしていないようなのですが、呼び出された LoadLibrary によって
本当に再ロードしたのかどうか?を何かで判定する方法はないでしょうか?宜しくお願い致します。
回答
-
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
すべての返信
-
とりあえずお手軽な方法としては。。。。
Reference Count が 0 にならない限り Unload はされないので、Unload させたくない DLL に対して、予め「余計」に LoadLibray() コールをしておけばいいのでは?
あるいは、FreeLibrary() と併せてコールされた回数をチェックするとか。
もっとも、誰かが意図的に複数回 FreeLibrary() コールを行ったら、この方法でも NG ですけど。----------------------------------
LoadLibraryA function
https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibraryaRemarks
.....
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
---------------------------------- -
Reloadの概念がよくわからないので教えてください。
FreeLibraryされずにReloadが発生することがあるのでしょうか?
Reloadは必ずLoadLibraryのタイミングでのみ発生するのでしょうか?(他のタイミングでは行われないことが保証されているのでしょうか?)
Reloadが発生すると質問文のようなパッチ内容がリセットされるのでしょうか? (他のタイミングではリセットされないことが保証されているのでしょうか?)もっとも気になるのが、FreeLibraryされずにReloadが発生した場合、当該ライブラリが確保していたリソースはどうなるのでしょうか? 例えば質問文ではshell32.dllについて言及しています。shell32.dllに含まれるExtractIconはハンドルを返します。Reloadが発生した場合、このハンドルは有効なのでしょうか?
-
私の言っている Reload とは、そのまんまの意味。
Unload された後に、LoadLibrary() が呼び出された場合の Reload。なぜ以下の質問をされるのか、その趣旨が私には理解できていませんが一応。。。。
LoadLibrary() や FreeLibrary () のリファレンスでも説明されてますが、モジュールの Unload を決定する要素は "Reference Count" です。
> FreeLibraryされずにReloadが発生することがあるのでしょうか?
> Reloadは必ずLoadLibraryのタイミングでのみ発生するのでしょうか?
> (他のタイミングでは行われないことが保証されているのでしょうか?)
裏を返せば、"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 補足
-
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 )を使うことで、ハンドルがまだ有効かどうか判定するという考え方でしょうか? 失敗→ハンドル無効→アンロードされた、と。
-
> LoadLibraryとFreeLibraryからReference Countを推測し、
> 0になった時点でアンロードされたとみなせばいいのでは。
> 最初からそう主張しています。
話が噛み合っていません。
私は佐祐理さんの、下記返信についていっているのです。> 正攻法で行くなら、同じDLLをロードする限りハンドル値は変化しないのでは?
繰り返しになりますが、LoadLibrary() API の戻り値は、そのモジュールがユーザモード空間にマップされたときの先頭アドレスです。
> そして同名でも別ファイルの場合は別のハンドル値になるのでは。
> つまり、LoadLibraryの戻り値を監視して、
> 未知の値であれば新規にロードされたと判断すればいいのでは?
> もちろんFreeLibraryも監視し、アンロードされたかを確認するとか。
「LoadLibraryの戻り値」、すなわちハンドル値で判別するのが「正攻法」と主張されてたのでは?
そもそも、佐祐理さんの返信で "Reference Count" と言う単語が出てきたのは今回が初めてなのに、「最初からそう主張しています。」と言われても、お馬鹿な私にはさっぱり分かりませんでした。
モジュールの Reload ではどこにマップされるかは不定なため、ハンドル値からの判別は無理だと指摘したのです。
(少なくとも、質問者さんは DLL Injection しているので、LoadLibrary の戻り値がモジュール先頭アドレスであることを理解しているはず。)
また、私は「Reference Countを推測」も無理だと思っています。(ここも噛み合ってない。。。)
私が一番初めの返信で提案したのは、「推測」ではなく、値そのものの「確認」です。
だから "Reference Count" の値を取得するサンプルが提示されている URL を張り付けたのです。 -
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