ELF¶
Level: 201 · for anyone with a hex editor open
One line: Byte 4 of an ELF file says how wide its offsets are and byte 5 says which end comes first, so the header is the one format in this chapter that tells its reader how to read the rest of it — a struct format string can be built from those two bytes, and the same seven fields are 52 bytes in one file and 64 in another.
What the loader reads¶
The Executable and Linking Format is the object format of Linux, the BSDs, Solaris and most embedded toolchains, defined in the System V ABI ↗. Every file opens with sixteen bytes called e_ident, and the first multi-byte number is not until byte 16. That ordering is the design: everything a reader needs in order to read a number is in the single bytes before the first number.
| offset | field | width | what it says |
|---|---|---|---|
| 0 | EI_MAG0..3 |
4 | 7f 45 4c 46 — a byte no text file begins with, then the letters ELF |
| 4 | EI_CLASS |
1 | 1 = 32-bit, 2 = 64-bit: the width of every address and offset field |
| 5 | EI_DATA |
1 | 1 = little-endian, 2 = big-endian: the byte order of every field after this one |
| 6 | EI_VERSION |
1 | 1 |
| 7 | EI_OSABI |
1 | 0 for System V, 3 for Linux, and so on |
| 8 | EI_ABIVERSION |
1 | usually 0 |
| 9 | pad | 7 | zero |
| 16 | e_type |
2 | 1 REL, 2 EXEC, 3 DYN, 4 CORE |
| 18 | e_machine |
2 | 3 i386, 0x28 ARM, 0x3E x86-64, 0xB7 AArch64, 0xF3 RISC-V |
| 20 | e_version |
4 | 1 |
| 24 | e_entry |
4 or 8 | where execution starts |
e_phoff, e_shoff |
4 or 8 each | file offsets of the program header table and the section header table | |
e_flags |
4 | processor-specific | |
e_ehsize |
2 | this header's size: 52 or 64 | |
e_phentsize, e_phnum |
2 each | size and count of program headers: 32 × n or 56 × n | |
e_shentsize, e_shnum |
2 each | size and count of section headers: 40 × n or 64 × n | |
e_shstrndx |
2 | which section holds the section names |
Three fields grow when EI_CLASS is 2, and that is the whole of the 52-to-64 difference. Ghidra's ElfHeader reads them in exactly that way — if (is32Bit()) readNextUnsignedInt() else readNextLong() three times, after reading the class byte — and the Python program below does the same with a format string.
In Python¶
Verified output of elf_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. THE FIRST SIXTEEN BYTES SAY HOW TO READ THE REST
------------------------------------------------------------------------
64-bit, little-endian 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
32-bit, big-endian 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
byte 0..3 7f 45 4c 46 a byte no text file starts with, then 'ELF'
byte 4 EI_CLASS 1 = 32-bit offsets, 2 = 64-bit offsets
byte 5 EI_DATA 1 = little-endian, 2 = big-endian
byte 6 EI_VERSION 1, the only value there has ever been
byte 7 EI_OSABI 0 = System V; 9..15 the pad
2. THE SAME SEVEN FIELDS, 52 OR 64 BYTES
------------------------------------------------------------------------
64-bit LSB 64 bytes format string '<HHIQQQIHHHHHH'
0010 02 00 3e 00 01 00 00 00 00 10 40 00 00 00 00 00
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030 00 00 00 00 40 00 38 00 00 00 40 00 00 00 00 00
32-bit MSB 52 bytes format string '>HHIIIIIHHHHHH'
0010 00 02 00 3e 00 00 00 01 00 40 10 00 00 00 00 00
0020 00 00 00 00 00 00 00 00 00 34 00 20 00 00 00 28
0030 00 00 00 00
e_entry, e_phoff and e_shoff are the three fields that grew from
4 bytes to 8; everything else is the same width in both. 52 + 12
is 64, and e_ehsize in each header says which it is.
3. READ EACH ONE WITH ITS OWN IDENT
------------------------------------------------------------------------
field 64-bit LSB 32-bit MSB
e_type 2 2
e_machine 0x3e 0x3e
e_version 1 1
e_entry 0x401000 0x401000
e_phoff 0 0
e_shoff 0 0
e_flags 0 0
e_ehsize 64 52
e_phentsize 56 32
e_phnum 0 0
e_shentsize 64 40
e_shnum 0 0
e_shstrndx 0 0
e_type 2 is EXEC, e_machine 0x3e is X86_64,
in both -- two layouts, one header, because the file said which
layout it used before its first multi-byte field.
4. READ THE BIG-ENDIAN ONE AS IF IT WERE LITTLE
------------------------------------------------------------------------
e_type 512 (0x0200, not in the table: 1..4)
e_machine 15872 (0x3e00, not x86-64)
e_entry 0x00104000
Nothing raised. Every wrong reading of a header is a number, which
is why byte 5 exists: the alternative is guessing from plausibility.
5. A SECTION'S NAME IS AN OFFSET INTO A STRING TABLE
------------------------------------------------------------------------
.shstrtab bytes 00 2e 74 65 78 74 00 2e 73 68 73 74 72 74 61 62 00
as text b'\x00.text\x00.shstrtab\x00'
sh_name = 1 -> read to the next NUL -> '.text' ok
sh_name = 7 -> read to the next NUL -> '.shstrtab' ok
A section header holds no name, only sh_name, an offset into the
section that e_shstrndx points at. Names are NUL-terminated there,
and offset 0 is an empty string on purpose: the unnamed section.
The file says which end, and almost nothing else here does¶
The bytes do not say which end is the chapter-1 lesson: four bytes on their own are two numbers, and the file does not record which was meant. ELF is the counter-example. Byte 5 is that record, and section 3 above is what it buys — the same header laid out two ways, read back to the same thirteen numbers with no guessing. Section 4 is what the absence of byte 5 would cost: read the big-endian header with the little-endian rule and e_type is 512, a perfectly good number that means nothing.
Of the twenty-four formats in this chapter, only DEX has a field like it. Mach-O lets you infer the order from how its magic reads; PE, COFF and the rest fixed it once in a document. Raw binary makes you type it in.
A section's name is not in its header¶
A section header is 40 or 64 bytes and holds no name — sh_name is an offset into a string table, and e_shstrndx in the file header says which section that table is. Names there are NUL-terminated, offset 0 is the empty string, and section 5 above reads two names out of a seventeen-byte table. This is the arrangement PE and COFF also use for long names (COFF, where the offset is written in ASCII decimal), and the opposite of NE, OMF and PEF, which put a length byte in front of every string instead.
Measured on real files¶
000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
000010 03 00 3e 00 01 00 00 00 30 6d 00 00 00 00 00 00
000020 40 00 00 00 00 00 00 00 28 24 02 00 00 00 00 00
000030 00 00 00 00 40 00 38 00 0d 00 40 00 1f 00 1e 00
byte 4 = 02 64-bit byte 5 = 01 little-endian
e_type 03 00 3 DYN, a position-independent executable
e_machine 3e 00 0x3e x86-64
e_entry 30 6d 00 00 00 00 00 00 0x6d30
e_phoff 0x40 e_shoff 0x22428 e_ehsize 0x40 = 64
e_phentsize 0x38 = 56 e_phnum 13 e_shentsize 0x40 = 64 e_shnum 31 e_shstrndx 30
Class: ELF64
Data: 2's complement, little endian
Type: DYN (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Entry point address: 0x6760
Start of section headers: 156712 (bytes into file)
Size of this header: 64 (bytes)
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
Two builds of ls, both 64-bit little-endian, and the fields that differ — entry point, table offsets, section count — are the ones that depend on what was linked. Everything readelf prints in words is a number in the dump above it, and the first two lines are bytes 4 and 5.
What Ghidra checks¶
ElfLoader ↗ constructs an ElfHeader, which refuses a file whose first four bytes are not 7f 45 4c 46, then asks its opinion service for a language matching e_machine and e_flags, and drops any candidate wider than 32 bits when byte 4 says the file is 32-bit. The name it shows is Executable and Linking Format (ELF).
If you are coming from Python or ABAP¶
Python. struct is the right tool and the ident is its argument: '<' if data[5] == 1 else '>' gives the first character, and the class byte picks I or Q for the three wide fields — which is all elf_py.py does. Read the sixteen bytes first, build the format, then unpack; a format string written down before the file is open is a guess about the file. int.from_bytes(b, 'little') does one field at a time and takes the same decision as an argument.
ABAP. (Not machine-checked — CI cannot run ABAP.) An xstring sliced with +off(len) reaches any field, and an assignment of a 4-byte slice to an i reads it as big-endian (Bytes, hex and int has the rule and its link). So the little-endian header above needs its bytes reversed before the assignment, and it is byte 5 that tells you whether to reverse — the same decision, taken by hand. There is no struct; write the field widths down, and take them from the class byte, because a 64-bit e_entry will not fit an i at all.
Try it¶
head -c 64 /bin/ls | xxdon a Linux machine. Say what bytes 4 and 5 are before you read anything else, then finde_shstrndxin the last two bytes.readelf -hon the same file and match every line to a field in the dump. The two lines that are words rather than numbers came from one byte each.- Find a 32-bit binary — an old Raspberry Pi image, a
.kofor an embedded board — and comparee_ehsize. It is 52. readelf -Slists section names. Thenreadelf -x .shstrtabdumps the table they are read from; count the NULs.- Take any ELF and flip byte 5 with a hex editor.
readelf -hon the copy: every number is now wrong and none is reported as an error.
Practice¶
Four idents. Here are the first six bytes of four ELF files:
For each, say the width, the byte order, the header size in bytes, and the struct format string for the thirteen fields after e_ident. Then the question with a trap in it: e_ehsize is in the header — why can a reader not simply read that field to learn the header size?
Answers
Verified output of elf_kata_py.py — regenerated by tools/run_examples.py, never hand-typed.
ident (first 6) class data header struct format
7f 45 4c 46 02 01 64-bit little 64 bytes '<HHIQQQIHHHHHH' Linux x86-64, the usual
7f 45 4c 46 01 02 32-bit big 52 bytes '>HHIIIIIHHHHHH' a 32-bit big-endian target, PowerPC or MIPS
7f 45 4c 46 02 02 64-bit big 64 bytes '>HHIQQQIHHHHHH' 64-bit big-endian: s390x, or SPARC64
7f 45 4c 46 01 01 32-bit little 52 bytes '<HHIIIIIHHHHHH' 32-bit little-endian: i386, ARM
The answer is two bytes long. Byte 4 picks the widths and so the
header size, 52 or 64; byte 5 picks the byte order and so the first
character of the format string. Nothing after byte 5 is readable
until both have been read, and nothing before it is multi-byte.
THE TRAP: e_ehsize is in the header too, as a 16-bit number -- so a
reader that wants the header size from e_ehsize has to know the byte
order to read it, and it is the ident that says. 0x0040 read the
wrong way round is 16384, which is not a header size anyone has.
See also¶
- The bytes do not say which end — the question byte 5 answers, asked of a file that has no byte 5
- Mach-O — the other Unix executable, which answers the same question by how its magic reads
- DEX — the only other format here with a byte-order field, and it is a 32-bit constant rather than a byte
- PE — the header that took its section-name-as-offset idea from COFF, one lineage over
- Packing a record — width, byte order and padding for a record you design; a header is one you were handed
- The first two bytes — the kernel's
binfmt_elfwants exactly these four bytes at offset 0, and decodes nothing - System V ABI, chapter 4: ELF header ↗ — the table the one above was checked against