先日作成した WinDbg でダンプ解析、ライブデバッグを行う時のチートシート からの派生で、 Windows のトラブルシューティング時に覚えておくと便利な GFlags の設定値についてまとめました。
個人的に役に立つと感じる設定のみ記載していますが、こちらも自分用のメモ書きなので、また今後別の場面で活用した設定などあれば追記していきたいと思います。
もくじ
まえがき
本記事の内容はすべて一般に公開されている情報や、出版された書籍、または個人の検証環境で動作確認を実施した結果のみを元に作成しています。
関連記事はこちら。
GFlags とは
GFlags(Global Flags Editor) は、特定のデバッグ機能の有効化/無効化に使用できるツールです。
Debugging Tools for Windows 10 (WinDbg) に含まれているので、Classic の WinDbg をインストールしている環境の場合は、WinDbg と同じフォルダ(C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
など)に gflags.exe と gflags.dll が配置されています。
参考:GFlags - Windows drivers | Microsoft Learn
GFlags は CLI および GUI の両方で使用することができ、これを利用してシステムやイメージ(プログラム)のグローバルフラグを操作することが可能です。
コマンドで GFlags を操作する方法は以下に記載されています。
参考:GFlags Commands - Windows drivers | Microsoft Learn
グローバルフラグとは
グローバルフラグは、Windows のシステムやイメージのデバッグやトレースをサポートするための変数です。
システムのグローバルフラグ
システムのグローバルフラグは、 NtGlobalFlag と NtGlobalFlag2 の 2 つのグローバル変数で管理されています。
これらのシステム変数はHKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager
で定義されていて、システムの起動時に初期化されます。(既定値はどちらも 0 で、グローバルフラグは設定されていません。)
システム側のグローバルフラグは、カーネルデバッグ時に!gflag
拡張機能を使用することで参照できます。
ただし、現在はこの拡張は NtGlobalFlag の設定値にしか対応していないようです。
!gflag -?
コマンドを実行すると、グローバルフラグのヘルプを出力することができます。
イメージファイルごとのグローバルフラグ
また、各イメージ(プログラム)にもグローバルフラグの設定が用意されています。
各イメージファイルごとのグローバルフラグはHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<Application>
に定義されています。
このレジストリ配下にはアプリケーション起動時にプログラムを開くアプリケーションを指定するDebugger
オプションや、アプリケーション終了時のプロセス起動に悪用できるSirentProcessExit
が含まれます。
これらの Image File Execution Options(IEFO) オプションは、デバッグ用途だけでなく Malware が Persistence の目的で悪用する場合があることも知られています。
各イメージのグローバルフラグは、ローダや Windows エラー報告(WER) などのユーザモードコンポーネントによって処理されます。
例えばローダがグローバルフラグを処理する場合には、プログラムが起動するためにプロセスを初期化して依存モジュールをロードする際にグローバルフラグが処理されます。
この時にローダが実行するLdrpInitializeExecutionOptions
関数は、イメージファイルの名前に基づいて IEFO のレジストリキーを参照し、グローバルフラグを取得します。
ローダがレジストリから取得したグローバルフラグの情報は PEB 内の NtGlobalFlag と NtGlobalFlag2 に格納されます。
実際に GFlags でグローバルフラグを設定したメモ帳アプリの PEB を参照してみると、以下のようにNtGlobalFlag
に値が設定されていることを確認できました。
> !ped
No export ped found
0:000> !peb
PEB at 00000030dbc1e000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: Yes
ImageBaseAddress: 00007ff689330000
NtGlobalFlag: 70
NtGlobalFlag2: 0
Ldr 00007ffcf7f9c4c0
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 000001744bbe2a10 . 000001744bbe3130
Ldr.InLoadOrderModuleList: 000001744bbe2bc0 . 000001744bbf04e0
Ldr.InMemoryOrderModuleList: 000001744bbe2bd0 . 000001744bbf04f0
カーネルモードスタックをページアウトされないようにする
GFlags からDisable paging of kernel stacks
のグローバルフラグを設定することで、カーネルモードスタックのページアウトを無効化することができます。
Disable paging of kernel stacks
は、FLG_DISABLE_PAGE_KERNEL_STACKS(0x80000)
で設定されます。
以下の Docs に記載の通り、カーネルモードスタックは通常ページングされず、メモリ内に常駐することが保証されています。
しかし、非アクティブなスレッドのカーネルスタックについてまれにページアウトが発生する場合があります。
カーネルスタックがページアウトされると、カーネルデバッグやフルダンプの解析時に対象のスレッドに関する情報を参照することができなくなります。
そのため、例えばデッドロック事象のデバッグや、すべてのスレッドを追跡する必要がある場合にこのグローバルフラグを設定することが有効です。
参考:Disable paging of kernel stacks - Windows drivers | Microsoft Learn
Disable paging of kernel stacks
は以下の箇所から設定できます。
なお、実際のデバッグの際には上記のカーネルスタックのページアウトの防止だけでなく、プール領域ののメモリ割り当てに関する統計を計算することができるFLG_POOL_ENABLE_TAGGING(0x400)
のグローバルフラグを設定するのも良いという情報が以下のブログに記載されていました。
参考:カーネルモードスタックをページアウトされないようにするための方法 | 窓のくすり箱
参考:Enable pool tagging - Windows drivers | Microsoft Learn
イメージローダの処理を見る
GFlags を使ってShow Loader Snaps
のデバッグ機能を有効化するグローバルフラグをセットすることで、対象のアプリケーションの起動時にイメージローダのデバッグ出力を参照できるようになります。
これにより、実行ファイルとライブラリのロードとアンロードの情報をデバッガに出力できます。
Show Loader Snaps
はFLG_SHOW_LDR_SNAPS(0x2)
で、システム全体に設定された場合はドライバのロードおよびアンロードの情報を出力します。
特定の ImageFile のみに設定された場合は、アプリケーション起動時の DLL のロードとアンロードに関する情報を出力します。
参考:Show loader snaps - Windows drivers | Microsoft Learn
例えば、gflags.exe を起動して [Image File] から以下のように設定することで、Notepad.exe に対してShow Loader Snaps
とDebugger
フラグを設定します。
この設定を適用することで、HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Notepad.exe
配下のDebugger
に WinDbg のパスが設定されます。
また、GlobalFlag
に値 2 がセットされます。(この環境では他に Notepad.exe に GlobalFlag は設定していないため、Show Loader Snaps
の Hexadecimal value である 0x2 がそのまま適用されます。)
これで Notepad.exe にはDebugger
フラグが設定されたため、メモ帳を起動しようとすると自動的に WinDbg がアタッチされ、以下のような出力が記録されます。
ModLoad: 00007ff7`8c3b0000 00007ff7`8c3e8000 notepad.exe
ModLoad: 00007ffd`1f2d0000 00007ffd`1f4c8000 ntdll.dll
22e0:0710 @ 00503718 - LdrpInitializeProcess - INFO: Beginning execution of notepad.exe (C:\WINDOWS\system32\notepad.exe)
Current directory: C:\Users\Tadpole01\
Package directories: (null)
22e0:0710 @ 00503718 - LdrLoadDll - ENTER: DLL name: KERNEL32.DLL
22e0:0710 @ 00503718 - LdrpLoadDllInternal - ENTER: DLL name: KERNEL32.DLL
22e0:0710 @ 00503718 - LdrpFindKnownDll - ENTER: DLL name: KERNEL32.DLL
22e0:0710 @ 00503718 - LdrpFindKnownDll - RETURN: Status: 0x00000000
22e0:0710 @ 00503718 - LdrpMinimalMapModule - ENTER: DLL name: C:\WINDOWS\System32\KERNEL32.DLL
ModLoad: 00007ffd`1d390000 00007ffd`1d44f000 C:\WINDOWS\System32\KERNEL32.DLL
22e0:0710 @ 00503718 - LdrpMinimalMapModule - RETURN: Status: 0x00000000
22e0:0710 @ 00503718 - LdrpPreprocessDllName - INFO: DLL api-ms-win-core-rtlsupport-l1-1-0.dll was redirected to C:\WINDOWS\SYSTEM32\ntdll.dll by API set
22e0:0710 @ 00503718 - LdrpFindKnownDll - ENTER: DLL name: KERNELBASE.dll
22e0:0710 @ 00503718 - LdrpFindKnownDll - RETURN: Status: 0x00000000
22e0:0710 @ 00503718 - LdrpMinimalMapModule - ENTER: DLL name: C:\WINDOWS\System32\KERNELBASE.dll
ModLoad: 00007ffd`1ca10000 00007ffd`1cd06000 C:\WINDOWS\System32\KERNELBASE.dll
22e0:0710 @ 00503718 - LdrpMinimalMapModule - RETURN: Status: 0x00000000
22e0:0710 @ 00503718 - LdrpPreprocessDllName - INFO: DLL api-ms-win-eventing-provider-l1-1-0.dll was redirected to C:\WINDOWS\SYSTEM32\kernelbase.dll by API set
22e0:0710 @ 00503718 - LdrpPreprocessDllName - INFO: DLL api-ms-win-core-apiquery-l1-1-0.dll was redirected to C:\WINDOWS\SYSTEM32\ntdll.dll by API set
22e0:0710 @ 00503718 - LdrpPreprocessDllName - INFO: DLL api-ms-win-core-apiquery-l1-1-1.dll was redirected to C:\WINDOWS\SYSTEM32\ntdll.dll by API set
出力はかなり多い(手元の環境で 120 KB くらい)ので、必要に応じてログファイルに出力しておくとよいと思います。
例えば、Debugger
フラグに以下のようにログ出力先のファイルパスを含めて実行すると、起動時のログをファイルに出力することができます。
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe -logo C:\Users\Public\debug.log
Show Loader Snaps
のフラグを有効化している場合は、アプリケーションの起動時だけでなく何らかの操作でモジュールが後からロードされる場合にも、WinDbg 上にイベントを出力します。
プログラムの起動時にデバッガを起動する
前述しましたが、各イメージごとのDebugger
オプションを設定することでプログラムの起動時にデバッガをアタッチすることができるようになります。
このような Image File Execution Options(IFEO) の設定はグローバルフラグとは異なる設定ですが、GFlags から設定することが可能です。
例えば、以下のように設定を行うことでメモ帳を起動する際に WinDbg を使用してプログラムを起動し、速やかにデバッグを開始することができます。
この設定は、例えば直接実行することが許可されず、何らかのインターフェースやジョブを通して起動する必要があり、かつシステムに常駐しないためにデバッガをアタッチすることが困難なシナリオでデバッグを行う必要がある際などに非常に役に立ちます。
参考:Image File Execution Options | Microsoft Learn
なお、この時Debugger
のパスに指定できるパスは実際のデバッガのパスに限定されません。
例えば以下のようにエディタやその他の任意のプログラムを指定すると、メモ帳アプリの起動時に自動的に指定したプログラムでメモ帳を開くようになります。
このような仕様は悪用される場合もあるので注意が必要です。
プログラムの終了を監視する
こちらも厳密にはグローバルフラグではないですが、各アプリケーションのSilent Process Exit
を有効化することでアプリケーションの終了を監視をすることができます。
監視できる終了操作は ExitProcess による自分自身の終了(右上の終了ボタンを押した場合など)か、TerminateProcess による他アプリケーションからの終了操作のみです。
Silent Process Exit
を設定すると、アプリケーションが終了した際にクラッシュダンプを出力したり、ホストに通知を送信したりできます。
また、Monitor process として任意のアプリケーションを起動することもできます。(こちらも悪用される場合があるので注意が必要です。)
設定は、GFlags のSilent Process Exit
から行うことができます。
この設定によって、クラッシュではない要因によって終了したプログラムのダンプを取得してトラブルシューティングを行ったり、プログラムを終了させた要因を特定できるようになります。
例えば以下のようなオプションでメモ帳アプリのSilent Process Exit
を設定したとします。
※ ダンプの種類を [Custom Dump] に設定した場合、取得できるダンプ種類はMINIDUMP_TYPE (minidumpapiset.h)の値によって決定されます。今回は、MiniDumpWithFullMemory
の値である 0x2 を設定しています。
この状況でタスクマネージャからメモ帳を強制終了した場合、以下のように終了したタスクマネージャのプロセスのダンプも取得されます。
また、Stop-Process -Name notepad
によって Power Shell から終了した場合は、このように Power Shell のダンプが取得できます。
参考:Monitoring Silent Process Exit - Windows drivers | Microsoft Learn
参考:プロセスを終了させたプロセスを見つける - Windows の資料採取に関する情報公開サイト
ちなみに、Monitor Process の設定にデバッガのパスを設定しても、残念ながら終了直前のプロセスにデバッガをアタッチすることはできません。
しかし、例えばプログラムの終了を通知するようなプログラムを起動させたり、プログラム終了直後のシステム情報を収集するようなツールを実行させるといった使い道など、活用方法は数多くありそうです。
まとめ
今回は、Windows のトラブルシューティング時に覚えておくと便利な GFlags の設定値についてまとめました。
個人的に役に立つと感じる設定のみ記載していますが、また今後別の場面で活用した設定などあれば追記していきたいと思います。