鷲ノ巣

C# とか PowerShell とか。当ブログの記事は(特に公開直後は)頻繁に改定される場合があることをご了承ください。

PE ファイルについて (3) - IMAGE_OPTIONAL_HEADER

第 3 回。
今回は IMAGE_OPTIONAL_HEADER をやっつけます。

IMAGE_OPTIONAL_HEADER

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

長い!
気を取り直してメンバー逐次解説行きます。

Magic

このファイルが 32bit 用か 64bit 用かを表します。

IMAGE_NT_OPTIONAL_HDR32_MAGIC (0x10b)
32bit 用
IMAGE_NT_OPTIONAL_HDR64_MAGIC (0x20b)
64bit 用

dumpbin で見ると、32bit 用の場合は "PE32"、64bit 用の場合は "PE32+" と表示されます。

MajorLinkerVersion

MinorLinkerVersion

このファイルを作成したリンカーのバージョンです。
VC++ 2015 の場合、14.0 とかになります。

SizeOfCode

実行可能なコード セクションのサイズです。
コード セクションが複数ある場合は合計のサイズになります。

SizeOfInitializedData

初期化済みデータ セクションのサイズです。
初期化済みデータ セクションが複数ある場合は合計のサイズになります。

SizeOfUninitializedData

未初期化データ セクションのサイズです。
未初期化データ セクションが複数ある場合は合計のサイズになります。

コード セクションや初期化済みデータ セクションは PE ファイル内にデータがありますが、未初期化データ セクションはファイルに含まれません。
この値は、PE ファイルがロードされるときにメモリ上に確保されるべきサイズ(の合計)を表します。

AddressOfEntryPoint

実行を開始するエントリーポイントのアドレスです。
VC++ ではリンカーオプション /ENTRY で制御できます。
また、/NOENTRY オプションをつけるとこの値は 0 になります。

/ENTRY を指定した場合はその関数のアドレスになりますが、指定しない場合は、通常、main 関数のアドレスにはなりません。
main 関数を実行する前にランタイム ライブラリの初期化などをするコードが埋め込まれるので、エントリーポイントのアドレスは、その初期化ルーチンの開始位置を指します。
逆に言えば、/ENTRY:main などと指定してはいけません。
/ENTRY オプションを指定する場合は、ランタイム ライブラリの初期化などを自分でやる必要があります。

BaseOfCode

実行可能なコードセクションの開始位置を表します。

BaseOfData

初期化済みデータセクションの開始位置を表します。

ImageBase

このファイルがロードされるべき望ましいメモリ上のアドレスを表します。
リンカーオプション /BASE で指定できます。
リンカーオプション /FIXED を指定している場合、このアドレスにロードできないと実行に失敗します。

SectionAlignment

この PE ファイルがメモリ上にロードされた時、各セクションは、SectionAlignment の値の倍数になるアドレスに配置されます。
リンカーオプション /ALIGN で変更できます。

FileAlignment

この PE ファイル中で、各セクションは FileAlignment の値の倍数になるアドレスに配置されています。

現在、単に PE ファイルをメモリマップドファイルとして見ているだけなので、各セクションは FileAlignment に従って配置されています。
これが、EXE ファイルを実行したり、LoadLibrary 関数などを使ってメモリ上にロードされると、各セクションは SectionAlignment の値に従って配置されます。
実行のためにロードする時も、メモリマップドファイルとして読み込まれるのですが、単にバイナリファイルとして読み込む場合と、実行のためにロードする場合では、内容が変化するのも PE ファイルの特徴です。

MajorOperatingSystemVersion

MinorOperatingSystemVersion

この PE ファイルを実行するのに必要となる OS のバージョンです。
が、どうやらこの値は使われていないようです。

MajorImageVersion

MinorImageVersion

この PE ファイルのバージョンです。
リンカーオプション /VERSION で設定することができます。

MajorSubsystemVersion

MinorSubsystemVersion

この PE ファイルを実行するのに必要となるサブシステムのバージョンです。
リンカーオプション /SUBSYSTEM で設定できます。
この値が、Windows の現在のバージョン(Windows 10 であれば 10.0)を上回る場合、この PE ファイルを実行することはできません。

Win32VersionValue

使用されていません。常に 0 です。

SizeOfImage

この PE ファイルをロードしたときにメモリ上に占めるサイズです。
SectionAlignment の値の倍数に丸められます。
ファイルサイズとは異なります。

SizeOfHeaders

この PE ファイル中の全てのヘッダーサイズの合計です。
FileAlignment の値の倍数に丸められます。

CheckSum

この PE ファイルのチェックサムです。
リンカーオプション /RELEASE を指定すると書き込まれます。
また、PE ファイルを書き換えて、チェックサムを再計算する必要がある場合は、MapFileAndCheckSum 関数や CheckSumMappedFile 関数を利用できます。

Subsystem

この PE ファイルが動作するサブシステムです。
リンカーオプション /SUBSYSTEM で設定できます。

以下のような値が定義されています。

IMAGE_SUBSYSTEM_UNKNOWN (0)
不明
IMAGE_SUBSYSTEM_NATIVE (1)
サブシステム無し
IMAGE_SUBSYSTEM_WINDOWS_GUI (2)
Windows GUI アプリケーション
IMAGE_SUBSYSTEM_WINDOWS_CUI (3)
Windows CUI(コンソール)アプリケーション
IMAGE_SUBSYSTEM_OS2_CUI (5)
OS/2
IMAGE_SUBSYSTEM_POSIX_CUI (7)
POSIX
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI (9)
Windows CE
IMAGE_SUBSYSTEM_EFI_APPLICATION (10)
EFI アプリケーション
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER (11)
EFI ブート サービス ドライバー
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER (12)
EFI ランタイム ドライバー
IMAGE_SUBSYSTEM_EFI_ROM (13)
EFI ROM
IMAGE_SUBSYSTEM_XBOX (14)
XBox
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION (16)
Windows ブート アプリケーション
IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG (17)
XBox コード カタログ?

DllCharacteristics

いろいろなフラグです。

IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA (0x0020)
64bit のアドレス空間で ASLR が有効であることを意味します。
リンカーオプション /HIGHENTROPYVA で有効にできます。
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE (0x0040)
ASLR が有効であることを意味します。
リンカーオプション /DYNAMICBASE で有効にできます。
ASLR (Address Space Load Randomization) とは、PE ファイルを毎回ランダムなアドレスにロードすることで、攻撃をしにくくする技術です。Windows Vista から導入されています。
ASLR が有効な場合、ImageBase は無視されます。
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY (0x0080)
コードのデジタル署名の検査が強制されることを意味します。
リンカーオプション /INTEGRITYCHECK で有効にできます。
IMAGE_DLLCHARACTERISTICS_NX_COMPAT (0x0100)
データ実行防止DEP)が有効であることを意味します。
リンカーオプション /NXCOMPAT で制御できます。
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION (0x0200)
このファイルがアプリケーション マニフェストを持たないことを意味します。
リンカーオプション /ALLOWISOLATION で制御できます。
IMAGE_DLLCHARACTERISTICS_NO_SEH (0x0400)
この PE ファイルが構造化例外ハンドラーを持たないことを意味します。
IMAGE_DLLCHARACTERISTICS_NO_BIND (0x0800)
この PE ファイルがバインドできないことを意味します。
リンカーオプション /ALLOWBIND で制御できます。
バインドについてはそのうち取り上げます。
IMAGE_DLLCHARACTERISTICS_APPCONTAINER (0x1000)
この PE ファイルが AppContainer 上で実行される必要があることを意味します。
Windows ストアアプリでは、このフラグが有効になっています。
リンカーオプション /APPCONTAINER で制御できます。
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER (0x2000)
このファイルが WDM ドライバーであることを意味します。
リンカーオプション /DRIVER:WDM で有効にできます。
IMAGE_DLLCHARACTERISTICS_GUARD_CF (0x4000)
制御フロー ガードが有効であることを意味します。
リンカー オプション /GUARD で有効にできます。
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE (0x8000)
アプリケーションがターミナル サーバーとの互換性があることを意味します。
リンカーオプション /TSAWARE で制御できます。
ターミナル サーバーとは、要するにリモート デスクトップのことですが、一台のマシンに複数ユーザーの同時ログオンを許可する「ユーザーの切り替え」にも、ターミナル サーバーの技術が利用されています。
このフラグがオフの場合、ターミナル サーバーとの互換性がないこと、つまり、一台のマシンに複数ユーザーが同時にログオンする環境を考慮していないことを意味します。そのため、このようなアプリケーションが動作する際には、ある種の仮想化が行われるようです。

SizeOfStackReserve

この PE ファイルがロードされるときにスタック領域として予約されるサイズです。
リンカーオプション /STACK で指定できます。

SizeOfStackCommit

この PE ファイルがロードされるときにスタック領域として確保されるサイズです。
リンカーオプション /STACK で指定できます。

SizeOfHeapReserve

この PE ファイルがロードされるときにヒープ領域として予約されるサイズです。
リンカーオプション /HEAP で指定できます。

SizeOfHeapCommit

この PE ファイルがロードされるときにヒープ領域として確保されるサイズです。
リンカーオプション /HEAP で指定できます。

LoaderFlags

使用されていません。

NumberOfRvaAndSizes

データ ディレクトリの数です。
通常は 16 です。

DataDirectory

データ ディレクトリです。
IMAGE_DATA_DIRECTORY 構造体の配列になっており、要素数は NumberOfRvaAndSizes で示されます。
データ ディレクトリについては後で詳細に取り上げます。

おわり

アドベント カレンダーの方は埋まったようで何よりです。
この連載はもうちょっと続きます。