Complete Tour of PE and ELF: Section Headers
In the previous part, we have discussed the ELF and Program Header. In this article, we will cover the remaining part i.e. section headers. We will also see what effect packers have on binaries headers.
What should you learn next?
Below is the structure of Section Header
- Sh_name: Remember in ELF Header we talked about string table. sh_name is an offset into the string table which points to the name of the section. Section Name can be any long in ELF where in PE section name can be only 8 bytes. In ELF, it just has to be null terminated.
-
Sh_type: This field is used by the linker to store relevant information into. It has many subtypes. Following are the important ones:
- SHT_PROGBITS: This is used to catch all things which are not special or you can also say generic things.
- SHT_STRTAB: This is used to map to String Tables.
- SHT_DYNAMIC: This contains all the dynamic linkage information
- SHT_NOBITS: This contains data which take no space over disk but gets mapped into memory (Remember .bss ?)
- SHT_HASH: It contains the symbol table hash
- SHT_SYMTAB: It contains symbol table information used for debugging.
-
Sh_flags: This occupies following values:
- SHF_WRITE: 0x01;means the section is writable
- SHF_ALLOC: 0x04; this flag specifies whether or not this section will occupy memory during execution or not.
- SHF_EXECINSTR:0x02; means the section contains executable code and when mapped into memory should be marked as executable
- Sh_addr: This section states wherein the memory this section starts. A value of 0 indicates that this section does not reside in memory
- Sh_offset: This tells the file offset to the start of this data
- Sh_size: This tells the size of the section.
-
Sh_addralign: This shows the section alignment. This value should be a multiple of 2.
Below is the screenshot which displays section headers
- readelf –S hello
See how each section has been assigned a particular type, for example, .bss NOBITS type. Most of the sections map to what we saw in PE. .got is Global Offset Table and .plt is a procedure linkage table. To understand these fields we have to go back to the concept of relocations. Whenever an elf file references a function from a shared library, it does not get filled up until it is called in the code, i.e., at link time we can see that the in our object file hello.o , puts does not have a relocation information yet and is thus referenced as a type of R_X86_64_PC32 . It says in the final binary patch the address of puts at offset 0xa .
If we can disassemble this file, we can also see that at offset 0xa it is waiting for a 4-byte address to be filled in.
As we can see above that call to printf(puts) is done at PLT 0x400410 which jumps to GOT at 0x601018
Examining the value at 601018 turns out to be next instruction i.e. pushq 0x0. Then the code makes a jump to PLT at 400400 which first pushed the value at 601008 (probably dynamic linker) and then call dynamic linker.
Whenever an external library function is referenced, then on first call the stub will be called which in turn will call dynamic linker with the address of the called function. The dynamic linker now knows that it has got the address of the function and must patch it up back in GOT. As we can see below now, the puts have to address to map to.
I think that we will be all for ELF section headers and thus the complete ELF structure.
Packers
Packers were originally used to compress executables to increase disk space but these days packers are being used to obfuscate binaries. So once packer packs the file, it is the packer responsibility to decompress the binary in memory as would OS loader have done. It dons the role of OS loader at that point. To a binary it does not matter, and it gets decompressed and then being referenced in memory. So this means that all of our sections(.text, rdata etc.) will not be visible in the packed file and thus, no inference about the data in their can be made until the file gets decompressed in memory.
As we can see above is that packer compresses the file on disk and loads it into memory. While loading it into memory, it allocates a chunk for empty memory size which is for the data that will be decompressed. After this, the packer decompresses logic will jump back to AddressOf EntryPoint so that binary can run properly.
I am running UPX packer. (Important point to note with upx packer is that your file has to be at least 40 bytes large otherwise UPX will throw a NotCompressibleException . Compile the static version of the small file that will have all the code loaded into it already so it will be large). Below is the output of what changes has UPX made to the headers of ELF.
Note there are only two program headers and no section headers. This means it will load this compressed load segments in memory and then decompress them.
The Same thing can be seen with PE files.
FREE role-guided training plans
So this brings us to the end of PE and ELF structures. Having a good understanding of these will help in performing malware analysis.