wanwan - みる会図書館


検索対象: ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法
10件見つかりました。

1. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

第 5 回関数の hooking になる。 stub の書き換えではスタックの辻褄が合わなくなっておかしくなるのか ? いやま てよ、 stack overflow ということはスタックを使い切っているというわけで・・・スタックを 使い切るにはどうすればいいのだ ? スタックをそんなに消費するのは、関数を無限に再帰 的に呼び出したときぐらいではないのか ? 「あっ ! 」 0 ケンイチは原因に気付いた。 HookedKiIlWanwan 関数の KillWanwan(); も、 hook されて いるので Hooked K Ⅲ Wanwan を呼び出してしまうのだ。よって無限の再帰ループになる。 ならば話は簡単だ。ケンイチはリスト 6 のように書き換え、ようやく関数を hook すること / / Ki は wanwan の stub の値を stub2 へジャンプするように書き換える uint stub2 (uint)(void*)HookedKillWanwan; / / HookedKiIIWanwan の stub のアドレスを取得 stub + JumpOffset + 5冫 g_OriginalKiIIWanwan / / 元の Ki は wanwan のアドレスを保存しておく uint& JumpOffset = *(uint*)((BYTE*)stub + 1)冫 ここに stub からのジャンプ先への offset カ材各納されている uint stub (uint)(void*)KillWanwan; / / KillWanwan の stub のアドレスを取得 void FuncHook()C ((v0id(*)())g_OriginaIKiIIWanwan)(); / / Ki [ lWanwan の呼び出し CheckVaLid(); / / 正当性のチェック void HookedKi [ lWanwan(){ uint g_OriginaIKiIIWanwan; typedef unsigned int uint; マリスト 6 関数 h 。 ok ( 完成版 ) に成功した。 DWORD dwOldProtect; VirtualProtect(&JumpOffset ′ &dwOldProtect); stub2—(stub + 5)冫 JumpOffset VirtualProtect(&JumpOffset ′ sizeof(JumpOffset) ′ sizeof(JumpOffset) ′ dwOIdProtect ′ PAGE_EXECUTE READWRITE ′ &dwOldProtect); 97

2. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

第 4 回 stub を乗り越えて コールスタックの活用 相対ジャンプということは、そこのアドレスが Ox100 で 0 升 set が 8 だとしたら Ox108 にジ ャンプということだろう。それくらいはなんとなくケンイチにもわかる。ケンイチは自分 の予想を信じてコードを書いた ( リスト 5 ) 。 ▽リスト 5 stub を考慮したプログラム / / これが stub を指している (uint)(void*)wanwan; uint p2 / / stub + 1 バイト目にジャンプ先への offset が格納される *(uint*)((BYTE*)p2 + 1)冫 uint offset wanwan / / stub のアドレスに offset を加えたところにジャンプ uint real wanwan (uint)((BYTE*)p2 + offset wanwan); 「 e wanwan には、 wanwan の関数実体が格納されているアドレスの値が入るはずだ。 ケンイチはデバッガで実行し、 real-wanwan の値を確認した。先ほどからデバッガで確認 uint real wanwan (uint)((BYTE*)p2 + offset wanwan + 5)冫 / / stub のアドレスに offset を加えたところにジャンプ そんなのでいいの ? と思った。 早井は、まるで風呂のお湯が足りなくなったときのような口調で言っ - た。ケンイチは、 「ああ、それか。 5 足しておけばいいよ」 ケンイチは泣き出しそうな声で早井に助けを求めた。 「早井先輩、助けてください。また 5 少ないみたいです」 していた wanwan と近い値になったものの、 5 だけ少ないようだ。 75 ャンプするという仕掛けだ」 値に 0 幵 set を加算して、そこを飛び先とする。だから、 5 バイト多めのアドレスにジ いるんだ。そして、ジャンプ命令は、その ( 5 を足された ) プログラムカウンタの ムカウンタ ( 次に実行すべき命令を指しているポインタ ) はすでに次の命令を指して ( 4 バイト ) = 5 バイトだ。この命令をプロセッサが処理しようとしたとき、プログラ 「 Oxe9 と言う 32 ビットの相対ジャンプの命令長は 1 バイト十 32 ビットのオフセット

3. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

デバッグが ひなた先生が教える一 し 1 ーーー . 丿くなるテクニック ケンイチの当初の目的であった関数の呼び出し元を判定するコードは結局、 うなものになった。 マリスト 6 関数の呼び出し元を判定する void nyanko(int a ′ int b){ typedef unsigned int uint; uint p = *(uint*)(((BYTE*)&a)—4); uint p2 (uint)(void*)wanwan; uint p3 (uint)(void*)nyanko; uint offset wanwan = *(uint*)((BYTE*)p2 + 1)冫 wanwan (uint)((BYTE*)p2 + offset_wanwan + 5)爰 uint offset_nyanko = *(uint*)((BYTE*)p3 + 1)冫 uint real_nyanko (uint)((BYTE*)p3 + offset_nyanko + 5)冫 if ((real wanwan く = p) & & (p く real_nyanko) ) { printf("Jump from wanwan\n"); } e [ se { printf("Jump from unknown\n"); 「ケンイチ君。あと 5 分で指がなくなるぞ。わっはつは ! 」 リスト 6 のよ 早井のいつもの悪い冗談が始まった。でも、こまでくればあと 5 分もあればデバッグ が完了するだろう。水曜日の昼下がり、ケンイチは自分を育ててくれている早井に感謝の 76 気持ちで満たされていた。

4. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

0 デこ匕も電 ) くなるテクニック 「は、、はい。その呼び出している部分のソースはあるんですよね ? 」 「ああ、あるある。だからして、もう、鬼に金棒だろ ? ケンイチ君」 0 「え、、ええ、、まあ、、」 何が鬼で何が金棒なのかさつばりわからなかったケンイチはあえて聞かないことにした。 誰がわんわん殺したの ? の巻 ケンイチは呼び出し部分のソースを調べてみた。バグの原因は簡単にめぼしが付いた。 KillWanwano こいつを呼び出している関数のどれかがおかしい。この関数は DLL 上の関 数だ。どの関数かが、 K Ⅲ Wanwan を不正なバラメータで呼び出しているのだと思った。 まず、 K Ⅲ Wanwan を呼び出しているところを g 「 ep で探し出そうと思ったが、よく考え ると他の DLL から間接的に K Ⅲ wanwan が呼び出されている可能性だってある。 K Ⅲ Wanwan のアドレスはわかるから、そのアドレスにデバッガでブレークポイントを設 定することだってできるだろう。しかし、 K Ⅲ wanwan が何千回と呼び出されているなら、 何度もそこで停止する。その都度手作業で調べるのは非現実的だ。 「よし、 K Ⅲ Wanwan を hook 注 2 してしまおう」 関数の呼び出し元を、コールスタックを調べることによって特定させるテクニックを先日開 発したケンイチは、そのとき関数呼び出しに stub が使われていることに気付いていた ( 図 9 ) 。 マ図 9 stub による間接ジャンプ 下位アドレス stub ひなた先生か教えるデバッグが Oxe9 ( 相対 Jump) Jump 先への Offset ( 4 バイト ) 上位アドレス 94

5. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

の デパッグが 2 らっ驫くなるテクニ ケンイチは 2 行書き足して、デバッガで、 void nyanko(int a ′ int b){ typedef void (*FUNC)(int ′ int); FUNC p = *(FUNC*)(((BYTE*)&a)—4); ひなた先生か教える一 こまで実行して p の値をウォッチした。 ック 70 printf("Jump from unknown\n"); } e [ se ( printf("Jump from wanwan\n"); if (((FUNC)wanwan く = p) & & (p<nyanko)){ FUNC p = *(FUNC*)(((BYTE*)&a)—4); / / ↓これが戻り先アドレス typedef void (*FUNC)(int ′ int); void nyanko(int a ′ int b){ V リスト 3 戻り先アドレスをチェックする ケンイチは、さっとタイプした ( リスト 3 ) 。 間違いない」 wanwan$p<nyanko であれば、わんわん関数から呼び出されたものだと考えて バイナリレベル ( 実行コード上 ) でも隣り合わせになると予想される。つまり、 「また、ソースコード上、 wanwan 関数の直後に nyanko 関数を記述してあるので、 アドレスが取得できることにケンイチは気付いた。 限りは ) 同じくスタック上に確保されるはずなので、この事実を利用すれば戻り先の関数 関数の引数が仮になかったとしても、ローカル変数を使用すればそれは ( 最適化されない 引数があるからこそ使えるテクニックだな、とケンイチは思った。しかしよく考えれば、 両者の値に非常に近いことから、 p の値が戻り先アドレスで間違いないとケンイチは確信 これは、わんわん関数のアドレスが表示されるはずだ。その値は Ox00411c50 だった。 0 ケンイチは wanwan の値もウォッチした。 バッガで「呼び出し履歴」を確認すると、 wanwan から飛んで来ているようだったので、 p の値をウォッチしてみると 0x00411c81 だった。これは戻り先のアドレスなのか ? デ

6. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

0 デパッグが 2 らも危つくなるテクニック 「これ、どうすればいいんだ・・・」 ケンイチは途方にくれた。該当行前までデバッガで実行して、「 &JumpOffset 」をデバ ッガでウォッチしてみた。 Ox41120e だった。ここを Visual studio の [ デバッグ ] インドウ ] [ メモリ ] で表示させて、手作業で書き換えてみた。 手作業でならば書き換えられるようだった。しかし、なぜプログラム上からは書き換え られないのかがわからなかった。該当行をコメントアウトしておき手作業で書き換えたと ころ、関数は正しく hook され、 K Ⅲ Wanwan を呼び出しているところで HookedK Ⅲ Wanwan に飛んできていた。成功だ ! stub の書き換えが必要になるのは、起動時の 1 回だけだから、プログラム開始直後に適 当なところでブレークさせてメモリ 0X41120e を手作業で書き換えることにした。 「こりや、ケンイチ君。何を原始的なことをやっとるのだ ! 」 早井がうしろからケンイチの作業しているモニタを覗き込んだ。 「だって、 stub の書き換えでアクセス違反になるんですよ」 「なるだろ。そのページに書き込み属性が付与されてないからな。だからして、 月リ 後で VirtuaIP 「 otect をしておけばョロシイ」 そう言うと、早井はさっとソースを書き換えた ( リスト 5 ) 。 リスト 5 VirtualProtect で解決 ! / / Ki は Wanwan の stub の値を stub2 へジャンプするように書き換える DWORD dwOLdProtect; VirtualProtect(&JumpOffset ′ sizeof(JumpOffset) ′ PAGE_EXECUTE—READWRITE ′ &dwOLdProtect); JumpOffset stub2—(stub + 5)冫 VirtualProtect(&JumpOffset ′ sizeof(JumpOffset), dwOldProtect ′ &dwOldProtect); 「はは一、そんなことできるんですか」 「うむうむ」 ひなた先生が教える [ ウ 0 臼 満足げなおももちで去っていく早井。 しかし、 KiIIWanwan の呼び出しが HookedKiII Wanwan のほうに hook されるようになっ たが、実行してみると HookedKiIIWanwan 関数の KillWanwan0; のところで stack overflow 96

7. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

の ひなた先生か教えるテ / ーッグが匕ふ = ド生成上のバグがあるはずがない。ケンイチはそう思った。 電 ~ - ー . ー」くなるテクニック 関数ポインタは嘘の値 ? ! 「関数ポインタ同士を比較しているのが、悪いのか ? 」 ケンイチは、リスト 4 のようにいったん整数に変換して、整数として比較することにした。 マリスト 4 関数ポインタをチェックする typedef unsigned int uint; uint p = *(uint*)(((BYTE*)&a)—4); printf("Jump from unknown\n"); printf("Jump from wanwan\n"); i f ( ( p2 く = p ) & & ( p く p3 ) ) { (uint)(void*)nyanko; (uint)(void*)wanwan; ) e L se { uint p3 uint p2 72 ある 0X0041150 なのだろうか ? そういう実装があっても不思議ではないが .. 。 ているのか ? つまり、 (*p2) の値が、さきほどデバッガで表示されていた wanwan の値で すなわち p2 の指し示す 0x004115fa には、 wanwan の関数実体へのポインタが格納され だとしたら、 p2 、 p3 の指しているものは何だ ? 関数ポインタテーブルか ? いるのかもしれない。ケンイチはそう思った。 らが本当の値なのかもしれない。関数アドレスを表示するときだけデバッガが嘘をついて しかし、これならば else 文のほうが実行されていたことには納得がいく。そもそもこち 「そんな馬鹿な。 cast したことで値が変わってしまっているじゃないか」 p2 と p3 の値をウォッチしてみた。なんと p2 、 p3 の値は Ox004115fa と OX004115 升だった。 実行してみると、やはり else 文のほうが実行されてしまう。不思議に思ったケンイチは、 が、とりあえずはこれで良いだろう」 「関数ポインタを void * に cast するのは ANSI C では許されていなかった気がする

8. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

第 4 回コールスタックの活用 ケンイチはデバッガでい p2 ) を表示させて調べてみたが、どうもそうではないようだ った。 「あっ ! ! 」 そのときケンイチは奇妙なことに気付いた。 p2 と p3 との値が 5 だけ離れているのだ。 5 というのは何だ ? もし p2 、 p3 が指しているのが関数テーブル上のどこかの値だとしたらポ インタのサイズである 4 ( 32bit 環境時 ) かその倍数だけ離れているはずだ。 5 というのは何だ ? 本当に wanwan 関数は 5 バイトしかないということか ? いやいや、そ れはありえない。 wanwan 関数は結構コードを書いてあるのに、それが 5 バイトに収まると は到底思えない。 「そろそろできたか ? 」 早井が覗き込んで来た。 「す、、すみません。まだです。デバッガの調子がおかしくて」 「は、、馬鹿も一ん ! ! 調子がおかしいのはお前の頭だろ ! 」 早井が瞬間湯沸かし器になった。 「だって、この p3-p2 が 5 になるんですよ ? 」 「そりや当然なるだろ ? 」 「へ ? なぜですか ? 」 ケンイチは目が点になった。 「 stub ( スタブ ) があるんだから当然そうなる」 「えっ ? そうなんですか ? ? その stub というのはジャンプ台だと考えていいので すか ? 73

9. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

第 4 回 コールスタックの活用 しかし、実行してみたところ「 Jump from unknown 」と表示されても「 Jump from wanwan 」が表示されることはなかった。 。「そんな馬鹿な・・・」 ケンイチは自分の目を疑った。「 Jumpfromunknown 」と表示している行にブレ i f ( ( ( F IJ N C ) w a n w a n ← p ) & & ( p く n ” n k 。 ) ) { FLINC p ニ * ( F LI N C * ) ( ( ( B Y T E * ) &a ト 4 ) ; t Y p e d e f v 0 i d ( * F LI N C ) ( i n t , i n い ; 日 vo i d nyanko ( i 黻 a , i 社 b ) { ▽図 4 それぞれの変数の値をウォッチ イントを仕掛け、それぞれの変数の値を調べてみた ( 図 4 ) 。 } 引託 { printf("Jump from wanwan*n ・。): printf("Jump from unknown* 「ド,): ークポ ウォッチ 1 : 名肯 nyanko く一 p<nyanko ロ x ロロ 411 C81 ロ x 411 c5 ロ nannan(int. int. int) ロ x ロロ 411 cbO nyanko(int. int) true true void (int. int)* void (int. int. int) void (int. int) bo 引 bo 引 「どういうことだ、これは・・・こんなことがあっていいのか ? 」 71 かく、 IO 数年もかけて天下の Microsoft が開発してきたコンバイラにこんな初歩的なコー ケンイチは、コンバイラのバグだと思った。しかし、素人が作ったコンパイラならとも else 文のほうが実行されること自体がおかしい。 どう見ても「 wanwanSp<nyanko 」という条件を満たしている。すなわち、リスト 3 の p は Ox00411 C81 、 wanwan は 0X00411 c50 、 nyanko は 0X00411 cb0 と表示されていた。

10. ひなた先生が教えるデバッグが256倍速くなるテクニック : 実践的ソフトウェアデバッギングの手法

第 5 回 関数の hooking ならば、この stub を書き換えてしまえば関数の呼び出しを hook することができるはずだ ( 図 10 ) 。 KilIWanwan の stub を HookedKilIWanwan にジャンプするように書き換えよう ( リスト 4 ) 。 マ図 10 stub の書き換えによる hooking KillWanwan 呼び出し¯¯> ーーーーー・ KillWanwan が実行される stub stub を書き換える KillWanwan 呼び出し一一 -4 ー・ -> H00kedKillWanwan が実行される stub KiIIWanwan 呼び出し マリスト 4 KillWanwan の hook void HookedKi lLWanwan(){ CheckValid(); / / 正当性のチェック KiIIWanwan(); / / 本来の関数を呼び出す void Hook()C / / KiLlWanwan の stub のアドレスを取得 uint stub (uint)(void*)KiLLWanwan; / / ここに stub からのジャンプ先への offset が格納されている uint&JumpOffset *(uint*)((BYTE*)stub + 1)冫 / / HookedKi [ lWanwan の stub のアドレスを取得 uint stub2 (uint)(void*)HookedKillWanwan; / / KillWanwan の stub の値を stub2 へ / / ジャンプするように書き換える stub2—(stub + 5)冫 JumpOffset 関数の呼び出し元特定のときに調べたことをそのまま使えば良いだけだったので、ケン イチにとってここまではさほど難しいことではなかった。しかし実行すると肝心の stub を 書き換える JumpOffset = stub2- ( stub + 5 ) ; でアクセス保護違反になるのだ。これは一体ど ういうことだ ? 注 2 hOOk こでは、関数の呼び出しを別の関数に書き換えることを言っている。 hook とは、「 ( ものを ) かぎで引っかける」の意味。 95