PEファイルについて
参考書籍 : リバースエンジニアリングバイブル、デバッガによるx86プログラム解析入門【x64対応版】
・PEファイルとは
EXEやDLLなど、Windows用の実行可能形式ファイルで採用されるファイルフォーマット
Portable Executable File Fomatの略であり、違う場所に移動しても(Portable)実行可能(Executable)になるようにするファイルフォーマットという意味
・PEファイルの生成処理
Windows上でビルドをするときの処理を見ていく。
ソースコードをビルドすると、そのソースコードに関連するすべてのヘッダーファイルとソースファイルをまとめてコンパイルし、objファイルを作成する。
続けてリンクを行い、リンカーが様々なリソースデータ、インポート/エクスポートテーブルを処理するための情報をファイルの決まった場所に書き出しておく。このときにリンクを行うだけでなく、使用する動的ライブラリなどの情報をPEヘッダーに書き出す。
・PEファイルの構造
PEファイルの構造は、おおまかに下の図のようになる。
PEファイルヘッダを詳細に見ていく。
PEファイルヘッダには、主に5つの構造体があり、
IMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_FILE_HEADER,
IMAGE_OPTIONAL_HEADER, IMAGE_SECTION_HEADER の5つがある。
1, IMAGE_DOS_HEADER
winnt.hの中に定義されている。
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
この中で特に重要なものは、e_magicとe_lfanewの2つである。
e_magic - マジックナンバーMZ(0x5A4D)を格納する。MZならPEファイルであると示す。
e_lfanew - IMAGE_NT_HEADER構造体の位置を示す。次のようなコードで使用する。
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpFIleBase; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
2, IMAGE_NT_HEADER
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
SignatureはPE\0\0(50 40 00 00)を格納するだけの役割である。
過去に、フィールドSignatureがチェックされず、マルウェア作成者が任意のデータを格納するフィールドとして使われた事があったため、現在はPE\0\0(50 40 00 00)以外の値が入っていると実行されないようになっている。
FileHeaderは次に説明するIMAGE_FILE_HEADERが格納される。
OptionalHeaderは4番目に説明するIMAGE_OPTIONAL_HEADERが格納される。
3, IMAGE_FILE_HEADER
ファイルを実行するための最も基本的なデータが含まれている。
typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine - このファイルがどのCPUで実行できるのかを示す。
NumberOfSections - セクションの数を示す。基本的には、.text, .rdata, .data, .rsrcの4つのセクションになる。パッキングやプロテクティングなどでセクションが増える場合もある。
TimeDateStamp - exe/dllが作成された日付が表示される。リバーシング時にこのフィールドを参照し、作成した時刻を予想することもできるが、このフィールドは書き換え可能なので完全に信頼はできない。
SizeOfOptionalHeader - IMAGE_OPTIONAL_HEADER32構造体のサイズ
Characteristics - 現在のファイルがどのような形式かを示す。dllかexeかを区別することができる。実際には、この目印以外にもdllかexeかを区別する方法がある。
4, IMAGE_OPTIONAL_HEADER
IMAGE_NT_HEADERに含まれるもう一つのヘッダー
typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // 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_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic - 32ビットの場合は0x10Bが入り、64ビットの場合は0x20Bが入る
SizeOfCode - コード全体のサイズを示す。マルウェアは、このフィールドを参照して自分のコードを複製する場所の基準にする。セキュリティソリューションでは、このフィールドの値を確認してチェック対象のサイズを割り出すことがある。
MajorLinkerVersion, MinorLinkerVersion - どのバージョンのコンパイラでビルドしたかを示す。
ImageBase - ファイルが実行されるときに実際の仮想メモリにロードされるアドレスを示す。exeファイルで特別なオプションがなければ、このアドレスは0x400000になる。デバッガで0x40xxxxといったアドレスにアクセスすることがあるのは、アドレスがImageBaseを基準に計算されているためである。
AddressOfEntryPoint - 実行ファイルがメモリ上で実行を開始するアドレス。デバッガを使用してファイルを実行すると、AddressOfEntryPointのアドレスとImageBaseを足した位置を実行開始位置に指定して停止する。
BaseOfCode - 実際のコードが実行されるアドレス。ImageBaseはファイル全体の開始アドレスであり、コード領域が開始されるアドレスはそれにBaseOfCodeを足したアドレスになる。特別なことがなければ、0x1000が指定される。
SectionAlignment, FileAlignment - 各セクションを整列するための基準となる単位、通常は0x1000が指定される。例えば、.textセクションが0x800バイトならば、800バイトの直後に次の.rdataセクションが始まるのではなく、800バイトの後の200バイトを0で埋め、0x1000をすべて満たしたあとに.rdataセクションが始まる。FileAlignmentはファイル上の間隔、SectionAlignmentはメモリにロードされたときの間隔と考える。
SizeOfImage - メモリにロードされたときの全体のサイズ。
SizeOfHeaders - PEヘッダーのサイズを示す。このフィールドの値が0x1000なら、メモリにロードされたときのアドレス計算は、ImageBaseをそのまま足すだけで良いので楽になるが、0x400等の場合もある。その場合は都度計算する必要がある。
Subsystem - このプログラムがGUI用かコンソール用かを示す。0x1ならドライバーモジュール、0x2ならGUI、0x3ならコンソールアプリケーション
DataDirectory - IMAGE_DATA_DIRECTORY構造体の配列、要素数(IMAGE_NUMBEROF_DIRECTORY_ENTRIES)は16であり、0~14までが示すものは以下の様に定義されている。
// Directory Entries #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
インデックス0にエクスポートディレクトリなど、各ディレクトリが一つの配列に纏められている。
IMAGE_DATA_DIRECTORY構造体には、2つのフィールドが存在する。
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
VirtualAddressに各ディレクトリの仮想アドレス、Sizeにサイズが格納される。
5, IMAGE_SECTION_HEADER
各セクションの名前、開始アドレスとサイズなどが格納されている。
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;
IMAGE_SECTION_HEADERは、セクションの数だけ存在する。
Characteristics - セクションの属性がフラグで指定されている。APIフッキングやコードフッキングをするには、コードセクションにVirtualProtect()というAPIを利用しMEM_WRITE属性を追加する必要がある。