Now that we've looked at what program headers are, it is time to look at section headers. I really want to point out here the distinction between the two; I often hear people calling sections, segments, and vice versa. A section is not a segment. Segments are necessary for program execution, and within each segment, there is either code or data divided up into sections. A section header table exists to reference the location and size of these sections and is primarily for linking and debugging purposes. Section headers are not necessary for program execution, and a program will execute just fine without having a section header table. This is because the section header table doesn't describe the program memory layout. That is the responsibility of the program header table. The section headers are really just complimentary to the program headers. The readelf –l command will show which sections are mapped to which segments, which helps to visualize the relationship between sections and segments.
If the section headers are stripped (missing from the binary), that doesn't mean that the sections are not there; it just means that they can't be referenced by section headers and less information is available for debuggers and disassembler programs.
Each section contains either code or data of some type. The data could range from program data, such as global variables, or dynamic linking information that is necessary for the linker. Now, as mentioned previously, every ELF object has sections, but not all ELF objects have section headers, primarily when someone has deliberately removed the section header table, which is not the default.
Usually, this is because the executable has been tampered with (for example, the section headers have been stripped so that debugging is harder). All of GNU's binutils such as objcopy, objdump, and other tools such as gdb rely on the section headers to locate symbol information that is stored in the sections specific to containing symbol data. Without section headers, tools such as gdb and objdump are nearly useless.
Section headers are convenient to have for granular inspection over what parts or sections of an ELF object we are viewing. In fact, section headers make reverse engineering a lot easier since they provide us with the ability to use certain tools that require them. For instance, if the section header table is stripped, then we can't access a section such as .dynsym, which contains imported/exported symbols describing function names and offsets/addresses.
Even if a section header table has been stripped from an executable, a moderate reverse engineer can actually reconstruct a section header table (and even part of a symbol table) by getting information from certain program headers since these will always exist in a program or shared library. We discussed the dynamic segment earlier and the different DT_TAG that contain information about the symbol table and relocation entries. We can use this to reconstruct other parts of the executable as shown in Chapter 8, ECFS – Extended Core File Snapshot Technology.
The following is what a 32-bit ELF section header looks like:
typedef struct {
uint32_t sh_name; // offset into shdr string table for shdr name
uint32_t sh_type; // shdr type I.E SHT_PROGBITS
uint32_t sh_flags; // shdr flags I.E SHT_WRITE|SHT_ALLOC
Elf32_Addr sh_addr; // address of where section begins
Elf32_Off sh_offset; // offset of shdr from beginning of file
uint32_t sh_size; // size that section takes up on disk
uint32_t sh_link; // points to another section
uint32_t sh_info; // interpretation depends on section type
uint32_t sh_addralign; // alignment for address of section
uint32_t sh_entsize; // size of each certain entries that may be in section
} Elf32_Shdr;Let's take a look at some of the most important sections and section types, once again allowing room to study the ELF(5) man pages and the official ELF specification for more detailed information about the sections.
The .text section is a code section that contains program code instructions. In an executable program where there are also Phdr's, this section would be within the range of the text segment. Because it contains program code, it is of section type SHT_PROGBITS.
The rodata section contains read-only data such as strings from a line of C code, such as the following command are stored in this section:
printf("Hello World!\n");This section is read-only and therefore must exist in a read-only segment of an executable. So you will find .rodata within the range of the text segment (not the data segment). Because this section is read-only, it is of type SHT_PROGBITS.
The procedure linkage table (PLT) will be discussed in depth later in this chapter, but it contains code necessary for the dynamic linker to call functions that are imported from shared libraries. It resides in the text segment and contains code, so it is marked as type SHT_PROGBITS.
The data section, not to be confused with the data segment, will exist within the data segment and contain data such as initialized global variables. It contains program variable data, so it is marked SHT_PROGBITS.
The bss section contains uninitialized global data as part of the data segment and therefore takes up no space on disk other than 4 bytes, which represents the section itself. The data is initialized to zero at program load time and the data can be assigned values during program execution. The bss section is marked SHT_NOBITS since it contains no actual data.
The Global offset table (GOT) section contains the global offset table. This works together with the PLT to provide access to imported shared library functions and is modified by the dynamic linker at runtime. This section in particular is often abused by attackers who gain a pointer-sized write primitive in heap or .bss exploits. We will discuss this in the ELF Dynamic Linking section of this chapter. This section has to do with program execution and therefore is marked SHT_PROGBITS.
The dynsym section contains dynamic symbol information imported from shared libraries. It is contained within the text segment and is marked as type SHT_DYNSYM.
The dynstr section contains the string table for dynamic symbols that has the name of each symbol in a series of null terminated strings.
Relocation sections contain information about how parts of an ELF object or process image need to be fixed up or modified at linking or runtime. We will discuss more about relocations in the ELF Relocations section of this chapter. Relocation sections are marked as type SHT_REL since they contain relocation data.
The hash section, sometimes called .gnu.hash, contains a hash table for symbol lookup. The following hash algorithm is used for symbol name lookups in Linux ELF:
uint32_t
dl_new_hash (const char *s)
{
uint32_t h = 5381;
for (unsigned char c = *s; c != '\0'; c = *++s)
h = h * 33 + c;
return h;
}The symtab section contains symbol information of type ElfN_Sym, which we will analyze more closely in the ELF symbols and relocations section of this chapter. The symtab section is marked as type SHT_SYMTAB as it contains symbol information.
The .strtab section contains the symbol string table that is referenced by the st_name entries within the ElfN_Sym structs of .symtab and is marked as type SHT_STRTAB since it contains a string table.
The shstrtab section contains the section header string table that is a set of null terminated strings containing the names of each section, such as .text, .data, and so on. This section is pointed to by the ELF file header entry called e_shstrndx that holds the offset of .shstrtab. This section is marked SHT_STRTAB since it contains a string table.
The .ctors (constructors) and .dtors (destructors) sections contain function pointers to initialization and finalization code that is to be executed before and after the actual main() body of program code.
The __constructor__ function attribute is sometimes used by hackers and virus writers to implement a function that performs an anti-debugging trick such as calling PTRACE_TRACEME so that the process traces itself and no debuggers can attach to it. This way the anti-debugging code gets executed before the program enters into main().
There are many other section names and types, but we have covered most of the primary ones found in a dynamically linked executable. One can now visualize how an executable is laid out with both phdrs and shdrs.
The text segments will be as follows:
[.text]: This is the program code[.rodata]: This is read-only data[.hash]: This is the symbol hash table[.dynsym ]: This is the shared object symbol data[.dynstr ]: This is the shared object symbol name[.plt]: This is the procedure linkage table[.rel.got]: This is the G.O.T relocation dataThe data segments will be as follows:
[.data]: These are the globally initialized variables[.dynamic]: These are the dynamic linking structures and objects[.got.plt]: This is the global offset table[.bss]: These are the globally uninitialized variablesLet's take a look at an ET_REL file (object file) section header with the readelf –S command:
ryan@alchemy:~$ gcc -c test.c ryan@alchemy:~$ readelf -S test.o
The following are 12 section headers, starting at offset 0 x 124:
[Nr] Name Type Addr Off
Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000
000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034
000034 00 AX 0 0 4
[ 2] .rel.text REL 00000000 0003d0
000010 08 10 1 4
[ 3] .data PROGBITS 00000000 000068
000000 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000068
000000 00 WA 0 0 4
[ 5] .comment PROGBITS 00000000 000068
00002b 01 MS 0 0 1
[ 6] .note.GNU-stack PROGBITS 00000000 000093
000000 00 0 0 1
[ 7] .eh_frame PROGBITS 00000000 000094
000038 00 A 0 0 4
[ 8] .rel.eh_frame REL 00000000 0003e0
000008 08 10 7 4
[ 9] .shstrtab STRTAB 00000000 0000cc
000057 00 0 0 1
[10] .symtab SYMTAB 00000000 000304
0000b0 10 11 8 4
[11] .strtab STRTAB 00000000 0003b4
00001a 00 0 0 1No program headers exist in relocatable objects (ELF files of type ET_REL) because .o files are meant to be linked into an executable, but not meant to be loaded directly into memory; therefore, readelf -l will yield no results on test.o. Linux loadable kernel modules are actually ET_REL objects and are an exception to the rule because they do get loaded directly into kernel memory and relocated on the fly.
We can see that many of the sections we talked about are present, but there are also some that are not. If we compile test.o into an executable, we will see that many new sections have been added, including .got.plt, .plt, .dynsym, and other sections that are related to dynamic linking and runtime relocations:
ryan@alchemy:~$ gcc evil.o -o evil ryan@alchemy:~$ readelf -S evil
The following are 30 section headers, starting at offset 0 x 1140:
[Nr] Name Type Addr Off
Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000
000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154
000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168
000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188
000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac
000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481cc 0001cc
000060 10 A 6 1 4
[ 6] .dynstr STRTAB 0804822c 00022c
000052 00 A 0 0 1
[ 7] .gnu.version VERSYM 0804827e 00027e
00000c 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0804828c 00028c
000020 00 A 6 1 4
[ 9] .rel.dyn REL 080482ac 0002ac
000008 08 A 5 0 4
[10] .rel.plt REL 080482b4 0002b4
000020 08 A 5 12 4
[11] .init PROGBITS 080482d4 0002d4
00002e 00 AX 0 0 4
[12] .plt PROGBITS 08048310 000310
000050 04 AX 0 0 16
[13] .text PROGBITS 08048360 000360
00019c 00 AX 0 0 16
[14] .fini PROGBITS 080484fc 0004fc
00001a 00 AX 0 0 4
[15] .rodata PROGBITS 08048518 000518
000008 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 08048520 000520
000034 00 A 0 0 4
[17] .eh_frame PROGBITS 08048554 000554
0000c4 00 A 0 0 4
[18] .ctors PROGBITS 08049f14 000f14
000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049f1c 000f1c
000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049f24 000f24
000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f28 000f28
0000c8 08 WA 6 0 4
[22] .got PROGBITS 08049ff0 000ff0
000004 04 WA 0 0 4
[23] .got.plt PROGBITS 08049ff4 000ff4
00001c 04 WA 0 0 4
[24] .data PROGBITS 0804a010 001010
000008 00 WA 0 0 4
[25] .bss NOBITS 0804a018 001018
000008 00 WA 0 0 4
[26] .comment PROGBITS 00000000 001018
00002a 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 001042
0000fc 00 0 0 1
[28] .symtab SYMTAB 00000000 0015f0
000420 10 29 45 4
[29] .strtab STRTAB 00000000 001a10
00020d 00 0 0As observed, a number of sections have been added, most notably the ones related to dynamic linking and constructors. I strongly suggest that the reader follows the exercise of deducing which sections have been changed or added and what purpose the added sections serve. Consult the ELF(5) man pages or the ELF specifications.