ByteScope

PE(Windows 実行ファイル)ファイル形式

Portable Executable — Windows の .exe/.dll バイナリ形式。

MIME タイプ
application/vnd.microsoft.portable-executable
拡張子
.exe, .dll, .sys
カテゴリ
実行ファイル・バイトコード

マジックナンバー(ファイル署名)

0x000000004D5AMZDOS 'MZ' stub

オフセット 0x3C の 4 バイトのオフセット値が 'PE\0\0'(50 45 00 00)署名を指します。

PE(Windows 実行ファイル)形式について

PE は Windows の実行ファイル・DLL・ドライバーの形式で、今も 1985 年の衣装を着たままです。どの PE ファイルも先頭に、メッセージを表示して終了するだけの動く MS-DOS プログラムを持っています。Windows のバイナリを DOS で実行したときにクラッシュではなく 1 行の文章が出るようにするためでした。本当のヘッダにはオフセット 0x3C に置かれたポインタからたどり着きます。その先にあるのが 4 バイトの 'PE\0\0'、マシンとセクション数を示す COFF ファイルヘッダ、そしてイメージにとってはまったく optional ではない optional header で、エントリポイント・希望するロードアドレス・データディレクトリを持っています。

ヘッダーの構造(バイト単位)

オフセット長さフィールド意味
02e_magic ('MZ')4D 5A、初期の MS-DOS 設計者の 1 人 Mark Zbikowski のイニシャルです。PE イメージも DOS の実行ファイルも .NET アセンブリも、すべてこの 2 バイトで始まるので、示せるのは一族の名前までで、それ以上具体的なことは分かりません。
258Rest of the MS-DOS headerページ数、再配置テーブルのオフセット、リアルモード DOS 用の初期スタックポインタと命令ポインタ。Windows はこれらを一切見ませんし、リンカはテンプレートを書くだけです。意味を持つのは長さだけで、それが本当のヘッダへのポインタの位置を確定させます。
604e_lfanewPE 署名のファイルオフセットをリトルエンディアンで。DOS ヘッダの中で Windows が読む唯一のフィールドです。MSVC や GCC の出力では 0x80(128)が定番ですが、本当に可変なので決め打ちしてはいけません。以下の行はすべてこの値からの相対で書いています。
64variableMS-DOS stub program本物の 16 ビットコードと 'This program cannot be run in DOS mode' という文字列。すべての Windows バイナリが今も背負っているお荷物で、e_lfanew が指す位置で終わります。
e_lfanew + 04PE signature50 45 00 00、PE の 2 文字と NUL バイト 2 つ。オフセット 60 に格納された位置にこの 4 バイトが無ければ、それはただの DOS 実行ファイルか壊れた PE であり、ローダーは受け付けません。
e_lfanew + 42Machine対象 CPU: x64 が 0x8664、32 ビット x86 が 0x014C、ARM64 が 0xAA64。アーキテクチャが合わないときに出る「有効な Win32 アプリケーションではありません」の背後にあるのがこのフィールドです。
e_lfanew + 62NumberOfSectionsoptional header の後ろに続くセクションヘッダの数。ふつうは 5 個から 20 個ほどで、パックされたバイナリや保護されたバイナリは .text でも .rdata でもない名前のセクションを 2〜3 個だけ持っていることがよくあります。
e_lfanew + 84TimeDateStampリンク時点の 1970 年からの秒数。古いツールチェーンでは本物のタイムスタンプですが、新しいものでは再現可能ビルドのためのハッシュなので、2107 年や 1970 年という日付は破損ではなくビルドシステムの産物です。
e_lfanew + 128PointerToSymbolTable + NumberOfSymbolsCOFF シンボルテーブル用の 4 バイトフィールドが 2 つ。イメージでは非推奨で、Microsoft のリンカが作ったものならたいていゼロです。MinGW のバイナリは今も埋めるので、GCC でビルドした .exe には Visual Studio のものには無いシンボルが入っています。
e_lfanew + 202SizeOfOptionalHeader後ろに続く optional header のバイト長。32 ビットイメージなら 224、64 ビットなら 240、オブジェクトファイルなら 0。optional header の開始位置にこれを足せば、間にあるものを一切パースせずにセクションテーブルへたどり着けます。
e_lfanew + 222Characteristics属性フラグ。0x0002 が実行可能イメージ、0x2000 が DLL であることを示します。これが .dll と .exe の実際の違いです。拡張子はただの慣習にすぎず、このビット 1 つでどちらにでも変わります。
e_lfanew + 242Optional header MagicPE32 なら 0x010B、64 ビット版の PE32+ なら 0x020B。Machine フィールドとは別物で、両方が矛盾なく揃っている必要があります。PE32+ ではイメージベースといくつかのサイズフィールドが広がるので、PE32+ のヘッダを PE32 として読むと、もっともらしく見えるでたらめが出てきます。
e_lfanew + 24 + SizeOfOptionalHeader40 per sectionSection tableセクションごとに 40 バイトのエントリが 1 つ。NUL 埋めの 8 バイトの名前、仮想サイズと仮想アドレス、そして生のサイズとファイルオフセット。hex エディタで見えているものとプロセスがメモリ上で見ているものを対応づけるのが、このテーブルです。

似た形式との見分け方

紛らわしい形式見分け方
MS-DOS executable本物の 16 ビット DOS プログラムも 4D 5A で始まりますが、PE ヘッダはまったく持ちません。オフセット 0x3C のバイトはポインタではなく、そのヘッダの一部にすぎないのです。ポインタとしてたどってみて、その先がファイル末尾より後ろだったり、そこの 4 バイトが 50 45 00 00 でなかったりすれば、手元にあるのは DOS 専用か壊れたファイルです。判定器が裸の MZ ファイルを「おそらく PE」としか言えないのはこのためです。
ELF 実行ファイルバイトレベルでは紛らわしいところは皆無で、4D 5A 対 7F 45 4C 46 です。それでもクロスコンパイルのビルドツリーでは最も多い取り違えです。.so と .dll が隣り合って落ち、どちらも lib なんとかという名前になり得るからです。拡張子を信じる前にオフセット 0 を読んでください。
OLE2 / CFBどちらも同時代の Microsoft のコンテナ形式で、緩い意味ではどちらも「プログラム」を抱えているので、マクロ入りの .doc と .exe は初期の切り分けで混同されがちです。OLE2 の compound file は D0 CF 11 E0 A1 B1 1A E1 で始まり、MZ になることは決してありませんし、構造もメモリへ写像されるセクションではなく、FAT めいたストリームのディレクトリです。
.NET assemblyマネージドの .exe や .dll は、まったく普通の PE ファイルです。MZ も PE 署名もセクションテーブルも同じで、ただネイティブコードがほとんど入っていません。手掛かりはデータディレクトリの 14 番目(CLR ヘッダ)が非ゼロであることと、.text セクションの中身が CIL のメタデータであること。.NET のバイナリが綺麗に逆コンパイルできて、C++ のものはそうならないのはこれが理由です。

よくある壊れ方

  • e_lfanew がどこも指していない。PE ファイルの前に何かを足すと(HTML のエラーページ、UTF-8 BOM、マージを誤ったパッチ)、MZ のマジックナンバーは無傷のまま、オフセット 0x3C の値だけが間違った基準で測られることになります。ローダーは 'PE\0\0' を探しに行ってセクションのデータを見つけます。ファイルは実行ファイルと識別され、そして起動を拒みます。
  • 途中切れ。セクションのデータはすべてのヘッダの後ろにあるので、途中までのダウンロードでもパースは完璧に通ります。マシンもセクション数もエントリポイントも正しく読めて、最後のセクションの生データだけが足りません。Windows は一般的なイメージエラーを報告しますが、診断は各セクションの PointerToRawData + SizeOfRawData を実際のファイルサイズと比べることです。
  • Authenticode 署名の失効。除外範囲の外で 1 バイトでも変われば署名ハッシュが壊れるので、パッチを当てた、追記した、インストーラーのスタブを貼り付けた、といったファイルは動きはするものの未署名または改ざん扱いになります。署名を強制しているマシンでは、そもそも動きません。
  • テキストモード転送。あらゆるバイナリを台無しにするいつもの CRLF 変換が、最初の 0D 0A より後ろを全部ずらします。PE のヘッダは走査ではなく絶対オフセットでたどり着くものなので、微妙なバグとしてではなく、読めないヘッダとして即座に表面化します。
  • 追記されたペイロードを破損と勘違いする。インストーラー、自己展開アーカイブ、一部のライセンスラッパーは最後のセクションの先へ正当にデータを追記します。だからヘッダが記述するサイズよりファイルが大きいのは PE では正常なことで、それだけでは破損の証拠になりません。

この形式の現在地

Windows のプログラム、DLL、カーネルドライバー、.NET アセンブリはすべて PE ですし、UEFI のファームウェアアプリケーションも PE なので、今どきのブートローダーはプラットフォームを問わずたいてい PE ファイルです。Windows 上でこれを置き換えたものはなく、置き換えようとしているものもありません。64 ビットアドレッシング、マネージドコード、ARM64、ARM64EC のハイブリッドイメージまで、互換性を切らさずに吸収してきました。1993 年のバイナリでも、今でも見覚えのあるヘッダを持っています。Windows の外での広がりは Wine と UEFI に限られ、だからクロスプラットフォームのプロジェクトは 1 つの成果物ではなく、同じプログラムの PE と ELF と Mach-O を配ることになります。バイトを読む側にとっての実務上の帰結は、この 3 つのうち本当のヘッダをポインタの奥に隠しているのは PE だけだということ。面白いものはすべて、オフセット 0x3C が言う位置から始まります。

PE(Windows 実行ファイル)ファイルをこのサイトで開く

どれもブラウザ内で動きます。ファイルが端末から出ることはありません。

関連する形式

PE(Windows 実行ファイル)と混同しやすい形式です。先頭バイトを見比べてください。

形式先頭バイト拡張子
ELF 実行ファイル7F 45 4C 46.elf
Mach-O 実行ファイルCF FA ED FE.dylib
OLE2 / CFBD0 CF 11 E0 A1 B1.doc

よくある質問

ファイルのマジックナンバーとは何ですか?

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。PNG は必ず 89 50 4E 47 で始まり、ZIP は 50 4B 03 04 で始まります。OS もブラウザも file コマンドも、拡張子を信用せずこのバイト列を読みます。だからファイル名を変えても中身の正体は変わりません。

マジックナンバーを自分で確認する方法は?

macOS や Linux なら xxd -l 16 ファイル名 で先頭 16 バイトが表示されます。Windows なら PowerShell で Format-Hex -Path ファイル名 -Count 16 です。あるいはこのサイトのファイル形式判別ツールにドロップすれば、ローカルでバイト列を読んで形式を教えてくれます。アップロードはありません。

2 つの形式が同じマジックナンバーを持つのはなぜ?

一方が他方の上に作られているからです。docx、xlsx、pptx、apk、jar、epub はすべて中身が ZIP アーカイブなので ZIP の署名で始まり、区別するには内部のエントリを見る必要があります。Java の class ファイルと macOS のユニバーサルバイナリはどちらも CA FE BA BE で始まり、直後のフィールドで見分けます。

マジックナンバーがまったくないファイルもある?

あります。XML、HTML、JSON、SVG、CSV といったテキストベースの形式には固定のバイト署名がないため、構造から判別します。先頭の <?xml 宣言、<svg 要素、あるいは { に続く引用符付きのキーなどです。これは確定ではなく推測なので、このリファレンスではそうした形式をプローブ判別として示しています。

マジックナンバーが一致すればそのファイルは安全?

いいえ。わかるのは形式であって、中身が無害かどうかではありません。正規の PDF や docx でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

PE(Windows 実行ファイル)かもしれないファイルをお持ちですか?ファイル形式判別ツールで調べる — マジックナンバーをローカルで読み取ります。アップロードは一切ありません。またはすべての形式を見る。