All Articles

Magical WinDbg VOL.1【付録 A WinDbg の Tips】

この章では、本書の内容に含めることができなかったデバッガ操作のテクニックをいくつか紹介します。

もくじ

Command ウィンドウの結果をファイルに出力する

WinDbg の Command ウインドウは、任意のファイルに実行結果を出力するように設定することが可能です。

これは、WinDbg を使用した解析の履歴を保存したい場合や、出力が膨大な量になるコマンドの実行結果を WinDbg 以外の方法で解析したい場合などに便利です。

WinDbg のコマンド出力結果を指定のファイルに保存したい場合、Command ウインドウにて .logopen <保存先のファイルのフルパス> コマンドを実行します。

このコマンドによって、以降のコマンド実行結果が ASCII テキスト形式で指定したパスのファイルに書き込まれるようになります。

書き込みを終了してファイルを閉じるには、.logclose コマンド実行します。

上記のコマンドを実行することで、以下の画像のように出力結果がログに保存されます。

ログファイルに出力されたコマンド実行結果

なお、.logopen コマンドでログの出力先を指定した場合、そのパスにすでにファイルが存在している場合は、既存のファイルのデータが削除され、新しいコマンド出力結果が上書きされるため注意してください。

もし、既存のファイルに追記する形でコマンド出力結果を保存したい場合は、.logopen コマンドではなく .logappend <保存先のファイルのフルパス> コマンドを使用します。

また、うっかり過去の解析結果を保存したファイルを上書きしてしまうことを避けたい場合には、.logopen コマンドの /t オプションを使用することもできます。

/t オプションを使用すると、以下の例のように、指定のファイルパスに自動的に記録の開始日時に関するタイムスタンプ情報が追加されるため、既存のファイルの上書きを防ぐことが可能になります。

0:000> .logopen /t C:\Users\Public\windbg.log
Closing open log file C:\Users\Public\windbg.log
Opened log file 'C:\Users\Public\windbg_1058_2023-09-24_08-36-13-088.log'

どのコマンドを使用する場合も、書き込みを終了してファイルを閉じるには、.logclose コマンド使用します。

WinDbg の出力結果の保存に関するより詳しい情報は、以下の公式ドキュメントを参照してください。


WinDbg でのログファイルの保持:

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/keeping-a-log-file-in-windbg


WinDbg コマンドをスクリプト化して実行する

WinDbg ではスクリプト化した複雑な処理を実行することが可能です。

本書でダンプファイルの解析にコマンドを使用した際には、WinDbg の Command ウインドウに単一のコマンド、もしくは セミコロン(;) で区切った複数のコマンドを入力して実行していました。

しかし、より効率的に解析を行う場合には、複雑なコマンドを一括で実行したり、条件分岐を設定して実行するコマンドを制御したりする必要がある場合があります。

例えば、以下のスクリプトはカーネルメモリ空間内の EPROCESS 構造体を列挙し、If 文を使用して D4C.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("D4C.exe","${$ImageName}")) { } .else {.echo ${$ImageName} at ${Procc}}
    }

    ad $ImageName
    ad Procc
}

このスクリプトを C:\Users\Win10\Downloads\windbg_script.txt として保存し、WinDbg で以下のように呼び出すことで実行できます。

6: kd> $$>a<C:\Users\Win10\Downloads\windbg_script.txt
D4C.exe at 0xffffc18f45284080

なお、スクリプトの呼び出しコマンド1や、このスクリプト内で使用している条件分岐2とループ3の構文、またエイリアス4や疑似レジスタ5の使用方法については、本書では詳しく解説しません。

各コマンドの詳細については公開ドキュメントの情報を参照してください。

MEX 拡張機能を使用する

MEX 拡張機能は、Microsoft が公式に提供している WinDbg 用の拡張機能です。

MEX 拡張機能のいくつかのコマンドについては 7 章で使用しました。

本書で実施した程度の解析であれば MEX 拡張機能を使用する必要はありませんが、覚えておくと便利かと思います。

MEX 拡張機能を利用するにはまず、以下の URL からダウンロード可能な Mex.exe を Windows システムで実行します。


Download MEX Debugging Extension from Official Microsoft Download Center:

https://www.microsoft.com/en-us/download/details.aspx?id=53304


次に、利用規約に同意した後に表示されるウィンドウで任意のフォルダパスを指定し、MEX 拡張機能のモジュールを配置します。

MEX モジュールの展開

上記で指定したパスをエクスプローラで参照すると Mex.zip というファイルが配置されていることを確認できるので、この ZIP ファイルを解凍してください。

解凍したフォルダ内に含まれる mex.dll というファイルを .load <mex.dll のフルパス> コマンドで WinDbg にロードすることで、MEX 拡張機能を利用できるようになります。

本書では MEX 拡張機能について詳細な解説は行いませんが、!mex.help -all コマンドを実行することで使用可能な各機能に関するヘルプを出力できます。

フルメモリダンプからファイルの中身を参照する

この項ではフルメモリダンプで取得したメモリデータからファイルのデータを取得するテクニックを紹介します。

Windows システムでは、キャッシュマネージャという仕組みによってファイルのコンテンツやファイルシステムのメタデータなどをキャッシュします。6

このキャッシュマネージャは「仮想ブロックキャッシュ(Virtual Block Caching)」という仕組みによってどのファイルのどの部分がキャッシュ内に存在するかを追跡します。

本書では、メモリにマップされたファイルオブジェクトからキャッシュマネージャの管理する「仮想アドレス制御ブロック(VACB)」と呼ばれる構造体のアドレスを特定することで、メモリ内にキャッシュされた情報からからシステム内のファイルコンテンツを抽出することを試みます。

なお、この方法はフルメモリダンプで取得したメモリ内のページにキャッシュデータが存在していることを前提とするため、取得したフルメモリダンプから必ずしもファイルのコンテンツを取得できるとは限らない点に注意が必要です。

フルメモリダンプからファイルコンテンツを抽出するため、まずは取得したフルメモリダンプを WinDbg にロードして !memusage コマンドを実行します。

!memusage コマンドの出力結果は膨大になるため、事前に .logopen コマンドで実行結果をファイルに出力する設定を有効化しておくことをおすすめします。

!memusage コマンドを実行して PFN データベースの情報を解析した結果、mapped_file として no-cached-file.txt と Microsoft-Windows-Windows Defender%4Operational.evtx(Microsoft Defender ウイルス対策のイベントログファイル) の存在を確認できました。

0: kd> !memusage
loading PFN database
{{ 省略 }}
Usage Summary (in Kb):
Control       Valid Standby Dirty Shared Locked PageTables  name
・・・
ffffc40861b53750     0      4     0     0     0     0  mapped_file( no-cached-file.txt )
・・・
ffffc40861b5c350    68      0     0     0    68     0  mapped_file( Microsoft-Windows-Windows Defender%4Operational.evtx )
・・・

ここで特定した Control の情報を参照するには !ca 拡張機能7を使用します。

mapped_file( no-cached-file.txt ) に対応するコントロール領域のアドレスである 0xffffc40861b53750 を使用して !ca ffffc40861b53750 コマンドを実行したところ、以下の結果を得ることができました。

!ca で no-cached-file.txt のコントロール領域の情報を取得する

同じく、mapped_file( Microsoft-Windows-Windows Defender%4Operational.evtx ) に対応するコントロール領域のアドレスである 0xffffc40861b5c350 を使用して !ca ffffc40861b5c350 コマンドを実行したところ、結果は以下のようになりました。

!ca でイベントログファイルのコントロール領域の情報を取得する

この出力結果から、各ファイルのパスとファイルオブジェクトのアドレスを確認できます。

例えば、no-cached-file.txt の出力結果では File Object ffffc40861f5f720 と表示されているため、このファイルのファイルオブジェクトが 0xffffc40861f5f720 に存在していることを特定できます。

また、イベントログファイルの出力結果は File Object ffffc40861920440 となっていることを確認できます。

確認のためにこれらのアドレスを使用して !fileobj 拡張機能を実行することで、ファイルオブジェクトの詳細情報を参照できることを確認しておきます。

# no-cached-file.txt のファイルオブジェクトを調査
0: kd> !fileobj ffffc40861f5f720

\Users\Vuln\Desktop\no-cached-file.txt

Device Object: 0xffffc4085b32ec00   \Driver\volmgr
Vpb: 0xffffc4085b3c69c0
Access: Read SharedRead SharedWrite 

Flags:  0x44042
	Synchronous IO
	Cache Supported
	Cleanup Complete
	Handle Created

Final Status: 80000005
FsContext: 0xffff83807b62d700	FsContext2: 0xffff83807ceb2580
CurrentByteOffset: 0
Cache Data:
  Section Object Pointers: ffffc40861f22a98
  Shared Cache Map: 00000000


# イベントログファイルのファイルオブジェクトを調査
0: kd> !fileobj ffffc40861920440

\Windows\System32\winevt\Logs\Microsoft-Windows-Windows Defender%4Operational.evtx

Device Object: 0xffffc4085b32ec00   \Driver\volmgr
Vpb: 0xffffc4085b3c69c0
Event signalled
Access: Read Write SharedRead 

Flags:  0x41042
	Synchronous IO
	Cache Supported
	Modified
	Handle Created

FsContext: 0xffff83807a55f700	FsContext2: 0xffff83807a55f970
Private Cache Map: 0xffffc40861582b98
CurrentByteOffset: 36a0
Cache Data:
  Section Object Pointers: ffffc40861981b38
  Shared Cache Map: ffffc40861582a20         File Offset: 36a0 in VACB number 0
  Vacb: ffffc4085abd5ea0
  Your data is at: ffff9688ea9436a0

各ファイルオブジェクトのアドレスを特定できたので、次に nt!_FILE_OBJECT 構造体内の情報から nt!_SECTION_OBJECT_POINTERS 構造体のアドレスを取得します。

# no-cached-file.txt のファイルオブジェクトを調査
0: kd> dt nt!_FILE_OBJECT ffffc40861f5f720 SectionObjectPointer
   +0x028 SectionObjectPointer : 0xffffc408`61f22a98 _SECTION_OBJECT_POINTERS

# イベントログファイルのファイルオブジェクトを調査
0: kd> dt nt!_FILE_OBJECT ffffc40861920440 SectionObjectPointer
   +0x028 SectionObjectPointer : 0xffffc408`61981b38 _SECTION_OBJECT_POINTERS

さらにこのアドレスが指す nt!_SECTION_OBJECT_POINTERS 構造体の情報をダンプします。

先に no-cached-file.txt の情報をダンプしてみると、以下のように SharedCacheMap が空であることがわかります。

この場合、no-cached-file.txt はキャッシュマネージャによってキャッシュされていないため、キャッシュからコンテンツを参照することができません。

0: kd> dt nt!_SECTION_OBJECT_POINTERS ffffc408`61f22a98 
   +0x000 DataSectionObject : 0xffffc408`61b53750 Void
   +0x008 SharedCacheMap   : (null) 
   +0x010 ImageSectionObject : (null)

では次に、イベントログファイルの構造体情報をダンプしてみます。

0: kd> dt nt!_SECTION_OBJECT_POINTERS ffffc40861981b38
   +0x000 DataSectionObject : 0xffffc408`61b5c350 Void
   +0x008 SharedCacheMap   : 0xffffc408`61582a20 Void
   +0x010 ImageSectionObject : (null) 

こちらは SharedCacheMap に 0xffffc40861582a20 というアドレスが格納されていることがわかります。

SharedCacheMap 構造体の中には VACB のアドレスが含まれています。

そのため、特定した SharedCacheMap のアドレスを使用して t nt!_SHARED_CACHE_MAP ffffc40861582a20 Vacbs コマンドを実行することで、対象ファイルのコンテンツが含まれるキャッシュを参照するための VACB のアドレスを特定できます。

0: kd> dt nt!_SHARED_CACHE_MAP ffffc40861582a20 Vacbs
   +0x058 Vacbs : 0xffffc408`61582a58  -> 0xffffc408`5abd5ea0 _VACB

上記のコマンドで特定した VACB のアドレス 0xffffc4085abd5ea0 を使用して nt!_VACB 構造体の情報をダンプすることで、以下の情報を取得できました。

0: kd> dt nt!_VACB ffffc4085abd5ea0
   +0x000 BaseAddress      : 0xffff9688`ea940000 Void
   +0x008 SharedCacheMap   : 0xffffc408`61582a20 _SHARED_CACHE_MAP
   +0x010 Overlay          : <anonymous-tag>
   +0x020 ArrayHead        : 0xffffc408`5abce000 _VACB_ARRAY_HEADER

VACB 構造体はシステムの VACB 配列によって管理されており、各 VACB エントリはシステムキャッシュ用に割り当てられている 256 KB のスロットのアドレスを記述しています。8

つまり、Microsoft Defender ウイルス対策のイベントログファイルのコンテンツは、VACB の BaseAddress で確認したアドレス 0xffff9688ea940000 にキャッシュされていることがわかります。

実際に db 0xffff9688ea940000 L60 コマンドで、BaseAddress から 60 バイト分のメモリをダンプしてみると、Windows のイベントログファイル(EVTX) フォーマットのシグネチャにあたる ElfFile\x00 から始まるデータであることがわかります。

イベントログファイルのキャッシュされたメモリのダンプ

※ Windows のイベントログファイル(EVTX) フォーマットの詳細な仕様は公開されていませんが、Github で公開されている libyal/libevtx リポジトリの情報から、ファイルヘッダやチャンクヘッダに関する情報を得ることができます。

さらに、この BaseAddress から始まるメモリ領域にイベントログファイルのデータが書き込まれているかどうか確認するため、.writemem C:\windefend-event.txt ffff9688ea940000 L10000 コマンドで適当に 10000 バイト分のデータを windefend-event.txt にダンプします。

イベントログファイル内のテキストはワイド文字列として書き込まれているので、strings -e l windefend-event.txt のようなコマンドを使用してファイルのテキストを抽出すると、以下のようなイベントログファイル内に記述されるデータを取得できていることがわかりました。

$ strings -e l windefend-event.txt
・・・
Microsoft Defender Antivirus
4.18.23080.2006
1.397.865.0
1.1.23080.2005
Security intelligence update
C:\ProgramData\Microsoft\Windows Defender\Scans\RtSigs\data\884898236a1fb17353dac39aea1e06cfeadc24e4
0.0.0.0
10/20/2023 11:36:23 AM
Duration
2592000000
Microsoft-Windows-Windows Defender
Microsoft-Windows-Windows Defender/Operational

ちなみに、今回は VACB のアドレスに到達するためにファイルオブジェクトのアドレスから構造体の情報を順に辿っていきましたが、!finddata 拡張機能9を使用すると、もっと簡単にコンテンツがキャッシュされたアドレスを特定できます。

ここまでに調査した no-cached-file.txt とイベントログファイルのファイルオブジェクトのアドレスを使用して !finddata コマンドを実行すると、以下のような結果が得られました。

# no-cached-file.txt のファイルオブジェクトを調査
0: kd> !finddata ffffc40861f5f720
・・・
FindData for FileObject ffffc40861f5f720   Section Object Pointers: ffffc40861f22a98
Shared Cache Map: 00000000Unable to read nt!_SHARED_CACHE_MAP at 0000000000000000


# イベントログファイルのファイルオブジェクトを調査
0: kd> !finddata ffffc40861920440
・・・
FindData for FileObject ffffc40861920440   Section Object Pointers: ffffc40861981b38
Shared Cache Map: ffffc40861582a20         File Offset: 0 in VACB number 0
Vacb: ffffc4085abd5ea0
Your data is at: ffff9688ea940000

メモリ内にキャッシュが存在していなかった no-cached-file.txt の場合は nt!_SHARED_CACHE_MAP 構造体のアドレスを参照できなかった旨のテキストが出力されています。

一方で、イベントログファイルの場合は、Your data is at: ffff9688ea940000 として、VACB 構造体の情報から特定したコンテンツのキャッシュされているアドレスが出力されました。

以上で、フルメモリダンプからシステムにキャッシュされたファイルのデータを抽出することができました。

この方法では狙ったファイルや元の完全なファイルを取得することは困難ですが、フルメモリダンプを解析する上で知っておくと面白いテクニックかと思います。

ダンプファイルから WinDbg で実行ファイルを抽出する

実際のトラブルシューティングの場面で使用する必要はありませんが、例えばマルウェアに感染した端末のダンプファイルを解析する場合などに、メモリダンプから元の実行ファイルを取得したい場合があります。

そのような場合は、プロセスのメモリ内に展開されたデータを WinDbg で抽出してファイルとしてエクスポートすることで、実行ファイル(PE ファイル)を取得できます。

しかし、この方法でプロセスのメモリから展開した実行ファイルは、元の実行ファイルと完全に同一のファイルではない点に注意する必要があります。10

この理由は複数ありますが、1 つは PE ファイルの一部のセクションがプロセスメモリ内に展開されないことが挙げられます。

また、メモリに展開されたグローバル変数の値や実行コードなどがプログラムによってに書き換えられた場合にも、元の実行ファイルを抽出することができなくなります。

プロセスメモリから元の実行ファイルと完全に同一のファイルを抽出することが困難な理由は他にもありますが、本書では詳細な解説は割愛します。

プロセスメモリからの実行ファイルの抽出については参考文献に記載している「The Art of Memory Forensics」で詳しく解説されています。

本書では、WinDbg を使用してプロセスのフルメモリダンプから D4C.exe の実行ファイル復元を行います。

基本的には、PE ファイルのローダの逆の操作を行い、プロセスのメモリ内に展開された PE ヘッダや各セクションの情報を抜き出して 1 つのファイルとしてマージすることでファイルの復元が可能です。

5 章や 6 章で紹介したように !dh -f コマンドと !dh -s コマンドを使用して IAT やセクションヘッダの情報を取得し、.writemem コマンドで各領域のメモリをファイルをとして出力していく方法でも実行ファイルの復元は技術的には可能です。

しかし、今回は Github 上で公開されているオープンソースの拡張機能(HongThatCong/dumpext)を利用して実行ファイルの抽出を行います。

なお、本書ではあくまでプロセスダンプから実行ファイルを抽出可能であるということの紹介のみを行うため、拡張機能の詳細については解説しません。

実行ファイルの抽出のため、6 章で取得した D4C.exe のプロセスダンプを使用します。

D4C.exe のプロセスダンプを WinDbg にロードしたら、HongThatCong/dumpext リポジトリからダウンロードした拡張機能を .load C:\Users\Public\Downloads\dumpext.dll コマンドで読み込みます。

拡張機能をロードしたら、!dumpext.dump_pe !D4C コマンドを実行してプロセスから実行ファイルをダンプします。

!dumpext.dump_pe !D4C による実行ファイルのダンプ

拡張機能によって出力されたバイナリは WinDbg の実行フォルダ(C:\Program Files (x86)\Windows Kits\10\Debuggers\x64) に dump.out として保存されるため、dump.exe などにリネームしておきます。

dump.exe を実行してみると、オリジナルの D4C.exe と同じように起動し、各種機能を利用できました。

しかし、ここで取得した dump.exe とオリジナルの D4C.exe のファイルハッシュを比較してみると、ダンプファイルから取得したファイルは元のファイルとは一致していないことがわかります。

ファイルハッシュの比較

以上の通り、プロセスダンプから元のファイルと完全には一致しないものの、実行可能な EXE ファイルを抽出できることが確認できました。

各章へのリンク


  1. <,<,><, <,<,><, $$ >a< Run Script File: https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/-----------------------a---run-script-file-

  2. .if: https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/-if

  3. .for: https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/-for

  4. as, aS Set Alias: https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/as—as—set-alias-

  5. 疑似レジスタの構文: https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/pseudo-register-syntax

  6. インサイド Windows 第 7 版 下 P.595 (Andrea Allievi, Mark E.Russinovich, Alex Ionescu, David A.Solomon 著 / 山内和朗 訳 / 日系 BP 社 / 2022 年)

  7. !ca 拡張機能 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/-ca

  8. インサイド Windows 第 7 版 下 P.606 (Andrea Allievi, Mark E.Russinovich, Alex Ionescu, David A.Solomon 著 / 山内和朗 訳 / 日系 BP 社 / 2022 年)

  9. !finddata 拡張機能 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/-finddata

  10. The Art of Memory Forensics: Detecting Malware and Threats in Windows, Linux, and Mac Memory 1st Edition P.239 (Michael Hale Ligh, Andrew Case, Jamie Levy, AAron Walters 著 / Wiley / 2014 年 )