Analyzing Section Headers

Section headers, segments and program headers; all of these are linked. Therefore, this article is aimed at establishing that understanding before we move to program headers table.

What are sections?

Sections are formally defined logical divisions inside an ELF file in the ELF specification. They describe how an ELF file organizes its contents for linking, loading, and debugging.

These are the section headers in our binary.

Section Headers:
  [Nr] Name              Type           Address            Offset     Size               EntSize           Flags  Link  Info  Align
  [ 0]                   NULL           0000000000000000   00000000   0000000000000000   0000000000000000            0     0     0
  [ 1] .note.gnu.pr[...] NOTE           0000000000000350   00000350   0000000000000020   0000000000000000    A       0     0     8
  [ 2] .note.gnu.bu[...] NOTE           0000000000000370   00000370   0000000000000024   0000000000000000    A       0     0     4
  [ 3] .interp           PROGBITS       0000000000000394   00000394   000000000000001c   0000000000000000    A       0     0     1
  [ 4] .gnu.hash         GNU_HASH       00000000000003b0   000003b0   0000000000000024   0000000000000000    A       5     0     8
  [ 5] .dynsym           DYNSYM         00000000000003d8   000003d8   00000000000000a8   0000000000000018    A       6     1     8
  [ 6] .dynstr           STRTAB         0000000000000480   00000480   000000000000008d   0000000000000000    A       0     0     1
  [ 7] .gnu.version      VERSYM         000000000000050e   0000050e   000000000000000e   0000000000000002    A       5     0     2
  [ 8] .gnu.version_r    VERNEED        0000000000000520   00000520   0000000000000030   0000000000000000    A       6     1     8
  [ 9] .rela.dyn         RELA           0000000000000550   00000550   00000000000000c0   0000000000000018    A       5     0     8
  [10] .rela.plt         RELA           0000000000000610   00000610   0000000000000018   0000000000000018   AI       5    24     8
  [11] .init             PROGBITS       0000000000001000   00001000   0000000000000017   0000000000000000   AX       0     0     4
  [12] .plt              PROGBITS       0000000000001020   00001020   0000000000000020   0000000000000010   AX       0     0     16
  [13] .plt.got          PROGBITS       0000000000001040   00001040   0000000000000008   0000000000000008   AX       0     0     8
  [14] .text             PROGBITS       0000000000001050   00001050   0000000000000103   0000000000000000   AX       0     0     16
  [15] .fini             PROGBITS       0000000000001154   00001154   0000000000000009   0000000000000000   AX       0     0     4
  [16] .rodata           PROGBITS       0000000000002000   00002000   0000000000000012   0000000000000000    A       0     0     4
  [17] .eh_frame_hdr     PROGBITS       0000000000002014   00002014   000000000000002c   0000000000000000    A       0     0     4
  [18] .eh_frame         PROGBITS       0000000000002040   00002040   00000000000000ac   0000000000000000    A       0     0     8
  [19] .note.ABI-tag     NOTE           00000000000020ec   000020ec   0000000000000020   0000000000000000    A       0     0     4
  [20] .init_array       INIT_ARRAY     0000000000003dd0   00002dd0   0000000000000008   0000000000000008   WA       0     0     8
  [21] .fini_array       FINI_ARRAY     0000000000003dd8   00002dd8   0000000000000008   0000000000000008   WA       0     0     8
  [22] .dynamic          DYNAMIC        0000000000003de0   00002de0   00000000000001e0   0000000000000010   WA       6     0     8
  [23] .got              PROGBITS       0000000000003fc0   00002fc0   0000000000000028   0000000000000008   WA       0     0     8
  [24] .got.plt          PROGBITS       0000000000003fe8   00002fe8   0000000000000020   0000000000000008   WA       0     0     8
  [25] .data             PROGBITS       0000000000004008   00003008   0000000000000010   0000000000000000   WA       0     0     8
  [26] .bss              NOBITS         0000000000004018   00003018   0000000000000008   0000000000000000   WA       0     0     1
  [27] .comment          PROGBITS       0000000000000000   00003018   000000000000001f   0000000000000001   MS       0     0     1
  [28] .symtab           SYMTAB         0000000000000000   00003038   0000000000000360   0000000000000018           29    18     8
  [29] .strtab           STRTAB         0000000000000000   00003398   00000000000001db   0000000000000000            0     0     1
  [30] .shstrtab         STRTAB         0000000000000000   00003573   000000000000011a   0000000000000000            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),
   D (mbind), l (large), p (processor specific)

Note: You may not find your output like this because I have formatted it manually so that it is clear and makes sense.

We are already familiar with the attributes of this table, so we'll skip that. Lets have a look at these sections.

Baseline Understanding Of Sections

Name
Type
Explanation
View

Section 0

NULL

This section is empty. It is their just of alignment purposes. Nothing much. That's why it is of type NULL and everything is zeroed out.

.note.gnu.property

NOTE

Metadata for hardware/ABI features (e.g., IBT, SHSTK).

readelf -n

.note.gnu.build-id

NOTE

Unique hash/fingerprint of the binary for debug symbol matching.

readelf -n

.interp

PROGBITS

Path to the dynamic loader program (ld.so) for resolving cross-references.

PROGBITS means a section that holds raw data to be loaded into memory, such as code, constants, or initialized variables.

readelf -p .interp

.gnu.hash

GNU_HASH

Hash table for faster dynamic symbol lookup. We need not to dive into this much.

NA

.dynsym

DYNSYM

Dynamic symbol table used at runtime linking by the interpreter program (ld-linux).

NA

.dynstr

STRTAB

String table for names in .dynsym.

readelf -p .dynstr

.gnu.version

VERSYM

Version info for each dynamic symbol.

NA

.gnu.version_r

VERNEED

Declares required versions of shared libraries.

NA

.rela.dyn

RELA

Relocation entries for global data and non-PLT addresses.

.rela.plt

RELA

Relocation entries via PLT.

.init

PROGBITS

Code run before main() (init routine).

NA

.plt

PROGBITS

Procedure Linkage Table — stubs for external function calls.

NA

.plt.got

PROGBITS

Used in lazy binding (jump to GOT entries).

NA

.text

PROGBITS

Main executable code section.

NA

.fini

PROGBITS

Code run after main() returns (cleanup).

NA

.rodata

PROGBITS

Read-only static data (e.g., strings, constants).

readelf -p .rodata

.eh_frame_hdr

PROGBITS

Header for exception handling frames.

.eh_frame

PROGBITS

Stack unwinding info for exceptions or debugging.

.note.ABI-tag

NOTE

Identifies the target OS/ABI version.

.init_array

INIT_ARRAY

List of constructor function pointers (run before main).

.fini_array

FINI_ARRAY

List of destructor function pointers (run after main).

.dynamic

DYNAMIC

Table used by the dynamic linker to resolve symbols/relocations.

.got

PROGBITS

Global Offset Table for storing resolved addresses.

.got.plt

PROGBITS

GOT entries specifically for .plt lazy binding.

.data

PROGBITS

Writable static/global data.

.bss

NOBITS

Uninitialized global/static data (zeroed at runtime).

.comment

PROGBITS

Compiler version or build metadata (ignored at runtime).

.symtab

SYMTAB

Full symbol table (for static linking/debugging).

.strtab

STRTAB

String table for names in .symtab.

readelf -p .strtab

.shstrtab

STRTAB

String table for section names themselves.

readelf -p .shstrtab

What is their significance?

Sections are used by the linker to organize our code/data/symbols etc. Lets usage an analogy to understand the importance of their existence.

Consider an ELF as a room, containing so many different types of things. There are clothes, pens and paper, bottle, bag, notebooks etc. And everything is scattered.

To make sense of them, you decided to clear the floor and put everything categorized on the floor.

  • You put papers together.

  • You put notebooks together.

  • You put pens together.

  • You put jeans together.

  • You put shirts together.

  • You put books together.

This resembles sections.

Now we have so many things, a little bit organized. But, further organization can be made.

  • Books, papers and pens are related things. They are all stationary. So, they can be put together in the bookshelf.

  • Jeans, shirts, t-shirts, lower, jacket etc.... all of them are clothes. So, they can be put together in the wardrobe.

  • And so on....

This further categorization is what segments are.

  • When you have to look for fiction books, or course books, you don't go to different place. They are within the same bookshelf.

  • When you want a lower or a jacket, you don't go to separate places again. They are within the same wardrobe. You want party clothes or comfy clothes, all of them are in the same wardrobe.

  • The idea behind segments is that you group sections logically to the point that no further categorization can be made and all the related things can be accessed at one place in a logical manner.

Ultimately, do you end up accessing the clothes individually or through the wardrobe? The answer is through the wardrobe.

  • Sections are just kind of intermediaries. They are used initially to do things but they are not the ones that are ultimately used in the end.

  • Segments, on the other hand, evolve from sections and these are what that get used at runtime.

Once these segments are created, program headers come into existence.

How does it fit in the bigger picture?

As we have read recently that the infrastructure we are understanding is basically made up of various object files, which get combined with our source code and the final binary becomes an executable.

Those object files are also ELF at the end of the day. They will also have sections like .text and .got . To form a combined binary, these sections are combined in a thoughtful way and a final version of that section emerges.

Therefore, the .text section we see in the final elf binary doesn't only contain our source code, but the source code from various shared object files.

Just for knowledge, some of these files include crt1.o, crtn.o etc.

This can be verified by looking at the full disassembly.

  • Line 341 on wards starts the disassembly of .text section.

  • First, we have _start. Then deregister_tm_clones, register_tm_clones, __do_global_dtors_aux, frame_dummy and, at last, main.

  • All these are coming from the shared object files.

Conclusion

  1. Sections are used at build time.

  2. Segments are used at runtime.

  3. The final binary is a mixture of our source code and shared object files.

Last updated