3l0h1m.s3c:     file format elf64-x86-64

 Disassembly of section .text:

 0000000000401000 <_start>:
   401000:   48 89 e5          mov    rbp, rsp
   401003:   bf 01 00 00 00    mov    edi, 0x1        ; web
   401008:   be 02 00 00 00    mov    esi, 0x2        ; mobile
   40100d:   ba 03 00 00 00    mov    edx, 0x3        ; active directory
   401012:   b9 04 00 00 00    mov    ecx, 0x4        ; binary exploitation
   401017:   41 b8 05 00 00 00 mov    r8d, 0x5        ; exploit dev
   40101d:   41 b9 06 00 00 00 mov    r9d, 0x6        ; low level
   401023:   e8 00 00 00 00    call   <blog_main>

Why Your Linux Kernel Starts With 'MZ' (Yes, the DOS/PE One)

2026-09-11 | author: elohim | tags: linux, kernel, uefi, efi, pe-coff, boot, x86, low-level, reverse-engineering

Every 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.

|=—[ TL;DR ]

Your Linux kernel image (/boot/vmlinuz-*) starts with the two bytes 4D 5A — "MZ" — the ancient MS-DOS executable magic number, followed a little further in by a real "PE\0\0" (COFF) header with Subsystem = IMAGE_SUBSYSTEM_EFI_APPLICATION. This is not a coincidence, a joke, or legacy cruft nobody bothered to remove. It’s required by the UEFI specification: UEFI firmware only knows how to load and execute PE32+ binaries. To let a UEFI system boot Linux with zero extra bootloader code, the kernel’s boot header was made to also be a valid PE/COFF executable — while simultaneously remaining a valid legacy BIOS boot sector for machines that don’t have UEFI at all. Same bytes, two boot protocols.

$ file /boot/vmlinuz-linux
/boot/vmlinuz-linux: Linux kernel x86 boot executable, bzImage, ...
32-bit EFI handoff entry point, 64-bit EFI handoff entry point, ...

$ xxd -l 4 /boot/vmlinuz-linux
00000000: 4d5a 0000                                MZ..

|=—[ Background: two executable formats, two boot worlds ]

A few things this post leans on, from scratch:

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).

|=—[ Hands-on: reading the header byte by byte ]

Let’s actually parse vmlinuz by hand, no tools beyond xxd.

Byte 0 — the DOS magic:

$ xxd -l 64 /boot/vmlinuz-linux
00000000: 4d5a 0000 0000 0000 0000 0000 0000 0000  MZ..............
00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 cd23 8281 4000 0000  .........#..@...

4d 5a = "MZ". This is IMAGE_DOS_SIGNATURE. It has to be the very first two bytes of the file for any PE-format tool (or UEFI firmware) to even bother looking further.

Byte 0x3C — the e_lfanew pointer:

$ xxd -s 0x3c -l 4 /boot/vmlinuz-linux
0000003c: 4000 0000                                @...

Little-endian 40 00 00 00 = 0x00000040. This says: “the PE header starts at offset 0x40 in this file.” Every PE parser — including UEFI firmware’s own loader — reads this field to jump straight past the DOS stub.

Following the pointer — the PE/COFF header:

$ xxd -s 0x40 -l 32 /boot/vmlinuz-linux
00000040: 5045 0000 6486 0400 0000 0000 0000 0000  PE..d...........
00000050: 0100 0000 a000 0602 0b02 0214 0090 0601  ................

Reading it field by field:

offset 0x40:  50 45 00 00            "PE\0\0"   IMAGE_NT_SIGNATURE
offset 0x44:  64 86                  Machine    = 0x8664 (AMD64)
offset 0x46:  04 00                  NumberOfSections = 4
offset 0x48:  00 00 00 00            TimeDateStamp = 0 (reproducible build)
offset 0x54:  a0 00                  SizeOfOptionalHeader = 0x00A0
offset 0x56:  06 02                  Characteristics = EXECUTABLE_IMAGE | ...
offset 0x58:  0b 02                  OptionalHeader Magic = 0x020B (PE32+)

0x020B confirms this is a PE32+ image — the 64-bit PE variant, same format used by every native 64-bit Windows .exe and every 64-bit UEFI driver.

The field that actually tells firmware “boot me”: Subsystem.

In a PE32+ optional header, Subsystem sits 68 bytes after the Magic field (0x58 + 68 = 0x9C):

$ xxd -s 0x9c -l 4 /boot/vmlinuz-linux
0000009c: 0a00 0001                                ....

0a 00 = 0x000A = 10 = IMAGE_SUBSYSTEM_EFI_APPLICATION. This single value is the whole point of the exercise: it’s the field UEFI firmware checks to decide “this is an EFI application, I know how to run this,” as opposed to IMAGE_SUBSYSTEM_WINDOWS_CUI (3, a normal Windows console app) or WINDOWS_GUI (2). Change one byte here and firmware would refuse to load a byte-for-byte identical kernel.

The section table — proof it’s a real, structured PE image, not a faked-up 4 bytes of magic:

$ xxd -s 0xf8 -l 40 /boot/vmlinuz-linux
000000f8: 2e73 6574 7570 0000 0030 0000 0010 0000  .setup...0......
00000108: 0030 0000 0010 0000 0000 0000 0000 0000  .0..............
00000118: 0000 0000 4000 0042 2e63 6f6d 7061 7400  ....@..B.compat.

Right after the optional header comes a normal PE section table, 40 bytes per entry, each starting with an 8-byte ASCII name. This kernel image declares (at least) four sections: .setup, .compat, .text, .data — a real PE loader will map these in exactly like it would for notepad.exe.

|=—[ The other half: it’s also a legacy BIOS boot sector ]

Here’s the part that makes this genuinely clever rather than just “the kernel happens to be a PE file.” Jump to offset 0x1FE, the one place in a file that matters to old-school BIOS booting:

$ xxd -s 0x1fe -l 16 /boot/vmlinuz-linux
000001fe: 55aa eb6a 4864 7253 0f02 0000 0000 0010  U..jHdrS........

55 aa — the mandatory BIOS boot-sector signature, at exactly the offset BIOS requires it. Right after it: 48 64 72 53 = "HdrS", the magic of the Linux kernel’s own setup_header structure (parsed by real-mode bootloaders like GRUB’s legacy BIOS path, or even the kernel’s own built-in real-mode setup code). This is a completely different, older, BIOS-era boot protocol, living in the exact same bytes as the PE header above it.

To see why this matters, compare against a pure EFI application on the same disk — GRUB’s own grubx64.efi, which has no reason to ever be BIOS-bootable:

$ xxd -s 0x1fe -l 16 /boot/EFI/GRUB/grubx64.efi
000001fe: 00c0 2e72 656c 6f63 0000 0020 0000 0050  ...reloc... ...P

$ file /boot/EFI/GRUB/grubx64.efi
/boot/EFI/GRUB/grubx64.efi: PE32+ executable for EFI (application),
x86-64 (stripped to external PDB), 4 sections

No 55 AA at 0x1FE — because GRUB’s .efi binary doesn’t need one, it is only ever loaded by UEFI firmware, never by a BIOS jumping to a disk sector. vmlinuz, on the other hand, deliberately keeps both markers valid at once:

OffsetBytesMeaningConsumed by
0x0004D 5A"MZ" / e_lfanew @ 0x3CAny PE loader, incl. UEFI
0x04050 45 00 00"PE\0\0" header, Subsystem=EFI_APPLICATIONUEFI firmware
0x1FE55 AABIOS boot-sector signatureLegacy BIOS / GRUB legacy
0x20248 64 72 53"HdrS", Linux setup_headerBIOS-era Linux bootloaders

One 17 MB file. Two mutually exclusive, decades-apart boot protocols, both satisfied simultaneously, because the byte ranges each protocol actually reads don’t overlap.

|=—[ Why: this is called the EFI stub, and it’s in the kernel source ]

This mechanism has a name: the EFI boot stub (CONFIG_EFI_STUB). It was added to arch/x86/boot/header.S by Matt Fleming, first posted to LKML in October 2011 (“x86, efi: EFI boot stub support”). The kernel’s own documentation describes the goal directly:

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.

Annotated, based on the structure of arch/x86/boot/header.S:

	.code16
	.section ".bstext", "ax"
	.byte	0xeb		# short (2-byte) jump
	.byte	start_of_setup-1f
	# ^ written as raw bytes, not a `jmp` mnemonic — the assembler
	#   would otherwise emit a 3-byte jump and shift every fixed
	#   offset below it, breaking the boot-sector/PE layout

#ifdef CONFIG_EFI_STUB
	.org	0x3c
	.long	pe_header		# e_lfanew: offset to the PE header
					# (this is the value we read above: 0x40)

	.word	IMAGE_DOS_SIGNATURE	# "MZ" — must be bytes 0-1 of the file

	...

pe_header:
	.long	IMAGE_NT_SIGNATURE	# "PE\0\0"

coff_header:
	.word	IMAGE_FILE_MACHINE_AMD64
	.word	section_count		# NumberOfSections
	...

optional_header:
	.word	PE_OPT_MAGIC_PE32PLUS	# 0x020B
	...
	.word	IMAGE_SUBSYSTEM_EFI_APPLICATION	# Subsystem = 10
	...
#endif /* CONFIG_EFI_STUB */

The whole thing is #ifdef’d in specifically because it’s only needed for EFI compatibility — a kernel built without CONFIG_EFI_STUB skips all of it and keeps the classic BIOS-only boot sector, no MZ/PE bytes at all.

The other side of the trick, drivers/firmware/efi/libstub/x86-stub.c, is the actual code that runs as the EFI application: it’s the efi_main() entry point UEFI firmware jumps to after LoadImage() succeeds, which sets up the environment (memory map, initrd, command line) and hands off to the kernel’s normal decompression/startup path — the same path a BIOS boot would eventually reach too. Both roads lead to the same kernel; only the on-ramp differs.

This 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.

|=—[ Reproduce it yourself ]

No special tools needed beyond xxd/file, which ship on essentially every Linux box:

# 1. confirm the DOS/PE magic
xxd -l 4 /boot/vmlinuz-linux                 # expect: 4d 5a 00 00

# 2. follow e_lfanew to the PE header
off=$(xxd -s 0x3c -l 4 -e /boot/vmlinuz-linux | awk '{print $2}')
xxd -s "$((16#${off}))" -l 4 /boot/vmlinuz-linux   # expect: 50 45 00 00 ("PE\0\0")

# 3. read the Subsystem field (0x9c for a PE32+ boot header)
xxd -s 0x9c -l 2 /boot/vmlinuz-linux          # expect: 0a 00 -> 10 (EFI_APPLICATION)

# 4. confirm it's still a legacy-bootable BIOS sector too
xxd -s 0x1fe -l 2 /boot/vmlinuz-linux         # expect: 55 aa

# 5. let `file` do all of the above for you at once
file /boot/vmlinuz-linux

If you want a broader sweep, grep/binwalk for the magic across your whole /boot:

$ for f in /boot/vmlinuz-* /boot/EFI/*/*.efi; do
    sig=$(xxd -l 2 -p "$f" 2>/dev/null)
    [ "$sig" = "4d5a" ] && echo "$f: MZ present"
  done
/boot/vmlinuz-linux: MZ present
/boot/EFI/GRUB/grubx64.efi: MZ present

Every UEFI-loadable file on the system starts the same way — the kernel is just the one that’s also something else at the same time.

|=—[ Takeaways ]

|=—[ References ]

- - - EOF - - -