A brief introduction to Executable & Linkable Format

RIXED LABS
RIXED_LABS
Published in
6 min readMar 6, 2021

Introduction

After a brief introduction to PE file format, this blog will be dedicated towards understanding ELF format, as understanding file formats is quite helpful while learning malware analysis & reverse engineering.

Glimpse of ELF file & it’s format

A random ELF file opened using 010 hex editor

As per Wikipedia & other blogs ELF or Executable and Linkable Format is a common standard file format for executables in Linux Systems, compared to other executables formats ELF is way flexible, and it is not bound to any particular processor or instruction set architecture, ELF format has replaced older formats such as COFF(Common Object File Format) in *NIX like operating systems. In this blog we will load up an ELF file into an ELF Parser & hex editor, I prefer using 010 hex editor, you are free to use one as per your comfort.

We loaded a small 32 bit ELF file into a hex editor and inside a decompiler to view the parts of the ELF executable, the executable mainly consists of parts like ELF Header, Segments, Sections. We will brief about each one of them.

ELF Header :

The ELF header is basically denoted by Elf32_Header structure, this structure mainly consists of various general information about the executable, definitions or info about these structure fields are :

  • char signature[4] : This is a 4-byte magic number identifying the file as an ELF object file where the value 0x7f denotes ELFMAG0.
  • uint8_t file_class = 0x1 : This byte identifies the file’s class or capacity where the value 0x1 denotes 32-bit objects, had it been 0x2 it would denote 64-bit objects.
  • uint8_ encoding = 0x1 : This denotes the data encoding of the processor-specific in the object file, here the value 1 stands for ELFDATA2LSB encoding with least significant byte occupying the lowest address, this encoding is also known as little endian, had it been 0x2 the encoding would have been big endian.
  • unit8_version = 0x1 : This byte denotes the ELF header version number.
  • unit8_os = 0x0 : This byte identifies the OS together with the ABI to which the object is targeted, the interpretation of flags and values that have operating system or ABI determined by the value of this byte.
  • uint8_t abi_version = 0x0 : This identifies the version of the ABI to which the object is targeted mostly used to distinguish among incompatible versions of an ABI.
  • char pad[7] = "\x00\x00\x00\x00\x00\x00" : This identifies the beginning of the unused bytes, these bytes are reserved and set to zero, the programs that read object files should ignore them, these values will change in future if currently unused bytes are given meaning.
  • type = ET_EXEC : This denotes the type of executable, this small table can make it more clear :
Executable generation        ELF type  DT_FLAGS_1  DF_1_PIE  chdmod +x      file 5.36
--------------------------- -------- ---------- -------- -------------- --------------
gcc -fpie -pie ET_DYN y y y pie executable
gcc -fno-pie -no-pie ET_EXEC n n y executablegcc -shared ET_DYN n n y pie executablegcc -shared ET_DYN n n n shared objectld ET_EXEC n n y executableld -pie --dynamic-linker ET_DYN y y y pie executableld -pie --no-dynamic-linker ET_DYN y y y pie executable

e_machine= EM_386 : This denotes file’s architecture, here the file was compiled inside an i386 based architecture, which makes it clear.

uint_32version = 0x1 : This denotes version of object file, mostly 0x1 denotes for the original version of ELF.

void (* entry)() = _start : This is the memory address of the entry point of the program, here in this binary it is 4 bytes long , depending on the format, had it been a 64 bit ELF executable it would have been 8 bytes long.

uint32_t program_header_offset = 0x34 : This denotes/ points to the start of the program header table, in this case the offset is 0x34 which denotes it is an 22 bit ELF executable, had the offset been 0x40, it would denote 64-bit ELF executable.

uint32_t section_header_offset = 0x1860 : This points to the start of the section header table.

uint32_t flags = 0x0 :This denotes processor-specifc flags associated with the file, this is presently 0 for x86 architectures

uint16_t header_size = 0x34 : This denotes ELF header’s size in bytes, these are mostly 64 bytes of 64-bit executable & 52 bytes for 32 bit executable.

uint16_t program_header_size = 0x20: This denotes the size in bytes of one entry in the file’s header table.

uint16_t program_header_count = 0x9:Contains the size of a program header table entry.

uint16_t section_header_size = 0x28: Contains the size of a section header table entry.

uint16_t section_header_count = 0x1f : This denotes the number of entries in the section header table.

uint16_t string_table = 0x1c : This field denotes the section containing the string table for section names.

Segments

Similarly to ELF header the segments which are also known as program headers break down the ELF binary to suitable chunks to prepare the executable to be loaded into memory, these are not needed during linktime.

The Segment or the Program header table comprise of Elf32_Program Header structure which contain all the segments like PT_NULL, PT_INTERP, and many more each segment have some structure fields in common like p_type, p_flags any many more, we will now describe them one by one.

p_type : Denotes the segment type here it can be any of them like PT_NULL which is usually the first entry of the PHT or can be PT_INTERP which holds the .interp section.

offset : File offset of the segment.

virtual_address: Denotes the virtual address of the segment.

physical_address : Physical address of the segment.

file_size : Size of segment on the disk.

memory_size: Denotes the size of a segment in the memory.

flags: Denotes Segment attributes.

align : Denotes segment alignment in memory.

Sections

Section contains all the information needed for linking an object file for a working executable, there is a section header table which contains array of structures, each structures have fields like sh_name, sh_type and many more, we will describe them one by one.

sh_name : Index of section name in section header string table, which can be .text or .data , .bss, .dynstr and many more.

sh_type : This denotes the section type, this table can describe what those actually mean:

sh_flags : Denotes section attributes such as writable, executable mostly the values are represented if we use an ELF Parser to view the file and from the values we can easily clear out the meaning of the values.

sh_address : Denotes the virtual address of the section in memory.

sh_offset : Denotes the offset of the section in the image.

sh_size : Denotes the section size.

sh_link : Denotes the section link index.

sh_info : This field denotes extra section information

sh_addralign: This field denotes section alignment.

Conclusion:

Therefore, this was a brief info about all the parts of an ELF executable, the upcoming blogs will be focused on how things are loaded into memory and some other stuffs related to ELF format. Till then happy learning.

Resources & References :

Special thanks to Ignacio Sanmillan for helping me out.

Blog by Nerd of AX1AL. Join us at the discord server.

--

--