ByteScope

ELF executable file format

Executable and Linkable Format — the Linux/Unix binary and shared-object format.

MIME type
application/x-elf
Extensions
.elf, .so, .o, .out
Category
Executables & bytecode

Magic-number signature

0x000000007F454C46·ELF0x7F 'ELF'

About the ELF executable format

ELF is the executable, shared-library, object-file and core-dump format of Linux, Android's native layer, the BSDs and most embedded toolchains. The first sixteen bytes are e_ident, a self-describing preamble: the magic, then a byte saying whether this is a 32-bit or 64-bit file and a byte saying whether it is little- or big-endian, which is what lets one parser read a binary for a machine it has never run on. Everything after e_ident comes in two tables — program headers, which the kernel uses to map the file into memory, and section headers, which the linker and the debugger use and which a stripped binary can survive losing.

Header layout, byte by byte

OffsetLengthFieldMeaning
04EI_MAG0..3 (magic)7F 45 4C 46 — a deliberately non-ASCII byte followed by the letters ELF, so a file mangled by a text-mode transfer or truncated to seven bits stops matching immediately.
41EI_CLASS1 for ELFCLASS32, 2 for ELFCLASS64. This single byte decides the width of every address field later in the header, so on a 32-bit file each of e_entry, e_phoff and e_shoff shrinks to four bytes and everything after offset 24 shifts down by twelve.
51EI_DATA1 for two's-complement little-endian, 2 for big-endian. It governs how every multi-byte field in the entire file is read, which is why a MIPS or older PowerPC binary looks like nonsense to a parser that assumes little-endian.
61EI_VERSIONThe ELF header version, always 1. A value of 0 or anything above 1 means the header is damaged, not that a newer ELF exists.
71EI_OSABI0 is UNIX System V, which is what most Linux toolchains actually emit; 3 is GNU/Linux, 9 FreeBSD, 12 OpenBSD. It only matters for interpreting OS-specific section and symbol types, so 0 versus 3 on a Linux binary is normal and not a defect.
81EI_ABIVERSIONSub-version of the ABI named above, almost always 0.
97EI_PADZero padding that rounds e_ident out to sixteen bytes. Non-zero bytes here are a reliable smell of a hand-patched or hand-forged header.
162e_type1 REL, a relocatable object file; 2 EXEC, a fixed-address executable; 3 DYN, which covers both a shared object and every position-independent executable built in the last decade; 4 CORE, a crash dump. The header alone cannot separate a .so from a PIE — that needs DF_1_PIE in the dynamic section.
182e_machineThe target CPU: 62 for x86-64, 183 for AArch64, 3 for i386, 40 for 32-bit ARM, 243 for RISC-V. This is the field to read when a library refuses to load with a wrong-architecture error.
204e_versionObject file version, again always 1. It duplicates EI_VERSION at a different width, a leftover of the original ABI.
248e_entryVirtual address the kernel jumps to after mapping the image; four bytes at this same offset on a 32-bit file. Shared objects normally report 0 here because they have no entry point of their own.
328e_phoffFile offset of the program header table. In practice this is 64 — the table starts immediately after the header — because the loader wants the segments in the first page it reads.
408e_shoffFile offset of the section header table, which normally sits at the very end of the file. Zero means the sections were stripped, and a value past the end of the file is the usual shape of a truncated binary.
484e_flagsProcessor-specific flags. Empty on x86-64, but on ARM and RISC-V it encodes the float ABI and instruction-set options, which is how a link fails with a soft-float versus hard-float mismatch.
522e_ehsizeSize of this header: 64 on a 64-bit file, 52 on a 32-bit one. It is a cheap self-consistency check — if it disagrees with EI_CLASS, stop trusting the rest.
542e_phentsizeBytes per program header entry: 56 on 64-bit, 32 on 32-bit. Fixed by the ABI, so a different value means the file is damaged rather than exotic.
562e_phnumNumber of program headers. Typically under a dozen; the special value 0xFFFF means the real count lives in the first section header, a workaround for binaries with enormous segment tables.
582e_shentsizeBytes per section header entry: 64 on 64-bit, 40 on 32-bit.
602e_shnumNumber of section headers. Multiply by e_shentsize, add e_shoff, and on an intact file the result is exactly the file size when the section table is last — the fastest truncation check there is.
622e_shstrndxIndex of the section holding section names. Without it every section is nameless, so a tool that prints numbered sections and no names is usually reading a file where this index is out of range.

Telling it apart from its neighbours

Easily confused withHow to tell
PE (Windows executable)The Windows counterpart, and the confusion is practical rather than byte-level: cross-compiled build outputs land in the same directory. PE opens with 4D 5A ('MZ') and hides its real header behind a four-byte pointer at offset 0x3C; ELF says 7F 45 4C 46 and puts everything at fixed offsets. A .so that is actually a .dll fails at offset 0.
Mach-O executableSame job on macOS. Mach-O opens with CF FA ED FE (64-bit little-endian) or CE FA ED FE (32-bit), never 7F 45 4C 46, and a universal binary opens CA FE BA BE. Both formats use the extension .o for object files and .dylib versus .so for libraries, so the extension proves nothing and the first four bytes prove everything.
ar archive (.a)A static library is not an ELF file: it is an ar archive whose members are ELF objects. It begins with the eight bytes 21 3C 61 72 63 68 3E 0A ('!<arch>' plus a newline), and the first 7F 45 4C 46 appears some sixty bytes in, after the first member header. Tools that report 'not an ELF file' on a .a are correct.
Core dumpA core file is a real ELF file with the same header, which is why the file command calls it ELF. The separator is e_type at offset 16: 4 means CORE, and such a file has program headers but no section headers and no symbols, so a debugger needs the original binary alongside it.
PIEA shared object and a position-independent executable both report e_type 3 (DYN), so the header genuinely cannot tell them apart — this is not a shortcut anyone has failed to find. The distinction is DF_1_PIE in the DT_FLAGS_1 entry of the dynamic section, which is exactly what readelf checks before printing 'Position-Independent Executable file'.

How these files break

  • Stripped versus damaged sections. A stripped binary sets e_shoff to zero and still runs perfectly, because the kernel only reads program headers; a damaged one leaves e_shoff pointing past the end of the file. Both make readelf print nothing useful, and only the e_shoff value says which of the two you are holding.
  • Truncation. The section header table normally lives at the end of an ELF file, so an interrupted download loses it first: the program headers parse, the entry point looks sane, and every section-based tool fails. Check e_shoff plus e_shnum times e_shentsize against the real file size.
  • Text-mode transfer. Copying a binary through a client that translates line endings turns every 0D 0A into 0A, shifting everything after the first occurrence. The magic often survives, so the file still identifies as ELF while the program headers point at nonsense offsets.
  • A wrong EI_CLASS or EI_DATA byte. Flip byte 4 or byte 5 and the header is still 'valid' — the parser simply reads every subsequent field at the wrong width or the wrong endianness, and reports gigantic offsets and an absurd section count instead of admitting the file is broken.
  • Patched in place without fixing offsets. Editing a string in .rodata to something longer, or inserting bytes anywhere, moves the section and program header tables without updating e_shoff and e_phoff. The binary loads until it jumps somewhere that is no longer code.

Where the format stands today

ELF is what every Linux program, shared library and kernel module is, what Android ships in lib/ inside an APK, and what almost every embedded toolchain emits before the image is flattened to a raw image or Intel HEX. It has no serious competition on Unix: a.out and COFF were gone by the mid-nineties, and even the formats that replaced ELF for distribution — a container image, a Flatpak, an APK — are archives full of ELF files. The places it does not reach are Windows, which uses PE, and Apple platforms, which use Mach-O, so a cross-compiled build tree contains all three and only the first four bytes tell them apart. For anything WebAssembly touches, ELF is what the toolchain uses internally before emitting a .wasm module, which is why an emscripten build directory is full of ELF objects that never ship.

Open ELF 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 ELF executable — compare their leading bytes.

FormatFirst bytesExtension
PE (Windows executable)4D 5A.exe
Mach-O executableCF FA ED FE.dylib
WebAssembly00 61 73 6D.wasm
Android DEX64 65 78 0A.dex

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 ELF executable format? Identify it with the File Type Identifier — it reads the magic number locally, nothing is uploaded. Or browse all formats.