DllMainでCreateThreadは動作する

以前にDllMainでCreateThreadを呼び出すことはWindowsのマナーに反する、という指摘をもらったことについて、その理屈がわからんという趣旨の記事を書いた。

今日知ったのだがBest Practices for Creating DLLsに拠れば、これは条件付きで正しく動作する。この文書にはDllMainで行うべきでないこと、および行っても安全なことのリストが記されており、行うべきでないことの中に次の記述がある。

Call CreateThread. Creating a thread can work if you do not synchronize with other threads, but it is risky.

CreateThreadを呼び出すこと。作成されたスレッドは、他のスレッドと同期しない限り動作しますが、それはリスキーです。


なにがどうリスキーなのか明確でないが動作する。動作するが should never であり risky である。これをどう解釈すべきかは悩ましいところかもしれない。


ちなみにDllMainでやってはいけないことの典型は、

  1. DllMainに入るためにローダはロックをかけるので、そのDllMainのなかで再度DllMainを実行するようなコードを呼び出すとデッドロックする
    • たとえば、直接ないしは内部的に LoadLibrary系 / GetModuleHandle系 を呼び出す行為、スレッドの同期
  2. DllMainが実行された段階では未初期化のDLLが存在する可能性があり、それらの未初期化DLLに依存する関数の呼び出しを行うとプロセスはクラッシュする
    • たとえば、user32.dll/gdi32.dllの関数や内部実装でadvapi32.dllを使用する関数、ダイナミックCRTライブラリのメモリ系関数の実行
  3. マネージドコードの使用

逆にファイルの読み書きや、一部を除くkernel32.dllの関数やメモリの確保などは問題ないと明記されている。マネージドコードの使用についての問題は混在アセンブリの初期化を参照するとよさそう。

DllMain Callback Function

イメージからのSDTリストア

ntoskrnl.exeをディスクから開き、PEヘッダを解析してプロセスメモリにマッピングした後、コードセクションを読み取りカーネルメモリと比較することでフックを検出する技法がある。

これの発展系として、SDT RestoreではKeServiceDescriptorTableを比較し異常を検出するのに加え、カーネルメモリへ書き戻してフックを解除する(ファイルイメージからのリストア)。SDT Restoreではユーザーモードで処理を完結させるため Device\PhysicalMemory を使用しているので現在のOSでは動作しないが、カーネルモードからであれば依然としてこの方法は可能(のはず)。


この技法自体は防御側のアプローチとして知っていたのだけど、偶然Trojan.Downexec.Bのテクニカルレポートで、これを攻撃に使用できうることを知った。Trojan.Downexec.BはKeServiceDescriptorTableがすでにIDSによってフックされていることを想定し、この防御を無効化するためにイメージからのリストアを行うらしい。
なるほど、いわれてみれば初期化は攻撃者にとっても有益ですわな。ほんと趣味プログラミングはアイディア勝負だね。