Questions about this topic? Sign up to ask in the talk tab.

ELF

From Security101 - Blackhat Techniques - Hacking Tutorials - Vulnerability Research - Security Tools
Jump to: navigation, search

Executable and Linkable Format (ELF) is the native binary executable format for the Linux operating system.

Contents

ELF Header

The ELF header contains information about the ELF binary's target platform, such as the version, operating system, and architecture. It also stores information about the program segment table and the section header table. Example hex diagram of an ELF64 header.

ELF Header
0 1 2 3 4 5 6 7 8 9 a b c d e f
0x00 EI_MAG EI_CLASS EI_DATA EI_VERSION EI_OSABI EI_ABIVERSION EI_PAD
0x10 E_TYPE E_MACHINE E_VERSION E_ENTRY
0x20 E_PHOFF E_SHOFF
0x30 E_FLAGS E_EHSIZE E_PHENTSIZE E_PHNUM E_SHENTSIZE E_SHNUM E_SHSTRNDX
EI_MAG
The EI_MAG (or magic string) identifies the file as an ELF binary and will always be the string "\x7fELF."
EI_CLASS
This field specifies whether the ELF file is 32-bit or 64-bit. Valid values are 0x01 and 0x02, respectively.
EI_DATA
Indicates the endianness of the binary file, 0x01 for little endian and 0x02 for big endian.
EI_VERSION
EI_VERSION specifies the version number of the ELF file, currently 0x01.
EI_OSABI
This field defines the OS version. Common values are:
  • Linux: 0x01
  • NetBSD: 0x02
  • FreeBSD: 0x09
  • OpenBSD: 0x0c
EI_ABIVERSION
Indicates the ABI (Application Binary Interface) version, this will usually be 0x00.
EI_PAD
Null padding.
E_TYPE
This is the ELF binary file type, valid values are:
  • No file type: 0x00
  • Relocatable file: 0x01
  • Executable file: 0x02
  • Shared object file: 0x03
  • Core file: 0x04
E_MACHINE
The E_MACHINE field specifies the target architecture of the ELF binary, x86_64 is 0x3e.
E_VERSION
Indicates the ELF version, this must always be 0x01.
E_ENTRY
Virtual address of the program entry point.
E_PHOFF
Offset of the program segment header table on disk.
E_SHOFF
Offset of the section header table on disk.
E_FLAGS
Processor specific flags, as of this writing no processor specific flags exist and this field should always be null.
E_EHSIZE
Size of the ELF header in bytes, on 32-bit this will be 0x34 bytes and on 64-bit this will be 0x40 bytes.
E_PHENTSIZE
Size of program header table entries in bytes.
E_PHNUM
Number of program header table entries.
E_SHENTSIZE
Size of section header table entries in bytes.
E_SHNUM
Number of section header table entries.
E_SHSTRNDX
The index of the section header string table in the section header table. The section header string table (or .shstrtab) contains the names of the section headers.

Program Segment Header

Program segment headers describes the information required to setup a program to be executed. A relevant hex ELF64 diagram is available.

Program Segment Header
0 1 2 3 4 5 6 7 8 9 a b c d e f
0x00 P_TYPE P_FLAGS P_OFFSET
0x10 P_VADDR P_PADDR
0x20 P_FILESZ P_MEMSZ
0x30 P_ALIGN
P_TYPE
The P_TYPE field identifies the type of program segment, values include:
  • Loadable program segment: 0x01
  • Linking information: 0x02
  • Program interpreter: 0x03
P_FLAGS
Stores a bitmask of segment permissions (read, write, execute).
P_OFFSET
Offset of segment on disk.
P_VADDR
Virtual address of segment in program memory.
P_PADDR
Physical address of segment in program memory.
P_FILESZ
Size on disk in bytes.
P_MEMSZ
Size in memory in program memory.
P_ALIGN
Byte alignment of the segment on disk and in memory.

Section Header

An ELF section contains data essential for program execution, such as program code, exported symbols, linking data, and other important information.

Section Header
0 1 2 3 4 5 6 7 8 9 a b c d e f
0x00 SH_NAME SH_TYPE SH_FLAGS
0x10 SH_ADDR SH_OFFSET
0x20 SH_SIZE SH_LINK SH_INFO
0x30 SH_ADDRALIGN SH_ENTSIZE
SH_NAME
The first field in a section header indicates the index of the name of the section in the string table (if any).
SH_TYPE
Indicates the section type, valid section types include:
SH_FLAGS
This section is a bitmask of section properties (such as read/write/execute permissions).
SH_ADDR
The virtual address of the section in memory at runtime.
SH_OFFSET
Offset of section on disk.
SH_SIZE
Size (in bytes) of the section.
SH_LINK
Index of a linked section (such as the string table for a symbol table).
SH_INFO
Contains section information that varies by section type.
SH_ADDRALIGN
Specifies the address alignment requirements for a given section (if any).
SH_ENTSIZE
Size of the entries in the section, if any.

Section Types

Symbol Table

The symbol table stores information about symbols in the ELF binary, such as exported functions in a shared library.

Symbol Table Entry
0 1 2 3 4 5 6 7 8 9 a b c d e f
0x00 ST_NAME ST_INFO ST_OTHER ST_SHNDX ST_VALUE
0x10 ST_SIZE
ST_NAME
Index of the symbol name in the symbol table's string table.
ST_INFO
Specifies a symbol's type and binding. Type values include:
  • Data: 0x01
  • Code: 0x02
  • Section: 0x03
  • File name: 0x04
Some valid bindings are:
  • Local symbol: 0x00
  • Global symbol: 0x01
ST_OTHER
Specifies the visibility of a symbol. Values are:
  • Default visibility: 0x00
  • Processor specific: 0x01
  • Unavailable in other modules: 0x02
  • Not exported: 0x03
ST_SHNDX
Specifies the index of the section header for the symbol in the section header table.
ST_VALUE
Contains the value of the symbol, for example, a virtual address.
ST_SIZE
Size of the symbol data, zero if not applicable.

String Table

A string table is a null character delimited list of C-strings. These are generally used for labels in a table.

Relocation Table

Relocation Table Entry
0 1 2 3 4 5 6 7 8 9 a b c d e f
0x00 R_OFFSET R_INFO
Relocation Table Entry with Addend
0 1 2 3 4 5 6 7 8 9 a b c d e f
0x00 R_OFFSET R_INFO
0x10 R_ADDEND
R_OFFSET
R_INFO
R_ADDEND

Dynamic

A dynamic symbol contains dynamic linking information, each symbol either stores an address or a hardcoded value.

Dynamic Section Entry
0 1 2 3 4 5 6 7 8 9 a b c d e f
0x00 D_TAG D_VAL/D_PTR
D_TAG
Specifies the type of dynamic tag. Valid values include:
  • Name of a required library: 0x01
  • String table: 0x05
  • Symbol table: 0x06

D_VAL and D_PTR are a union, so only one of them is used in a given dynamic symbol. The D_TAG specifies which one.

D_VAL
A value stored by the dynamic symbol.
D_PTR
A pointer stored by the dynamic symbol.

Program Bits

Program bits sections contain binary program data, such as machine code.

Sections

GOT

The Global Offset Table, or GOT, is a table of the memory addresses of data that can be found in shared libraries. Depending on RELRO implementation, this table may contain nulls on-disk, then be populated as procedures are called through the PLT. In some cases this table is populated on-disk with specific pointers. In other cases, all of these pointers are populated at once during the staging of the executable, then remapped as read-only before the executable even begins its execution. In these cases, the procedure linkage table always directs the application to the pointer stored in the GOT, which never invokes a trampoline for runtime function resolution. This section can be navigated from the dynamic section using dynamic shellcode.

PLT

The Procedure Linkage Table, or PLT, is a section of executable memory that jumps to the address of functions found in the GOT. This is entirely dependant on the way that RELRO is implemented into the binary. Partial RELRO results in functions being looked up as they are called, whereas full RELRO will autonomously resolve all of the import table before execution of the program begins. For example, the following C source code:

#include <stdio.h>
int main() {
    printf("hello\n");
    char lol[4];
    sprintf(lol, "hi\n");
    free(lol);
    return(0);
}

There are obviously serious problems with calling free on a non-heap object, however that's not the purpose of this exercise; "printf()" resolves to "puts()", and "free", of course, resolves to "free()". When disassembling the binary, the following is found in its main dis-assembly:

   0x4005b4 <main>:	push   %rbp
   0x4005b5 <main+1>:	mov    %rsp,%rbp
   0x4005b8 <main+4>:	sub    $0x10,%rsp
   0x4005bc <main+8>:	mov    %fs:0x28,%rax
   0x4005c5 <main+17>:	mov    %rax,-0x8(%rbp)
   0x4005c9 <main+21>:	xor    %eax,%eax
   0x4005cb <main+23>:	mov    $0x4006fc,%edi
   0x4005d0 <main+28>:	callq  0x4004a0 <puts@plt>
   0x4005d5 <main+33>:	movl   $0xa6968,-0x10(%rbp)
   0x4005dc <main+40>:	lea    -0x10(%rbp),%rax
   0x4005e0 <main+44>:	mov    %rax,%rdi
   0x4005e3 <main+47>:	callq  0x400490 <free@plt>

Taking a quick look at puts@plt:

   0x4004a0 <puts@plt>:	jmpq   *0x200b62(%rip)        # 0x601008 <[email protected]>
   0x4004a6 <puts@plt+6>:	pushq  $0x1
   0x4004ab <puts@plt+11>:	jmpq   0x400480

Following this jump:

0x601008 <[email protected]>:	0xe0	0xbc	0xa8	0xf7	0xff	0x7f	0x00	0x00

We can see that puts contains an absolute pointer to the function puts() from libc. However, this function has already been resolved. This is because a breakpoint was set at puts(). Lets examine free() next.

(gdb) x/10i 0x400490
   0x400490 <free@plt>:	jmpq   *0x200b6a(%rip)        # 0x601000 <[email protected]>
   0x400496 <free@plt+6>:	pushq  $0x0
   0x40049b <free@plt+11>:	jmpq   0x400480

Following this to [email protected]:

(gdb) x/8bx 0x601000
0x601000 <[email protected]>:	0x96	0x04	0x40	0x00	0x00	0x00	0x00	0x00

Here we can see a little-endian address, 0x400496. This address refers to the pushq 0x0 instruction in free@plt+6. Notice the push $0x0. This refers to free() being the first dynamically imported jump slot in the binary:

di@phonics:~/diagram$ objdump -R lol

lol:     file format elf64-x86-64


DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
0000000000600fe0 R_X86_64_GLOB_DAT  __gmon_start__
0000000000601000 R_X86_64_JUMP_SLOT  free
0000000000601008 R_X86_64_JUMP_SLOT  puts
0000000000601010 R_X86_64_JUMP_SLOT  __stack_chk_fail
0000000000601018 R_X86_64_JUMP_SLOT  __libc_start_main

The first entry is a data entry, and will be ignored when the jump to 0x400480 occurs. You'll notice the next function being puts() and in its plt block, there is a pushq 0x01. This is a function index. The following is present at 0x400480:

   0x400480:	pushq  0x200b6a(%rip)        # 0x600ff0 <_GLOBAL_OFFSET_TABLE_+8>
   0x400486:	jmpq   *0x200b6c(%rip)        # 0x600ff8 <_GLOBAL_OFFSET_TABLE_+16>
   0x40048c:	nopl   0x0(%rax)

These sections described as _GLOBAL_OFFSET_TABLE_ by GDB are part of the dynamic segment and merged at runtime. When the pushq occurs, a symbol pointer is placed into the stack. When the jmpq occurs, it lands on a trampoline function called __dl_runtime_resolv. This trampoline takes the code into the kernel to call _dl_fixup, resolves the address, and places the appropriate address for free into [email protected] Then the code returns-through-free back to the executing code. The next time free() needs to be called, it is already present at the appropriate address.

.interp

The .interp section contains a string specifying the full path of the program interpreter, e.g. "/lib64/ld-linux-x86-64.so.2."

.text

The .text section is a special program bits section that contains the bulk of the executable code for a binary. This may be compiled C or C++; it could also be assembly written by hand.

.data

The .data section is a program data section that stores hardcoded variables.

.bss

The .bss section is used by static variables at runtime and is blank on disk.

Personal tools
 


VPS-Heaven now accepting BitCoin!



Our research is made possible by your support.