Demystifying the ELF Binary Format
A comprehensive guide to understanding ELF headers, sections, and the linking process.
kos1
January 9, 2026
11 min read
Introduction
ELF (Executable and Linkable Format) is the standard binary format on Linux and most Unix systems. Understanding ELF is essential for reverse engineering, malware analysis, and low-level debugging.
ELF Structure Overview
Every ELF file consists of:
- ELF Header - File metadata and entry point
- Program Headers - Describe segments for loading
- Section Headers - Describe sections for linking
- Data - Code, data, symbols, relocations, etc.
The ELF Header
The first 64 bytes (for ELF64) contain critical information:
typedef struct {
unsigned char e_ident[16]; // Magic number and info
uint16_t e_type; // Object file type
uint16_t e_machine; // Architecture
uint32_t e_version; // Object file version
uint64_t e_entry; // Entry point address
uint64_t e_phoff; // Program header offset
uint64_t e_shoff; // Section header offset
uint32_t e_flags; // Processor flags
uint16_t e_ehsize; // ELF header size
uint16_t e_phentsize; // Program header entry size
uint16_t e_phnum; // Number of program headers
uint16_t e_shentsize; // Section header entry size
uint16_t e_shnum; // Number of section headers
uint16_t e_shstrndx; // Section name string table index
} Elf64_Ehdr;
The Magic Number
$ hexdump -C /bin/ls | head -1
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
7f 45 4c 46 = "\x7fELF" - The magic number
02 = 64-bit (01 = 32-bit)
01 = Little endian (02 = big endian)
01 = ELF version
Program Headers (Segments)
Program headers tell the loader how to create the process image:
$ readelf -l /bin/ls
Program Headers:
Type Offset VirtAddr FileSiz MemSiz Flg
PHDR 0x000040 0x0000000000400040 0x0002d8 0x0002d8 R
INTERP 0x000318 0x0000000000400318 0x00001c 0x00001c R
LOAD 0x000000 0x0000000000400000 0x019d14 0x019d14 R E <- Code
LOAD 0x01a000 0x000000000061a000 0x001a50 0x003b30 RW <- Data
DYNAMIC 0x01a570 0x000000000061a570 0x0001f0 0x0001f0 RW
GNU_STACK 0x000000 0x0000000000000000 0x000000 0x000000 RW <- Stack perms
Section Headers
Sections organize the binary for linking and debugging:
$ readelf -S /bin/ls
Section Headers:
[Nr] Name Type Address Size
[ 1] .interp PROGBITS 0x0000000000400318 0x1c
[ 2] .text PROGBITS 0x0000000000401000 0x15a4a <- Code
[ 3] .rodata PROGBITS 0x0000000000417000 0x4d14 <- Read-only data
[12] .data PROGBITS 0x000000000061a000 0x0270 <- Initialized data
[13] .bss NOBITS 0x000000000061c000 0x2100 <- Uninitialized data
[14] .symtab SYMTAB 0x0000000000000000 0x3a80 <- Symbols
[15] .strtab STRTAB 0x0000000000000000 0x1e20 <- String table
Parsing ELF in Code
#include
#include
#include
void parse_elf(const char *path) {
int fd = open(path, O_RDONLY);
struct stat st;
fstat(fd, &st);
void *map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
Elf64_Ehdr *ehdr = (Elf64_Ehdr *)map;
// Verify magic
if (memcmp(ehdr->e_ident, "\x7fELF", 4) != 0) {
printf("Not an ELF file\n");
return;
}
printf("Entry point: 0x%lx\n", ehdr->e_entry);
printf("Sections: %d\n", ehdr->e_shnum);
// Iterate sections
Elf64_Shdr *shdr = (Elf64_Shdr *)(map + ehdr->e_shoff);
char *strtab = (char *)(map + shdr[ehdr->e_shstrndx].sh_offset);
for (int i = 0; i < ehdr->e_shnum; i++) {
printf("Section: %s\n", strtab + shdr[i].sh_name);
}
}
Useful Tools
readelf- Display ELF informationobjdump- Disassemble and display object infonm- List symbolspatchelf- Modify ELF binaries
Conclusion
ELF is the foundation of executable formats on Linux. Understanding its structure opens doors to debugging, security research, and systems programming.