ELF Executables

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.10, “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).

Example 8.10. Segments of an executable file
  1 $ readelf --segments /bin/ls
    
    Elf file type is EXEC (Executable file)
    Entry point 0x4046d4
  5 There are 8 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
 10   PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                     0x00000000000001c0 0x00000000000001c0  R E    8
      INTERP         0x0000000000000200 0x0000000000400200 0x0000000000400200
                     0x000000000000001c 0x000000000000001c  R      1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
 15   LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                     0x0000000000019ef4 0x0000000000019ef4  R E    200000
      LOAD           0x000000000001a000 0x000000000061a000 0x000000000061a000
                     0x000000000000077c 0x0000000000001500  RW     200000
      DYNAMIC        0x000000000001a028 0x000000000061a028 0x000000000061a028
 20                  0x00000000000001d0 0x00000000000001d0  RW     8
      NOTE           0x000000000000021c 0x000000000040021c 0x000000000040021c
                     0x0000000000000044 0x0000000000000044  R      4
      GNU_EH_FRAME   0x0000000000017768 0x0000000000417768 0x0000000000417768
                     0x00000000000006fc 0x00000000000006fc  R      4
 25   GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     8
    
     Section to Segment mapping:
      Segment Sections...
 30    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 
       04     .dynamic 
 35    05     .note.ABI-tag .note.gnu.build-id 
       06     .eh_frame_hdr 
       07     
    

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.10, “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.