Executables are of course one of the primary uses of the ELF format. Contained within the binary is everything required for the operating system to execute the code as intended.
Since an executable is designed to be run in a process with
a unique address space (see Chapter 6, Virtual Memory) the
code can make assumptions about where the various parts of the
program will be loaded in memory. Example 8.13, “Segments of an executable file” shows an example using the
readelf™ tool to examine the segments of
an executable file. We can see the virtual addresses at which the
LOAD
segments are required to be
placed at. We can further see that one segment is for code
— it has read and execute permissions only — and one
is for data, unsurprisingly with read and write permissions, but
importantly no execute permissions (without execute permissions,
even if a bug allowed an attacker to introduce arbitrary data the
pages backing it would not be marked with execute permissions, and
most processors will hence disallow any execution of code in those
pages).
1 $ readelf --segments /bin/ls Elf file type is EXEC (Executable file) 5 Entry point 0x4046d4 There are 8 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr 10 FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001c0 0x00000000000001c0 R E 8 INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1 15 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000019ef4 0x0000000000019ef4 R E 200000 LOAD 0x000000000001a000 0x000000000061a000 0x000000000061a000 0x000000000000077c 0x0000000000001500 RW 200000 20 DYNAMIC 0x000000000001a028 0x000000000061a028 0x000000000061a028 0x00000000000001d0 0x00000000000001d0 RW 8 NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c 0x0000000000000044 0x0000000000000044 R 4 GNU_EH_FRAME 0x0000000000017768 0x0000000000417768 0x0000000000417768 25 0x00000000000006fc 0x00000000000006fc R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 8 Section to Segment mapping: 30 Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 35 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 40
The program segments must be loaded at these addresses; the last step of the linker is to resolve most relocations (the section called “Symbols and Relocations”) and patch them with the assumed absolute addresses — the data describing the relocation is then discarded in the final binary and there is no longer a way to find this information.
In reality, executables generally have external dependencies on shared libraries, or pieces of common code abstracted and shared among the entire system — almost all of the confusing parts of Example 8.13, “Segments of an executable file” relate to the use of shared libraries. Libraries are discussed in the section called “Libraries”, dynamic libraries in Chapter 9, Dynamic Linking.