#------------------------------------------------------------------------------- # elftools: elf/relocation.py # # ELF relocations # # Eli Bendersky (eliben@gmail.com) # This code is in the public domain #------------------------------------------------------------------------------- from collections import namedtuple from ..common.exceptions import ELFRelocationError from ..common.utils import elf_assert, struct_parse from .sections import Section from .enums import ( ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, ENUM_RELOC_TYPE_MIPS, ENUM_RELOC_TYPE_ARM, ENUM_D_TAG) class Relocation(object): """ Relocation object - representing a single relocation entry. Allows dictionary-like access to the entry's fields. Can be either a REL or RELA relocation. """ def __init__(self, entry, elffile): self.entry = entry self.elffile = elffile def is_RELA(self): """ Is this a RELA relocation? If not, it's REL. """ return 'r_addend' in self.entry def __getitem__(self, name): """ Dict-like access to entries """ return self.entry[name] def __repr__(self): return '' % ( 'RELA' if self.is_RELA() else 'REL', self.entry) def __str__(self): return self.__repr__() class RelocationTable(object): """ Shared functionality between relocation sections and relocation tables """ def __init__(self, elffile, offset, size, is_rela): self._stream = elffile.stream self._elffile = elffile self._elfstructs = elffile.structs self._size = size self._offset = offset self._is_rela = is_rela if is_rela: self.entry_struct = self._elfstructs.Elf_Rela else: self.entry_struct = self._elfstructs.Elf_Rel self.entry_size = self.entry_struct.sizeof() def is_RELA(self): """ Is this a RELA relocation section? If not, it's REL. """ return self._is_rela def num_relocations(self): """ Number of relocations in the section """ return self._size // self.entry_size def get_relocation(self, n): """ Get the relocation at index #n from the section (Relocation object) """ entry_offset = self._offset + n * self.entry_size entry = struct_parse( self.entry_struct, self._stream, stream_pos=entry_offset) return Relocation(entry, self._elffile) def iter_relocations(self): """ Yield all the relocations in the section """ for i in range(self.num_relocations()): yield self.get_relocation(i) class RelocationSection(Section, RelocationTable): """ ELF relocation section. Serves as a collection of Relocation entries. """ def __init__(self, header, name, elffile): Section.__init__(self, header, name, elffile) RelocationTable.__init__(self, self.elffile, self['sh_offset'], self['sh_size'], header['sh_type'] == 'SHT_RELA') elf_assert(header['sh_type'] in ('SHT_REL', 'SHT_RELA'), 'Unknown relocation type section') elf_assert(header['sh_entsize'] == self.entry_size, 'Expected sh_entsize of %s section to be %s' % ( header['sh_type'], self.entry_size)) class RelocationHandler(object): """ Handles the logic of relocations in ELF files. """ def __init__(self, elffile): self.elffile = elffile def find_relocations_for_section(self, section): """ Given a section, find the relocation section for it in the ELF file. Return a RelocationSection object, or None if none was found. """ reloc_section_names = ( '.rel' + section.name, '.rela' + section.name) # Find the relocation section aimed at this one. Currently assume # that either .rel or .rela section exists for this section, but # not both. for relsection in self.elffile.iter_sections(): if ( isinstance(relsection, RelocationSection) and relsection.name in reloc_section_names): return relsection return None def apply_section_relocations(self, stream, reloc_section): """ Apply all relocations in reloc_section (a RelocationSection object) to the given stream, that contains the data of the section that is being relocated. The stream is modified as a result. """ # The symbol table associated with this relocation section symtab = self.elffile.get_section(reloc_section['sh_link']) for reloc in reloc_section.iter_relocations(): self._do_apply_relocation(stream, reloc, symtab) def _do_apply_relocation(self, stream, reloc, symtab): # Preparations for performing the relocation: obtain the value of # the symbol mentioned in the relocation, as well as the relocation # recipe which tells us how to actually perform it. # All peppered with some sanity checking. if reloc['r_info_sym'] >= symtab.num_symbols(): raise ELFRelocationError( 'Invalid symbol reference in relocation: index %s' % ( reloc['r_info_sym'])) sym_value = symtab.get_symbol(reloc['r_info_sym'])['st_value'] reloc_type = reloc['r_info_type'] recipe = None if self.elffile.get_machine_arch() == 'x86': if reloc.is_RELA(): raise ELFRelocationError( 'Unexpected RELA relocation for x86: %s' % reloc) recipe = self._RELOCATION_RECIPES_X86.get(reloc_type, None) elif self.elffile.get_machine_arch() == 'x64': if not reloc.is_RELA(): raise ELFRelocationError( 'Unexpected REL relocation for x64: %s' % reloc) recipe = self._RELOCATION_RECIPES_X64.get(reloc_type, None) elif self.elffile.get_machine_arch() == 'MIPS': if reloc.is_RELA(): raise ELFRelocationError( 'Unexpected RELA relocation for MIPS: %s' % reloc) recipe = self._RELOCATION_RECIPES_MIPS.get(reloc_type, None) elif self.elffile.get_machine_arch() == 'ARM': if reloc.is_RELA(): raise ELFRelocationError( 'Unexpected RELA relocation for ARM: %s' % reloc) recipe = self._RELOCATION_RECIPES_ARM.get(reloc_type, None) if recipe is None: raise ELFRelocationError( 'Unsupported relocation type: %s' % reloc_type) # So now we have everything we need to actually perform the relocation. # Let's get to it: # 0. Find out which struct we're going to be using to read this value # from the stream and write it back. if recipe.bytesize == 4: value_struct = self.elffile.structs.Elf_word('') elif recipe.bytesize == 8: value_struct = self.elffile.structs.Elf_word64('') else: raise ELFRelocationError('Invalid bytesize %s for relocation' % recipe.bytesize) # 1. Read the value from the stream (with correct size and endianness) original_value = struct_parse( value_struct, stream, stream_pos=reloc['r_offset']) # 2. Apply the relocation to the value, acting according to the recipe relocated_value = recipe.calc_func( value=original_value, sym_value=sym_value, offset=reloc['r_offset'], addend=reloc['r_addend'] if recipe.has_addend else 0) # 3. Write the relocated value back into the stream stream.seek(reloc['r_offset']) # Make sure the relocated value fits back by wrapping it around. This # looks like a problem, but it seems to be the way this is done in # binutils too. relocated_value = relocated_value % (2 ** (recipe.bytesize * 8)) value_struct.build_stream(relocated_value, stream) # Relocations are represented by "recipes". Each recipe specifies: # bytesize: The number of bytes to read (and write back) to the section. # This is the unit of data on which relocation is performed. # has_addend: Does this relocation have an extra addend? # calc_func: A function that performs the relocation on an extracted # value, and returns the updated value. # _RELOCATION_RECIPE_TYPE = namedtuple('_RELOCATION_RECIPE_TYPE', 'bytesize has_addend calc_func') def _reloc_calc_identity(value, sym_value, offset, addend=0): return value def _reloc_calc_sym_plus_value(value, sym_value, offset, addend=0): return sym_value + value def _reloc_calc_sym_plus_value_pcrel(value, sym_value, offset, addend=0): return sym_value + value - offset def _reloc_calc_sym_plus_addend(value, sym_value, offset, addend=0): return sym_value + addend def _reloc_calc_sym_plus_addend_pcrel(value, sym_value, offset, addend=0): return sym_value + addend - offset def _arm_reloc_calc_sym_plus_value_pcrel(value, sym_value, offset, addend=0): return sym_value // 4 + value - offset // 4 _RELOCATION_RECIPES_ARM = { ENUM_RELOC_TYPE_ARM['R_ARM_ABS32']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_sym_plus_value), ENUM_RELOC_TYPE_ARM['R_ARM_CALL']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_arm_reloc_calc_sym_plus_value_pcrel), } # https://dmz-portal.mips.com/wiki/MIPS_relocation_types _RELOCATION_RECIPES_MIPS = { ENUM_RELOC_TYPE_MIPS['R_MIPS_NONE']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_identity), ENUM_RELOC_TYPE_MIPS['R_MIPS_32']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_sym_plus_value), } _RELOCATION_RECIPES_X86 = { ENUM_RELOC_TYPE_i386['R_386_NONE']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_identity), ENUM_RELOC_TYPE_i386['R_386_32']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_sym_plus_value), ENUM_RELOC_TYPE_i386['R_386_PC32']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_sym_plus_value_pcrel), } _RELOCATION_RECIPES_X64 = { ENUM_RELOC_TYPE_x64['R_X86_64_NONE']: _RELOCATION_RECIPE_TYPE( bytesize=8, has_addend=True, calc_func=_reloc_calc_identity), ENUM_RELOC_TYPE_x64['R_X86_64_64']: _RELOCATION_RECIPE_TYPE( bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend), ENUM_RELOC_TYPE_x64['R_X86_64_PC32']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend_pcrel), ENUM_RELOC_TYPE_x64['R_X86_64_32']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend), ENUM_RELOC_TYPE_x64['R_X86_64_32S']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend), }