Lukas' Notes

compilers file-formats

Definition

ELF

The Executable and Linkable Format (ELF) is the standard object-file format on Linux and other UNIX-derived systems. An object file here is the binary representation intended to be executed directly on a processor — it comes in three types: relocatable (code and data to be combined later by the linker), executable (a ready-to-run program), and shared object (a library that can be linked or dynamically loaded).

Structure

What an ELF file is composed of

Any ELF file is built from four parts, in order:

The ELF header at the top describes the file as a whole; the program header table tells the loader how to build a process image; the section sequence is what the linker consumes; and the section header table indexes those sections.

Relevant Sections

The sections a binary analyst looks at

SectionHolds
.textthe executable instructions of the program
.bssuninitialised data that contributes to the program’s memory image
.datainitialised writable data that contributes to the memory image
.rodatainitialised read-only data
.symtabthe program’s symbol table
.pltProcedure Linkage Table — entry points for calling functions from shared libraries
.gotGlobal Offset Table — holds the resolved addresses the PLT looks up at runtime

Permissions

Each section is mapped with its own protection bits

Sections are loaded into separate memory pages with different permissions — Read, Write, eXecute — so a page that needs just one of those is not given the others. .text holds code and must be executable; .bss holds mutable uninitialised data and must be readable and writable but not executable; the ELF header itself should be neither writable nor executable. Not all memory regions in a process are executable — running code from an unintended section is what a segmentation fault reports when a buffer overflow overwrites the saved return address with garbage.

Example

Where a few C declarations land

const float pi = 3.14159;      // constant           --> .rodata
char name[20];                // uninitialised array --> .bss
char default_name[] = "Alice"; // initialised array   --> .data
                               // the literal "Name: " used by scanf --> .rodata

Constants go to .rodata (read-only), uninitialised globals to .bss, initialised mutable globals to .data. Matching a declaration to its section is the first static-analysis step before reading any disassembly.