はじめに
前回で解説したPEヘッダに引き続き、今回もEXEファイル内のデータフォーマットの一部分「セクション」に関する話題で突き進みます。
それにしても、EXEファイルは調べ手に休ませる余地を与えません。PEヘッダという大きな構造体をクリアしたと思ったら、お次は複数個も存在するセクションが登場するのです。PEヘッダは大きくても1つしかなかったのでまだ良いのですが、セクションだけは更に細分化されたデータ構造を指定するため、少々厄介です。更に、セクションにはデータサイズ・RVA(イメージベースからの相対オフセット)など、生データに関するより詳細な情報が保持されます。
経験豊富な方はこのような文章を読んでお気づきかもしれませんが、薄い資料でセクションに関するリサーチを行うと、メモリダンプと睨めっこという状況に陥ります。筆者は数週間の間、セクション周りのメモリダンプと暮らしたことがあるので間違いありません!(こんなことを自慢しても、何の役にも立ちませんが…)
セクションとは
PEヘッダの後にネイティブコード、リソース情報、デバッグ情報など、必要に応じてさまざまなデータが配置されています。それらのデータは、種類ごとにセクションという区切りで格納されています。
各セクションのファイル内の保存位置、またはメモリにロードされたときの位置はアライメントに従わなければならない点も注意しておきましょう。例えば、IMAGE_OPTIONAL_HEADER
32構造体のFileAlignment
メンバに0x1000が設定されているときは、各セクションのファイルの保存位置は0x1000で割り切れる数になります。
セクションヘッダ(IMAGE_SECTION_HEADER構造体)
PEヘッダなどの後に続いて、ネイティブコード、リソース情報などの各セクションが配置される形になりますが、その前にセクションヘッダと呼ばれる、各セクションの「特徴」を示すための領域が存在します。
ここで言う特徴とは、対象となるセクションがどのようなサイズで格納されているのか、読み込み専用であるのか、または書き込みもできるのか、実行可能な領域であるのかなどのことを指し、これらの情報はIMAGE_SECTION_HEADER
構造体で指定できます。
IMAGE_SECTION_HEADER
構造体は、PEヘッダの直後にファイルアライメントを考慮しながら配置されます。また、IMAGE_SECTION_HEADER
構造体はセクションの数だけ数珠つながりのように存在します。例えば、セクションが3つ存在する場合は、
- 1つ目の
IMAGE_SECTION_HEADER
構造体 - 2つ目の
IMAGE_SECTION_HEADER
構造体 - 3つ目の
IMAGE_SECTION_HEADER
構造体 - 1つ目のセクションデータ
- 2つ目のセクションデータ
- 3つ目のセクションデータ
という順序でEXEファイル内に格納されます。
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
- Name
- VirtualSize
- VirtualAddress, SizeOfRawData, PointerToRawData
- PointerToRelocations
- PointerToLinenumbers
- NumberOfRelocations, NumberOfLinenumbers
- Characteristics
Name
にはセクションの名前を指定します。一般的には次に挙げる名前が多く使われています。実際には第2回で解説したデータディクショナリからの指定アドレスでセクションの役割が決定するので、この名前は単なる参考値になります。名前 | 役割 |
.text | ネイティブコード |
.data | グローバル変数用の領域、初期値 |
.idata | インポート関数情報 |
.edata | エクスポート関数情報 |
.rsrc | リソース情報 |
.debug | デバッグ情報 |
.reloc | 再配置情報 |
.tls | スレッドごとの静的変数に関する情報 |
VirtualSize
はメモリ上におけるセクションのサイズです。SizeOfRawData
メンバの値よりも大きいときは、差分領域が0で埋め尽くされます。VirtualAddress
はメモリ上におけるセクションの位置であり、SizeOfRawData
はファイル上におけるセクションのサイズです。続く、PointerToRawData
はファイル上におけるセクションの位置です。PointerToRelocations
は、再配置エントリ情報が格納されているファイル上の位置です。一般的なEXEファイルの場合は0です。PointerToLinenumbers
は、行番号エントリ情報が格納されているファイル上の位置です。COFF行番号が存在しないときは0になります。NumberOfRelocations
は再配置エントリの数です。一般的なEXEファイルの場合は0です。NumberOfLinenumbers
は行番号エントリの数です。COFF行番号が存在しないときは0です。Characteristics
にはセクションの特性を指定します。次のフラグを組み合わせることで表現できます。名前 | 値 | 説明 |
IMAGE_SCN_CNT_CODE | 0x00000020 | セクションに実行コードが含まれています。 |
IMAGE_SCN_CNT_INITIALIZED_DATA | 0x00000040 | セクションに初期化されたデータが含まれています。 |
IMAGE_SCN_CNT_UNINITIALIZED_DATA | 0x00000080 | セクションに初期化されていないデータが含まれています。 |
IMAGE_SCN_LNK_OVFL | 0x01000000 | セクションに拡張された再配置が含まれています。 |
IMAGE_SCN_MEM_DISCARDABLE | 0x02000000 | セクションは必要があれば破棄できます。 |
IMAGE_SCN_MEM_NOT_CACHED | 0x04000000 | セクションはキャッシュできません。 |
IMAGE_SCN_MEM_NOT_PAGED | 0x08000000 | セクションはページングできません。 |
IMAGE_SCN_MEM_SHARED | 0x10000000 | セクションをメモリ中で共有できます。 |
IMAGE_SCN_MEM_EXECUTE | 0x20000000 | セクションをコードとして実行できます。 |
IMAGE_SCN_MEM_READ | 0x40000000 | セクションを読むことができます。 |
IMAGE_SCN_MEM_WRITE | 0x80000000 | セクションに書き込めます。 |