- Learning Linux Binary Analysis
- Ryan “elfmaster” O'Neill
- 2275字
- 2021-07-16 12:56:53
ELF section headers
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 pided 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.
Note
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
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
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 .plt section
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
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
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 .got.plt section
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
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
The dynstr
section contains the string table for dynamic symbols that has the name of each symbol in a series of null terminated strings.
The .rel.* section
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
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; }
Note
h = h * 33 + c
is often seen coded as h = ((h << 5) + h) + c
The .symtab section
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
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
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 and .dtors sections
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.
Note
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 data
The 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 variables
Let'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 1
No 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 0
As 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.
- Vue.js 3.x快速入門
- Java語言程序設計
- 數據庫系統教程(第2版)
- Reporting with Visual Studio and Crystal Reports
- QTP自動化測試進階
- 零基礎學Python網絡爬蟲案例實戰全流程詳解(高級進階篇)
- C#實踐教程(第2版)
- Nginx Lua開發實戰
- 常用工具軟件立體化教程(微課版)
- OpenMP核心技術指南
- BeagleBone Robotic Projects(Second Edition)
- 實戰Java高并發程序設計(第2版)
- Python計算機視覺和自然語言處理
- Learning Python Data Visualization
- Django Design Patterns and Best Practices