DOS「ZM」実行ファイルは存在するのか?MZヘッダと5A4Dの正体を徹底解説

「DOS ZM executable」という表記を見かけてモヤっとした人向けに、実在性と技術的根拠を徹底検証します。結論から言えば “ZM” は実行形式として事実上存在せず、MZ(5A4D)の誤読・誤記が発生源です。公式ヘッダ定義、ヘッダ構造、現物確認の手順まで具体的に解説します。

目次

DOS「ZM」実行ファイルは実在するのか:結論

結論:ZM という実行形式は事実上存在しません。

  • Windows SDK の公式ヘッダ(winnt.h 等)では、DOS実行形式のシグネチャを IMAGE_DOS_SIGNATURE = 0x5A4D と定義し、これは ASCII 文字列の “MZ” に相当します。
  • 同じく公式定義には OS/2 の NE、LE(場合により LX も参照されます)、および NT の PE\0\0 が挙げられますが、“ZM” は列挙されません。
  • “ZM” という表記は、16ビット値 0x5A4D を「バイト列(4D 5A)」ではなく数値として扱った際の逆順表示や、誤植・タイプミスが伝搬したものと考えられます。

質問の背景:なぜ “ZM” と書かれることがあるのか

ファイルシグネチャの一覧表には、値が「16ビットの数値」として記されることがあります。MZ の場合、e_magic フィールドは 0x5A4D(リトルエンディアンの 16ビット値)で、実際の先頭2バイトは 4D 5A(ASCII の “MZ”)です。ところが、表を作る過程で 0x5A4D を そのまま「ZM」と誤って文字列化したり、「5A 4D」のバイト順を視覚的に反転して “ZM” と書いてしまうケースが散見されます。

“MZ” と “5A4D/4D5A” の正しい理解

混乱を防ぐために、「数値(ワード値)表記」と「バイト列表記」を分けて理解しましょう。

観点表記意味補足
ASCII文字MZファイル先頭2バイトが 0x4D(M), 0x5A(Z)目視で “MZ” と読める
16ビット値0x5A4Dリトルエンディアンの 16ビット値ヘッダ構造体の e_magic がこの値
バイト列4D 5Aファイルに実際に記録される順序ヘッダ先頭の2バイト

代表的シグネチャと用途の整理

シグネチャ(バイト列)文字列主な用途備考
5A 4D/4D 5AMZMS‑DOS実行形式(および PE の DOS スタブ)先頭2バイトが “MZ”
4E 45NE16bit OS/2 実行形式(New Executable)
4C 45LELinear Executable(主に VxD 等)OS/2 系や DOS エクステンダで使われた例もあり
4C 58LXOS/2 32bit 実行形式資料によっては LE/LX を併記
50 45 00 00PE\0\0Windows PE(NT系)PE ヘッダは DOS ヘッダの e_lfanew が指す位置にある

公式定義(ヘッダ)に現れる識別子

Windows プラットフォーム向けの公式ヘッダでは、以下のようなマクロ(概念)が定義されます。ここに “ZM” は登場しません。

// 代表例(概念を示す抜粋イメージ)
#define IMAGE_DOS_SIGNATURE     0x5A4D   // 'MZ'
#define IMAGE_OS2_SIGNATURE     0x454E   // 'NE'
#define IMAGE_OS2_SIGNATURE_LE  0x454C   // 'LE'
#define IMAGE_NT_SIGNATURE      0x00004550 // 'PE\0\0'

ポイントは、“MZ” は 0x5A4D(ワード値)であり、実際のバイト列は 4D 5A だということです。したがって “ZM” という文字列を正解として扱う文脈はありません。

“ZM” が生まれる典型的な誤読パターン

  1. エンディアンの逆読み:0x5A4D を「5A」「4D」の順に視覚化し、“ZM” と誤って文字列化。
  2. 表示ツールのモード違い:16ビットワード単位で表示するツールが、5A4D の「数値」を示し、ユーザーがそれを文字列で解釈。
  3. まとめサイトのコピペ連鎖:初出の誤記が修正されないまま拡散し、信憑性が膨張。
  4. 翻訳・編集時の取り違え:“MZ (0x5A4D)” と書くべき箇所が “ZM (0x5A4D)” へ。

現物で確認する:EXEを自分で検証する手順

任意の EXE(DOS 実行ファイル/PE ファイル)を用意し、先頭2バイトと PE ヘッダの在処を確かめます。

手順の概略

  1. バイナリエディタまたはダンプツールで 先頭2バイトを確認(4D 5A になっているか)。
  2. DOS ヘッダの e_lfanew(オフセット 0x3C の 4 バイト値)を読み、そこが PE ヘッダの先頭か確認(50 45 00 00)。
  3. NE/LE/LX の場合は、MZ ヘッダの後に対応するシグネチャが続くことを確認。

Windows(PowerShell)での確認例

# 先頭 16 バイト程度を表示
Format-Hex -Path .\sample.exe -Count 16

# e_lfanew(0x3C)の値を読み取り、そこへシークして 4 バイトを確認

$bytes   = [System.IO.File]::ReadAllBytes(".\sample.exe")
$lfanew  = [BitConverter]::ToInt32($bytes, 0x3C)
"{0:X8}" -f $lfanew
$peMagic = $bytes[$lfanew..($lfanew+3)]
($peMagic | ForEach-Object { "{0:X2}" -f $_ }) -join " "

# => 50 45 00 00(PE\0\0)

Linux/macOS(xxd/hexdump)での確認例

# 先頭2バイト(ASCII を含む)を表示
xxd -l 16 -g 1 sample.exe | head -n 1
# 行頭が "4d 5a" で始まるはず(ASCII欄が MZ)

# e_lfanew の読み取り

printf "e_lfanew: 0x%08X\n" 
$(xxd -s 0x3C -l 4 -e -g 4 -ps sample.exe | tr -d '\n' | xargs printf "0x%s")

# その位置の4バイトを確認

OFFSET=$(xxd -s 0x3C -l 4 -e -g 4 -ps sample.exe | tr -d '\n')
OFFSET=$((16#${OFFSET}))
xxd -s ${OFFSET} -l 4 -g 1 sample.exe

# => 50 45 00 00

最小コードで確認(C/C++)

#include <stdio.h>
#include <stdint.h>

#pragma pack(push, 1)
typedef struct {
uint16_t e_magic;    // 0x5A4D ('MZ')
uint16_t e_cblp;     // 以下略
uint16_t e_cp;
uint16_t e_crlc;
uint16_t e_cparhdr;
uint16_t e_minalloc;
uint16_t e_maxalloc;
uint16_t e_ss;
uint16_t e_sp;
uint16_t e_csum;
uint16_t e_ip;
uint16_t e_cs;
uint16_t e_lfarlc;
uint16_t e_ovno;
uint16_t e_res[4];
uint16_t e_oemid;
uint16_t e_oeminfo;
uint16_t e_res2[10];
uint32_t e_lfanew;   // PEヘッダ先頭へのオフセット
} IMAGE_DOS_HEADER;
#pragma pack(pop)

int main(int argc, char** argv) {
if (argc < 2) return 1;
FILE* f = fopen(argv[1], "rb");
if (!f) return 2;


IMAGE_DOS_HEADER dos = {0};
fread(&dos, sizeof(dos), 1, f);
if (dos.e_magic != 0x5A4D) {
    printf("MZではありません (e_magic=%04X)\n", dos.e_magic);
    return 3;
}
printf("MZヘッダを検出 (e_magic=%04X)\n", dos.e_magic);

fseek(f, dos.e_lfanew, SEEK_SET);
uint32_t nt = 0;
fread(&nt, sizeof(nt), 1, f);
if (nt == 0x00004550) {
    printf("PEヘッダを検出 ('PE\\0\\0')\n");
} else {
    printf("PEではありません (signature=%08X)\n", nt);
}
fclose(f);
return 0;


} 

MZ ヘッダのフィールド概要(よく使う箇所)

DOS ヘッダ(MZ)は、DOSプログラムの実行に必要な最小情報と、後続ヘッダ(NE/LE/LX/PE)の位置を伝えるための情報を持ちます。解析で頻出する主要フィールドをまとめます。

オフセットサイズフィールド意味
0x002e_magic0x5A4D(“MZ”)
0x022e_cblp最終ページのバイト数
0x042e_cp512バイト単位の総ページ数
0x182e_lfarlcリロケーションテーブルの先頭
0x3C4e_lfanew後続ヘッダ(NE/LE/LX/PE)の位置

PE との関係:DOS スタブから PE ヘッダへ

現在の Windows で流通する多くの EXE は PE 形式ですが、先頭には必ず MZ ヘッダ(DOS スタブ)が置かれています。これは互換性のためで、DOS 上で実行すると「This program cannot be run in DOS mode」などの短いメッセージを表示する小さなプログラムになっています。PE ローダは DOS ヘッダの e_lfanew を読み、そこにある PE\0\0 シグネチャへジャンプします。ここでも “ZM” は出てきません。

NE/LE/LX と MZ の関係

OS/2 系や Windows 3.x の一部では、NE/LE/LX の各形式が用いられました。これらも先頭は MZ ヘッダで始まり、e_lfanew が後続の NE/LE/LX を指します。従って、古い実行形式でも最初に確認すべきは “MZ”であり、“ZM” ではありません。

「ZM サンプル」が見つからない理由

  • 公式定義に存在しないため、正規のツールチェーンが “ZM” と解釈するバイナリを生成しません。
  • 検索エンジン結果の多くは、「MZ(0x5A4D)」の誤記から発生した記述です。実体がないため、いくら探しても「ZM 形式の EXE」は見つかりません。
  • もし “ZM” を先頭に含むように 人為的に改変すれば、単に壊れた MZとして扱われるだけです(ローダが拒否して実行不能)。

実務で役立つチェックリスト

  • 先頭2バイトを必ず バイト列で確認(4D 5A)。
  • 一覧表や仕様書で 0x5A4D を見たら、ASCII では “MZ”だと脳内変換する。
  • “ZM” と書かれていたら、出典を辿って誤記を疑う。元文献の原文表記を確認する。
  • PE の場合は e_lfanew → PE\0\0 を追って二段階で検証。
  • NE/LE/LX に遭遇したら、最初は MZ、続いて各形式のシグネチャを確認する流れを徹底。

トラブルシューティング:表示が “ZM” に見えてしまう時

いくつかのツールやビューアは、デフォルトで ワード単位の表示を行うことがあります。例えば 16 ビット単位で 0x5A4D が見えたとき、脳内で “ZM” と読んでしまうと混乱の原因です。以下のいずれかを試してください。

  • 表示単位を「バイト」に切替(1Bごとの 16進表示に)。
  • ASCII 表示を併用し、MZ が見えるかを直接確認。
  • PowerShell の Format-Hex や xxd のように、先頭 16 バイトをバイト粒度で表示。

コラム:MZ の由来

“MZ” は MS-DOS 実行形式の設計に関わったエンジニアのイニシャルに由来する、と広く伝えられています。歴史的経緯の細部はさておき、“MZ” が DOS 実行ヘッダの代名詞である、という点が技術的・文化的に定着しており、“ZM” が同格として扱われることはありません。

現場で役立つミニクックブック

「MZ だけど PE ではない?」を切り分ける

  1. 先頭 4D 5A を確認。
  2. e_lfanew → PE\0\0 を確認。
  3. PE が無ければ、NE/LE/LX の可能性を順にチェック。

「壊れた EXE」を見つけたら

  • e_cblp と e_cp の整合性を点検(ファイルサイズと矛盾しないか)。
  • リロケーションテーブル(e_lfarlc)の範囲が妥当か確認。
  • e_lfanew がファイル末尾を超えていないか(オフセットの破損)。

「ZM」を見かけたときの対処法

  • まずは原典(仕様書・公式ヘッダの定義)に立ち返る。
  • 一覧表や Wiki に “ZM” とあれば、誤記の可能性を指摘し、根拠とともに修正提案するのが建設的。
  • 社内ナレッジや手順書では、「ワード値(0x5A4D)とバイト列(4D 5A)の区別」を明記して再発防止。

サンプルで理解する:典型的な PE ファイルの先頭

00000000: 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00  MZ..............
00000010: b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  ........@.......
...
0000003c: 80 00 00 00                                      &lt;-- e_lfanew (例)
00000080: 50 45 00 00                                      &lt;-- 'PE\0\0'

このように、先頭は “MZ”、そして e_lfanew が指す先に “PE\0\0” が現れます。ここに “ZM” は存在しません。

よくある質問(FAQ)

Q. “ZM” と書かれた資料を見つけました。本当に全部間違いですか?
A. その可能性が高いです。原文が 0x5A4D (‘MZ’) であるのに、翻訳や編集で “ZM” と転記されたケースが典型です。まずは元資料のバイト列例(4D 5A)やヘッダ構造の説明を確認しましょう。

Q. “5A 4D” と書いてあるのに、なんで “MZ” なんですか?
A. それは 16ビット値の表記(0x5A4D)をバイトに分解したときの並びを示している可能性があります。実際の先頭2バイトは 4D 5A(M, Z)である点に注意してください。

Q. “ZM” の EXE をどうしても見たいのですが?
A. 意図的に先頭を 5A 4D(“ZM”)へ書き換えることはできますが、それは 壊れた MZ であり、ローダは実行しません。実在する別形式としての “ZM” はありません。

まとめ

  • “ZM” 実行形式は事実上存在しない。正しくは MZ(0x5A4D)。
  • 公式ヘッダ定義や実ファイルの先頭2バイトを確認すれば、一目で判別できる。
  • “ZM” 表記を見かけたら、誤記・誤読を疑い、原典で裏取りしよう。
  • サンプルを探す必要はない。あなたの手元の Windows の EXE が、既に MZ のサンプルです。

実務メモ:チェックを仕組みにする

CI/CD やマルウェア解析の前処理に、ヘッダ検査を自動化すると安全です。例えば「先頭2バイトが 4D 5A か」「e_lfanew がファイル内に収まるか」「PE\0\0 が存在するか」をスクリプトで検証しておけば、誤ったファイルや破損ファイルを早期に弾けます。誤情報に振り回される前に、ファクト(バイト列)に当たる習慣を付けましょう。


補足:実務での参照用サマリ

代表的シグネチャ文字列主な用途備考
5A 4D/4D 5AMZMS‑DOS実行形式/PE の DOS スタブ先頭2バイトが “MZ”
4E 45NE16bit OS/2 実行形式New Executable
4C 45LELinear Executable(VxD 他)
4C 58LXOS/2 32bit 実行形式
50 45 00 00PE00Windows PE“PE\0\0”

検証フロー(簡易)

  1. EXE を開く → 先頭が 4D 5A か? → YES: MZ / NO: MZ ではない
  2. e_lfanew を読む → その位置が 50 45 00 00 か? → YES: PE / NO: NE/LE/LX 等
  3. “ZM” の表記を見かけたら → 誤記を疑う → 原典で確認

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次