Javaプロセスのスレッドダンプを取るツール(SendSignal追跡編)

SendSignalは結構凄いツールだと思う。普通にWin32APIを呼んだだけじゃこのツールは実現できないはず。仕組みが非常に気になるので解説を読んでみた。


GenerateConsoleCtrlEventがそれっぽかったけど,自分にしかシグナルを送れなくて…」やっぱり最初はおんなじことが書いてある。でも,ここから先はなんかすごいことになってる。


「console ctrl ハンドラを作って,自分でGenerateConsoleCtrlEventを発生させて,その時のスタックベースを見てkernel32!BaseThreadStartの引数を探して…」
???さっぱり意味が分からん。一体何を言ってるんでしょう?何かわからんけど,Louisはすごいマニアックなスキルを持ってるようだ。これは是非とも理解しておきたい。


さっそくソースをダウンロードしてみた。


ライセンスを見るとなんかごちゃごちゃ書いてあるけど,公開しても問題なさそうなので,ここに書いても大丈夫でしょう。多分。よく読んでない。勝手にパクってごめんなさい。面白かったのでつい…


以下に物凄く断片的に,自分以外には全く意味不明と思われる説明を書いとく。




大まかな流れは以下のような感じだった。

  1. コマンドに,Ctrl+Breakイベントを受信した場合にカーネルが実行するイベントハンドル用メソッドMyHandlerを作成。このメソッド内で,カーネルがCtrl+Breakイベントを受信した場合に実行するデフォルトのメソッドの実行アドレスを取得する。らしい。
  2. コマンドプロセス内でSetConsoleCtrlHandlerを実行し,Ctrl+Breakイベントを受信した場合にMyHandlerを実行するようセット。
  3. コマンドプロセス内でGenerateConsoleCtrlEventを実行し,プロセスID「0」にCtrl+Breakイベントを送ってカーネルの実行アドレスを取得。
  4. ターゲットのプロセスのハンドルを取得。
  5. コマンドプロセスからターゲットのプロセスにCreateRemoteThreadで取得した実行アドレスを実行するスレッドを注入。で,めでたくターゲットのプロセスでCtrl+Break実行完了。


つまり,「同一マシンのプロセスなら,カーネルがCtrl+Breakイベントを受信した場合に実行するメソッドの実行アドレスは同じなので,まず自プロセスでそれを取得して,ターゲットのプロセスにスレッドを作ってそのアドレスから実行する」ということらしい。今となっては理解できるが,最初は全く意味がわからんかったぞ。




詳しく見ていく。
まず1
MyHandlerの処理は大体こんな感じになってた。

	DWORD g_dwCtrlRoutineAddr=NULL;		

	BOOL WINAPI MyHandler(DWORD dwCtrlType) {
			:

 		// read the stack base address from the TEB
 		#define TEB_OFFSET 4
  		DWORD * pStackBase;
        		__asm { mov eax, fs:[TEB_OFFSET] }
        		__asm { mov pStackBase, eax }

        		// read the parameter off the stack
        		#define PARAM_0_OF_BASE_THEAD_START_OFFSET -3
        		g_dwCtrlRoutineAddr=pStackBase[PARAM_0_OF_BASE_THEAD_START_OFFSET];
			:
	}

いきなりインラインアセンブラが入ってて意味不明です。一応アセンブラは理解できるものの意図が全くわからん。FSレジスタって何が入ってんの?TEBってなんじゃ?


ググッてみた。ふむふむ。どうやらTEBはThread Environment Blockの略で,スレッドに固有の情報をいろいろ保持している構造体らしい。


インサイド MS WINDOWS 第4版 上 (マイクロソフト公式解説書)っていう本にTEBの解説が詳しく書いてあったので,念のため買ってみた。高い。5800円もした。この本は,ある程度詳しい人には非常に役立つと思うけど,ペーペーにはとってもつらいです。ほとんど理解不能です。


そして,TEBの細かい情報は,http://undocumented.ntinternals.net/にあるらしい。このHPはなんじゃ?ドキュメント化されてないWindowsの内部情報がバシバシ書いてあるようです。凄い労力がかかってる気がする。世の中いろんなことしてる人がおるなぁ…


で,FSレジスタとの関係は?手元にあるアセンブラの本には,「FSレジスタはユーザプログラムからは使用することはほとんどない」みたいなことしか書いてない。ありゃりゃ。


さらにググッてみた。ふむふむ。どうやらWindowsではFSレジスタにTEBのアドレスがこっそり設定されているらしい。で,TEBやらTIBやらPEBの情報を知りたい時は,FSレジスタ経由で,

	mov eax, fs:[オフセット]
	mov hoge, eax

ってやって読み出すのが定石らしい。マイクロソフトのHPにもちょこっと書いてあったし,一部の人の間では常識らしい。


ハッカー・プログラミング大全 攻撃編にも書いてあった。この本も面白そうなので買ってみた。4200円もした。ちょっと泣きそう。でもとっても勉強になった。トップを走ってる人の仕事量は半端じゃないな。


TEBの4バイト目(つまりTIBの4バイト目。TIBはwinNT.hで定義されてる)から4バイト分はスタックベースのアドレスになってるので,それをpStackBaseに設定している。ここまでは調べればなんとなくわかったので良しとしよう。


で,次。実行アドレスを取得するところ。

	#define PARAM_0_OF_BASE_THEAD_START_OFFSET -3
	g_dwCtrlRoutineAddr=pStackBase[PARAM_0_OF_BASE_THEAD_START_OFFSET];

とありますが,はて?なんでしょうこれは。なんでここに実行アドレスが入ってるって分かったんでしょう?Louisのコメントを見ると,「kernel32!BaseThreadStartの引数を調べるために,スタックのトップ(ベースか?)を見て,スレッドの実行開始アドレス,つまりkernel32!trlRoutineのアドレスをゲットした」と書いてある。


…うーむ,私,もはや完全に置いていかれとります。


だんだんめんどくさくなってきたので,もう断念しようかと思ったけど,やっぱり悔しいのでもうちょっとがんばろう。


何を調べたらいいんかわからんのでボケ−とインサイド MS WINDOWS 第4版 上 (マイクロソフト公式解説書)を眺めてると,「カーネルデバッガを使えばなんでもイケる!便利だぜ!」って書いてあった。ほほぅ。なるほど。


「Debugging Tools for Windows」なるものを導入してwindbgを適当に使ってみた。


おお!詳しい使い方はわからんがとりあえず,各プロセスのスレッドのスタックが見れる。kernel32!BaseThreadStartとかが出てる。ドライバとか作ってる人はこういうツールを駆使してるんかな。こういうツール作れる人ってすごいなぁ…


windbgでMyHandlerを実行してるときのスタックを見てみた。



スレッドが実行されるときは必ずkernel32!BaseThreadStartから始まるらしく,その第一引数が実行アドレスになってるということらしいです。多分。つまり赤の四角のとこがkernel32!BaseThreadStartの第一引数で,kernel32!CtrlRoutineの実行アドレスなんでしょうな。


で,このツールはスタックをわかり易く表示してくれてるけど,実際のスタックは

  [スタックベースからの位置]   [値]
	   :		       :
	pStackBase[-3]	+---------------+
			|  実行アドレス |
	pStackBase[-2]	+---------------+
			|    第2引数   |
	pStackBase[-1]	+---------------+
			|    第3引数   |
	pStackBase[0]	+---------------+

ってなってるから,

	g_dwCtrlRoutineAddr=pStackBase[-3]

で,めでたくCtrl+Breakイベントを受信した場合に実行されるメソッドの実行アドレスゲットということらしい。多分Louisもこうやって調べていったんでしょう。凄い知識量と根性やな。いちいち感心しすぎで我ながらくどいけど…


そして,いざ実行。2,3に書いたようにMyHandlerを実行して,実際にg_dwCtrlRoutineAddrに実行アドレスを取得する。


4では,コマンドの引数で指定したCtrl+Break送信対象プロセスのハンドルを取得する。


そして5
CreateRemoteThreadによって,4で取得したプロセスにスレッドを生成してg_dwCtrlRoutineAddrを実行する。このCreateRemoteThreadっていうAPIは…あらびっくり!


「指定したプロセスに,指定した実行アドレスを実行するスレッドを生成することができる」らしい。こんなAPI全然知らんがな!まるでちょっとしたレイプじゃないですか・・・セキュリティ的に問題アリな気がするがまあ余計なお世話でしょう。


ハッカー・プログラミング大全 攻撃編とかhttp://japan.internet.com/developer/20050830/print26.htmlとかにこのAPIの素敵な使い方が解説されてた。SendSignalは単純にkernel32.dllのメソッドを実行してるだけやけど,はっかーのみなさんはここからさらに工夫して,いろんなコードを注入してうまいことやっていくようです。なるほどねぇ。


とにかくこれで,カーネルがCtrl+Breakを受け取ったときに実行する処理を,任意のユーザプロセスで実行することが出来る。アクセス権が問題になることがあるみたいやけど,そのへんの細かいことは省略。





以上で大体肝心なとこは理解できたはず。
今までこういうちょっとトリッキーなことをやってるソースをじっくり読んだことがなかったから非常に勉強になった。まだまだ修行が足りんです<(_ _)>