堕落したWindowsプログラマのレベル -10

  1. XPのタスクマネージャーで表示される「メモリ使用量」の意味を理解している。そのうえで、これを減らすためだけの技巧を凝らす。メモリ使用量が少なく見えれば、ユーザーの満足度は上がるさ!
  2. DLLインジェクション+APIフックが便利だ。フックする際は、IATフックが有効でない場面、インラインフックが危険な場面を理解しつつも、めんどくさいと思い気にしなくなった(クラッシュするのは僕のプログラムじゃないしね)。
  3. Undocumentedな構造体やAPIも便利だと思えば使う。興味深い機能は逆アセンブルして実現しているAPIとその使い方を特定することがある。
  4. ユーザーモードでは力不足と感じて、カーネルモードコードの開発も選択肢に含める。コードの量も少なくてすむし、スマートな方法だ。青画面になるかもしれないが、Readmeに一言書けばいいだろうと考える。
  5. ユーザーモードプログラミングは面倒が多いのでカーネル空間でやった方が早い、という目的・状況がしばしばある。
  6. GDI32の関数のほとんどを知らないか、忘れた。USER32はSetWindowsHookExをexportしていることだけ覚えている。それで特に困らない。
  7. こんな複雑なカーネルモードのAPIを使うくらいなら、システムの内部構造体を直接参照した方が楽だなあ、と迷う。
  8. Windowsカーネルの逆アセンブルを見ながら青画面の発生を回避するためのHackを重ねている。
  9. Windowsカーネルのなかで動く、自分の書いたコード以外のすべてのコードが邪魔で、Windowsうざい、とか思う。
  10. オープンソースなOSの素晴らしさに気づく。

ネタ元:堕落したCプログラマのレベル -10

main が呼ばれる前に実行されるコードのコールスタック

プログラムのエントリーポイントは、main関数ではなく、PE内に定義されたアドレスであることはよく知られているが、それより以前にコード実行できるTLS Callbacksという仕組みはあまり知られていない。

TLS CallbacksはPE内に適切にセクションとディレクトリテーブルを作り、そのテーブルにコールバック関数のアドレスを設定しておくと、イメージローダー(ntdll.dll)によるプロセス初期化処理中に、その関数が実行されるという仕組みである。

この関数の呼び出しをブレークするのは、PEを解析して手動でブレークを置いたり、対応しているデバッガ(OllyDbg 2.0)でブレークをかけたりなどの方法があるが、呼び出しのシーケンスを把握しておいた方が、いざというとき小回りが利くので調べてみた。

続きを読む

細かいWinデバッグテクニックのメモ

(12/05)追記したら記事の方向性がわかりやすくなったのでタイトルも変えた(笑

UserDebuggerHotKey

GUIアプリケーションに対してデバッガがアタッチしているとき、このレジストリキーで設定されたキーを押すと、ブレークが発生しデバッグすることができようになる。環境によってはできないこともあるみたい?

.ocommand (WinDbg

  1. WinDbg上でプロセスにアタッチする
  2. .ocommand メタコマンドを実行する
  3. 以後 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のコードまではステップ実行する必要がある。

*1:マルウェアが悪用することもあるっぽい。某製品がこのキーの変更を監視してた^^;

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にだけ実行時ブレークポイントを設定する。

*1:もちろんデバッグレジスタの内容を確認されたら検知される

続きを読む

Visual Studioでasmファイルを使う

x64ではインラインアセンブラが使えないので生のMASMを書く必要がある。

  1. asmファイルをプロジェクト内に追加する
  2. 追加したasmファイルのプロパティを開き、「カスタム ビルド ステップ」>「全般」の
    • コマンドライン」に ml64 /c /Cx /W0 /Fo$(IntDir)\$(InputName).obj "$(InputPath)"
    • 「出力ファイル」に $(IntDir)\$(InputName).obj
  3. 普通にビルド
  4. (゚Д゚)ウマー


ちなみにドライバ開発をやっているときは、

  1. プロジェクトのソースコードがあるディレクトリ内に
  2. SOURCESファイルの
    • I386_SOURCESにx86用asmファイル名を指定する
    • AMD64_SOURCESにx64用asmファイル名を指定する
  3. 普通にbuild
  4. (゚Д゚)ウマー

以下は例。

続きを読む