「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 5A | MZ | MS‑DOS実行形式(および PE の DOS スタブ) | 先頭2バイトが “MZ” |
4E 45 | NE | 16bit OS/2 実行形式(New Executable) | |
4C 45 | LE | Linear Executable(主に VxD 等) | OS/2 系や DOS エクステンダで使われた例もあり |
4C 58 | LX | OS/2 32bit 実行形式 | 資料によっては LE/LX を併記 |
50 45 00 00 | PE\0\0 | Windows 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” が生まれる典型的な誤読パターン
- エンディアンの逆読み:
0x5A4Dを「5A」「4D」の順に視覚化し、“ZM” と誤って文字列化。 - 表示ツールのモード違い:16ビットワード単位で表示するツールが、
5A4Dの「数値」を示し、ユーザーがそれを文字列で解釈。 - まとめサイトのコピペ連鎖:初出の誤記が修正されないまま拡散し、信憑性が膨張。
- 翻訳・編集時の取り違え:
“MZ (0x5A4D)”
と書くべき箇所が“ZM (0x5A4D)”
へ。
現物で確認する:EXEを自分で検証する手順
任意の EXE(DOS 実行ファイル/PE ファイル)を用意し、先頭2バイトと PE ヘッダの在処を確かめます。
手順の概略
- バイナリエディタまたはダンプツールで 先頭2バイトを確認(
4D 5Aになっているか)。 - DOS ヘッダの
e_lfanew(オフセット0x3Cの 4 バイト値)を読み、そこが PE ヘッダの先頭か確認(50 45 00 00)。 - 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)の位置を伝えるための情報を持ちます。解析で頻出する主要フィールドをまとめます。
| オフセット | サイズ | フィールド | 意味 |
|---|---|---|---|
0x00 | 2 | e_magic | 0x5A4D(“MZ”) |
0x02 | 2 | e_cblp | 最終ページのバイト数 |
0x04 | 2 | e_cp | 512バイト単位の総ページ数 |
0x18 | 2 | e_lfarlc | リロケーションテーブルの先頭 |
0x3C | 4 | e_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 ではない?」を切り分ける
- 先頭
4D 5Aを確認。 e_lfanew→PE\0\0を確認。- 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 <-- e_lfanew (例)
00000080: 50 45 00 00 <-- '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 5A | MZ | MS‑DOS実行形式/PE の DOS スタブ | 先頭2バイトが “MZ” |
4E 45 | NE | 16bit OS/2 実行形式 | New Executable |
4C 45 | LE | Linear Executable(VxD 他) | |
4C 58 | LX | OS/2 32bit 実行形式 | |
50 45 00 00 | PE00 | Windows PE | “PE\0\0” |
検証フロー(簡易)
- EXE を開く → 先頭が
4D 5Aか? → YES: MZ / NO: MZ ではない e_lfanewを読む → その位置が50 45 00 00か? → YES: PE / NO: NE/LE/LX 等- “ZM” の表記を見かけたら → 誤記を疑う → 原典で確認

コメント