個人的によく使う WinDbg コマンドなどをまとめたチートシートです。
内容については今後追加していく予定です。
もくじ
- まえがき
- よく使うリンク集
- モジュール一覧の操作、情報取得
- レジスタの参照と操作
- ユーザモードスレッド
- ユーザモードプロセス
- スタックバックトレースとフレームの表示
- メモリの参照と編集
- メモリ内の検索
- アセンブリコードの出力
- 変数と構造体の出力
- コマンド出力をファイルに保存する
- Config コマンド
- その他のコマンド
まえがき
本記事の内容はすべて一般に公開されている情報や、出版された書籍、または個人の検証環境で動作確認を実施した結果のみを元に作成しています。
関連記事はこちら。
よく使うリンク集
参考:Debugging Resources - Windows drivers | Microsoft Learn
参考:Commands - Windows drivers | Microsoft Learn
参考:Docs 検索用リンク
参考:WinDBG
参考:WinDbg Release notes - Windows drivers | Microsoft Learn
モジュール一覧の操作、情報取得
# モジュールの列挙
lm
# a から始まるモジュールの列挙
lm m a*
# モジュール情報の表示、タイムスタンプなども表示できる
# !lmi でも、モジュールの詳細情報を取得できる
lm Dvm <modulename>
# シンボルからモジュールの main 関数のアドレスを解決
x modulename!*main*
# a から始まる関数名の列挙
x /D /f modulename!a*
# アドレスからシンボル名を解決
ln address
参考:lm (List Loaded Modules) - Windows drivers | Microsoft Learn
参考:x (Examine Symbols) - Windows drivers | Microsoft Learn
参考:ln (List Nearest Symbols) - Windows drivers | Microsoft Learn
レジスタの参照と操作
# レジスタの一覧表示
r
# 特定レジスタ、複数レジスタの表示
r eip
r zf
r eax,ebx,ecx,edx,ebp,eip
# レジスタの値の書き換え
r eax=00000001
r zf=0
# 2 番目のスレッドに割り当てられているレジスタの表示
# または、~1s などで現在のスレッドを切り替えてから出力する
~1 r
# すべてのスレッドに関連づけられているレジスタの表示
~* r eax
# xmm0 を符号なし 16 バイトとして表示
# xmm1 は倍精度浮動小数点形式で表示
r xmm0:16ub, xmm1:d
# 疑似レジスタの表示
r $peb,$teb
# eax に ebx の値をコピー
r eax = @ebx
参考:r (Registers) - Windows drivers | Microsoft Learn
ユーザモードスレッド
# ユーザモードデバッグもしくはプロセスダンプの解析にて、すべてのスレッドのコールスタックをダンプ
~*k
# TEB の情報をダンプ
!teb
# !teb で特定した TEB のアドレスを使用して構造体を出力
dt ntdll!_TEB <TEB のアドレス>
ユーザモードプロセス
# PEB の情報をダンプ
!peb
# !peb で特定した PEB のアドレスを使用して構造体を出力
dt ntdll!_PEB <PEB のアドレス>
スタックバックトレースとフレームの表示
# スタックバックトレースの表示(先頭の数字はフレーム番号)
# フレーム番号(n)、フレームポインタ(v)、フレームが使用しているメモリサイズ(f)を含めて表示させる
kvnf
# すべてのスレッドのスタックバックトレースの表示
~*kvnf
# 特定のスレッドのスタックバックトレースのみ表示
~1kvnf
# 現在のプロセスのすべてのスレッドのコールスタックを表示
!uniqstack
参考:k, kb, kc, kd, kp, kP, kv (Display Stack Backtrace) - Windows drivers | Microsoft Learn
kvn
コマンドなどを実行してスタックトレースのChildEBP
(保存された EBP)やRetAddr
、Args to Child
を取得できますが、これは基本的にスタック内の情報を参照しています。
そのため、64bit バイナリの呼び出し規約では有効な値を含まないため注意が必要です。
参考:x64 calling convention | Microsoft Learn
# フレーム番号を指定してローカル変数を表示
# /t は変数の型、/V はローカル変数の相対アドレスを含めて表示するオプション
.frame 01;dv
.frame 01;dv /t /V
参考:dv (Display Local Variables) - Windows drivers | Microsoft Learn
参考:.frame (Set Local Context) - Windows drivers | Microsoft Learn
メモリの参照と編集
# メモリアドレスの範囲またはオフセットを指定してメモリ内の Hex と ASCII を出力
dc 0x1000 0x1200
# L はオブジェクトカウントを指す
# dc @addr L1 は DWORD 1 つ分、dq @addr L1 は QWORD 1 つ分
dc @eip L1
dc 0x1000 L20
# アドレス単体や、アドレス値を持つレジスタを参照することも可能
dc 0x1000
dc eax
# ポインタサイズの値を取得できる
dps 0x1000
dqs 0x1000
# 32bit(ddp)、64bit(dqp)でメモリを表示
ddp 0x1000
dqp 0x1000
# ASCII 文字列で表示
da 0x1000
# バイト値の書き換え
eb 0x1000
# ASCII 文字列の書き換え
ea 0x1000 "change ascii"
参考:d, da, db, dc, dd, dD, df, dp, dq, du, dw (Display Memory) - Windows drivers | Microsoft Learn
参考:dds, dps, dqs (Display Words and Symbols) - Windows drivers | Microsoft Learn
参考:e, ea, eb, ed, eD, ef, ep, eq, eu, ew, eza (Enter Values) - Windows drivers | Microsoft Learn
メモリ内の検索
基本的なメモリ内の探索は以下のコマンドで実現できます。
# RSP のアドレスから 1000000 バイトの範囲で DWORD 値 H を検索する
s -d @rsp L1000000 'H'
# RSP のアドレスから 1000000 バイトの範囲で ASCII 文字列 Hello を検索する
s -a @rsp L10000000 "Hello"
# 0 から 0x7fffffff までの広い範囲で ASCII 文字列 Hello を検索する
# 256 MB 以上の大きな範囲を検索する場合は、 L? の指定が必ず必要になる
s -a 0 L?7fffffff "Hello"
# ASCII と同様に、Unicord 文字列も検索することができる
s -u @rsp L1000000 'Hello'
s -u 0 L?7fffffff "Hello"
以下は、ASCII 文字列と Unicord 文字列をそれぞれ検索した場合の差分を確認した結果です。
また、特定のバイトパターンを指定してメモリ内の情報を検索することもできます。
# RSP のアドレスから 400 バイトの範囲で「長さ 4 文字以上の ASCII 文字列」を検索
s -[l4]sa @rsp L400
# RSP のアドレスから 400 バイトの範囲内の「書き込み可能アドレス」から「長さ 4 文字以上の ASCII 文字列」を検索
s -[wl4]sa @rsp L400
参考:s (Search Memory) - Windows drivers | Microsoft Learn
アセンブリコードの出力
以下のコマンドで Disassembly ウインドウで参照できるものと同じアセンブリコードを出力できます。
# EIP から数行のアセンブリコードを出力(ub は逆方向に出力)
u
ub
# 指定のアドレスから 10 行分のアセンブリコードを出力
u 0x1000 L10
# 特定のレジスタ内のアドレスを使用することも可能
u eax L10
u eip L20
# 関数のアドレスまたはシンボル名から特定の関数全体のコードを出力
uf 0x1000
uf module!function
# 対象関するルーチン内の call 命令のみ抽出
uf /c 0x1000
参考:u, ub, uu (Unassemble) - Windows drivers | Microsoft Learn
参考:uf (Unassemble Function) - Windows drivers | Microsoft Learn
変数と構造体の出力
# 現在のスコープ内のローカル変数を出力(/t で型名を表示)
dv
dv /t
# 変数の型名は dt コマンドからも解決することができる
dt local_val_name
# シンボル名またはアドレスから構造体を解決
dt MyStruct
# 構造体のメンバになっているサブ構造体の情報も再帰的に列挙するために -r もしくは -b を使用する
dt -r MyStruct
# -r1 -r2 などのようにして再帰的に表示する構造体の階層を指定できる
dt -r2 MyStruct
# 特定の構造体のアドレスを指定することで実際の値を取得できる
dt nt!_EPROCESS <EPROCESSのアドレス>
# サブタイプ名を指定することで、特定の値のみ抽出することもできる
dt nt!_EPROCESS ImageFileName <EPROCESSのアドレス>
# -l オプションでリンクされたリストをたどって構造体情報をダンプする
# -y オプションで「指定の文字列が名前の先頭部分に一致する」行を出力する
# -o オプションでオフセットを非表示にする
# -i オプションでサブタイプのインデントを行わない
dt nt!_EPROCESS -l ActiveProcessLinks.Flink -y Ima -yoi Uni <EPROCESSのアドレス>
参考:dv (Display Local Variables) - Windows drivers | Microsoft Learn
参考:dt (Display Type) - Windows drivers | Microsoft Learn
解析対象の構造体に双方向リストが含まれる場合、dt コマンドで再帰的にリストで連結された情報を取得することができます。(カーネルデバッグ時の_EPROCESS
構造体の列挙など)
また、dv コマンドは 64bit 呼び出し規約やFast calling convention
(引数を可能な限りレジスタ経由で渡す呼び出し規約)では有効に動作しません。
例えば、x86 Windows で標準的に使用される呼び出し規約__cdecl
は引数がすべてスタック経由で与えられるため、dv コマンドで安定してローカル引数を参照することが可能です。
参考:抄訳メモ/Calling Conventions Demystified - Glamenv-Septzen.net
ブレークポイントの設定
ブレークポイントは、bp、bu、bm の 3 つのコマンドを使用して設定することができます。
bp は通常のブレークポイント設定で、コマンドで指定したシンボルやアドレスにブレークポイントを設定します。
一方で、未解決のアドレスや bp で解決できなかったアドレスについては bu を使用してブレークポイントを設定します。
さらに、bm コマンドを使用することでパターンを使用してシンボルに対してブレークポイントを設定できます。
# オフセットを指定してブレークポイントを設定
bp module+0x123
bu dllmodule!DLLMain
# mem から始まる複数のモジュールにまとめてブレークポイントを設定
bm myprogram!mem*
また、ブレークポイントを設定する場合にはブレークポイントにマッチした場合の処理や、停止されるまでに該当のコードが呼び出される回数を指定することも可能です。
加えて、IF 文などの条件分岐を定義してブレーク時の動作を細かく条件付けできます。
条件式に使用できる演算子しては、MASM Numbers and Operators などが利用できます。
条件分岐は .if や j コマンドで定義できますが、現在はこの記法は推奨されておらず、Conditional breakpoints in WinDbg の通りbp /w "(Condition)" Address
の記法を使用することができます。
# ブレークポイントを設定した処理が 7 回目に呼び出された場合に実行を停止する
bp MyTest+0xb 7
# ブレークポイントにマッチした後、 EAX と変数 MyVar の出力を行い、自動的に実行を再開する
bp ntdll!RtlRaiseException "r eax; dt MyVar; gc"
# echo で任意の文字列を出力して処理を続行
bp module!myFunction ".echo myFunction executed; gc"
# IF 文条件式の基本的な構文
bp module!myFunction ".if () {} .else {}"
# var のアドレスを poi で逆解決し、10 進数の 10 と比較
# 一致すれば何もせずブレークし、一致しない場合は処理を継続する
bp module!myFunction ".if ( poi(var) == 0n10 ) {} .else { gc }"
# 以下も上記と同等の結果を得られる
bp module!myFunction "j ( poi(var) == 0n10 ) ''; 'gc'"
ちなみに、gc コマンドは条件付きブレークポイントからの移動を指示するコマンドで、これをブレーク時の条件に含める場合、このブレークポイントに到達した際の実行コマンド(g や F10 など)と同じ方法で処理を進めるように指定することができます。
参考:bp, bu, bm (Set Breakpoint) - Windows drivers | Microsoft Learn
参考:gc (Go from Conditional Breakpoint) - Windows drivers | Microsoft Learn
参考:j (Execute If - Else) - Windows drivers | Microsoft Learn
参考:Conditional breakpoints in WinDbg and other Windows debuggers - Windows drivers | Microsoft Learn
また、ブレークポイントの条件はスクリプトファイルを使用して制御することも可能です。
詳しくはスクリプトファイルの使用に記載します。
読み取り、書き込みアクセスを監視する
ba コマンドを使用することで、実行コードだけでなく、特定のメモリ領域にアクセスが発生した場合のブレークポイントも設定することができます。
この時指定可能な値は大きく「実行(e)」、「読み取り/書き込み(r)」、「書き込み(w)」の 3 つです。
また、カーネルデバッグ時限定で、「I/O(i)」オプションで特定の I/O ポートに対するアクセスが行われた場合のブレークポイントを構成することもできます。
# 変数名または変数のアドレスから「4 バイト分の領域に読み取りアクセスが発生した場合」にブレークする
ba r4 myVar
ba r4 0x1000
# 対象に書き込みアクセスが発生した場合にブレークする
ba w4 0x1000
# 3f8 から 3f8+4 の範囲のポートで I/O が発生した場合にブレークする(カーネルデバッグ時のみ)
ba i4 3f8
参考:ba (Break on Access) - Windows drivers | Microsoft Learn
ブレークポイント設定の管理
現在のブレークポイント設定の確認や有効化/無効化は以下のコマンドで行うことができます。
# 現在設定されているブレークポイントの列挙
# ここで表示されるブレークポイントの ID は他の管理コマンドで対象を指定する際に使用できる
bl
# すべてのブレークポイントを無効化する
bd *
# ID 1 のブレークポイントのみを無効化する
bd 1
# すべてのブレークポイントを有効化する
be *
# ID 1 のブレークポイントのみを有効化する
be 1
# 現在のブレークポイント設定に使用したコマンドを上から順にリストする
.bpcmds
参考:bl (Breakpoint List) - Windows drivers | Microsoft Learn
参考:be (Breakpoint Enable) - Windows drivers | Microsoft Learn
参考:bd (Breakpoint Disable) - Windows drivers | Microsoft Learn
また、現在のブレークポイント設定や条件式を更新したい場合は以下のコマンドを使用します。
# ブレークポイントの ID を変更します
# 複数の ID 変更を一括で行うこともできます
br OldID NewID
br OldID NewID OldID2 NewID2 OldID3 NewID3
# ブレークポイントのコマンド部分を修正できます
bs ID ["CommandString"]
# ブレークポイントの条件文を変更できます
bsc ID Condition ["CommandString"]
参考:br (Breakpoint Renumber) - Windows drivers | Microsoft Learn
参考:bs (Update Breakpoint Command) - Windows drivers | Microsoft Learn
参考:bsc (Update Conditional Breakpoint) - Windows drivers | Microsoft Learn
コード実行コマンド
WinDbg でコード実行を行う場合、大きく分けて以下の 3 つの操作を行うことができます。
- Step:単一の命令またはコードを実行する(Step Over)
- Trace:単一の命令またはソース行を実行する(Step Into)
- Go:プロセスまたはスレッドの実行を再開する
Step Over(p)
p コマンドや F10 キーで単一の命令またはコードを実行します。
WinDbg では p コマンドや F10 キーで実行する操作は Step Over に該当し、 call 命令では呼び出し先の関数には移動せず、call 命令の次の行の処理に進みます。
ちなみに、デフォルトでは Step Over 時にレジスタとフラグが出力されますが、pr のように各コマンドに r を付与するか、.prompt_allow -reg
コマンドを実行することでレジスタとフラグの出力を無効化することができます。(元に戻す場合は.prompt_allow +reg
コマンドを実行します)
# ステップオーバーの処理を行う
p (または F10)
# 5 行分ステップオーバーを実行する
p 5
# ステップオーバーの実行後に WinDbg コマンドを実行する
p "k;r eax"
# 指定のアドレスに到達するまで Step Over を繰り返す
pa StopAddress
# 次の call 命令の呼び出し行まで Step Over する
pc
# pc の場合もカウント実行が可能
pc 3
# 次の ret 命令の呼び出し行まで Step Over する
pt
# 次の call 命令または ret 命令の呼び出し行まで Step Over する
pct
# 次の条件分岐の呼び出しまで Step Over する
ph
参考:p (Step) - Windows drivers | Microsoft Learn
参考:pa (Step to Address) - Windows drivers | Microsoft Learn
参考:pc (Step to Next Call) - Windows drivers | Microsoft Learn
参考:pt (Step to Next Return) - Windows drivers | Microsoft Learn
参考:pct (Step to Next Call or Return) - Windows drivers | Microsoft Learn
参考:ph (Step to Next Branching Instruction) - Windows drivers | Microsoft Learn
Step Into(t)
TODO:追記予定
参考:t (Trace) - Windows drivers | Microsoft Learn
go(g)
TODO:追記予定
参考:g (Go) - Windows drivers | Microsoft Learn
コマンド出力をファイルに保存する
.logopen コマンドを使用すると、WinDbg のコマンド実行とその出力結果を任意のファイルに保存することができます。
デフォルトでは出力されるテキストは ASCII 文字列ですが、 /u オプションを使用することで Unicord 文字列を指定することができます。
特に、process コマンドなどのような大量の情報を一気に出力する場合などに便利です。
# 任意のログファイルにコマンド出力を記録する(ASCII テキスト)
.logopen /t C:\Users\Public\windbg.log
# プロセス ID と現在の日時を付与(/t)して任意のログファイルにコマンド出力を記録する
.logopen /t C:\Users\Public\windbg.log
# プロセスやファイル情報などから自動的にファイル名を決定して保存
.logopen /d
# 既存のログファイルに追記する
.logappend C:\Users\Public\windbg.log
# ファイルを閉じて記録を終了する
.logclose
参考:Keeping a Log File in WinDbg - Windows drivers | Microsoft Learn
参考:.logopen (Open Log File) - Windows drivers | Microsoft Learn
参考:.logappend (Append Log File) - Windows drivers | Microsoft Learn
参考:.logclose (Close Log File) - Windows drivers | Microsoft Learn
コマンドラインオプションを使用して出力をファイルに保存する
-logo FilePath
オプションを使用して WinDbg を起動することで、コマンドウインドウの出力をすべて指定のファイルに記録することができます。
これは、アプリケーションの Debugger フラグに WinDbg のパスを指定する場合にも使用できます。
また、ショートカットの編集から、WinDbg の起動時に-logo
オプションを指定することもできます。
例えば、以下のコマンドラインをショートカットに設定すると、C:\Users\Public\debug.log
に常に出力結果が保存されるようになります。
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe -logo C:\Users\Public\debug.log
なお、上記の例で-logo
オプションを使用する場合、ファイルは毎回 WinDbg を起動するたびに上書きされるので注意が必要です。
拡張機能
ロードされている拡張機能は.chain
で確認できます。
デフォルトの拡張機能は以下の種類があり、それぞれ DLL として読み込まれます。
- ext
- wow64exts
- dbghelp
- exts
- uext
- ntsdexts
!help:拡張機能のヘルプ
!help
拡張機能は既定では ext 拡張機能のコマンド一覧を出力します。
ただし、help 拡張機能は他にもいくつかの拡張機能に存在しており、!ext.help
や!wow64exts.help
などの記法でそれぞれの拡張機能のコマンド一覧にアクセスできます。
# ext 拡張機能のヘルプを出力
!ext.help
analyze [-v][level] - Analyzes current exception or bugcheck (levels are 0..9)
owner [symbol!module] - Displays the Owner for current exception or bugcheck
comment - Displays the Dump's Comment(s)
error [errorcode] - Displays Win32 or NTSTATUS error string
gle [-all] - Displays the Last Error & Last Status of the current thread
address [address] - Displays the address space layout
[-UsageType] - Displays the address space regions of the given type
cpuid [processor] - Displays the CPU information for a specific or all CPUs
exchain - Displays exception chain for the current thread
for_each_process <cmd> - Executes command for each process
for_each_thread <cmd> - Executes command for each thread
for_each_frame <cmd> - Executes command for each frame in the current thread
for_each_local <cmd> $$<n> - Executes command for each local variable in the current frame,
substituting the fixed-name alias $u<n> for each occurrence of $$<n>
imggp <imagebase> - Displays GP directory entry for 64-bit image
imgreloc <imagebase> - Relocates modules for an image
str <address> - Displays ANSI_STRING or OEM_STRING
ustr <address> - Displays UNICODE_STRING
list [-? | parameters] - Displays lists
cppexr <exraddress> - Displays a C++ EXCEPTION_RECORD
obja <address> - Displays OBJECT_ATTRIBUTES[32|64]
rtlavl <address> - Displays RTL_AVL_TABLE
std_map <address> - Displays a std::map<>
!analyze:例外やバグチェックに関する情報の取得
!analyze
は ext 拡張に含まれるコマンドで、クラッシュダンプなどの解析で非常に有用です。
# 主要な解析の実行
!analyze -v
# グローバルフラグ
NTGLOBALFLAG
# プロセスやアプリケーション関連の情報が記録される
PROCESS_BAM_CURRENT_THROTTLED
PROCESS_BAM_PREVIOUS_THROTTLED
APPLICATION_VERIFIER_FLAGS
# EXCEPTION_RECORD には例外発生、クラッシュしたプロセスの情報が記録される
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00007ffce5c60910 (ntdll!LdrpDoDebuggerBreak+0x0000000000000030)
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
# 例外発生時のスタックコールバックがきろくされる
# ~0s ; .cxr ; kb のコマンドでも同等の内容を出力できる
STACK_TEXT
# 問題のカテゴリを表示する
FAILURE_BUCKET_ID
参考:WinDBG !analyze extension command
参考:analyze (WinDbg) - Windows drivers | Microsoft Learn
!address:メモリやプロセスに関する情報の参照
!address
も ext 拡張に含まれるコマンドで、指定したアドレスのメモリ領域やプロセスに関する情報を表示します。
例えば、malloc
関数の戻り値のアドレスに対して!address
コマンドを発行するなどによって、対象のメモリ領域に関する情報を取得することができます。
# 各メモリブロックの情報を出力する
!address
# 指定のアドレスに関する情報を出力する
!address <Address>
# ユーザモードデバッグでメモリに関する統計情報を出力する
!address -summary
以下の Docs のパラメータ表の通り、対象のメモリ領域の用途やステータス、保護の状態など様々な情報を取得することができます。
参考:address (WinDbg) - Windows drivers | Microsoft Learn
!handle:システムまたはプロセスのハンドル情報の参照
!handle
も ext 拡張に含まれるコマンドで、システムやプロセスのハンドルに関する情報を取得できます。
ユーザモードデバッグで使用した場合、そのプロセスのハンドル情報の一覧や統計情報を取得できる。
オプションなしの!handle
コマンドで取得できる情報は、Process Explorer のハンドルビューと非常に近しい情報を取得することができます。
# ハンドル情報の取得
!handle
# すべてのハンドルの詳細な情報をダンプ
!handle 0 0xf
# ハンドル ID が 430 のハンドルの詳細な情報を取得
!handle 430 0xf
参考:handle (WinDbg) - Windows drivers | Microsoft Learn
スレッド環境ブロック(TEB)の情報取得
# TEB の情報をダンプ
!teb
参考:teb (WinDbg) - Windows drivers | Microsoft Learn
プロセス環境ブロック(PEB)の情報取得
# PEB の情報をダンプ
!peb
参考:peb (WinDbg) - Windows drivers | Microsoft Learn
Config コマンド
# シンボル設定の確認
.sympath
# シンボルキャッシュとソースの設定
.sympath cache*C:\symbols;srv*https://msdl.microsoft.com/download/symbols
.reload
# シンボル読み込み時の詳細を出力/抑制
!sym noisy
!sym quiet
# 不一致シンボルの強制読み込み(非推奨)
.reload /f /i <modulename>
# ロードされている拡張機能のリストを出力
.chain
# 追加の拡張機能のロード
.load <拡張機能 DLL のパス>
# 拡張機能の詳細を表示
.extmatch /D /e <モジュール> *
# ヘルプウィンドウの起動
.hh
# ダンプファイルとして保存
# https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/-dump--create-dump-file-
.dump [options] FileName
カーネルデバッグ、フルダンプの解析
カーネルデバッガの接続(Hyper-V、VirtualBox)
以下の記事にまとめています。
参考:WinDbgでWindows10環境のカーネルデバッグを行う最初の一歩
フルメモリダンプの取得設定
以下の記事にまとめています。
参考:Windows環境でカーネルメモリダンプを手動で取得し、WinDbgで解析する方法
カーネル構造体の情報を列挙する
# NT カーネル構造体の情報を列挙する
dt nt!_*
dt nt!_*interrupt*
# カーネル構造体の情報をダンプする
dt nt!_KINTERRUPT
プロセス情報を列挙する
カーネルデバッグやフルダンプの解析を行う場合、以下のコマンドでプロセスの情報をダンプできます。
.process
は、デバッガのコンテキストを変更できるコマンドで、非常に有用です。
カーネルデバッグは通常、ほとんどのユーザモードアドレス空間の情報は表示されませんが、.process
で特定のユーザモードプロセスの紺的とに変更することで、カーネルデバッグ中に特定のユーザモードプロセスを解析することができるようになります。
※ コンテキストを変更した後、元のようにカーネル情報を表示する状態に戻す方法はわからないので、都度カーネルデバッガを再接続させています。
# システム内のすべてのプロセスを列挙
!process 0 0
# 特定のプロセスの完全な詳細情報を取得
!process 0 7 notepad.exe
# デバッガのコンテキストを指定のプロセスに変更する(!peb などが使えるようになる)
.process /r /P <!process で特定した EPROCESS ブロックのアドレス>
# すべてのプロセスを探索する(すべてのプロセスに対してコマンドを発行することも可能)
!for_each_process
参考:!process (WinDbg) - Windows drivers | Microsoft Learn
参考:.process (Set Process Context) - Windows drivers | Microsoft Learn
参考:foreachprocess - Windows drivers | Microsoft Learn
参考:Changing Contexts - Windows drivers | Microsoft Learn
nt!_EPROCESS
からプロセス情報を直接列挙することもできます。
# System プロセスの ActiveProcessLinks へのポインタアドレスを取得
r $t0 = nt!PsActiveProcessHead;r $t1 = poi(@$t0);r $t1
# ActiveProcessLinks.Flink のオフセットを参照
dt nt!_EPROCESS ActiveProcessLinks
> +0x448 ActiveProcessLinks
# _EPROCESS をダンプ(.process 0 0 と同等の出力)
dt nt!_EPROCESS -l ActiveProcessLinks.Flink -y Ima -yoi Uni $t1-0x448
参考:デバッガー コマンド プログラムの例 - Windows drivers | Microsoft Learn
プロセスのハンドル情報を列挙する
!handle
コマンドは、ユーザモードデバッグとカーネルデバッグで少々利用方法に差異が発生します。
!handle notepad.exe
参考:handle (WinDbg) - Windows drivers | Microsoft Learn
KPCR と KPRCB を参照して CPU の統計情報を読む
カーネルプロセッサ制御領域(KPCR)とカーネルプロセッサ制御ブロック(KPRCB)には、プロセッサ固有のデータや状態、統計に関する情報が記録されています。
# 指定のプロセッサの KPRCB の情報を出力
!prcb <CPU 番号>
# KPCR の情報をダンプ
dt nt!_KPCR @$pcr
その他のコマンド
# ?<Hex> で 10 進数変換が可能
?ff
> Evaluate expression: 255 = 000000ff
# 以下のように演算結果を評価することも可能
?ffff-f
> Evaluate expression: 65520 = 0000fff0
# ユーザモードデバッグで現在のプロセスのステータスを表示する
|
# デバッグ中のシステムを表示する
||
参考:Commands - Windows drivers | Microsoft Learn
スクリプトファイルの使用
WinDgb コマンドを記載したテキストファイルを使用することで、WinDgb のコマンド実行をある程度自動化することができます。
# ファイルで定義したスクリプトを実行
$<C:\windbgcmd.txt
# 単一のコマンドブロックとして実行(ブレークポイントを使うならこちらの方がよい)
$><C:\windbgcmd.txt
# コマンド引数を設定(標準入力ではない)
# .echo argument is ${$arg1} などのコマンドを使う場合に使用可能
$$>a<C:\windbgcmd.txt test_text
# eax レジスタに 1234 が含まれるまで実行を続けるスクリプト eaxstep を再帰的に呼び出す
# .if (@eax == 1234) { .echo 1234 } .else { t "$<eaxstep" }
t "$<C:\\eaxstep"
参考: , , $$ a (Run Script File) - Windows drivers | Microsoft Learn
参考:Executing Until a Specified State is Reached - Windows drivers | Microsoft Learn
条件分岐
WinDbg コマンドもしくはスクリプトの内部では以下のような条件式を扱うことができます。
# IF トークンの使用
.if (Condition) { Commands }
.if (Condition) { Commands } .else { Commands }
.if (Condition) { Commands } .elsif (Condition) { Commands }
.if (Condition) { Commands } .elsif (Condition) { Commands } .else { Commands }
参考:.if (WinDbg) - Windows drivers | Microsoft Learn
参考:Control Flow Tokens - Windows drivers | Microsoft Learn
この時、Condition には様々な上限を含めることができます。
WinDbg の場合、MASM 演算子と C++ の演算子がサポートされています。
特に MASM 演算子のpoi
によるアドレス解決や Non-Numeric な演算子である$scmp("String1", "String2")
による文字列比較などは頻繁に使用します。
# $scmp は C++ の strcmp と同等の評価を行うため、文字列が一致した場合は 0 を返す
.if ($scmp("${$ImageName}","notepad.exe")) { } .else { .echo match }
# 疑似レジスタ $t1 のアドレスが指す値を $t1 に再代入する
r $t1 = poi(@$t1)
参考:MASM Numbers and Operators - Windows drivers | Microsoft Learn
参考:C++ numbers and operators - Windows drivers | Microsoft Learn
その他、条件式に使える演算子は以下から探すことができます。
参考:Numerical Expression Syntax - Windows drivers | Microsoft Learn
ループコマンド
WinDbg の.for
コマンドは C 言語の for とほぼ同等の動作になります。
また、.foreach
コマンドは WinDbg のコマンド出力やファイル内の情報を要素ごとに分割して 1 つずつ指定のコマンドに与えることができます。
.foreach
コマンドで与えられる要素は、処理中のplace
を置き換えます。
また、.while
と.do
もループコマンドとして使用できます。
# WinDbg の for 構文(カンマではなくセミコロンで分割)
.for (InitialCommand ; Condition ; IncrementCommands) { Commands }
# foreach 構文
.foreach [Options] ( Variable { InCommands } ) { OutCommands }
.foreach [Options] /s ( Variable "InString" ) { OutCommands }
.foreach [Options] /f ( Variable "InFile" ) { OutCommands }
# 処理をループさせて実行させたい場合
.while (1) { Commands }
参考:.for (WinDbg) - Windows drivers | Microsoft Learn
参考:.foreach (WinDbg) - Windows drivers | Microsoft Learn
参考:.while (WinDbg) - Windows drivers | Microsoft Learn
参考:.do (WinDbg) - Windows drivers | Microsoft Learn
.for
コマンドは以下のような構文で使用できます。
.continue
と.break
によるループ操作は、C 言語と同じように動作します。
# 疑似レジスタを使用して for ループを実行
.for (r $t1 = 0; $t1 < 7; r $t1 = $t1 + 1) { r $t1 }
# continue で次のループにジャンプする
.for (r $t1 = 0; $t1 < 7; r $t1 = $t1 + 1) { .if ( $t1 == 3 ) { .echo skip ; .continue } ; r $t1 }
# break でループを停止する
.for (r $t1 = 0; $t1 < 7; r $t1 = $t1 + 1) { .if ( $t1 == 3 ) { .echo stop ; .break } ; r $t1 }
以下は、関数内の call 時の引数をすべてファイルにダンプすることができるサンプルです。
.logopen /t C:\Users\Public\windbg.log
.while (1) { pc;.echo rcd;dc @rcx L10;.echo rdx; dc @rdx L10;.echo r8; dc @r8 L10;.echo r9; dc @r9 L10;.echo stack; dc @rsp L10;.echo rip; u rip L1 }
ブロックを作成する
.block
コマンドは、コード内に新しいブロックを追加します。
これは、後述する Alias を変数のように使用する場合などに頻繁に使用します。
Commands ; .block { Commands } ; Commands
参考:.block (WinDbg) - Windows drivers | Microsoft Learn
エイリアスを作成、削除する
as
とaS
コマンドは、Alias の定義を行います。
WinDbg では、疑似レジスタと同様に、Alias を変数のように使用できます。
ただし、定義した Alias は現在のブロックでは機能しないため、Alias を定義した後に.block
コマンドで新しいブロックを作成する必要があります。
# 文字列や数値を定義する
as Name 10
aS Name "Test Value" ; .block { .echo Name }
# Address からヌル終端までの ASCII 文字列を定義
as /ma Name Address
# Address からヌル終端までの Unicode 文字列を定義
as /mu Name Address
# Address から 64bit の値を定義
as /x Name Expression
# ファイル の内容と同等の値を定義
aS /f Name File
# コマンド出力の値を定義
as /c Name CommandString
参考:as, aS (Set Alias) - Windows drivers | Microsoft Learn
疑似レジスタを使用する
WinDbg では値を保持するための疑似レジスタをサポートしており、既定の疑似レジスタを通して値を参照したり、任意の疑似レジスタを登録することができます。
疑似レジスタは、$
マークを先頭に持ちます。MASM 構文を使用する場合は@
を付与することができます。
# 疑似レジスタの値を出力
? $ip
? @$ip
r $ip
r @$ip
# 任意の疑似レジスタを定義する
r $t0 = 7
# 疑似レジスタを使用して、任意のアドレスのポインタを poi で参照する
r $t0 = nt!PsActiveProcessHead;r $t1 = poi(@$t0);r $t1
# 疑似レジスタで演算を行う
?? @$t0+10
# r コマンドで型を指定して疑似レジスタを定義する
r? $t15 = * (UNICODE_STRING*) 0x12ffbc
# 現在のスレッドが ntopenfile を呼び出した場合のみ処理を停止する
bp /t @$thread nt!ntopenfile
参考:Pseudo-Register Syntax - Windows drivers | Microsoft Learn
既定で設定されている疑似レジスタは 疑似レジスタ にまとめられていますが、いくつかの疑似レジスタを以下に記載します。
- $ea:最後の命令の実効アドレス
- $ip:命令ポインタレジスタ
- $scopeip:現在のローカル コンテキストの命令ポインター
- $exentry:現在のプロセスの実行可能ファイルのエントリ ポイントのアドレス
- $thread:現在のスレッドのアドレス(カーネルデバッグの場合は ETHREAD ブロック、ユーザモードの場合は TEB のアドレス)
- $peb:現在のプロセスの PEB のアドレス
- $teb:現在のプロセスの TEB のアドレス
- $tpid: 現在のプロセスの PID を取得する
スクリプトサンプル
- カーネルデバッグ中にプロセスを列挙し、
notepad.exe
のアドレスを取得するスクリプト。
$$ Get process list LIST_ENTRY in $t0.
r $t0 = nt!PsActiveProcessHead
$$ Iterate over all processes in list.
.for (r $t1 = poi(@$t0);
(@$t1 != 0) & (@$t1 != @$t0);
r $t1 = poi(@$t1))
{
r? $t2 = #CONTAINING_RECORD(@$t1, nt!_EPROCESS, ActiveProcessLinks);
as /x Procc @$t2
$$ Get image name into $ImageName.
as /ma $ImageName @@c++(&@$t2->ImageFileName[0])
.block
{
.if ($scmp("notepad.exe","${$ImageName}")) { } .else {.echo ${$ImageName} at ${Procc}}
}
ad $ImageName
ad Procc
}
解析に必要な知識
32bit と 64bit の呼び出し規約を理解する
TODO:別記事で作成予定。
NTGLOBALFLAG
NTGLOBALFLAG
は PEB に存在しており、デフォルト値は 0 ですがデバッグ時に値が設定されます。
32bit プロセスの場合は 0x68
、64bit プロセスの場合は0xBC
のオフセットに存在しています。
そのため、プログラムが PEB 構造体のアドレスに対して ZwQueryInformationProcess
などでアクセスし、アンチデバッグの目的で使用する場合もあります。
参考:Debugger Detection Using NtGlobalFlag | 🔐Blog of Osanda
EXCEPTION_RECORD
EXCEPTION_RECORD
には例外やクラッシュに関する情報が含まれます。
参考:EXCEPTION_RECORD (winnt.h) - Win32 apps | Microsoft Learn
ExceptionCode
には例外の発生理由が含まれています。
一般的に記録される例外の種類については上記のページに記載があります。
EPROCESS、ETHREAD
EPROCESS
構造体はドキュメンテーションされていない構造体ですが、システムプロセスを記述するカーネルメモリ構造体であり、nt!_EPROCESS
としてアクセスできることが知られています。
この構造体は、双方向連結リスト(LIST_ENTRY
)ActiveProcessLinks
によって連結されており、cmd /c tasklist
コマンドなどが実行された場合には、このリストをたどってEPROCESS
構造体が探索されることで、プロセスの一覧として出力されます。
そのため、WinDbg 上でもカーネルデバッガでこのリストをたどることで、アクティブなプロセスを探索することができます。
一方で、マルウェアなどがカーネルメモリにアクセスできる場合には、ActiveProcessLinks
のポインタを付け替えることで、プロセス一覧に悪意のあるプロセスが表示されないように隠ぺいを行う場合があります。
参考:Windows kernel opaque structures - Windows drivers | Microsoft Learn
参考:Manipulating ActiveProcessLinks to Hide Processes in Userland - Red Team Notes
sysenter 命令
TODO:追記
環境設定
Visual Studio のリンカ設定を変更する
最新の Visual Studio のデフォルト設定では、エクスポートテーブルがシンボルファイルに含まれません。
そのため、プロジェクトの設定から、以下のように/DEBUG:FULL
の構成を設定します。
Release ビルド時の最適化を無効化する
WinDbg の検証目的でプログラムをビルドする場合は、Release ビルド時の最適化設定を無効化しておくと良い場合があります。
なお、Debug ビルドはデフォルトで最適化が無効化されています。
その他の Tips
WinDbg(X86) と WinDbg(X64) どちらを使用すべきですか?
基本的には、64bit アプリケーションのデバッグやダンプ解析を行う場合には WinDbg(X64) を使用し、32 bit アプリケーションの場合は WinDbg(X86) を使用します。
WinDbg(X64) は 64bit と 32bit どちらのアプリケーションも解析することができますが、WinDbg(X64) で 32bit アプリケーションを解析する場合には WOW64 レイヤーが追加される影響で、とくにダンプの解析を行う場合に解析を困難にする問題を引き起こす可能性があります。
なお、.effmach x86
コマンドを使用することで問題を回避できる場合もありますが、WinDbg(X86) を使用する場合とイコールではないため、可能な限り WinDbg(X86) を使用する方が良いです。
参考:.effmach (Effective Machine) - Windows drivers | Microsoft Learn
WinDbg(Classic) でダークテーマを使用する
僕が普段使用しているテーマは以下から取得できます。
参考:kash1064/WinDbg-Classic-Dark: The Dark theme of WinDbg Classic
見た目としては以下のような感じで、できるだけ色の種類を減らしてシンプルにしつつ、使い慣れた VSCode のダークテーマに近い配色を目指してカスタマイズしています。