{"article":{"slug":"why-your-linux-kernel-starts-with-mz-yes-the-dos-pe-one","title":"Why Your Linux Kernel Starts With 'MZ' (Yes, the DOS/PE One)","subtitle":null,"summary":"A deep dive into why the Linux kernel image begins with the DOS/PE 'MZ' header—boot compatibility history, EFI stubs, and what the legacy magic bytes still do on modern systems.","content_type":"blog_post","language":"en","canonical_url":"https://elohim666.github.io/posts/why-linux-kernel-has-mz-pe-header/","author":{"name":null,"url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"3l0h1m.s3c","url":"https://elohim666.github.io/","listing_slug":null,"listing":null},"topics":[{"name":"Linux","slug":"linux","url":"https://listedarticles.com/topics/linux"},{"name":"Systems Programming","slug":"systems-programming","url":"https://listedarticles.com/topics/systems-programming"},{"name":"Hardware","slug":"hardware","url":"https://listedarticles.com/topics/hardware"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":2082,"reading_minutes":9,"published_at":"2026-09-11T00:00:00.000Z","added_at":"2026-09-27T18:12:04.156Z","updated_at":"2026-09-27T18:12:04.156Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/why-your-linux-kernel-starts-with-mz-yes-the-dos-pe-one","markdown_url":"https://listedarticles.com/articles/why-your-linux-kernel-starts-with-mz-yes-the-dos-pe-one.md","example":false,"citation":"3l0h1m.s3c. \"Why Your Linux Kernel Starts With 'MZ' (Yes, the DOS/PE One).\" 11 Sept 2026. https://elohim666.github.io/posts/why-linux-kernel-has-mz-pe-header/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://elohim666.github.io/posts/why-linux-kernel-has-mz-pe-header/"},"body_markdown":"# Why Your Linux Kernel Starts With 'MZ' (Yes, the DOS/PE One)\n\nEvery command and hex dump in this post was run live on this machine (`uname -r`: 7.2.4-arch1-2) against its real `/boot/vmlinuz-linux`. Nothing here is a mockup — you can reproduce every byte on your own box.\n\n## |=—[ TL;DR ]\n\nYour Linux kernel image (`/boot/vmlinuz-*`) starts with the two bytes\n`4D 5A` — `\"MZ\"` — the ancient MS-DOS executable magic number, followed a\nlittle further in by a real `\"PE\\0\\0\"` (COFF) header with\n`Subsystem = IMAGE_SUBSYSTEM_EFI_APPLICATION`. This is not a coincidence, a\njoke, or legacy cruft nobody bothered to remove. It’s **required by the\nUEFI specification**: UEFI firmware only knows how to load and execute\nPE32+ binaries. To let a UEFI system boot Linux with zero extra\nbootloader code, the kernel’s boot header was made to **also be a\nvalid PE/COFF executable** — while *simultaneously* remaining a valid\nlegacy BIOS boot sector for machines that don’t have UEFI at all. Same\nbytes, two boot protocols.\n\n```\n$ file /boot/vmlinuz-linux\n/boot/vmlinuz-linux: Linux kernel x86 boot executable, bzImage, ...\n32-bit EFI handoff entry point, 64-bit EFI handoff entry point, ...\n$ xxd -l 4 /boot/vmlinuz-linux\n00000000: 4d5a 0000                                MZ..\n```\n## |=—[ Background: two executable formats, two boot worlds ]\n\nA few things this post leans on, from scratch:\n\n- **MZ / PE** :`MZ` is the magic number of the old MS-DOS executable\nformat (`.EXE` ), named after Mark Zbikowski, one of its authors at\nMicrosoft. Windows NT’s executable format,**PE** (Portable\nExecutable,`.exe` /`.dll` /`.efi` /`.sys` ), is backward-compatible with it\non purpose: a PE file*starts* with a real MS-DOS header (so old DOS\nwould print “This program cannot be run in DOS mode” instead of\ncrashing), and at a fixed offset — byte`0x3C` — that DOS header stores\na 4-byte pointer called`e_lfanew` , pointing forward to where the`PE\\0\\0` header begins. Everything between the DOS header and the PE\nheader is technically a tiny, real, runnable DOS program (the “DOS\nstub”).\n- **UEFI** : the modern replacement for BIOS. Critically for this post,**the UEFI spec defines its executables as PE32+ (PE32 for 64-bit\naddress space) images** — UEFI firmware, at its core, is a minimal\nPE-loader. Anything you want firmware to`LoadImage()` /`StartImage()` directly — a bootloader, a driver, a recovery tool — has to be a valid\nPE/COFF binary with`Subsystem = IMAGE_SUBSYSTEM_EFI_APPLICATION` .\n- **The old BIOS way** : BIOS doesn’t know or care about executable\nformats. It just loads the first 512-byte sector of a disk into memory\nand jumps to it, provided that sector ends with the magic bytes`55 AA` at offset`0x1FE` . Whatever’s in those 512 bytes is 100% up to you.\n\nSo: two completely different firmware interfaces, two completely different expectations about what a “bootable file” looks like. The Linux kernel image has to satisfy both, because it’s built once and needs to boot on both old BIOS machines and modern UEFI ones (directly, or via GRUB/systemd-boot, which themselves are PE binaries for the same reason).\n\n## |=—[ Hands-on: reading the header byte by byte ]\n\nLet’s actually parse `vmlinuz` by hand, no tools beyond `xxd`.\n\n**Byte 0 — the DOS magic:**\n\n```\n$ xxd -l 64 /boot/vmlinuz-linux\n00000000: 4d5a 0000 0000 0000 0000 0000 0000 0000  MZ..............\n00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................\n00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................\n00000030: 0000 0000 0000 0000 cd23 8281 4000 0000  .........#..@...\n```\n`4d 5a` = `\"MZ\"`. This is `IMAGE_DOS_SIGNATURE`. It has to be the very\nfirst two bytes of the file for *any* PE-format tool (or UEFI firmware)\nto even bother looking further.\n\n**Byte 0x3C — the `e_lfanew` pointer:**\n\n```\n$ xxd -s 0x3c -l 4 /boot/vmlinuz-linux\n0000003c: 4000 0000                                @...\n```\nLittle-endian `40 00 00 00` = `0x00000040`. This says: “the PE\nheader starts at offset 0x40 in this file.” Every PE parser — including\nUEFI firmware’s own loader — reads this field to jump straight past the\nDOS stub.\n\n**Following the pointer — the PE/COFF header:**\n\n```\n$ xxd -s 0x40 -l 32 /boot/vmlinuz-linux\n00000040: 5045 0000 6486 0400 0000 0000 0000 0000  PE..d...........\n00000050: 0100 0000 a000 0602 0b02 0214 0090 0601  ................\n```\nReading it field by field:\n\n```\noffset 0x40:  50 45 00 00            \"PE\\0\\0\"   IMAGE_NT_SIGNATURE\noffset 0x44:  64 86                  Machine    = 0x8664 (AMD64)\noffset 0x46:  04 00                  NumberOfSections = 4\noffset 0x48:  00 00 00 00            TimeDateStamp = 0 (reproducible build)\noffset 0x54:  a0 00                  SizeOfOptionalHeader = 0x00A0\noffset 0x56:  06 02                  Characteristics = EXECUTABLE_IMAGE | ...\noffset 0x58:  0b 02                  OptionalHeader Magic = 0x020B (PE32+)\n```\n`0x020B` confirms this is a **PE32+** image — the 64-bit PE variant,\nsame format used by every native 64-bit Windows `.exe` and every 64-bit\nUEFI driver.\n\n**The field that actually tells firmware “boot me”: `Subsystem`.**\n\nIn a PE32+ optional header, `Subsystem` sits 68 bytes after the `Magic`\nfield (`0x58 + 68 = 0x9C`):\n\n```\n$ xxd -s 0x9c -l 4 /boot/vmlinuz-linux\n0000009c: 0a00 0001                                ....\n```\n`0a 00` = `0x000A` = **10** = `IMAGE_SUBSYSTEM_EFI_APPLICATION`. This\nsingle value is the whole point of the exercise: it’s the field UEFI\nfirmware checks to decide “this is an EFI application, I know how to run\nthis,” as opposed to `IMAGE_SUBSYSTEM_WINDOWS_CUI` (3, a normal Windows\nconsole app) or `WINDOWS_GUI` (2). Change one byte here and firmware\nwould refuse to load a byte-for-byte identical kernel.\n\n**The section table — proof it’s a real, structured PE image, not a\nfaked-up 4 bytes of magic:**\n\n```\n$ xxd -s 0xf8 -l 40 /boot/vmlinuz-linux\n000000f8: 2e73 6574 7570 0000 0030 0000 0010 0000  .setup...0......\n00000108: 0030 0000 0010 0000 0000 0000 0000 0000  .0..............\n00000118: 0000 0000 4000 0042 2e63 6f6d 7061 7400  ....@..B.compat.\n```\nRight after the optional header comes a normal PE section table, 40\nbytes per entry, each starting with an 8-byte ASCII name. This kernel\nimage declares (at least) four sections: **`.setup`**, **`.compat`**,\n`.text`, `.data` — a real PE loader will map these in exactly like it\nwould for `notepad.exe`.\n\n## |=—[ The other half: it’s *also* a legacy BIOS boot sector ]\n\nHere’s the part that makes this genuinely clever rather than just “the\nkernel happens to be a PE file.” Jump to offset `0x1FE`, the one place\nin a file that matters to old-school BIOS booting:\n\n```\n$ xxd -s 0x1fe -l 16 /boot/vmlinuz-linux\n000001fe: 55aa eb6a 4864 7253 0f02 0000 0000 0010  U..jHdrS........\n```\n`55 aa` — the mandatory **BIOS boot-sector signature**, at exactly the\noffset BIOS requires it. Right after it: `48 64 72 53` = `\"HdrS\"`, the\nmagic of the Linux kernel’s own `setup_header` structure (parsed by\nreal-mode bootloaders like GRUB’s legacy BIOS path, or even the kernel’s\nown built-in real-mode setup code). This is a **completely different,\nolder, BIOS-era boot protocol**, living in the exact same bytes as the\nPE header above it.\n\nTo see why this matters, compare against a *pure* EFI application on\nthe same disk — GRUB’s own `grubx64.efi`, which has no reason to ever be\nBIOS-bootable:\n\n```\n$ xxd -s 0x1fe -l 16 /boot/EFI/GRUB/grubx64.efi\n000001fe: 00c0 2e72 656c 6f63 0000 0020 0000 0050  ...reloc... ...P\n$ file /boot/EFI/GRUB/grubx64.efi\n/boot/EFI/GRUB/grubx64.efi: PE32+ executable for EFI (application),\nx86-64 (stripped to external PDB), 4 sections\n```\nNo `55 AA` at `0x1FE` — because GRUB’s `.efi` binary doesn’t need one, it\nis *only* ever loaded by UEFI firmware, never by a BIOS jumping to a\ndisk sector. `vmlinuz`, on the other hand, deliberately keeps both\nmarkers valid at once:\n\n| Offset | Bytes | Meaning | Consumed by | \n|---|---|---|---|\n| `0x000` | `4D 5A` | `\"MZ\"` /`e_lfanew` @`0x3C` | Any PE loader, incl. UEFI | \n| `0x040` | `50 45 00 00` | `\"PE\\0\\0\"` header,`Subsystem=EFI_APPLICATION` | UEFI firmware | \n| `0x1FE` | `55 AA` | BIOS boot-sector signature | Legacy BIOS / GRUB legacy | \n| `0x202` | `48 64 72 53` | `\"HdrS\"` , Linux`setup_header` | BIOS-era Linux bootloaders | \n\nOne 17 MB file. Two mutually exclusive, decades-apart boot protocols,\nboth satisfied simultaneously, because the byte ranges each protocol\nactually *reads* don’t overlap.\n\n## |=—[ Why: this is called the EFI stub, and it’s in the kernel source ]\n\nThis mechanism has a name: the **EFI boot stub** (`CONFIG_EFI_STUB`). It\nwas added to `arch/x86/boot/header.S` by Matt Fleming, first posted to\nLKML in October 2011 (“x86, efi: EFI boot stub support”). The kernel’s\nown documentation describes the goal directly:\n\nOn the x86 and arm platforms, a kernel zImage/bzImage is made to masquerade as a PE/COFF image, thereby convincing EFI firmware loaders to load it as an EFI executable.\n\nAnnotated, based on the structure of `arch/x86/boot/header.S`:\n\n```\n\t.code16\n\t.section \".bstext\", \"ax\"\n\t.byte\t0xeb\t\t# short (2-byte) jump\n\t.byte\tstart_of_setup-1f\n\t# ^ written as raw bytes, not a `jmp` mnemonic — the assembler\n\t#   would otherwise emit a 3-byte jump and shift every fixed\n\t#   offset below it, breaking the boot-sector/PE layout\n#ifdef CONFIG_EFI_STUB\n\t.org\t0x3c\n\t.long\tpe_header\t\t# e_lfanew: offset to the PE header\n\t\t\t\t\t# (this is the value we read above: 0x40)\n\t.word\tIMAGE_DOS_SIGNATURE\t# \"MZ\" — must be bytes 0-1 of the file\n\t...\npe_header:\n\t.long\tIMAGE_NT_SIGNATURE\t# \"PE\\0\\0\"\ncoff_header:\n\t.word\tIMAGE_FILE_MACHINE_AMD64\n\t.word\tsection_count\t\t# NumberOfSections\n\t...\noptional_header:\n\t.word\tPE_OPT_MAGIC_PE32PLUS\t# 0x020B\n\t...\n\t.word\tIMAGE_SUBSYSTEM_EFI_APPLICATION\t# Subsystem = 10\n\t...\n#endif /* CONFIG_EFI_STUB */\n```\nThe whole thing is `#ifdef`’d in specifically because it’s *only*\nneeded for EFI compatibility — a kernel built without\n`CONFIG_EFI_STUB` skips all of it and keeps the classic BIOS-only boot\nsector, no MZ/PE bytes at all.\n\nThe other side of the trick, `drivers/firmware/efi/libstub/x86-stub.c`,\nis the actual code that runs *as* the EFI application: it’s the\n`efi_main()` entry point UEFI firmware jumps to after `LoadImage()`\nsucceeds, which sets up the environment (memory map, initrd, command\nline) and hands off to the kernel’s normal decompression/startup path —\nthe same path a BIOS boot would eventually reach too. Both roads lead to\nthe same kernel; only the on-ramp differs.\n\nThis is also why you can boot a raw `vmlinuz` file **directly** from a UEFI shell or a UEFI boot entry, no GRUB required — that's the entire point of the EFI stub. `systemd-boot` and unified kernel images (UKIs) lean on exactly this property.\n\n## |=—[ Reproduce it yourself ]\n\nNo special tools needed beyond `xxd`/`file`, which ship on essentially\nevery Linux box:\n\n```\n# 1. confirm the DOS/PE magic\nxxd -l 4 /boot/vmlinuz-linux                 # expect: 4d 5a 00 00\n# 2. follow e_lfanew to the PE header\noff=$(xxd -s 0x3c -l 4 -e /boot/vmlinuz-linux | awk '{print $2}')\nxxd -s \"$((16#${off}))\" -l 4 /boot/vmlinuz-linux   # expect: 50 45 00 00 (\"PE\\0\\0\")\n# 3. read the Subsystem field (0x9c for a PE32+ boot header)\nxxd -s 0x9c -l 2 /boot/vmlinuz-linux          # expect: 0a 00 -> 10 (EFI_APPLICATION)\n# 4. confirm it's still a legacy-bootable BIOS sector too\nxxd -s 0x1fe -l 2 /boot/vmlinuz-linux         # expect: 55 aa\n# 5. let `file` do all of the above for you at once\nfile /boot/vmlinuz-linux\n```\nIf you want a broader sweep, `grep`/`binwalk` for the magic across your\nwhole `/boot`:\n\n```\n$ for f in /boot/vmlinuz-* /boot/EFI/*/*.efi; do\n    sig=$(xxd -l 2 -p \"$f\" 2>/dev/null)\n    [ \"$sig\" = \"4d5a\" ] && echo \"$f: MZ present\"\n  done\n/boot/vmlinuz-linux: MZ present\n/boot/EFI/GRUB/grubx64.efi: MZ present\n```\nEvery UEFI-loadable file on the system starts the same way — the kernel\nis just the one that’s *also* something else at the same time.\n\n## |=—[ Takeaways ]\n\n- **`MZ` in `vmlinuz` isn’t legacy junk — it’s a spec requirement.** UEFI\nfirmware is fundamentally a PE loader; anything meant to run under it\nhas to look like a PE binary, full stop, including the kernel.\n- **One file, two file formats, zero conflict** , because BIOS only ever\nreads byte`0x1FE` and PE loaders only ever follow the`e_lfanew` pointer from`0x3C` — the two protocols were designed decades apart\nand happen not to step on each other’s bytes.\n- **A single flipped bit can matter enormously.**`Subsystem = 10` is\nthe entire difference between “firmware treats this as a bootable EFI\napplication” and “firmware refuses to load it.” Worth remembering\nnext time a “cosmetic” header field looks safe to touch.\n- If you ever need to identify or fingerprint boot files during\nforensics or malware triage: `MZ` + a*valid*`e_lfanew` pointing at a\nreal`PE\\0\\0` is a much stronger signal than the two magic bytes\nalone — check where`e_lfanew` actually points before trusting the\nfile type.\n\n## |=—[ References ]\n\n- Linux kernel docs — The EFI Boot Stub\n- Kernel source — `arch/x86/boot/header.S`\n- Kernel source — `arch/x86/boot/setup.ld`\n- LKML — Matt Fleming, `[PATCH v5 10/10] x86, efi: EFI boot stub support` , Oct 2011\n- Microsoft — PE Format specification (DOS header, `e_lfanew` ,`IMAGE_SUBSYSTEM_*` values)\n- UEFI Forum — UEFI Specification, §2.1 “PE/COFF Image Loading”","body_html":"<h1 id=\"why-your-linux-kernel-starts-with-mz-yes-the-dos-pe-one\">Why Your Linux Kernel Starts With &#39;MZ&#39; (Yes, the DOS/PE One)</h1>\n<p>Every command and hex dump in this post was run live on this machine (<code>uname -r</code>: 7.2.4-arch1-2) against its real <code>/boot/vmlinuz-linux</code>. Nothing here is a mockup — you can reproduce every byte on your own box.</p>\n<h2 id=\"tl-dr\">|=—[ TL;DR ]</h2>\n<p>Your Linux kernel image (<code>/boot/vmlinuz-*</code>) starts with the two bytes\n<code>4D 5A</code> — <code>&quot;MZ&quot;</code> — the ancient MS-DOS executable magic number, followed a\nlittle further in by a real <code>&quot;PE\\0\\0&quot;</code> (COFF) header with\n<code>Subsystem = IMAGE_SUBSYSTEM_EFI_APPLICATION</code>. This is not a coincidence, a\njoke, or legacy cruft nobody bothered to remove. It’s <strong>required by the\nUEFI specification</strong>: UEFI firmware only knows how to load and execute\nPE32+ binaries. To let a UEFI system boot Linux with zero extra\nbootloader code, the kernel’s boot header was made to <strong>also be a\nvalid PE/COFF executable</strong> — while <em>simultaneously</em> remaining a valid\nlegacy BIOS boot sector for machines that don’t have UEFI at all. Same\nbytes, two boot protocols.</p>\n<pre><code>$ file /boot/vmlinuz-linux\n/boot/vmlinuz-linux: Linux kernel x86 boot executable, bzImage, ...\n32-bit EFI handoff entry point, 64-bit EFI handoff entry point, ...\n$ xxd -l 4 /boot/vmlinuz-linux\n00000000: 4d5a 0000                                MZ..</code></pre>\n<h2 id=\"background-two-executable-formats-two-boot-worlds\">|=—[ Background: two executable formats, two boot worlds ]</h2>\n<p>A few things this post leans on, from scratch:</p>\n<ul><li><p><strong>MZ / PE</strong> :<code>MZ</code> is the magic number of the old MS-DOS executable</p><p>format (<code>.EXE</code> ), named after Mark Zbikowski, one of its authors at\nMicrosoft. Windows NT’s executable format,<strong>PE</strong> (Portable\nExecutable,<code>.exe</code> /<code>.dll</code> /<code>.efi</code> /<code>.sys</code> ), is backward-compatible with it\non purpose: a PE file<em>starts</em> with a real MS-DOS header (so old DOS\nwould print “This program cannot be run in DOS mode” instead of\ncrashing), and at a fixed offset — byte<code>0x3C</code> — that DOS header stores\na 4-byte pointer called<code>e_lfanew</code> , pointing forward to where the<code>PE\\0\\0</code> header begins. Everything between the DOS header and the PE\nheader is technically a tiny, real, runnable DOS program (the “DOS\nstub”).</p></li><li><p><strong>UEFI</strong> : the modern replacement for BIOS. Critically for this post,**the UEFI spec defines its executables as PE32+ (PE32 for 64-bit</p><p>address space) images** — UEFI firmware, at its core, is a minimal\nPE-loader. Anything you want firmware to<code>LoadImage()</code> /<code>StartImage()</code> directly — a bootloader, a driver, a recovery tool — has to be a valid\nPE/COFF binary with<code>Subsystem = IMAGE_SUBSYSTEM_EFI_APPLICATION</code> .</p></li><li><p><strong>The old BIOS way</strong> : BIOS doesn’t know or care about executable</p><p>formats. It just loads the first 512-byte sector of a disk into memory\nand jumps to it, provided that sector ends with the magic bytes<code>55 AA</code> at offset<code>0x1FE</code> . Whatever’s in those 512 bytes is 100% up to you.</p></li></ul>\n<p>So: two completely different firmware interfaces, two completely different expectations about what a “bootable file” looks like. The Linux kernel image has to satisfy both, because it’s built once and needs to boot on both old BIOS machines and modern UEFI ones (directly, or via GRUB/systemd-boot, which themselves are PE binaries for the same reason).</p>\n<h2 id=\"hands-on-reading-the-header-byte-by-byte\">|=—[ Hands-on: reading the header byte by byte ]</h2>\n<p>Let’s actually parse <code>vmlinuz</code> by hand, no tools beyond <code>xxd</code>.</p>\n<p><strong>Byte 0 — the DOS magic:</strong></p>\n<pre><code>$ xxd -l 64 /boot/vmlinuz-linux\n00000000: 4d5a 0000 0000 0000 0000 0000 0000 0000  MZ..............\n00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................\n00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................\n00000030: 0000 0000 0000 0000 cd23 8281 4000 0000  .........#..@...</code></pre>\n<p><code>4d 5a</code> = <code>&quot;MZ&quot;</code>. This is <code>IMAGE_DOS_SIGNATURE</code>. It has to be the very\nfirst two bytes of the file for <em>any</em> PE-format tool (or UEFI firmware)\nto even bother looking further.</p>\n<p><strong>Byte 0x3C — the <code>e_lfanew</code> pointer:</strong></p>\n<pre><code>$ xxd -s 0x3c -l 4 /boot/vmlinuz-linux\n0000003c: 4000 0000                                @...</code></pre>\n<p>Little-endian <code>40 00 00 00</code> = <code>0x00000040</code>. This says: “the PE\nheader starts at offset 0x40 in this file.” Every PE parser — including\nUEFI firmware’s own loader — reads this field to jump straight past the\nDOS stub.</p>\n<p><strong>Following the pointer — the PE/COFF header:</strong></p>\n<pre><code>$ xxd -s 0x40 -l 32 /boot/vmlinuz-linux\n00000040: 5045 0000 6486 0400 0000 0000 0000 0000  PE..d...........\n00000050: 0100 0000 a000 0602 0b02 0214 0090 0601  ................</code></pre>\n<p>Reading it field by field:</p>\n<pre><code>offset 0x40:  50 45 00 00            &quot;PE\\0\\0&quot;   IMAGE_NT_SIGNATURE\noffset 0x44:  64 86                  Machine    = 0x8664 (AMD64)\noffset 0x46:  04 00                  NumberOfSections = 4\noffset 0x48:  00 00 00 00            TimeDateStamp = 0 (reproducible build)\noffset 0x54:  a0 00                  SizeOfOptionalHeader = 0x00A0\noffset 0x56:  06 02                  Characteristics = EXECUTABLE_IMAGE | ...\noffset 0x58:  0b 02                  OptionalHeader Magic = 0x020B (PE32+)</code></pre>\n<p><code>0x020B</code> confirms this is a <strong>PE32+</strong> image — the 64-bit PE variant,\nsame format used by every native 64-bit Windows <code>.exe</code> and every 64-bit\nUEFI driver.</p>\n<p><strong>The field that actually tells firmware “boot me”: <code>Subsystem</code>.</strong></p>\n<p>In a PE32+ optional header, <code>Subsystem</code> sits 68 bytes after the <code>Magic</code>\nfield (<code>0x58 + 68 = 0x9C</code>):</p>\n<pre><code>$ xxd -s 0x9c -l 4 /boot/vmlinuz-linux\n0000009c: 0a00 0001                                ....</code></pre>\n<p><code>0a 00</code> = <code>0x000A</code> = <strong>10</strong> = <code>IMAGE_SUBSYSTEM_EFI_APPLICATION</code>. This\nsingle value is the whole point of the exercise: it’s the field UEFI\nfirmware checks to decide “this is an EFI application, I know how to run\nthis,” as opposed to <code>IMAGE_SUBSYSTEM_WINDOWS_CUI</code> (3, a normal Windows\nconsole app) or <code>WINDOWS_GUI</code> (2). Change one byte here and firmware\nwould refuse to load a byte-for-byte identical kernel.</p>\n<p><strong>The section table — proof it’s a real, structured PE image, not a\nfaked-up 4 bytes of magic:</strong></p>\n<pre><code>$ xxd -s 0xf8 -l 40 /boot/vmlinuz-linux\n000000f8: 2e73 6574 7570 0000 0030 0000 0010 0000  .setup...0......\n00000108: 0030 0000 0010 0000 0000 0000 0000 0000  .0..............\n00000118: 0000 0000 4000 0042 2e63 6f6d 7061 7400  ....@..B.compat.</code></pre>\n<p>Right after the optional header comes a normal PE section table, 40\nbytes per entry, each starting with an 8-byte ASCII name. This kernel\nimage declares (at least) four sections: <strong><code>.setup</code></strong>, <strong><code>.compat</code></strong>,\n<code>.text</code>, <code>.data</code> — a real PE loader will map these in exactly like it\nwould for <code>notepad.exe</code>.</p>\n<h2 id=\"the-other-half-it-s-also-a-legacy-bios-boot-sector\">|=—[ The other half: it’s <em>also</em> a legacy BIOS boot sector ]</h2>\n<p>Here’s the part that makes this genuinely clever rather than just “the\nkernel happens to be a PE file.” Jump to offset <code>0x1FE</code>, the one place\nin a file that matters to old-school BIOS booting:</p>\n<pre><code>$ xxd -s 0x1fe -l 16 /boot/vmlinuz-linux\n000001fe: 55aa eb6a 4864 7253 0f02 0000 0000 0010  U..jHdrS........</code></pre>\n<p><code>55 aa</code> — the mandatory <strong>BIOS boot-sector signature</strong>, at exactly the\noffset BIOS requires it. Right after it: <code>48 64 72 53</code> = <code>&quot;HdrS&quot;</code>, the\nmagic of the Linux kernel’s own <code>setup_header</code> structure (parsed by\nreal-mode bootloaders like GRUB’s legacy BIOS path, or even the kernel’s\nown built-in real-mode setup code). This is a <strong>completely different,\nolder, BIOS-era boot protocol</strong>, living in the exact same bytes as the\nPE header above it.</p>\n<p>To see why this matters, compare against a <em>pure</em> EFI application on\nthe same disk — GRUB’s own <code>grubx64.efi</code>, which has no reason to ever be\nBIOS-bootable:</p>\n<pre><code>$ xxd -s 0x1fe -l 16 /boot/EFI/GRUB/grubx64.efi\n000001fe: 00c0 2e72 656c 6f63 0000 0020 0000 0050  ...reloc... ...P\n$ file /boot/EFI/GRUB/grubx64.efi\n/boot/EFI/GRUB/grubx64.efi: PE32+ executable for EFI (application),\nx86-64 (stripped to external PDB), 4 sections</code></pre>\n<p>No <code>55 AA</code> at <code>0x1FE</code> — because GRUB’s <code>.efi</code> binary doesn’t need one, it\nis <em>only</em> ever loaded by UEFI firmware, never by a BIOS jumping to a\ndisk sector. <code>vmlinuz</code>, on the other hand, deliberately keeps both\nmarkers valid at once:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Offset</th><th>Bytes</th><th>Meaning</th><th>Consumed by</th></tr></thead><tbody><tr><td><code>0x000</code></td><td><code>4D 5A</code></td><td><code>&quot;MZ&quot;</code> /<code>e_lfanew</code> @<code>0x3C</code></td><td>Any PE loader, incl. UEFI</td></tr><tr><td><code>0x040</code></td><td><code>50 45 00 00</code></td><td><code>&quot;PE\\0\\0&quot;</code> header,<code>Subsystem=EFI_APPLICATION</code></td><td>UEFI firmware</td></tr><tr><td><code>0x1FE</code></td><td><code>55 AA</code></td><td>BIOS boot-sector signature</td><td>Legacy BIOS / GRUB legacy</td></tr><tr><td><code>0x202</code></td><td><code>48 64 72 53</code></td><td><code>&quot;HdrS&quot;</code> , Linux<code>setup_header</code></td><td>BIOS-era Linux bootloaders</td></tr></tbody></table></div>\n<p>One 17 MB file. Two mutually exclusive, decades-apart boot protocols,\nboth satisfied simultaneously, because the byte ranges each protocol\nactually <em>reads</em> don’t overlap.</p>\n<h2 id=\"why-this-is-called-the-efi-stub-and-it-s-in-the-kernel-source\">|=—[ Why: this is called the EFI stub, and it’s in the kernel source ]</h2>\n<p>This mechanism has a name: the <strong>EFI boot stub</strong> (<code>CONFIG_EFI_STUB</code>). It\nwas added to <code>arch/x86/boot/header.S</code> by Matt Fleming, first posted to\nLKML in October 2011 (“x86, efi: EFI boot stub support”). The kernel’s\nown documentation describes the goal directly:</p>\n<p>On the x86 and arm platforms, a kernel zImage/bzImage is made to masquerade as a PE/COFF image, thereby convincing EFI firmware loaders to load it as an EFI executable.</p>\n<p>Annotated, based on the structure of <code>arch/x86/boot/header.S</code>:</p>\n<pre><code>    .code16\n    .section &quot;.bstext&quot;, &quot;ax&quot;\n    .byte    0xeb        # short (2-byte) jump\n    .byte    start_of_setup-1f\n    # ^ written as raw bytes, not a `jmp` mnemonic — the assembler\n    #   would otherwise emit a 3-byte jump and shift every fixed\n    #   offset below it, breaking the boot-sector/PE layout\n#ifdef CONFIG_EFI_STUB\n    .org    0x3c\n    .long    pe_header        # e_lfanew: offset to the PE header\n                    # (this is the value we read above: 0x40)\n    .word    IMAGE_DOS_SIGNATURE    # &quot;MZ&quot; — must be bytes 0-1 of the file\n    ...\npe_header:\n    .long    IMAGE_NT_SIGNATURE    # &quot;PE\\0\\0&quot;\ncoff_header:\n    .word    IMAGE_FILE_MACHINE_AMD64\n    .word    section_count        # NumberOfSections\n    ...\noptional_header:\n    .word    PE_OPT_MAGIC_PE32PLUS    # 0x020B\n    ...\n    .word    IMAGE_SUBSYSTEM_EFI_APPLICATION    # Subsystem = 10\n    ...\n#endif /* CONFIG_EFI_STUB */</code></pre>\n<p>The whole thing is <code>#ifdef</code>’d in specifically because it’s <em>only</em>\nneeded for EFI compatibility — a kernel built without\n<code>CONFIG_EFI_STUB</code> skips all of it and keeps the classic BIOS-only boot\nsector, no MZ/PE bytes at all.</p>\n<p>The other side of the trick, <code>drivers/firmware/efi/libstub/x86-stub.c</code>,\nis the actual code that runs <em>as</em> the EFI application: it’s the\n<code>efi_main()</code> entry point UEFI firmware jumps to after <code>LoadImage()</code>\nsucceeds, which sets up the environment (memory map, initrd, command\nline) and hands off to the kernel’s normal decompression/startup path —\nthe same path a BIOS boot would eventually reach too. Both roads lead to\nthe same kernel; only the on-ramp differs.</p>\n<p>This is also why you can boot a raw <code>vmlinuz</code> file <strong>directly</strong> from a UEFI shell or a UEFI boot entry, no GRUB required — that&#39;s the entire point of the EFI stub. <code>systemd-boot</code> and unified kernel images (UKIs) lean on exactly this property.</p>\n<h2 id=\"reproduce-it-yourself\">|=—[ Reproduce it yourself ]</h2>\n<p>No special tools needed beyond <code>xxd</code>/<code>file</code>, which ship on essentially\nevery Linux box:</p>\n<pre><code># 1. confirm the DOS/PE magic\nxxd -l 4 /boot/vmlinuz-linux                 # expect: 4d 5a 00 00\n# 2. follow e_lfanew to the PE header\noff=$(xxd -s 0x3c -l 4 -e /boot/vmlinuz-linux | awk &#39;{print $2}&#39;)\nxxd -s &quot;$((16#${off}))&quot; -l 4 /boot/vmlinuz-linux   # expect: 50 45 00 00 (&quot;PE\\0\\0&quot;)\n# 3. read the Subsystem field (0x9c for a PE32+ boot header)\nxxd -s 0x9c -l 2 /boot/vmlinuz-linux          # expect: 0a 00 -&gt; 10 (EFI_APPLICATION)\n# 4. confirm it&#39;s still a legacy-bootable BIOS sector too\nxxd -s 0x1fe -l 2 /boot/vmlinuz-linux         # expect: 55 aa\n# 5. let `file` do all of the above for you at once\nfile /boot/vmlinuz-linux</code></pre>\n<p>If you want a broader sweep, <code>grep</code>/<code>binwalk</code> for the magic across your\nwhole <code>/boot</code>:</p>\n<pre><code>$ for f in /boot/vmlinuz-* /boot/EFI/*/*.efi; do\n    sig=$(xxd -l 2 -p &quot;$f&quot; 2&gt;/dev/null)\n    [ &quot;$sig&quot; = &quot;4d5a&quot; ] &amp;&amp; echo &quot;$f: MZ present&quot;\n  done\n/boot/vmlinuz-linux: MZ present\n/boot/EFI/GRUB/grubx64.efi: MZ present</code></pre>\n<p>Every UEFI-loadable file on the system starts the same way — the kernel\nis just the one that’s <em>also</em> something else at the same time.</p>\n<h2 id=\"takeaways\">|=—[ Takeaways ]</h2>\n<ul><li><p><strong><code>MZ</code> in <code>vmlinuz</code> isn’t legacy junk — it’s a spec requirement.</strong> UEFI</p><p>firmware is fundamentally a PE loader; anything meant to run under it\nhas to look like a PE binary, full stop, including the kernel.</p></li><li><p><strong>One file, two file formats, zero conflict</strong> , because BIOS only ever</p><p>reads byte<code>0x1FE</code> and PE loaders only ever follow the<code>e_lfanew</code> pointer from<code>0x3C</code> — the two protocols were designed decades apart\nand happen not to step on each other’s bytes.</p></li><li><p><strong>A single flipped bit can matter enormously.</strong><code>Subsystem = 10</code> is</p><p>the entire difference between “firmware treats this as a bootable EFI\napplication” and “firmware refuses to load it.” Worth remembering\nnext time a “cosmetic” header field looks safe to touch.</p></li><li><p>If you ever need to identify or fingerprint boot files during</p><p>forensics or malware triage: <code>MZ</code> + a<em>valid</em><code>e_lfanew</code> pointing at a\nreal<code>PE\\0\\0</code> is a much stronger signal than the two magic bytes\nalone — check where<code>e_lfanew</code> actually points before trusting the\nfile type.</p></li></ul>\n<h2 id=\"references\">|=—[ References ]</h2>\n<ul><li>Linux kernel docs — The EFI Boot Stub</li><li>Kernel source — <code>arch/x86/boot/header.S</code></li><li>Kernel source — <code>arch/x86/boot/setup.ld</code></li><li>LKML — Matt Fleming, <code>[PATCH v5 10/10] x86, efi: EFI boot stub support</code> , Oct 2011</li><li>Microsoft — PE Format specification (DOS header, <code>e_lfanew</code> ,<code>IMAGE_SUBSYSTEM_*</code> values)</li><li>UEFI Forum — UEFI Specification, §2.1 “PE/COFF Image Loading”</li></ul>","headings":[{"level":1,"text":"Why Your Linux Kernel Starts With 'MZ' (Yes, the DOS/PE One)","id":"why-your-linux-kernel-starts-with-mz-yes-the-dos-pe-one"},{"level":2,"text":"|=—[ TL;DR ]","id":"tl-dr"},{"level":2,"text":"|=—[ Background: two executable formats, two boot worlds ]","id":"background-two-executable-formats-two-boot-worlds"},{"level":2,"text":"|=—[ Hands-on: reading the header byte by byte ]","id":"hands-on-reading-the-header-byte-by-byte"},{"level":2,"text":"|=—[ The other half: it’s *also* a legacy BIOS boot sector ]","id":"the-other-half-it-s-also-a-legacy-bios-boot-sector"},{"level":2,"text":"|=—[ Why: this is called the EFI stub, and it’s in the kernel source ]","id":"why-this-is-called-the-efi-stub-and-it-s-in-the-kernel-source"},{"level":2,"text":"|=—[ Reproduce it yourself ]","id":"reproduce-it-yourself"},{"level":2,"text":"|=—[ Takeaways ]","id":"takeaways"},{"level":2,"text":"|=—[ References ]","id":"references"}]}}