リバースエンジニアリングへの道 - その21

# リバースエンジニアリングへの道
出田 守です。
最近、情報セキュリティに興味を持ち、『リバースエンジニアリング-Pythonによるバイナリ解析技法』という本(以降、「教科書」と呼びます)を読みました。
「こんな世界があるのか！かっこいい！」と感動し、私も触れてみたいということでド素人からリバースエンジニアリングができるまでを書いていきたいと思います。
ちなみに、教科書ではPython言語が使用されているので私もPython言語を使用しています。
ここを見ていただいた諸先輩方からの意見をお待ちしております。

## 軌跡
[リバースエンジニアリングへの道 - その20](https://qiita.com/deta-mamoru/items/057436c3a0766b7158ba)

## 環境
OS: Windows10 64bit Home (日本語)
CPU: Intel® Core™ i3-6006U CPU @ 2.00GHz × 1
メモリ: 2048MB
Python: 3.6.5

私の環境は、普段Ubuntu16.04を使っていますが、ここではWindows10 64bitを仮想マシン上で立ち上げております。
ちなみに教科書では、Windowsの32bitで紹介されています。

## さらっとPEフォーマット
[前回](https://qiita.com/deta-mamoru/items/057436c3a0766b7158ba)はさらっとELFを学びました。ということはPEフォーマットについてもさらっと学ぼうということになります(私の本能がそう言っています笑)。なので今回はPEフォーマットをさらっと学びます。

では[公式ドキュメント?](https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format)とヘッダファイル、他のサイトページなどを参考にして進めていきます。

### PEフォーマットとは
PE(Portable Executable)フォーマットは、現在Windowsで主流の実行(イメージ)ファイルフォーマットです。Windowsでは、実行ファイルをイメージファイルということが多いようです。
ちなみにドキュメントには他にもCOFF(Common Object File Format)という記載がありました。これについて調べてみました。ざっくりですがつまり、COFFが昔Unixで流行ってたけど色々問題があって、今では一部の派生バージョンとPEフォーマットが普及しているという状況らしいです。ということはPEフォーマットの中にCOFFが受け継がれているというイメージでしょうか。それともCOFFはオブジェクトファイルの事でしょうか。どちらにしても調べていくうちに分かってくるでしょう。

実際のPEフォーマットを確認するにはWindows SDKのwinnt.hを参照すると良いようです。私もこのヘッダファイルを参照していきます。

### 用語
ドキュメントでは最初用語解説をしているので、その一部を示します。

| Name                         | Description                                                                                                                                                                                                                                                                                                                                                                                                                             |
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| date/time stamp              | スタンプはPEファイルあるいはCOFFファイルの様々な場所でそれぞれ違った用途で使用されます。多くは、各スタンプのフォーマットはCランタイムライブラリのtime関数と同じです。                                                                                                                                                                                                                                                                                       |
| file pointer                 | (オブジェクトファイルの場合)リンカと(イメージファイルの場合)ローダのよる処理の前の、ファイル自体のアイテムの位置。それ以外は、ディスクのファイルの位置を指します。                                                                                                                                                                                                                                                                      |
| linker                       | Microsoft Visual Studioが提供するリンカを指します。                                                                                                                                                                                                                                                                                                                                                                                     |
| object file                  | リンカに入力として与えるファイルを指します。リンカはローダに入力するために使われるイメージファイルを生成します。                                                                                                                                                                                                                                                                                                                        |
| RVA(Relative virtual address | 相対仮想アドレスを指します。イメージファイルでは、アイテムのアドレスはイメージファイルのベースアドレスからのオフセットです。アイテムのRVAはほとんどの場合常にファイルポインタの位置がとは異なります。オブジェクトファイルではメモリに割り当てられないため意味がありません。この場合、RVAはリンク中に再配置が後で適用されるセクション内のアドレスを指します。簡単にするためには、コンパイラは各セクションのRVAをゼロに設定するだけです。 |
| section                      | PEファイルあるいはCOFFファイル内の基本コードあるいはデータを指します。例えば、オブジェクトファイルのすべてのコードは単一のセクションにまとめることができ、あるいは(コンパイラの動作に応じて)各関数は独自のセクションを占有することができます。                                                                                                                                                                                                              |
| VA                           | 仮想アドレスを指します。イメージファイルのベースアドレスからのオフセットではないということ以外はRVAと同じです。このアドレスは物理メモリとは独立した各プロセスに明確なVAスペースをWindowsが作成しているためVAと呼ばれます。ほとんどの場合で、VAをアドレスとみなすべきです。VAはローダが希望の場所にイメージをロードしない可能性があるため、RVAほど位置を予測できません。                                                                 |

### PEフォーマット
ここからはPEフォーマットの中身を見ていきます。
ただ、めちゃくちゃヘッダとメンバの数が多そうなんですよ。これを一個一個調べていくのは面倒ですし、必要ないと思うのです。
なので必要そうなのを選択して調べていきます。それ以外のものは今後必要になったら学んでいくことにします。

めちゃめちゃ参考になりましたサイトを載せておきます。
http://www.atmarkit.co.jp/ait/articles/1202/17/news129.html
http://www.openrce.org/reference_library/files/reference/PE%20Format.pdf
https://qiita.com/cha1aza/items/f64dc4351517a2477ef1#ms-dos-headerms-dos-real-mode-stub-progrum
http://home.a00.itscom.net/hatada/mcc/doc/pe.html
http://hp.vector.co.jp/authors/VA050396/index.html
http://hp.vector.co.jp/authors/VA050396/tech_11.html

上記PDFも全体像が見られて便利ですね。もしかしたら古いかもしれませんが。

#### IMAGE_DOS_HEADER

```c: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  
  マジックナンバー。0x5A4D(MZ)という値が格納される。
- e_lfanew  
  参考サイトより
  > 新しい形式のヘッダへのファイル内オフセットを示している。新しい形式のヘッダとはPE ファイルフォーマットなら NT ヘッダのことであり、e_lfanew は NT ヘッダへのオフセットということになる。そして、そのNTヘッダこそがPEファイルの真のヘッダということになる。MS-DOS Real-Mode Stub Programのサイズは可変長なので、このオフセットの値から新しい形式のヘッダをたどらなければいけない。
  
#### NT_HEADERS

```c:winnt.h
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32
```

- Signature  
  4Byteのシグネチャで、"PE\0\0"の値を持ちます。
- FileHeader  
  IMAGE_FILE_HEADER構造体を指します。
- OptionalHeader  
  IMAGE_OPTIONAL_HEADER構造体を指します。

#### IMAGE_OPTIONAL_HEADER

```c:winnt.h
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  
  イメージファイルの状態を表します。各状態値を以下に示します。
  - IMAGE_NT_OPTIONAL_HDR_MAGIC32(0x10b)  
	32bitの実行可能イメージファイル。
  - IMAGE_NT_OPTIONAL_HDR_MAGIC64(0x20b)  
	64bitの実行可能イメージファイル。
  - IMAGE_ROM_OPTIONAL_HDR_MAGIC(0x107)  
    ROMイメージファイル。
- AddressOfEntryPoint  
  ImageBaseアドレスを基準にしたエントリポイントのアドレス。  
  実行ファイルでは、これは開始アドレスを指します。デバイスドライバでは、これは初期化関数のアドレスを指します。DLLではエントリポイントはオプションです。これが必要ない時は0を指定します。  
  参考サイトによると実際のエントリポイントのアドレスはImageBaseにAddressOfEntryPointを加算することで得られるとのことです。  
- ImageBase  
  メモリにロードされた時のイメージの最初のByteの希望するアドレス。つまりイメージファイルがメモリにロードされたアドレスです。この値は64KByteの倍数です。  
  デフォルトアドレスはDLLでは0x10000000に、アプリケーション(実行ファイル)では0x00400000に、Windows CE(組込み向けのOS)では0x00010000に配置されます。  
  イメージファイルのロードがImageBaseのアドレスで失敗した場合、再配置情報を持っていればそれを用いて別のアドレスにロードしてくれます。
- SectionAlignment  
  セクションがメモリにロードされる時の境界を指します。この値はFileAlignment値以上でなければなりません。デフォルト値はシステムのページサイズです。  
  参考サイトより  
  > 例えば、セクションのサイズそのものが10byteでも、この値が0x1000だと次のセクションは0x1000に配置される。また、サイズが0x1010の場合には次のセクションは0x2000となる。
- FileAlignment  
  イメージファイルのセクションデータの境界を指します。値は、512〜64Kの間の2の累乗でなければなりません。デフォルトは512Byteです。  
  もしSectionAlignmentがシステムのページサイズより少なければ、FileAlignmentの値もSectionAlignmentに合わせる必要があります。  
- SizeOfImage  
  すべてのヘッダを含むイメージのByteサイズ。SectionAlignmentの倍数である必要があります。
  参考サイトより  
  > まず最初にセクションが必要とするバイトを求め、それからページ境界に揃え、最終的にSectionAlignment境界に揃えたサイズの合計から算出される。
- SizeOfHeaders  
  FileAlignmentで指定された値の倍数に丸められた次のアイテムの合計サイズです。
  - IMAGE_DOS_HEADERのe_lfanew
  - 4Byte signature
  - IMAGE_FILE_HEADERのサイズ
  - optional headerのサイズ
  - すべてのセクションヘッダのサイズ
- CheckSum  
  イメージファイルのチェックサムです。IMAGEHELP.DLLに計算アルゴリズムがあるようです。
- SizeOfStackReserve  
  スタックとして予約されたByteサイズ。
- SizeOfStackCommit  
  スタックとしてコミットするByteサイズ。
- SizeOfHeapReserve  
  ヒープとして予約されたByteサイズ。
- SizeOfHeapCommit  
  ヒープとしてコミットするByteサイズ。
- DataDirectory  
  IMAGE_DATA_DIRECTORY構造体の最初のポインタ。

#### IMAGE_SECTION_HEADER
IMAGE_OPTIONAL_HEADERの直後に、IMAGE_SECTION_HEADERが続くようです。

```c:winnt.h
#define IMAGE_SIZEOF_SHORT_NAME              8

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;

#define IMAGE_SIZEOF_SECTION_HEADER          40
```

- Name  
  8ByteのnullパディングUTF-8文字列のセクション名。
- Misc.PhysicalAddress  
  物理アドレス。
- Misc.VirtualSize  
  メモリへロードされた時のセクションの合計Byteサイズ。SizeOfRawDataよりもこの値が大きければ、セクションはゼロで埋められます。  
  このフィールドは実行可能イメージでのみ有効で、オブジェクトファイルでは0がセットされます。
- VirtualAddress  
  メモリへロードされた時のセクションの最初のByteのImageBaseからの相対アドレス。オブジェクトファイルでは、再配置が適用される前の最初のByteのアドレスを指します。
- SizeOfRawData  
  ディスクの初期化されたデータのByteサイズ。これはIMAGE_OPTIONAL_HEADERのFileAlignmentの倍数の値である必要があります。  
  この値がMisc.VirtualSizeよりも小さい場合、セクションの残りの部分はゼロで埋められます。
- PointerToRawData  
  COFFファイル内の最初のページのファイルポインタ。これはIMAGE_OPTIONAL_HEADERのFileAlignmentの倍数の値である必要があります。  
  未初期化データだけがセクションに格納されている場合、値にはゼロがセットされます。
- PointerToRelocations  
  セクションの再配置エントリの始点ファイルポインタ。再配置情報がない場合、値にはゼロがセットされます。
- PointerToLinenumbers  
  セクションの行番号エントリの始点ファイルポインタ。COFF行番号がない場合、値にはゼロがセットされます。
- NumberOfRelocations  
  セクションの再配置エントリの数。実行可能ファイルではこの値はゼロです。
- NumberOfLinenumbers  
  セクションの行番号エントリの数。
- Characteristics  
  参考サイトより  
  > セクションの特性を表すフラグの組み合わせ。各フラグはIMAGE_SCN_XXXXとしてWinNT.hに定義されています。たとえば、コードが配置されるセクションなら、コードを含むIMAGE_SCN_CNT_CODEフラグ、読取専用を示すIMAGE_SCN_MEM_READフラグ、実行可能であることを示すIMAGE_SCN_MEM_EXECUTEフラグがセットされます。


#### IMAGE_DATA_DIRECTORY
IMAGE_OPTIONAL_HEADERのDataDirectoryのインデックスでIMAGE_DATA_DIRECTORYの意味が変わるようです。

```c:winnt.h
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16
```

- VirtualAddress  
  テーブルの相対仮想アドレス。
- Size  
  テーブルのByteサイズ

以下にオフセットと意味を載せておきます。

[https://docs.microsoft.com/ja-jp/windows/desktop/api/winnt/ns-winnt-_image_data_directory より引用]

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254464/a7dea86d-8541-5a22-b62a-ded73eb5fa24.png)

PEフォーマットについてはまだまだ深いことを学ばなければいけません。
今後も色々やっていくうちにPEフォーマット参照することも多くなってくると思うので、その都度学んでいきたいと思います。
今回はここまでにします。

## まとめ
- PEフォーマットはWindowsで現在主流の実行ファイルフォーマット
- PEフォーマットではRVAの考え方が重要になってきそう
- IMAGE_OPTIONAL_HEADERのDataDirectoryのインデックスでIMAGE_DATA_DIRECTORYの意味が変わる