Wiki / Concepts

ELF Format

The container for Linux executables and libraries. Its header tells you the CPU, endianness, and linking, everything you need before opening a disassembler.

ELF file layout (link view)
ELF header 0x0
Program headers
.text (code)
.rodata (constants)
.data / .bss
Section headers + .symtab
Two views of the same file: program headers group bytes into loadable segments for the OS; section headers name fine-grained sections (.text, .rodata, .symtab) for tools. Stripping removes .symtab/.strtab and often the section headers entirely.

ELF (Executable and Linkable Format) is the standard container for executables, shared libraries, and object files on Linux and most embedded Unix systems. The binaries you pull out of a rootfs are almost always ELF.

What it is

An ELF file wraps compiled code and data in a self-describing container. It starts with the magic \x7fELF, followed by a header that records the class (32- or 64-bit), the endianness, the target machine (ARM, MIPS, Xtensa, RISC-V, x86), the entry point, and the offsets of two tables. Those two tables give two views of the same bytes: the program header table describes loadable segments for the operating system, and the section header table describes named sections (.text, .rodata, .data, .bss, .symtab) for linkers and analysis tools.

Why it matters

Before you can reverse a binary you have to load it correctly, and the header tells you how. The machine field selects the right Ghidra or IDA processor; the class and endianness set the disassembly mode; the dynamic section lists the shared libraries the binary needs, which hints at its capabilities. Get these wrong and the disassembler produces nonsense. Reading the header first turns a mystery blob into a target you can set up in seconds.

How it works

At load time the kernel and dynamic linker read the program headers and map each PT_LOAD segment into memory with the right permissions: .text read-execute, .rodata read-only, .data/.bss read-write. Section headers are not needed to run the program at all; they exist for tooling, which is why they can be stripped. The symbol table (.symtab) and string table (.strtab) map addresses to function and variable names, and the dynamic symbol table (.dynsym) lists imported and exported symbols for linking.

How to inspect it

file and readelf answer the setup questions immediately.

file ./binary                 # arch, endianness, static vs dynamic, stripped?
readelf -h ./binary           # full header: class, machine, entry point
readelf -d ./binary           # dynamic section: NEEDED shared libraries
readelf -S ./binary           # section headers: .text, .rodata, .symtab
nm ./binary                   # symbol table (empty if stripped)
strings -a ./binary           # embedded strings: URLs, format specifiers, secrets

file in one line tells you the CPU and whether the binary is stripped; readelf -h and -d give the architecture and the linked libraries so you can pick the matching processor. .rodata and strings surface hardcoded URLs, error messages, and sometimes credentials that shortcut the whole analysis. Any surviving symbols make the disassembly dramatically easier to read.

Pitfalls

  • Stripped binaries drop .symtab and often the section headers; the program headers still tell you where code loads, so reverse from the entry point and imports.
  • The .bss section occupies no file bytes (it is zero-filled at load), so its size in the file is zero even though it uses memory at runtime.
  • Do not assume x86. Embedded ELF is frequently big-endian MIPS or Xtensa; always confirm the machine field before loading.
  • A valid ELF header can be faked or corrupted. If tools disagree, trust readelf on the raw bytes over a file extension.

Further reading