堕落したWindowsプログラマのレベル -10
- XPのタスクマネージャーで表示される「メモリ使用量」の意味を理解している。そのうえで、これを減らすためだけの技巧を凝らす。メモリ使用量が少なく見えれば、ユーザーの満足度は上がるさ!
- DLLインジェクション+APIフックが便利だ。フックする際は、IATフックが有効でない場面、インラインフックが危険な場面を理解しつつも、めんどくさいと思い気にしなくなった(クラッシュするのは僕のプログラムじゃないしね)。
- Undocumentedな構造体やAPIも便利だと思えば使う。興味深い機能は逆アセンブルして実現しているAPIとその使い方を特定することがある。
- ユーザーモードでは力不足と感じて、カーネルモードコードの開発も選択肢に含める。コードの量も少なくてすむし、スマートな方法だ。青画面になるかもしれないが、Readmeに一言書けばいいだろうと考える。
- ユーザーモードプログラミングは面倒が多いのでカーネル空間でやった方が早い、という目的・状況がしばしばある。
- GDI32の関数のほとんどを知らないか、忘れた。USER32はSetWindowsHookExをexportしていることだけ覚えている。それで特に困らない。
- こんな複雑なカーネルモードのAPIを使うくらいなら、システムの内部構造体を直接参照した方が楽だなあ、と迷う。
- Windowsカーネルの逆アセンブルを見ながら青画面の発生を回避するためのHackを重ねている。
- Windowsカーネルのなかで動く、自分の書いたコード以外のすべてのコードが邪魔で、Windowsうざい、とか思う。
- オープンソースなOSの素晴らしさに気づく。
main が呼ばれる前に実行されるコードのコールスタック
プログラムのエントリーポイントは、main関数ではなく、PE内に定義されたアドレスであることはよく知られているが、それより以前にコード実行できるTLS Callbacksという仕組みはあまり知られていない。
TLS CallbacksはPE内に適切にセクションとディレクトリテーブルを作り、そのテーブルにコールバック関数のアドレスを設定しておくと、イメージローダー(ntdll.dll)によるプロセス初期化処理中に、その関数が実行されるという仕組みである。
この関数の呼び出しをブレークするのは、PEを解析して手動でブレークを置いたり、対応しているデバッガ(OllyDbg 2.0)でブレークをかけたりなどの方法があるが、呼び出しのシーケンスを把握しておいた方が、いざというとき小回りが利くので調べてみた。
続きを読む細かいWinデバッグテクニックのメモ
(12/05)追記したら記事の方向性がわかりやすくなったのでタイトルも変えた(笑
UserDebuggerHotKey
GUIアプリケーションに対してデバッガがアタッチしているとき、このレジストリキーで設定されたキーを押すと、ブレークが発生しデバッグすることができようになる。環境によってはできないこともあるみたい?
.ocommand (WinDbg)
- WinDbg上でプロセスにアタッチする
- .ocommand
メタコマンドを実行する - 以後 OutputDebugString のプレフィックスに
がついているものは 以降がWinDbgコマンドとして解釈される
残念ながら、カーネルランドでは利用できない。
指定したプロセスの起動を置き換える(MSDN)
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<もともとのイメージ名>
Debugger="置き換えた先のファイルパス"
キー名で指定したイメージ名を持つプロセスが起動されようとしたとき、代わりに、Debuggerで指定されたプロセスが起動し、引数にもともと起動しようとしたプロセスのフルパスが渡される。
もともとのイメージが起動時に特権ユーザーを要求していても影響はないが、Debugger側が起動時に特権ユーザーを要求する場合、結果的には起動することができない。
[ファイル名を指定して実行]を含めて、起動プロセスが置換されるので結構便利*1。望むなら、cmdとタイプしてpowershellが起動するように設定することもできる。
指定したDLLがロードされる直前にブレークする(Code Project)
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<ブレークしたいDLL名>
BreakOnDllLoad=1 (DWORD)
とすると、デバッガでアタッチしているプロセスが上記で指定したDLLをロードする直前にブレークする。ntdllのコードの中でブレークするので、DLLのコードまではステップ実行する必要がある。
WinDbgでコードパッチを簡単に検出する
!chkimgコマンドを使うと、コードパッチを簡単に検出することができる。以下の実行例では、ntカーネル(ntoskrnl.exe)に10byteの改ざんがあること、それらが5byteの改ざん2つ(そして関数フック)であることを確認している。
C:\>livekd ... 0: kd> .exepath c:\windows\system32 Executable image search path is: c:\windows\system32 Expanded Executable image search path is: c:\windows\system32 0: kd> !chkimg nt 10 errors : nt (8332b2d0-8332b349) 0: kd> !chkimg -d nt 8332b2d0-8332b2d4 5 bytes - nt!InbvSolidColorFill [ 8b ff 55 8b ec:e9 11 ce 26 06 ] 8332b345-8332b349 5 bytes - nt!InbvSetTextColor (+0x75) [ 8b ff 55 8b ec:e9 e0 cd 26 06 ] 10 errors : nt (8332b2d0-8332b349) 0: kd> u 8332b2d0 nt!InbvSolidColorFill: 8332b2d0 e911ce2606 jmp bsodp_nt+0x10e6 (895980e6) 8332b2d5 51 push ecx 8332b2d6 51 push ecx ... 0: kd> u 8332b345 nt!InbvSetTextColor: 8332b345 e9e0cd2606 jmp bsodp_nt+0x112a (8959812a) 8332b34a 83ec0c sub esp,0Ch 8332b34d 6a25 push 25h ...
SSDTなどのデータの改ざんは検出できないものの、モジュール全体を(関数のエントリーポイントに限定したりしないで)調査するので、とりあえずlivekdからたたいて見るのも良いかもしれない。もちろん、ユーザーモードプロセスのフックを検出したいのであれば、プロセスにアタッチしてたたいても良い。
原理は調べていないのだけど(少なくともProcmonで見る限り、ディスクイメージを開いて比較、とかではない模様)、コードパッチ自体を隠蔽しようとするようなRootkitはかなりレアだと思われるので、結構効果的なのではないかと思う。
ただし、改ざんがあった=ウイルスの所為 ではない。感染していない環境でも改ざんが行われていることがあるので、正常時のベースラインを把握しておくことが重要。
SetThreadContextが変な動きをする
64bitプロセスがSetThreadContextをCONTEXT_CONTROLフラグなしで行うと、対象になったスレッドのCSがなぜか勝手に0x23になる場合がある。64bitプロセスのCSは本来0x33で、0x23はWOW64(32bitプロセス)のための値である。この異常な変更が行われると、そのスレッドは32bitを超えるアドレスを正しく扱えなくなるため、正常に動作しなくなる。
手元のx64 Vista SP2/x64 XP SP2でこの異常な変更を確認できた。x64 Win7 ではこのような現象は起こらない。回避策としては、(仮に値が不要でも)常にCONTEXT_CONTROLフラグをつけ、正しい値を取得したうえでSetThreadContextする。以下は再現コード。
続きを読むハードウェアブレークポイントを使う
コードを実際に書いたことがなかったので書いてみた。デバッグレジスタは本来特権レベルでなければ操作できないが、Windowsの場合はSetThreadContext系APIを使って設定することができる。
ハードウェアブレークポイントはソフトウェアブレーク(int 3)ほど使われていないものの、ReadやWrite時にブレークさせることができたり、仮想メモリの書き換えを行わない(チェックサムなどの整合性検査で検知されない*1)で設定できたり、コンテキストに紐づくためスレッド単位の制御が可能であったりと、用途によっては有用だったりする。
以下のプログラムは、Sleepを実行するスレッドを2つ生成し、片方のスレッドのSleepにだけ実行時ブレークポイントを設定する。
続きを読むVisual Studioでasmファイルを使う
x64ではインラインアセンブラが使えないので生のMASMを書く必要がある。
- asmファイルをプロジェクト内に追加する
- 追加したasmファイルのプロパティを開き、「カスタム ビルド ステップ」>「全般」の
- 「コマンドライン」に ml64 /c /Cx /W0 /Fo$(IntDir)\$(InputName).obj "$(InputPath)"
- 「出力ファイル」に $(IntDir)\$(InputName).obj
- 普通にビルド
- (゚Д゚)ウマー
ちなみにドライバ開発をやっているときは、
以下は例。
続きを読む