ByteScope

Mach-O executable file format

A macOS/iOS Mach-O executable, dylib or bundle.

MIME type
application/x-mach-binary
Extensions
.dylib, .bundle, .o
Category
Executables & bytecode

Magic-number signature

0x00000000CFFAEDFE····Mach-O 64-bit LE magic

About the Mach-O executable format

Mach-O is the native binary format of macOS and iOS, and unlike ELF it puts nothing in fixed-size tables: after a seven- or eight-field header comes a stream of variable-length load commands, each announcing its own type and size, and the loader walks them in order. That design is why one file can carry segment maps, the dynamic linker's path, the minimum OS version, the entry point and a code signature without any of them having a reserved slot. The magic doubles as an endianness and width marker: CF FA ED FE is a 64-bit little-endian image, CE FA ED FE the 32-bit one, and the byte-reversed forms belong to the big-endian PowerPC era.

Header layout, byte by byte

OffsetLengthFieldMeaning
04magicCF FA ED FE on disk for a 64-bit little-endian image — the value 0xFEEDFACF read little-endian. CE FA ED FE is the 32-bit form (0xFEEDFACE), and FE ED FA CF / FE ED FA CE are the big-endian variants a PowerPC binary carries. Four bytes therefore tell you the format, the word size and the byte order at once.
44cputype0x01000007 for x86-64 and 0x0100000C for arm64: the high bit 0x01000000 means 64-bit, and the low bits are the base architecture (7 for x86, 12 for ARM). A 32-bit build of the same architecture drops that flag, so 7 alone is i386 and 12 alone is 32-bit ARM.
84cpusubtypeThe variant within the architecture, with the top bits used as capability flags. Apple silicon binaries commonly show 0x80000002 here — subtype 2, arm64e, with the high flag set — which is the field that separates an arm64e system binary from an ordinary arm64 app.
124filetype1 MH_OBJECT (a .o), 2 MH_EXECUTE, 6 MH_DYLIB, 8 MH_BUNDLE, plus MH_DSYM for a debug companion. This is the byte-level answer to whether the thing you are holding is a program, a library or a loadable plug-in, since .dylib, .so and no extension at all are all used in practice.
164ncmdsHow many load commands follow the header. A trivial command-line tool has roughly fifteen to twenty; a large framework has many more, and each one is walked in sequence because there is no index.
204sizeofcmdsTotal bytes occupied by all load commands. Walking each command's own cmdsize and summing must land exactly on this number — the strongest single integrity check the format offers, and the first thing to run on a file you suspect has been edited.
244flagsAttribute bits: whether the image is a PIE, whether two-level namespace lookup is used, whether it has no undefined symbols. A typical modern executable reads something like 0x00200085 here.
284reservedPresent only in the 64-bit header and always zero. It exists purely to align what follows to eight bytes, which is why the first load command sits at 32 in a 64-bit file and at 28 in a 32-bit one.
32variableFirst load commandEvery load command starts with a four-byte cmd and a four-byte cmdsize, so a reader can skip one it does not understand. LC_SEGMENT_64 (0x19) usually comes first, and the commands that matter for triage are LC_LOAD_DYLIB, LC_MAIN, LC_CODE_SIGNATURE and LC_BUILD_VERSION.

Telling it apart from its neighbours

Easily confused withHow to tell
Mach-O (universal/fat)A universal binary is not a Mach-O file with extra fields — it is a wrapper holding several complete Mach-O images. It opens CA FE BA BE (big-endian) instead of CF FA ED FE, and the thin binary's magic appears further in, at the offset each fat_arch entry names. Reading a fat file as thin gives a nonsense cputype straight away.
Java class fileThe famous collision, but only with the fat form: a Java class file also begins CA FE BA BE. The next four bytes settle it — a class file's major_version (bytes 6-7) is 45 or above, while a fat binary's nfat_arch is a small count whose upper half is therefore zero.
ELF executableBoth use .o for objects and both call their shared libraries by a bare name, so the extension is no help; a .so built on Linux and a .dylib built on macOS look identical in a directory listing. Offset 0 decides: 7F 45 4C 46 is ELF, CF FA ED FE is Mach-O. Note that Mach-O's magic is stored byte-reversed on disk, so a hex dump reads CF FA ED FE while documentation writes 0xFEEDFACF.
Big-endian (CIGAM) Mach-OFiles for the PowerPC era store the same magic byte-swapped: FE ED FA CE and FE ED FA CF. Every field in the header and in every load command is then big-endian too, so a parser that recognises only the little-endian magics does not misread these — it correctly refuses them.

How these files break

  • Any in-place edit at all, on Apple silicon. arm64 code is signed even when the signature is ad-hoc, so changing a single byte in the text segment invalidates the hash in LC_CODE_SIGNATURE and the kernel kills the process on launch rather than running the modified code. Patching a Mach-O means re-signing it; there is no equivalent of quietly editing a byte in an ELF.
  • Truncation. The load commands sit at the front and parse fine, so a partial download reports the right architecture and file type while segment data past the cut is missing. The check is walking the load commands and testing each segment's fileoff plus filesize against the actual file length.
  • sizeofcmds no longer matching the commands. Inserting or removing a load command without fixing ncmds and sizeofcmds leaves a header that looks plausible and a walk that overruns into segment data — the reason a tool reports an absurd load-command type instead of an error message.
  • A slice extracted from a fat binary with the wrong length. Copying out a slice using the fat header's offset but somebody else's size yields a file with a perfect Mach-O header and a truncated tail, which is the usual result of hand-carving architectures instead of using lipo.
  • Text-mode transfer or a stray BOM. Standard for any binary: line-ending translation shifts everything after the first 0D 0A, and a prepended BOM pushes the magic off offset 0, at which point the file is unrecognisable even though every byte of code is still present.

Where the format stands today

Mach-O is what every macOS and iOS executable, framework, dylib, kernel extension and .o file is, and it is the only format Apple's loader accepts, so nothing has ever displaced it there. It is inherited from NeXTSTEP by way of Mach, which is why its vocabulary — load commands, segments and sections, dyld — differs from ELF's for the same ideas. In practice most shipped Mach-O files today are single-architecture arm64 images inside an app bundle; the universal form is what a developer builds during a transition and what Homebrew and Rosetta-era software still ship. For anyone inspecting bytes, the important consequence of Apple's code-signing rules is that Mach-O is effectively read-only: you can dissect it freely, but a modified copy will not launch until it is signed again.

Open Mach-O executable files on this site

Every one of these runs in your browser — the file never leaves your machine.

Related formats

Formats that are easy to confuse with Mach-O executable — compare their leading bytes.

FormatFirst bytesExtension
Mach-O (universal/fat)CA FE BA BE.dylib
ELF executable7F 45 4C 46.elf
PE (Windows executable)4D 5A.exe

Frequently asked questions

What is a magic number in a file?

The first few bytes of a file, which identify its format regardless of what the name says. A PNG always starts with 89 50 4E 47; a ZIP with 50 4B 03 04. Operating systems, browsers and the file command all read these bytes rather than trusting the extension, which is why renaming a file does not change what it actually is.

How do I check a file's magic number myself?

On macOS or Linux, xxd -l 16 yourfile prints the first 16 bytes; on Windows, Format-Hex -Path yourfile -Count 16 in PowerShell. Or drop the file into the File Type Identifier on this site, which reads the bytes locally and names the format for you — nothing is uploaded.

Why do two formats share the same magic number?

Because one is built on the other. Every docx, xlsx, pptx, apk, jar and epub begins with the ZIP signature, since all of them are ZIP archives underneath; telling them apart means looking at the entries inside. Java class files and macOS universal binaries both start with CA FE BA BE and are distinguished by the field that follows.

Can a file have no magic number at all?

Yes. Text-based formats — XML, HTML, JSON, SVG, CSV — have no fixed byte signature, so they are recognized structurally instead: a leading <?xml declaration, a <svg element, or a { followed by a quoted key. That detection is a good guess rather than a certainty, which is why this reference marks those formats as probe-detected.

Does a matching magic number mean the file is safe?

No. It tells you what the format is, not whether the contents are harmless — a genuine PDF or docx can still carry a malicious payload. Identifying the true type is a useful first check, especially when the extension disagrees with the bytes, but it is not a malware scan.

Have a file that might be in the Mach-O executable format? Identify it with the File Type Identifier — it reads the magic number locally, nothing is uploaded. Or browse all formats.