セキュリティ系の勉強、その他開発メモとか雑談. GithubはUnity触っていた頃ものがメイン Twitterフォローもよろしくです

PEファイルフォーマットについて

PEファイル

Windowsの実装ファイルの形式。exe,dll等
ローダがプログラムをロードするのに必要な情報が格納されたヘッダと、プログラムコードやリソースデータが記録されているセクションで構成されている。


f:id:thinline196:20180821222142p:plain

RVA(Relative Virtual Address)相対仮想アドレス

プログラムがメモリにロードされたアドレスからの相対アドレスを表している。RVAはファイルの先頭からのオフセットとは異なる。


DOS MZヘッダ

PEファイルフォーマットの先頭にある領域で、winnt.h内ではIMAGE_DOS_HEADERとして定義されている。サイズは64バイトで、19個のメンバから構成されている。e_magicはDOS MZヘッダの先頭2バイト分でWindows実行ファイルの場合は(MZ (0x4D, 0x5A))という値がシグネチャとして設定されている。e_lfanewは最後のメンバで、PEヘッダへのオフセット値担っている。

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;

DOSスタブ

MS-DOSでプログラムを実行した時に動作する。[This program cannot be run in DOS mode.]と表示するようなコードになっている。


f:id:thinline196:20180821231936p:plain




PEヘッダ

winnt.hでIMAGE_NT_HEADERとして定義されていて、Signature, FileHeader, OptionalHeaderの3つのメンバで構成されている。SignatureはPEヘッダの先頭4バイトで、PEファイルの場合[PE(0x50, 0x45, 0x00, 0x00)]という値が設定されている。

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

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;
NumberOfSections

後述するセクションの数を示す

SizeOfOptionalHeader

後述するOptionalHeaderの数

Characteristics

ファイルの属性を表すフラグ値の論理和

フラグ 意味
IMAGE_FILE_RELOCS_STRIPPED 0x0001 再配置情報が含まれない
IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 ファイルは実行可能である
IMAGE_FILE_32BIT_MACHINE 0x0100 32ビットアーキテクチャのファイルである
IMAGE_FILE_SYSTEM 0x1000 システムファイルである(ユーザプログラムではない)
IMAGE_FILE_DLL 0x2000 ダイナミックリンクライブラリ(DLL)ファイルである

OptionalHeader

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;
AddressOfEntryPoint

プログラムの実行開始アドレス(RVA)

ImageBase

プログラムがメモリ上にロードされる時の理想的な開始アドレス。実行ファイルの場合は0x00400000, DLLファイルの場合は0x10000000になるのが一般的。ロードしようとしたアドレスが使用されていたら再配置が行われる。

SectionAlignment

メモリ上にロードされるときに各セクションが配置されるアラインメント。各セクションの開始アドレスはSectionA;ignmentの倍数になる。SectionAlignmentが0x1000, 最初のセクションの開始アドレスが0x00401000,サイズが0x20バイトだとすると、次のセクションは0x00402000から始まり、その間はNULLで埋められるらしい。

FileAlignment

ディスク上で各セクションが配置されるアラインメント。各セクションの開始アドレスはFileAlignmentの倍数になる。

SizeOfImage

メモリ上にロードされたプログラムのサイズ。ロードされる前の実行ファイルのサイズとは異なる。

DataDirectory

IMAGE_DATA_DIRECTORY構造体。各エントリがある位置のRVAとサイズで構成されている。配列の長さは16でDataDirectory[0]にはエクスポートテーブル、DataDirectory[1]にはインポートテーブルのエントリが保持されている。

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;


f:id:thinline196:20180822000237p:plain

先ほどのe_lfanewの値が0x000000E0だったので、PEヘッダの先頭はそこからになっています。Singatureは[PE]という値に。
FileHeaderのnumberOfSectionsは0x0008なのでこのプログラムには8つのセクションがあることがわかります。
Characteristicsは0x0102なので、32ビットアーキテクチャのファイルで実行可能であることがわかります。

次にOptionalHeaderに注目します。AddressOfEntryPointが0x00011055, ImageBaseが0x00400000なので、このプログラムはメモリの0x00400000からロードされ0x00411055に位置するコードから実行されることがわかります。
インポートテーブルのDataDirectory[1]はVirtualAddressが0x0001A1E4でSizeが0x64なので、ImageBaseの値より、メモリ上の0x0041A1E4から0x64分がインポートテーブルとなることがわかります。

セクションテーブル

IMAGE_SECTION_HEADERとして定義されている。コードやデータ、リソース情報など種類ごとにまとめられたセクションの領域のサイズや開始アドレス、属性を指定している、1つのセクションテーブルには1つのセクションの情報だけ格納されているので、セクションテーブル自体はNumberOfSectionsの数だけある。

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

セクションにつけられる名前。8バイト。ASCIIである必要はなく、NULL(0x00)で終わる必要もない。.textだとコードセクション、.idataだとインポートセクションというケースが多いが、Nameの値はプログラムの実行において意味がないのでそのまま判断するのは危険らしい。

VirtualSize

プログラムがメモリ上にロードされた時のセクションのサイズ。一般にSizeOfRawDataよりも小さな値となる。

VirtualAddress

プログラムがメモリ上にロードされた時のセクションの先頭アドレスのRVA。

SizeOfRawData

ディスク上におけるセクションのサイズ

PointerToRawData

ディスク上におけるセクションの先頭へのオフセット。

Characteristics

セクションの属性を表す属性の論理和。よく見られるものは以下。

フラグ 意味
IMAGE_SCN_CNT_CODE 0x00000020 実行コードを含む
IMAGE_SCN_MEM_EXECUTE 0x20000000 実行可能
IMAGE_SCN_MEM_READ 0x40000000 読み取り可能
IMAGE_SCN_MEM_WRITE 0x80000000 書き込み可能


f:id:thinline196:20180822185607p:plain

SizeOfOptionalHeaderは0xE0でOptionalHeaderの先頭は0xF8から始まっているので、0x1D8からセクションテーブルが始まっていることがわかります。
NumberOfSectionは0x08なので8つのセクションテーブルとそれに対応するセクションが存在します。
セクションテーブルはそれぞれ40バイトとなっている。.text(Name)に着目すると、このプログラムをメモリにロードした時、.textというセクションは0x00411000から始まり(VirtualAddressが0x11000でこれはRVAなので、ImageBaseの0x400000を加える)、そのサイズは0x4EF4(VirtualSize)であることがわかります。ロードする前のファイルがディスク上にある状態では0x400(PointerToRawData)から0x5000バイト(SizeOfRawData)分が.textのセクションである。


セクション

プログラムの根本をなすコードやデータ、リソース情報などが含まれている。役割に応じていくつかの種類がある。

コードセクション

実行されるコードのバイナリデータが記録されている領域。

データセクション

文字列や定数など、プログラム中で使われるデータが記録されている領域。

リソースセクション

プログラムで使用されるリソースに関連する情報が記録されている領域

エクスポートセクション

DLLなどのエクスポート関数の情報が記録されている領域

インポートセクション

外部DLLなどから関数をインポートしてくる場合、インポートしている関数に関する情報が記録されている領域。

TLS(Thread Local Storage)セクション

スレッド固有のデータ領域に関する情報が記録されている領域。

インポートセクション

外部DLLからインポートする関数の情報が含まれている。インポートテーブルにはインポートしている関数の情報があり、IMAGE_IMPORT_DESCRIPTOR構造体の配列になっている。インポートテーブルの長さは決まっていないため、終端は全て0で埋められている。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;               // 0 if not bound,
    DWORD   ForwarderChain;               // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                      // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThunk

インポートルックアップテーブル(Import Lookup Table:ILT)のRVA. ILTはIMAGE_THUNK_DATA構造体の配列。4バイトの共用体となっており、ILTではインポートする関数のヒント(Hint)と関数名の構造体であるIMAGE_IMPORT_BY_NAME構造体へのポインタ(AddressOfData)として使われる。

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        DWORD ForwarderString;      // PBYTE 
        DWORD Function;             // PDWORD
        DWORD Ordinal;
        DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA32;
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Name

インポートしているDLL名のRVA

FirstThunk

インポートアドレステーブル(Import Address Table:IAT)のRVA. IATはプログラムがロードされる前後で書き換えられる。ロードされる前はIMAGE_THUNK_DATA構造体の配列になっていて、ILTと同一の値が格納されている。ロード後は、実際にインポートする各関数のアドレスに書き換えられる。


dumpbin

Visual Studioに付属しているPEフォーマットを表示してくれるもの。今までのようなことをする必要がなくなりそう。
Vitual Studioコマンドプロンプトを開いて以下のコマンドを入力

>dumpbin.exe /HEADERS hoge.exe


f:id:thinline196:20180822221332p:plain




参照元