本章では、ダンプファイルの解析を行うにあたり最低限必要となる情報を簡単に紹介しておきます。
しかし、まえがきに記載の通り、本書では前提となる知識については詳細な解説を行いません。
詳細については、参考情報として記載している公式ドキュメントや書籍をご確認ください。
もくじ
- カーネルモードとユーザーモード
- Windows のプロセスについて
- Windows のスレッドについて
- Windows の実行ファイルのフォーマットについて
- 仮想アドレス(VA)、相対仮想アドレス(RVA) について
- アセンブリ言語について
- レジスタについて
- 3 章のまとめ
- 各章へのリンク
カーネルモードとユーザーモード
Windows を実行するプロセッサでは、ユーザの使用するアプリケーションがシステムの重要なデータに直接アクセスしたり変更したりすることを防ぐ仕組みとして、カーネルモードとユーザーモードの 2 つのモードを使用します。1
通常、OS に組み込まれているシステムサービスやデバイスドライバなどはカーネルモードで実行されます。
一方で、ブラウザやテキストエディタなど、OS 上で実行するアプリケーションはすべてユーザモードで実行されます。
ユーザモードで実行されるアプリケーションの実行コードはシステムの重要なリソースに直接アクセスすることができません。
そのため、メモリやハードディスクなどのハードウェアとの情報のやり取りなどはすべて、カーネルモードで動作するシステムサービスに依存することになります。
ユーザモードで動作するアプリケーションがメモリやハードディスクにアクセスする必要がある場合、Windows API などの専用のインターフェースを通してカーネルモードで実行されているシステムサービスに処理を要求します。
このような仕組みによって、万が一アプリケーションの動作に問題や異常が発生した場合にも、OS 自体の稼働継続や、システムの重要なデータなどの保護を実現しています。
また、ユーザモードでアプリケーションが開始されると、Windows はそのアプリケーションと紐づくプロセスを作成し、それぞれにプライベートな仮想アドレス空間とハンドルテーブルが割り当てられます。
通常はユーザモードで動作するアプリケーションはプロセスごとに分離されているため、SeDebugPrivilege などの特別な特権が割り当てられていない限り、他のアプリケーションのメモリ領域にアクセスすることはできません。
一方で、カーネルモードで実行されるコードはすべて単一の仮想アドレス空間を共有します。2
さらに、カーネルモードで実行されるコードは OS そのもの(他の OS コンポーネントやドライバと分離されない)であり、ユーザモードで稼働するすべてのプロセスのメモリ領域にアクセスできます。
Windows ダンプファイル解析の観点では、解析対象がユーザモードで実行されているかカーネルモードで実行されているかを区別することは非常に重要です。
ユーザモードで実行されているアプリケーションに関する問題の調査のためには、ユーザモードプロセスの情報を含むダンプファイルを取得する必要があります。
逆に、解析対象がカーネルモードで実行されているコードの場合には、ユーザモードのプロセスダンプからの調査は困難なため、カーネルモードの情報を含むシステムのフルメモリダンプが必要になります。
ユーザモードやカーネルモードについて理解するには、プロセッサの仕様や一般的な OS の仕組みについて知ることが有効かと思います。
Windows についてはほとんど言及されませんが、一般的な OS の仕組みについて知るためには自作 OS 関連の入門書34などが参考になると思います。
また、Unix v65 や、教育用に Unix v6 を x86 アーキテクチャに移植した xv6 Unix などの少ないコード量で記述された OS について学ぶ事も理解を助けるかもしれません。
Windows のプロセスについて
Windows におけるプロセスは、プログラムのインスタンスを実行する際に必要なリソースのためのコンテナ(器)と表現されます。7
本書では詳しくは扱いませんが、Windows のプロセスは、主に以下の要素で構成されます。
- プライベートな仮想アドレス空間
- 仮想アドレス空間にマッピングされた実行可能プログラム
- ハンドルのリスト
- セキュリティコンテキスト
- ユニークなプロセス ID
- 1 つ以上のスレッド
Windows のプロセスはシステムのアドレス空間に存在するエクゼクティブプロセス(EPROCESS) 構造体で表現されます。
ただし、ユーザモードアプリケーションからアクセスされるプロセス環境ブロック(PEB) だけは、ユーザモードのアドレス空間に存在します。(つまり、ユーザモードアプリケーションのクラッシュダンプを解析する場合でも、PEB の情報についてはデバッガで参照できます)8
フルメモリダンプの解析を行う場合には、適切なプロセスのコンテキストを指定していく必要があるので、Windows がプロセスの情報をどのように保持しているのかについて理解しておくことは非常に重要です。
Windows のスレッドについて
スレッドは、Windows がプロセスと対応するプログラムを実行するために不可欠なオブジェクトです。
そのため、通常のプロセスは 1 つ以上のスレッドを必ず管理しています。
スレッドは、実行中のプロセッサの状態に対応した CPU レジスタの情報や、カーネルモードとユーザモードでの実行用に用意された 2 つのスタック、またユニークなスレッド ID などの情報から構成されています。9
Windows のスレッドはエグゼクティブスレッドオブジェクト(ETHREAD) 構造体によって表現され、ETHREAD 構造体の最初のメンバーとしてカーネルスレッド(KTHREAD) 構造体が定義されています。10
トラブルシューティングを行う際には、プロセスの場合と同じく適切な解析対象のスレッドを特定して、デバッガ上でスレッドのコンテキストを指定しながらダンプファイルの解析を進めます。
Windows の実行ファイルのフォーマットについて
Windows システムで実行可能なファイル(EXE ファイル)は通常、Portable Executable(PE) というフォーマットで作成されたファイルです。
Windows のダンプファイルを解析する場合には、この PE ファイルフォーマットでファイルヘッダに埋め込まれている情報や、プログラムの実行時にそれらの情報がどのようにメモリに展開されるのかについてある程度把握しておく必要があります。
PE ファイルフォーマットでファイルヘッダに埋め込まれている情報の詳細については、以下の公式ドキュメントが参考になります。
PE Format:
https://learn.microsoft.com/ja-jp/windows/win32/debug/pe-format
PE フォーマットで作成された実行ファイルのファイルヘッダに埋め込まれている情報は、Explorer Suite11に含まれている CFF Explorer や PEStudio12 などのツールで簡単に確認できます。
例えば、本書で使用する実行ファイル D4C.exe を CFF Explorer で解析すると以下の情報を得ることができます。
また、PEStudito で D4C.exe を解析した場合も、以下のように PE ファイルヘッダに埋め込まれている情報を参照できます。
これらのヘッダ情報は、Windows システム内で実行される際にプロセスのメモリに以下の順序で展開されます。13
- ファイルヘッダから DOS ヘッダ、PE ヘッダ、セクションヘッダの情報を読み出す。
- セクションヘッダの情報を使用し、実行プロセスのために確保したアドレス空間にファイルの各セクションをマップしていく。
- インポートセクション内の DLL のリストを参照し、未ロードの DLL がある場合はシステムにロードする。
- インポートセクション内のインポートシンボルを解決する。
- PE ヘッダの情報からスタックとヒープを作成し、初期スレッドを生成してプロセスを起動する。
本書では詳しい解説は行いませんが、ダンプファイルの解析を行う際には、上記のようなプログラム実行時に PE ファイルの情報をプロセスのメモリ領域にロードする流れを把握しておくと良いと思います。
仮想アドレス(VA)、相対仮想アドレス(RVA) について
前述の通り、Windows システム内で実行されたプログラムの情報は、各プログラムごとに割り当てられたプロセスのメモリ領域に展開されます。
この時に各プログラムごとに割り当てられる連続したメモリ領域を「仮想アドレス領域(仮想メモリ)」と呼びます。14
Windows の 64 bit プロセスには、仮想アドレス領域として 0x00000000000 から 0x7FFFFFFFFFFF までの 128 TB 分の仮想アドレス領域が割り当てられます。
OS がこのような仕組みを用いる利点はいくつかありますが、物理メモリ上では連続していないメモリ領域に対して一連のアドレス範囲としてアクセスすることができる点や、プログラムのコードの再配置が容易になる点などがあります。
また、上記の利点を活かして OS 内で複数のアプリケーションを並列稼働させることができる点や、プロセスごとにメモリ空間を分離できる点なども仮想メモリを利用する利点です。15
Windows でプログラムを実行した場合、実行コードはプロセスの仮想メモリ領域に展開されますが、展開された実行コードに対しては仮想アドレス(VA)を通してアクセスできます。
仮想メモリは OS によって物理メモリおよびページにマッピングされているため、アクセスされた仮想アドレスは OS によって物理メモリ内のアドレスに変換されます。16
ちなみに、仮想アドレス(VA)とは、PE ファイルをプロセスメモリに展開した際のアドレスである相対仮想アドレス(RVA)に、プロセスごとに割り当てられた読み込み先のイメージベースアドレスを足したアドレスです。17
イメージベースアドレスは通常、プロセスの実行ごとにアドレス空間ランダム化(ASLR)の仕組みによって異なるアドレスが割り当てられるため、仮想アドレス(VA)も毎回異なる点に注意が必要です。
例えば、同じプログラムを複数回実行してそれぞれのプロセスダンプを取得した場合、各ダンプファイルで同じアドレスを指定しても異なる情報が表示されることがあります。
これは、WinDbg でプロセスダンプをロードした際に表示されるアドレスが仮想アドレス(VA)であるためです。
そのため、ダンプファイルの解析を行う場合には、仮想アドレス(VA)からイメージベースアドレスを引いて相対仮想アドレス(RVA)を求めた上で、<モジュール名> + RVA
などのようにして相対オフセットを利用して解析を行うことをおすすめします。
アセンブリ言語について
前述の通り、実行ファイル(PE ファイル)で定義された実行コードを含む情報は、プログラムの実行時にプロセスに割り当てられた一連の仮想メモリ領域内に展開されます。
WinDbg でアプリケーションのプロセスダンプを解析する場合、展開された実行コードのディスアセンブル結果を参照することが可能です。
本書ではアセンブリ言語については扱いませんが、皆さんが実際に自分でダンプファイルの解析を行う際には、アセンブリ言語から実行コードの処理を読み解く必要があるかと思います。
x64 アーキテクチャのアセンブリ言語については入門にちょうどよい、平易で質の高い入門書が複数ありますので、注釈にて紹介しておきます。181920
また、より詳しく学びたい場合には「大熱血! アセンブラ入門」21 などもおすすめです。
レジスタについて
レジスタとは、CPU の内部にある記憶領域です。
メモリやストレージと比較して保存できる情報が極めて小さい一方で、CPU から非常に高速にアクセスすることができるため、一時的な情報や何らかの演算結果などの情報が保存されます。
WinDbg でデバッグやダンプファイルの解析を行う際には頻繁にレジスタの情報を確認することになります。
また、前項のアセンブリを読み解く際にも、レジスタに関する知識が不可欠になります。
本書ではレジスタについては詳しくは扱いませんが、ダンプファイルを解析する上で最低限必要な汎用レジスタの種類や主な用途について記載します。22
- アキュムレータ(RAX/EAX/AX/AL/AH):様々な演算に使用する。x64 呼び出し規約では関数の戻り値が格納される23点も重要。
- ベースレジスタ(RBX/EBX/BX/BL/BH):アドレス指定などによく使用する。
- カウントレジスタ(RCX/ECX/CX/CL/CH):ループやシフトローテートの命令によく使用される。x64 呼び出し規約では関数 Call 時の第一引数が格納される23点も重要。
- データレジスタ(RDX/EDX/DX/DL/DH):演算などによく使用する。x64 呼び出し規約では関数 Call 時の第二引数が格納される23点も重要。
- ソースインデックスレジスタ(RSI/ESI/SI):ストリーム操作によるデータ転送などで、入力元のポインタを指すためによく使用する。
- ディスティネーションインデックスレジスタ(RDI/EDI/DI):ストリーム操作によるデータ転送などで、出力先のポインタを指すためによく使用する。
- スタックポインタ(RSP/ESP/SP):スタックのトップを指すポインタが格納される。スレッドのスタックを参照したい場合によく使用する。
- ベースポインタ(RBP/EBP/BP):スタックのベースを指すポインタが格納される。コールスタックの追跡などに重要。
- インストラクションポインタ(RIP/EIP/IP):命令ポインタであり、次に実行する命令のアドレスが格納される。
- ステータスフラグ(EFLAGS):操作の結果などのステータスを格納するために使用。例えば、条件分岐の比較結果の格納などにも使用される。
ちなみに、アキュムレータレジスタなど、いくつかのレジスタには RAX、EAX、AX などの複数の名称を記載していますが、これはレジスタに対するアクセス方法を意味しています。
例えば、x64 アーキテクチャの CPU の場合アキュムレータレジスタには 64 bit 分のデータを格納でき、RAX に対してアクセスすることで 64 bit 分の領域すべてにアクセスできます。
一方で、アキュムレータレジスタに EAX としてアクセスする場合、64 bit 分のデータを格納できるアキュムレータレジスタの下位 32 bit 分の領域のみにアクセスすることを意味します。
さらに、AX は下位 16 bit 分の領域へのアクセスを意味し、AL は AX の指す 16 bit 分の領域のうちの下位(LSB 側) 8 bit、AH は上位(MSB 側) 8 bit へのアクセスを意味します。
3 章のまとめ
この章では、Windows ダンプ解析の前提となる最低限の前提知識について紹介しました。
ダンプファイルを解析するためには、レジスタや仮想メモリ、プロセスやスレッドなどの OS や CPU に関するものに加えて、PE ファイルフォーマットやアセンブリ言語などのプログラムに関するものなど、一般に難解とされる領域に関する幅広い知識が必要になります。
しかし、これらのダンプファイルの解析に必要な前提知識は非常に広範であるため、本章では十分に網羅することはできませんでした。
このような必要となる前提知識の幅広さや多さは、そのままダンプファイル解析のハードルの高さと言っても過言ではないと思います。
しかし、本書ではこれからダンプファイルの解析に取り組む方に、少しでもダンプファイルの解析を身近に感じてもらえるよう、4 章以降で解説するダンプファイルの解析では可能な限り必要とする前提知識を排除する構成としています。
そのため、もし本章で紹介した項目について十分に理解できていないと感じる場合でも、ぜひ 4 章以降の内容を読み進めていただいて構いません。
解析に必要な知識はダンプファイルを読み進める中で段々と身に付いていくかと思いますので、まずは解析を楽しんでいきましょう。
各章へのリンク
- まえがき
- 1 章 環境構築
- 2 章 WinDbg の基本操作
- 3 章 解析に必要な前提知識
- 4 章 アプリケーションのクラッシュダンプを解析する
- 5 章 システムクラッシュ時のフルメモリダンプを解析する
- 6 章 プロセスダンプからユーザモードアプリケーションのメモリリーク事象を調査する
- 7 章 フルメモリダンプからユーザモードメモリリーク事象を調査する
- 付録 A WinDbg の Tips
- 付録 B Volatility 3 でクラッシュダンプを解析する
-
インサイド Windows 第 7 版 上 P.25 (Pavel Yosifovich, Alex Ionescu, Mark E. Russinovich, David A. Solomon 著 / 山内 和朗 訳 / 日系 BP 社 / 2018 年)
↩ -
ユーザーモードとカーネルモード https://learn.microsoft.com/ja-jp/windows-hardware/drivers/gettingstarted/user-mode-and-kernel-mode
↩ -
ゼロからの OS 自作入門 ( 内田 公太 著 / マイナビ出版 / 2021 年)
↩ -
作って理解するOS x86系コンピュータを動かす理論と実装 (林 高勲 著 / 川合 秀実 監修 / 技術評論社 / 2019 年 )
↩ -
はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (青柳 隆宏 著 / 技術評論社 / 2013 年)
↩ -
Xv6, a simple Unix-like teaching operating system https://pdos.csail.mit.edu/6.828/2012/xv6.html
↩ -
インサイド Windows 第 7 版 上 P.9 (Pavel Yosifovich, Alex Ionescu, Mark E. Russinovich, David A. Solomon 著 / 山内 和朗 訳 / 日系 BP 社 / 2018 年)
↩ -
インサイド Windows 第 7 版 上 P.115 (Pavel Yosifovich, Alex Ionescu, Mark E. Russinovich, David A. Solomon 著 / 山内 和朗 訳 / 日系 BP 社 / 2018 年)
↩ -
インサイド Windows 第 7 版 上 P.19 (Pavel Yosifovich, Alex Ionescu, Mark E. Russinovich, David A. Solomon 著 / 山内 和朗 訳 / 日系 BP 社 / 2018 年)
↩ -
インサイド Windows 第 7 版 上 P.210 (Pavel Yosifovich, Alex Ionescu, Mark E. Russinovich, David A. Solomon 著 / 山内 和朗 訳 / 日系 BP 社 / 2018 年)
↩ -
Explorer Suite https://ntcore.com/?page_id=388
↩ -
PEStudio https://www.winitor.com/download
↩ -
Linkers & Loaders P.82 (JohnR. Levine 著 / 榊原 一矢, ポジティブエッジ 訳 / オーム社 / 2001 年)
↩ -
仮想アドレス領域 https://learn.microsoft.com/ja-jp/windows-hardware/drivers/gettingstarted/virtual-address-spaces
↩ -
コンピュータアーキテクチャのエッセンス 第2版 P.247 (Douglas E. Comer 著 / 吉川 邦夫 訳 / 翔泳社 / 2020 年)
↩ -
インサイド Windows 第 7 版 上 P.408 (Pavel Yosifovich, Alex Ionescu, Mark E. Russinovich, David A. Solomon 著 / 山内 和朗 訳 / 日系 BP 社 / 2018 年)
↩ -
PE フォーマット https://learn.microsoft.com/ja-jp/windows/win32/debug/pe-format
↩ -
詳解セキュリティコンテスト CTF で学ぶ脆弱性攻略の技術 (梅内 翼, 清水 祐太郎, 藤原 裕大, 前田 優人, 米内 貴志, 渡部 裕 著 / マイナビ出版 / 2021 年)
↩ -
デバッガによるx86プログラム解析入門 x64 対応版 (Digital Travesia管理人 うさぴょん 著 / 集和システム / 2018 年)
↩ -
リバースエンジニアリングツール Ghidra 実践ガイド セキュリティコンテスト入門からマルウェア解析まで (中島 将太, 小竹 泰一, 原 弘明, 川畑 公平 著 / マイナビ出版 / 2020 年)
↩ -
大熱血! アセンブラ入門 (坂井弘亮 著 / 秀和システム / 2017 年)
↩ -
デバッガによるx86プログラム解析入門 x64 対応版 P.33 (Digital Travesia管理人 うさぴょん 著 / 集和システム / 2018 年)
↩ -
x64 での呼び出し規則 https://learn.microsoft.com/ja-jp/cpp/build/x64-calling-convention?view=msvc-170
↩