Docuboxer
By Sergio Alonzo Piña··7 min read

Why the file extension can't be trusted (magic bytes can)

Renaming an .exe to .pdf changes nothing about what it is. What magic bytes are, how double extensions trick you, and how to verify a file without opening it.

A file extension is just text after a dot in the name. You can change it in a second and not one byte of the contents moves. What a file actually is lives in its first few bytes — the file signature, better known as magic bytes — and renaming never touches those. An .exe renamed to .pdf is still a Windows executable. You can check a file's real type against its extension right in your browser, with nothing uploaded anywhere.

What magic bytes are

Nearly every binary format opens with a fixed byte sequence that works as an identity card. It is two to eight bytes long, it sits inside the file, and it survives any rename. The ones worth memorising:

  • PDF — %PDF- (hex 25 50 44 46 2D). It is plain readable text: open any PDF in a text editor and the first line starts there.
  • PNG — 89 50 4E 47 0D 0A 1A 0A, which reads as \x89PNG followed by line-break bytes. Those odd bytes are deliberate: they break loudly if a transfer mangles the file.
  • JPEG — FF D8 FF.
  • GIF — GIF87a or GIF89a, also readable.
  • ZIP — PK (50 4B 03 04), the initials of Phil Katz, who created the format. This is the useful one: .docx, .xlsx, .pptx, .odt, .jar, .apk and .epub are all ZIP archives underneath, so they all start with PK.
  • Windows executables (.exe, .dll, .scr) — MZ (4D 5A), the initials of Mark Zbikowski, the Microsoft engineer who designed the format.
  • Linux executables — 7F 45 4C 46, a control byte followed by ELF.
  • Legacy Office documents (.doc, .xls, .ppt) — D0 CF 11 E0 A1 B1 1A E1, the OLE2 compound file container.

Unix has leaned on this for decades. The file command ignores the name completely and answers from the bytes, using a signature database called libmagic. file --mime-type report.pdf is the fastest honest answer you can get on macOS or Linux. Windows ships no equivalent, which is part of why the trick below keeps working there.

The extension picks the handler, not the type

Windows decides which program opens a file almost entirely from its extension, via a lookup table in the registry. Double-click something ending in .exe and it runs; double-click something ending in .pdf and the PDF reader is launched. That decision happens before anything reads the contents.

This produces an asymmetry worth getting straight. Rename an executable to .pdf and a double click no longer runs it — Windows hands it to the PDF reader, which chokes because there is no %PDF- at the front. The file is not disarmed; renaming it back or invoking it from a shell runs it fine. But the mismatch itself is a loud signal, because nobody renames files like that by accident. macOS and Linux weigh the contents and system metadata more heavily, though they still use the extension as a hint.

The double-extension trick

The classic attack is not renaming an executable to look like a document. It is naming it invoice.pdf.exe. The system reads the last extension, .exe, and runs it. The person reads the first one and expects an invoice.

It works because File Explorer hides extensions for known file types by default, and .exe qualifies, so what you see on screen is literally invoice.pdf. Add a PDF icon compiled into the executable and the disguise is complete. First fix: turn on "File name extensions" in the Explorer View tab and never turn it off.

There is a nastier variant that abuses a Unicode right-to-left override character (U+202E) placed mid-name. Everything after it renders backwards, so a file whose real name ends in .exe can be displayed ending in .pdf. Reading carefully does not save you from that one; only looking at the bytes does.

Where signature checks stop working

Magic bytes are strong evidence, not a universal answer. Three caveats:

  • Signatures at an offset. MP4 and related video containers put ftyp at byte 4, not byte 0. TAR archives put ustar at byte 257. A naive check that only reads the first four bytes reports "unknown" for perfectly valid files.
  • Formats with no signature at all. .txt, .csv, .json, .html and .svg are plain text with no magic number, so all you can do is parse the structure — a heuristic. SVG is the awkward case: it is text that browsers execute scripts inside.
  • Shared signatures. PK tells you it is a ZIP container, not whether it holds a spreadsheet, an Android app, or a compressed executable. The next step is listing what is inside the archive.

That last point has a common real-world case: a file advertised as .docx that starts with D0 CF 11 E0 instead of PK is not a modern document at all. It is a legacy OLE2 container wearing a modern extension — the format where classic macro payloads live. Worth a second look every time.

If you accept uploads, extension checks are not validation

Anyone building a file upload hits this from the other side. Both signals the client gives you — the file name and the Content-Type header — are attacker-controlled, so neither is evidence. A defensible upload path:

  • Read the leading bytes server-side and derive the type yourself, then reject anything outside your allow-list.
  • Never reuse the uploaded file name for storage. Generate your own, and derive the extension from the type you detected.
  • Serve the file back with an explicit Content-Type and X-Content-Type-Options: nosniff, so browsers cannot decide for themselves that your "image" is really HTML and render it.
  • Serve user uploads from a separate origin when you can, so anything that does slip through is not same-origin with your app.

A related Windows detail worth knowing as a user: browser downloads get tagged with a Mark of the Web, which is what makes Office open them in Protected View. Files extracted from archives have historically shed that tag — one reason so much malicious mail arrives as a ZIP rather than a bare attachment.

Entropy: why a packed executable looks encrypted

Entropy measures how much information each byte carries, on a 0-to-8 scale. English prose sits around 4 to 5 — it is full of repetition. Compressed or encrypted data pushes toward 8, because good compression works by removing every repetition, and the result is statistically indistinguishable from noise.

A normal executable sits in the middle, so a high reading suggests the code is packed: compressed or encrypted with a small loader that unpacks it in memory at launch. Packing legitimately shrinks binaries and deters copying, and it is also the standard way to frustrate analysis. Be honest about what the number proves: a legitimate ZIP, a JPEG and a packed binary all read high. High entropy accuses nobody. It tells you there is no plain text inside — context, never a verdict.

How to check a file without opening it

In order of effort:

  • Compare the extension against the signature. The file inspector reads the leading bytes, names the real type, measures entropy, and lists the contents when the file is a ZIP. It runs in your browser — the file is never uploaded.
  • Hash it. The hash generator gives you a SHA-256. Two uses: confirming a download matches the checksum a vendor publishes, and looking that hash up in reputation services without handing them the file. Searching by hash reveals nothing about your contents; uploading the file does.
  • If it really is a PDF, look inside it. A correct signature does not close the case. Genuine PDFs can carry JavaScript, auto-run actions and embedded attachments — the PDF scanner lists those markers.
  • If it is an image, read the metadata. The EXIF viewer shows what is attached: GPS coordinates, camera model, original timestamp. Useful both for checking provenance and for stripping what you would rather not share.

And the caveat that holds all of this together: matching is not the same as safe. Comparing the extension against the magic bytes rules out the disguise, which is the cheapest and most common trick going. It says nothing about whether a file that genuinely is what it claims to be does something you did not ask for. No signature check replaces an antivirus, and no antivirus replaces not opening things you were not expecting.

Frequently asked questions

What is the difference between a file extension and a file signature?

The extension is text in the file name, after the last dot — anyone can change it in a second without touching the contents. The file signature, or magic bytes, is a fixed sequence at the start of the data itself: %PDF- for PDFs, PK for ZIP containers, MZ for Windows executables. The extension is a label; the signature is part of the file.

Does renaming an .exe to .pdf make it harmless?

No. Renaming changes the label, not a single byte of the payload. All it changes is which program Windows hands the file to on a double click. Rename it back, or launch it from a shell, and it runs exactly as before. The mismatch is a red flag, not a fix.

How do I check a file's real type on Windows, macOS or Linux?

On macOS and Linux, the file command reads the leading bytes and ignores the name entirely: file report.pdf tells you what it actually is. Windows has no built-in equivalent, so use a local inspector that reads the signature in the browser and compares it against the extension.

Why does Windows show invoice.pdf when the file is invoice.pdf.exe?

File Explorer hides extensions for known file types by default, and .exe is a known type. The real name ends in .exe, but the visible name stops at invoice.pdf. Turn on File name extensions in the Explorer View tab and leave it on permanently.

If the extension matches the magic bytes, is the file safe?

No. A matching signature only proves the file is the type it claims to be. A genuine PDF can still carry JavaScript and auto-run actions, and a genuine .docx can still carry macros. Matching rules out the disguise, not the payload — clean on this check is not the same as safe.

Is checking the extension enough to validate an upload?

No. Extension checks are trivially bypassed, and so is the Content-Type header the browser sends, since both are attacker-controlled. Validate uploads by reading the leading bytes server-side, store files with a name you generate yourself, and serve them with an explicit Content-Type plus X-Content-Type-Options: nosniff.

Find out what that file really is

Extension vs magic bytes, hashes, entropy and ZIP contents — in your browser, nothing uploaded.

Open the file inspector →

Related tools

You might also like: what your photos reveal about you and what PDF metadata gives away.