Skip to content

Instantly share code, notes, and snippets.

@fzakaria
Created October 10, 2023 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fzakaria/62d776353b54d6175f4e35e40404d4b9 to your computer and use it in GitHub Desktop.
Save fzakaria/62d776353b54d6175f4e35e40404d4b9 to your computer and use it in GitHub Desktop.
ELF Loader in python
import ctypes
import mmap
import struct
# Simplified ELF Header Structure
class Elf32_Ehdr(ctypes.Structure):
_fields_ = [
("e_ident", ctypes.c_char * 16),
("e_type", ctypes.c_uint16),
("e_machine", ctypes.c_uint16),
("e_version", ctypes.c_uint32),
("e_entry", ctypes.c_uint32),
("e_phoff", ctypes.c_uint32),
("e_shoff", ctypes.c_uint32),
("e_flags", ctypes.c_uint32),
("e_ehsize", ctypes.c_uint16),
("e_phentsize", ctypes.c_uint16),
("e_phnum", ctypes.c_uint16),
("e_shentsize", ctypes.c_uint16),
("e_shnum", ctypes.c_uint16),
("e_shstrndx", ctypes.c_uint16),
]
# Simplified Program Header Structure
class Elf32_Phdr(ctypes.Structure):
_fields_ = [
("p_type", ctypes.c_uint32),
("p_offset", ctypes.c_uint32),
("p_vaddr", ctypes.c_uint32),
("p_paddr", ctypes.c_uint32),
("p_filesz", ctypes.c_uint32),
("p_memsz", ctypes.c_uint32),
("p_flags", ctypes.c_uint32),
("p_align", ctypes.c_uint32),
]
# Load ELF binary into memory and execute its main function
def load_and_execute_elf(file_path):
with open(file_path, "rb") as f:
# Map the file into memory
mmapped_file = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
# Read the ELF header
elf_header = Elf32_Ehdr.from_buffer(mmapped_file)
# Validate the ELF magic number
if elf_header.e_ident[:4] != b'\x7fELF':
raise ValueError("Not a valid ELF file")
# Find the program header table
phoff = elf_header.e_phoff
phentsize = elf_header.e_phentsize
phnum = elf_header.e_phnum
# Iterate through program header entries
for i in range(phnum):
# Read the program header entry
phdr_data = mmapped_file[phoff + i * phentsize : phoff + (i + 1) * phentsize]
phdr = Elf32_Phdr.from_buffer_copy(phdr_data)
# Check if the segment is loadable
if phdr.p_type == 1: # PT_LOAD
# Map the segment into memory
segment = mmap.mmap(-1, phdr.p_memsz, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC)
segment.write(mmapped_file[phdr.p_offset : phdr.p_offset + phdr.p_filesz])
# Assume the entry point is in this segment
if elf_header.e_entry >= phdr.p_vaddr and elf_header.e_entry < phdr.p_vaddr + phdr.p_memsz:
entry_offset = elf_header.e_entry - phdr.p_vaddr
# Define a ctypes function type for the entry point
FUNCTYPE = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_char_p))
# Create a ctypes function pointer
entry_func = FUNCTYPE(segment[entry_offset:].tobytes())
# Call the entry function
args = (ctypes.c_int(1), ctypes.POINTER(ctypes.c_char_p)(ctypes.c_char_p(b'example')))
result = entry_func(*args)
print("Program returned:", result)
break
# Example usage
# load_and_execute_elf("path_to_your_elf_file")
@fzakaria
Copy link
Author

import ctypes

# ... [Previous code] ...

# Auxiliary Vector Entry
class Elf32_auxv_t(ctypes.Structure):
    _fields_ = [
        ("a_type", ctypes.c_uint32),
        ("a_val", ctypes.c_uint32),
    ]

# ... [Previous code] ...

def load_and_execute_elf(file_path):
    # ... [Previous code] ...
    
    # Assume the entry point is in this segment
    if elf_header.e_entry >= phdr.p_vaddr and elf_header.e_entry < phdr.p_vaddr + phdr.p_memsz:
        entry_offset = elf_header.e_entry - phdr.p_vaddr
        
        # Define a ctypes function type for the entry point
        FUNCTYPE = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(Elf32_auxv_t))
        
        # Create a ctypes function pointer
        entry_func = FUNCTYPE(segment[entry_offset:].tobytes())
        
        # Create a synthetic auxiliary vector
        auxv_entries = [
            Elf32_auxv_t(3, 4096),  # AT_PAGESZ: 4096-byte pages
            Elf32_auxv_t(6, 100),   # AT_PHENT: Program header entry size
            # ... [Other auxv entries] ...
            Elf32_auxv_t(0, 0),     # AT_NULL: End of vector
        ]
        auxv = (Elf32_auxv_t * len(auxv_entries))(*auxv_entries)
        
        # Call the entry function
        args = (ctypes.c_int(1), ctypes.POINTER(ctypes.c_char_p)(ctypes.c_char_p(b'example')), ctypes.pointer(auxv))
        result = entry_func(*args)
        
        print("Program returned:", result)
        break

# ... [Previous code] ...

@fzakaria
Copy link
Author

import ctypes
import mmap
import struct

# ... [Previous code and structures] ...

# ELF Relocation Entry with Addend
class Elf32_Rela(ctypes.Structure):
    _fields_ = [
        ("r_offset", ctypes.c_uint32),
        ("r_info", ctypes.c_uint32),
        ("r_addend", ctypes.c_int32),
    ]

# ... [Previous code] ...

def perform_relocations(segment, rela_entries, symtab, strtab):
    for rela in rela_entries:
        # Extract the symbol index and type from r_info
        sym_index = rela.r_info >> 8
        rel_type = rela.r_info & 0xff
        
        # Find the symbol entry
        symbol = symtab[sym_index]
        
        # Find the symbol name
        sym_name = strtab[symbol.st_name:].split(b'\0', 1)[0]
        
        # TODO: Find the actual address of the symbol
        # This would typically involve looking up the symbol in the dynamic linker's
        # symbol table or in the symbol tables of other loaded libraries.
        # For simplicity, let's assume the symbol addresses are fixed.
        symbol_addr = ...  # Replace with actual address
        
        # Perform the relocation
        if rel_type == 7:  # R_386_JUMP_SLOT
            # Replace the function address in the PLT with the actual address
            ctypes.memmove(segment + rela.r_offset, ctypes.pointer(ctypes.c_uint32(symbol_addr)), 4)
        elif rel_type == 6:  # R_386_GLOB_DAT
            # Replace the global data address with the actual address
            ctypes.memmove(segment + rela.r_offset, ctypes.pointer(ctypes.c_uint32(symbol_addr)), 4)
        # TODO: Handle other relocation types

# ... [Previous code] ...

def load_and_execute_elf(file_path):
    # ... [Previous code] ...
    
    # Assume the entry point is in this segment
    if elf_header.e_entry >= phdr.p_vaddr and elf_header.e_entry < phdr.p_vaddr + phdr.p_memsz:
        # ... [Previous code] ...
        
        # TODO: Find the .rela.dyn and .dynsym sections in the ELF file
        # and read the relocation entries, symbol table, and string table.
        # For simplicity, let's assume we have arrays of the entries.
        rela_entries = [...]  # Replace with actual entries
        symtab = [...]  # Replace with actual entries
        strtab = b'...'  # Replace with actual string table
        
        # Perform relocations
        perform_relocations(segment, rela_entries, symtab, strtab)
        
        # ... [Previous code] ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment