PEファイルについて

参考書籍 : リバースエンジニアリングバイブル、デバッガによるx86プログラム解析入門【x64対応版】


・PEファイルとは
EXEやDLLなど、Windows用の実行可能形式ファイルで採用されるファイルフォーマット
Portable Executable File Fomatの略であり、違う場所に移動しても(Portable)実行可能(Executable)になるようにするファイルフォーマットという意味



・PEファイルの生成処理
Windows上でビルドをするときの処理を見ていく。
f:id:kamishiroHW:20200727151645j:plain
ソースコードをビルドすると、そのソースコードに関連するすべてのヘッダーファイルとソースファイルをまとめてコンパイルし、objファイルを作成する。
続けてリンクを行い、リンカーが様々なリソースデータ、インポート/エクスポートテーブルを処理するための情報をファイルの決まった場所に書き出しておく。このときにリンクを行うだけでなく、使用する動的ライブラリなどの情報をPEヘッダーに書き出す。



・PEファイルの構造
PEファイルの構造は、おおまかに下の図のようになる。
f:id:kamishiroHW:20200727153215j:plain
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属性を追加する必要がある。