Skip to content
Lesson 23 of 24

Step 1 of 5 · Reading · ~3 min

Read

File Systems & Beyond

ELF Loading & Process Image

exec is the system call that turns a file on disk into a running program. The kernel must read the file, decide whether it is something it can run, and build an address space from it, without trusting a single byte, because the file is attacker-supplied. In this lesson your program is the validation and planning half of Linux's ELF loader: given the bytes of an ELF64 executable, it checks the header and the program headers and reports the memory image the kernel would build.

The ELF header

An ELF file starts with a fixed 64-byte header (little-endian on x86-64):

OffsetSizeFieldTypical value
04magic7f 45 4c 46 ("\x7fELF")
41class2 = 64-bit
51data1 = little-endian
162type2 = executable, 3 = shared object / PIE
182machine0x3e = x86-64
248entryvirtual address of the first instruction
328phofffile offset of the program header table
542phentsize56
562phnumnumber of program headers

The first bytes of a real binary read 7f 45 4c 46 02 01 01 00 ...: magic, 64-bit, little-endian, version 1.

Program headers: what to map

The loader ignores sections (those are for linkers and debuggers) and walks the program headers, 56 bytes each:

OffsetSizeField
04p_type: 1 LOAD, 3 INTERP, 6 PHDR, 0x6474e551 GNU_STACK
44p_flags: 4 = R, 2 = W, 1 = X
88p_offset: where the bytes are in the file
168p_vaddr: where they go in memory
328p_filesz: bytes to copy from the file
408p_memsz: bytes of memory to provide
488p_align: usually 0x1000

For each LOAD segment the kernel allocates pages covering p_vaddr to p_vaddr + p_memsz, maps them with permissions from the flags (R-X for code, RW- for data), copies p_filesz bytes from the file, and zero-fills the rest. That tail is the .bss: variables initialised to zero take no space on disk, only p_memsz - p_filesz bytes in memory. Flags map directly to page-table bits: a segment without W is a read-only page, one without X gets NX. Security-conscious loaders refuse W+X.

What can be wrong

A loader must check each of these before using the file:

  • p_filesz larger than p_memsz, or p_offset + p_filesz past the end of the file (it would read garbage or beyond the buffer);
  • alignment: p_vaddr and p_offset must agree modulo p_align, because the kernel maps whole file pages at whole memory pages;
  • the segment lies in user space: it must not reach kernel addresses, and p_vaddr + p_memsz must not wrap around 64 bits;
  • segments must not overlap in pages;
  • the entry point must lie in an executable segment.

Interpreters and the stack

A dynamically linked program has a PT_INTERP segment holding a path such as /lib64/ld-linux-x86-64.so.2: the kernel loads that interpreter too and starts it, and it loads the shared libraries. PT_GNU_STACK states whether the stack may be executable (it should not be). After mapping, exec builds the user stack with argc, argv and envp, sets the stack pointer and jumps to the entry point in ring 3.

Your exercise

The starter turns the hex input into bytes and prints your report. You write the analysis: the header checks, the program header walk, the segment checks and the page count.

Up nextModern Kernel ArchitecturesFile Systems & Beyond

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…