Safe Penetration Testing Labs · intermediate · ~25 min
Read-only: parse the first 64 bytes of any Linux binary.
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.
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.
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.
#include <elf.h>
/* Elf64_Ehdr type provided. Cast a 64-byte buffer to it on x86_64. */
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.
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;
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 */
readelf -h /bin/ls shows the parsed header. file /bin/ls is the one-liner version. Compare your parser's output against readelf.
Refuse anything where read length < 64 bytes. Refuse if magic doesn't match.
file(1), readelf, objdump, every reverse-engineering tool, every package manager (verifies executables before install).
64 bytes; magic + ident + type + machine + entry + table offsets. Read it, never mutate it.