Safe Penetration Testing Labs · intermediate · ~25 min

ELF header — the binary's calling card

Read-only: parse the first 64 bytes of any Linux binary.

Overview

ELF (Executable and Linkable Format) is the on-disk shape of every Linux binary. The 64-byte header tells you the machine type, the entry-point address, and the offsets of the section and program-header tables.

Why it matters

Reverse engineering starts with reading the ELF header. Defensive forensics ("what is this dropped file?") starts with reading the ELF header. It's the universal binary fingerprint.

Core concepts

Magic bytes. First 4 bytes are 0x7F 'E' 'L' 'F'. Anything else: not ELF.

e_ident. Class (ELFCLASS32 / ELFCLASS64), data (LSB / MSB), version, OS ABI, ABI version.

e_machine. A small int identifying the CPU: 0x3E = x86_64, 0xB7 = AArch64, 0x28 = ARM, 0x14 = PowerPC.

e_type. ET_EXEC (statically-linked binary), ET_DYN (PIE binary or shared library), ET_REL (object file), ET_CORE (core dump).

e_entry. Virtual address of the program's first instruction.

Pentester mindset. When triaging a suspicious file: read the ELF header first. Wrong endianness or wrong machine = it can't run here. PIE vs non-PIE matters for ASLR analysis.

Defensive coding habit. Always validate the magic and bounds. Never trust e_phoff / e_shoff from the file — verify they're within the file size before seeking.

Syntax notes

#include <elf.h>
/* Elf64_Ehdr type provided. Cast a 64-byte buffer to it on x86_64. */

Lesson

Every Linux executable, shared library, and core dump starts with an ELF header. The first four bytes are the magic \x7fELF; the next 12 bytes identify class (32/64-bit), endianness, ABI, and version; then a 48-byte table tells you what kind of file it is and where to find its sections. This lesson teaches reading the header — never modifying.

Code examples

struct {
    unsigned char e_ident[16];   /* 0x7f 'E' 'L' 'F' + 12 ident bytes */
    uint16_t e_type;              /* ET_EXEC, ET_DYN, ET_REL ... */
    uint16_t e_machine;           /* EM_X86_64 = 0x3E, EM_AARCH64 = 0xB7 */
    uint32_t e_version;
    uint64_t e_entry;             /* virtual address of entry point */
    /* ... 24 more bytes ... */
} elf64_ehdr;

Line by line

uint8_t buf[64]; read(fd, buf, 64);
if (memcmp(buf, "\x7f" "ELF", 4) != 0) return -1;   /* magic check */
int klass = buf[4];                                    /* 1=32-bit, 2=64-bit */
int data  = buf[5];                                    /* 1=LSB, 2=MSB */
uint16_t e_machine = buf[18] | (buf[19] << 8);        /* assuming LSB */

Common mistakes

  • Assuming the host's endianness matches the binary's. Always check e_ident[5].

Debugging tips

readelf -h /bin/ls shows the parsed header. file /bin/ls is the one-liner version. Compare your parser's output against readelf.

Memory safety

Refuse anything where read length < 64 bytes. Refuse if magic doesn't match.

Real-world uses

file(1), readelf, objdump, every reverse-engineering tool, every package manager (verifies executables before install).

Practice tasks

  1. Detect ELF magic in a fixture. 2. Print e_machine in human-readable form. 3. Verify e_entry lies within the file.

Summary

64 bytes; magic + ident + type + machine + entry + table offsets. Read it, never mutate it.

Practice with these exercises