BINARY_DISSECTION_COURSE

SHT (SECTION HEADER TABLE)

A binary as we know comprises of code and data. The content of binary file is organised into many blocks (made up of meaningful bytes) and each such block is termed as a Section. Each section in a binary has a header associated with it which describes that section. In other words each section is determined or known by its section header. Section Headers in a file does not overlap meaning that no byte can be present in more than one section.

SECTION HEADERS

Now, let’s have a look at the sections of the elf binary main. We’ll use readelf tool with -S option to look at the ‘Section Headers’ of the executable.

critical@d3ad:~/BINARY_DISECTION_COURSE/ELF/SECTION_HEADER_TABLE$ readelf -S --wide main
There are 29 section headers, starting at offset 0x1900:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000400238 000238 00001c 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            0000000000400254 000254 000020 00   A  0   0  4
  [ 3] .note.gnu.build-id NOTE            0000000000400274 000274 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        0000000000400298 000298 00001c 00   A  5   0  8
  [ 5] .dynsym           DYNSYM          00000000004002b8 0002b8 000060 18   A  6   1  8
  [ 6] .dynstr           STRTAB          0000000000400318 000318 00003d 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0000000000400356 000356 000008 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0000000000400360 000360 000020 00   A  6   1  8
  [ 9] .rela.dyn         RELA            0000000000400380 000380 000030 18   A  5   0  8
  [10] .rela.plt         RELA            00000000004003b0 0003b0 000018 18  AI  5  22  8
  [11] .init             PROGBITS        00000000004003c8 0003c8 000017 00  AX  0   0  4
  [12] .plt              PROGBITS        00000000004003e0 0003e0 000020 10  AX  0   0 16
  [13] .text             PROGBITS        0000000000400400 000400 000172 00  AX  0   0 16
  [14] .fini             PROGBITS        0000000000400574 000574 000009 00  AX  0   0  4
  [15] .rodata           PROGBITS        0000000000400580 000580 000013 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        0000000000400594 000594 00003c 00   A  0   0  4
  [17] .eh_frame         PROGBITS        00000000004005d0 0005d0 000100 00   A  0   0  8
  [18] .init_array       INIT_ARRAY      0000000000600e10 000e10 000008 08  WA  0   0  8
  [19] .fini_array       FINI_ARRAY      0000000000600e18 000e18 000008 08  WA  0   0  8
  [20] .dynamic          DYNAMIC         0000000000600e20 000e20 0001d0 10  WA  6   0  8
  [21] .got              PROGBITS        0000000000600ff0 000ff0 000010 08  WA  0   0  8
  [22] .got.plt          PROGBITS        0000000000601000 001000 000020 08  WA  0   0  8
  [23] .data             PROGBITS        0000000000601020 001020 000014 00  WA  0   0  8
  [24] .bss              NOBITS          0000000000601034 001034 000004 00  WA  0   0  1
  [25] .comment          PROGBITS        0000000000000000 001034 000024 01  MS  0   0  1
  [26] .symtab           SYMTAB          0000000000000000 001058 0005d0 18     27  43  8
  [27] .strtab           STRTAB          0000000000000000 001628 0001d3 00      0   0  1
  [28] .shstrtab         STRTAB          0000000000000000 0017fb 000103 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Don’t get stunned by this evil looking output. Now we’re going to understand the output piece-by-piece.

HOW TO READ THE OUTPUT

This is probably the most important part for beginners. Consider the snippet taken from above.

THE FIRST LINE

There are 29 section headers, starting at offset 0x1900:

It briefs about the number of section headers the file has (remember the e_shnum field of ELF header) and the offset to the section header Table (remember the e_shoff field).

FORMAT OF SECTION HEADER TABLE

It describes the format of section header table.

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
FIELD Description
[Nr] Identifies the index of section
Name Identifies the name of the section
Type Identifies the section type. Type describes what kind of data section stores.
See SECTION TYPES. It is defined by sh_type member of Elf64_Shdr structure
defined in /usr/include/elf.h.
Address Identifies the virtual address(logical addresss) at which the section
starts. (Each process has its own Virtual Address provided by the OS kernel).
For beginners, it can be thought of as the memory address in RAM where the
section starts.
Offset It is the distance (in bytes) from the begining of the file to the section. Don’t
confuse youself between offset and Virtual Address, Virtual addresss is a
runtime concept whereas offset tells tells about physical location in the file.
Size Identifies the size of each section .
EntSize Contains a 0 value if the section does not hold any table of fixed size entries.
Flags Identifies the attributes/properties of a section. A lot can be judged from the
Flags of the Executable. Most common ones are R(Readable), W(Writable),
X(Executable), A(Allocate memory), T(Thread Local Storage).
Link Stores the link information. The interpretation of this field depends on the
SECTION TYPE.
Info Stores the auxiliary information. The interpretation of this field depends on
the SECTION TYPE.
Align Allignment constraint. This value ensures that the offset of the section should
be divisible by this value. Value 0 means that there is no alignment constraint.

The structure named Elf64_Shdr (Elf32_Shdr on a 32-bit platform) defined in /usr/include/elf.h describes a section header of an ELF.

critical@d3ad:~$ cat /usr/include/elf.h | grep -B12 "} Elf64_Shdr"
typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string table index) */
  Elf64_Word	sh_type;		/* Section type */
  Elf64_Xword	sh_flags;		/* Section flags */
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf64_Off	sh_offset;		/* Section file offset */
  Elf64_Xword	sh_size;		/* Section size in bytes */
  Elf64_Word	sh_link;		/* Link to another section */
  Elf64_Word	sh_info;		/* Additional section information */
  Elf64_Xword	sh_addralign;		/* Section alignment */
  Elf64_Xword	sh_entsize;		/* Entry size if section holds table */
} Elf64_Shdr;

Let’s see what all we understood by talking about some interesting entries from above.

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0

The first entry is always a NULL entry. It marks the section as inactive and is always present at the begining of Section Header Table (I mark it useless in my subconcious mind though). This section header is perhaps this way due to historical reasons when first entry was zeroed out to identify NULL references in a program (where all NULL references were made to jump to NULL entries) which certainly doesn’t make sense to put in SHT as it doesn’t contribute to program runtime (afterall, it was just a wild guess from my end!).

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
...
  [13] .text             PROGBITS        0000000000400400 000400 000172 00  AX  0   0 16

...

Here,

Go through each section names and analyse the Section Header Table (at least to be familiarised with the output). Now, I guess we understand how to read the output of readelf.

Below is how you can programatically parse SHT. Please look parse_sht.c for full source. I guess, e_shoff (SHT offset) and e_shnum (number of entries in SHT) as provided by the ELF header are enough to parse the entire SHT.

void parseSht (uint8_t *map) {
	Elf64_Ehdr *ehdr = (Elf64_Ehdr *) map;
	Elf64_Shdr *sht  = (Elf64_Shdr *) &map[ehdr->e_shoff];
	char *shstrtab   = &map[ sht[ehdr->e_shstrndx].sh_offset ]; 

	fprintf (stderr, "shstrtab: first string: %s\n", shstrtab);

	for (int i = 0; i < ehdr->e_shnum; ++i) {

		/* sh_name */
		fprintf (stderr, "[%d] %s\n\t", i, &shstrtab[sht[i].sh_name]);
		
		/* sh_type */
		switch (sht[i].sh_type) {
			case SHT_NULL: 		fprintf (stderr, "NULL");
				       			break;
			case SHT_PROGBITS:	fprintf (stderr, "PROGBITS");
					  			break;
			case SHT_SYMTAB:	fprintf (stderr, "SYMTAB");
								break;
			case SHT_STRTAB: 	fprintf (stderr, "STRTAB");
								break;
			case SHT_RELA:		fprintf (stderr, "RELA");
								break;
			case SHT_GNU_HASH:  fprintf (stderr, "GNU HASH");
								break;
			case SHT_DYNAMIC:   fprintf (stderr, "DYNAMIC");
                                break;
			case SHT_NOTE:      fprintf (stderr, "NOTE");
                                break;
            case SHT_NOBITS:    fprintf (stderr, "NOBITS");
                                break;
			case SHT_REL:       fprintf (stderr, "REL");
                                break;
            case SHT_SHLIB:     fprintf (stderr, "SHLIB");
                                break;
			case SHT_DYNSYM:    fprintf (stderr, "DYNSYM");
                                break;
            case SHT_LOPROC:    fprintf (stderr, "LOPROC");
                                break;
			case SHT_HIPROC:    fprintf (stderr, "HIPROC");
                                break;
            case SHT_LOUSER:    fprintf (stderr, "LOUSER");
                                break;
			case SHT_HIUSER:    fprintf (stderr, "HIUSER");
                                break;
            default:			fprintf (stderr, "Unknown Section");
								break;	
		}		
		fprintf (stderr, "\t");

		/* sh_flags */
		if (sht[i].sh_flags & SHF_WRITE)	fprintf (stderr, "W");
		else								fprintf (stderr, " ");
		if (sht[i].sh_flags & SHF_ALLOC)	fprintf (stderr, "A");
											fprintf (stderr, " ");
		if (sht[i].sh_flags & SHF_EXECINSTR)fprintf (stderr, "X");
											fprintf (stderr, " ");
		if (sht[i].sh_flags & SHF_MASKPROC) fprintf (stderr, "M");
											fprintf (stderr, " ");
		fprintf (stderr, "\t");

		/* Similarly, one can access any field of a section header */

		fprintf (stderr, "\n");
	}	
}	

Similarly, we can access any field inside a section header. Next, we’ll dig a little deeper to gain an understanding on the purpose of various sections listed in section header table.


PREV - ELF HEADER
NEXT - SECTIONS DESCRIPTION