LAB-20Easy

ELF Header Forensics

Walk a raw ELF64 header byte-by-byte and pull out the fields a loader relies on to map and run the file.

15 min5 prerequisites7 tools

Investigate

Hints

Hint 1

e_ident (the first 16 bytes) is fixed-size and always precedes the rest of the header — everything after it is a field defined by struct offset, not a search.

Hint 2

Every Elf64_* field is little-endian: bytes 0x40 0x00 0x00 0x00 0x00 0x00 0x00 0x00 read as the value 0x40, not 0x4000000000000000.

Hint 3

e_phnum sits right after e_phentsize in the fixed Elf64_Ehdr layout — both are 2-byte fields.

Hint 4

ET_DYN binaries are position-independent — that's what lets ASLR randomize their base address at load time. Modern Linux binaries default to this.

Submit Findings

Prerequisites

  • l1-elf-format-deep-dive
  • f1-binary-anatomy
  • ELF64 header structure (e_ident, e_type, e_machine, e_entry, e_phoff, e_phnum)
  • Little-endian byte order
  • PIE vs ET_EXEC

Methodology

Investigation Methodology

1. ELF64 Header Layout (Fixed 64 bytes)

Unlike PE's indirection via e_lfanew, ELF header is at fixed offset 0:

typedef struct {
  unsigned char e_ident[16];  // 0x00-0x0F
  uint16_t      e_type;       // 0x10-0x11
  uint16_t      e_machine;    // 0x12-0x13
  uint32_t      e_version;    // 0x14-0x17
  uint64_t      e_entry;      // 0x18-0x1F
  uint64_t      e_phoff;      // 0x20-0x27
  uint64_t      e_shoff;      // 0x28-0x2F
  uint32_t      e_flags;      // 0x30-0x33
  uint16_t      e_ehsize;     // 0x34-0x35
  uint16_t      e_phentsize;  // 0x36-0x37
  uint16_t      e_phnum;      // 0x38-0x39
  uint16_t      e_shentsize;  // 0x3A-0x3B
  uint16_t      e_shnum;      // 0x3C-0x3D
  uint16_t      e_shstrndx;   // 0x3E-0x3F
} Elf64_Ehdr;

2. e_ident (0x00-0x0F) - Magic + Meta

Offset Field Value
0x00 EI_MAG0-3 0x7F 'E' 'L' 'F'
0x04 EI_CLASS 0x02 = 64-bit
0x05 EI_DATA 0x01 = LSB (little-endian)
0x06 EI_VERSION 0x01 = current
0x07 EI_OSABI 0x00 = System V
0x08 EI_ABIVERSION 0x00
0x09-0x0F EI_PAD Zero

3. Key Fields (Little-Endian!)

Field Offset Size Example Bytes Value
e_type 0x10 2 03 00 0x0003 (ET_DYN)
e_machine 0x12 2 3E 00 0x003E (x86_64)
e_version 0x14 4 01 00 00 00 1
e_entry 0x18 8 20 10 00 00 00 00 00 00 0x1020
e_phoff 0x20 8 40 00 00 00 00 00 00 00 0x40
e_shoff 0x28 8 ... ...
e_phentsize 0x36 2 38 00 0x38 (56 bytes)
e_phnum 0x38 2 04 00 4

4. e_type Values

Value Constant Meaning
0x01 ET_REL Relocatable (.o)
0x02 ET_EXEC Executable (fixed base)
0x03 ET_DYN Shared object / PIE
0x04 ET_CORE Core dump

PIE (ET_DYN) = Position Independent Executable = ASLR-friendly.

5. Program Headers (at e_phoff)

Each 56 bytes (e_phentsize):

typedef struct {
  uint32_t p_type;    // PT_LOAD, PT_INTERP, PT_PHDR, PT_DYNAMIC, PT_TLS
  uint32_t p_flags;   // PF_R, PF_W, PF_X
  uint64_t p_offset;  // File offset
  uint64_t p_vaddr;   // Virtual address
  uint64_t p_paddr;   // Physical (ignored)
  uint64_t p_filesz;  // Size in file
  uint64_t p_memsz;   // Size in memory
  uint64_t p_align;   // Alignment
} Elf64_Phdr;

6. PT_LOAD Segments = Memory Mappings

p_type p_flags Purpose
PT_LOAD R+X Code segment (.text)
PT_LOAD R+W Data segment (.data, .bss)
PT_INTERP - Dynamic linker path (e.g., /lib64/ld-linux-x86-64.so.2)
PT_DYNAMIC - .dynamic section (relocations, needed libs)
PT_TLS - Thread-local storage

7. Dynamic Section (.dynamic)

Entries: DT_NEEDED (libraries), DT_STRTAB/DT_SYMTAB (symbols), DT_RELA/DT_REL (relocations), DT_INIT/DT_FINI (constructors/destructors).

Common Pitfalls

  • Reading multi-byte fields as big-endian (ELF is little-endian on x86_64)
  • Confusing e_shoff (section header table) with e_phoff (program header table)
  • Missing that ET_DYN = PIE = ASLR-enabled (modern default)
  • Not verifying e_ident magic (0x7F 'ELF') first
  • Assuming section headers exist (stripped binaries have e_shoff=0)

Tools

  • readelf -h sample.elf (header)
  • readelf -l sample.elf (program headers/segments)
  • readelf -d sample.elf (dynamic section)
  • objdump -x sample.elf (all headers)
  • Ghidra / Binary Ninja (ELF support)
  • pefile equivalent: pyelftools (Python)
  • Built-in Hex Viewer

References

  • Lesson L1: ELF Format Deep Dive
  • Lesson F1: Binary Anatomy (PE vs ELF comparison)
  • ELF Specification (System V ABI)
  • man 5 elf
  • How programs get run (execve, ld-linux)

Extensions & Challenges

  • Parse the program headers manually from the hex dump
  • Identify which PT_LOAD is code (R+X) and which is data (R+W)
  • Find the PT_INTERP path — the dynamic linker
  • Research: How does the dynamic linker resolve DT_NEEDED libraries?
  • Analyze a stripped vs unstripped ELF — what's missing?