From 430e3a550f9c7b64b4b3630d319442aa79e3db19 Mon Sep 17 00:00:00 2001 From: Robert Xiao Date: Sat, 16 Mar 2019 06:48:47 -0700 Subject: [PATCH] Enable parsing of relocations pointed to by DYNAMIC. (#135) --- elftools/elf/dynamic.py | 39 +++++++++++- elftools/elf/relocation.py | 56 +++++++++++------- test/test_relocations.py | 48 +++++++++++++++ .../x64_bad_sections.elf | Bin 0 -> 12333 bytes 4 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 test/test_relocations.py create mode 100644 test/testfiles_for_unittests/x64_bad_sections.elf diff --git a/elftools/elf/dynamic.py b/elftools/elf/dynamic.py index e75c16e..202a1f4 100644 --- a/elftools/elf/dynamic.py +++ b/elftools/elf/dynamic.py @@ -9,9 +9,11 @@ import itertools from .sections import Section, Symbol +from .enums import ENUM_D_TAG from .segments import Segment +from .relocation import RelocationTable from ..common.exceptions import ELFError -from ..common.utils import struct_parse, parse_cstring_from_stream +from ..common.utils import elf_assert, struct_parse, parse_cstring_from_stream class _DynamicStringTable(object): @@ -160,6 +162,41 @@ class Dynamic(object): self._num_tags = n + 1 return self._num_tags + def get_relocation_tables(self): + """ Load all available relocation tables from DYNAMIC tags. + + Returns a dictionary mapping found table types (REL, RELA, + JMPREL) to RelocationTable objects. + """ + + result = {} + + if list(self.iter_tags('DT_REL')): + result['REL'] = RelocationTable(self.elffile, + self.get_table_offset('DT_REL')[1], + next(self.iter_tags('DT_RELSZ'))['d_val'], False) + + relentsz = next(self.iter_tags('DT_RELENT'))['d_val'] + elf_assert(result['REL'].entry_size == relentsz, + 'Expected DT_RELENT to be %s' % relentsz) + + if list(self.iter_tags('DT_RELA')): + result['RELA'] = RelocationTable(self.elffile, + self.get_table_offset('DT_RELA')[1], + next(self.iter_tags('DT_RELASZ'))['d_val'], True) + + relentsz = next(self.iter_tags('DT_RELAENT'))['d_val'] + elf_assert(result['RELA'].entry_size == relentsz, + 'Expected DT_RELAENT to be %s' % relentsz) + + if list(self.iter_tags('DT_JMPREL')): + result['JMPREL'] = RelocationTable(self.elffile, + self.get_table_offset('DT_JMPREL')[1], + next(self.iter_tags('DT_PLTRELSZ'))['d_val'], + next(self.iter_tags('DT_PLTREL'))['d_val'] == ENUM_D_TAG['DT_RELA']) + + return result + class DynamicSection(Section, Dynamic): """ ELF dynamic table section. Knows how to process the list of tags. diff --git a/elftools/elf/relocation.py b/elftools/elf/relocation.py index e245df4..be03165 100644 --- a/elftools/elf/relocation.py +++ b/elftools/elf/relocation.py @@ -12,7 +12,7 @@ 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_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, ENUM_RELOC_TYPE_MIPS, ENUM_RELOC_TYPE_ARM, ENUM_D_TAG) class Relocation(object): @@ -44,43 +44,44 @@ class Relocation(object): return self.__repr__() -class RelocationSection(Section): - """ ELF relocation section. Serves as a collection of Relocation entries. +class RelocationTable(object): + """ Shared functionality between relocation sections and relocation tables """ - def __init__(self, header, name, elffile): - super(RelocationSection, self).__init__(header, name, elffile) - if self.header['sh_type'] == 'SHT_REL': - expected_size = self.structs.Elf_Rel.sizeof() - self.entry_struct = self.structs.Elf_Rel - elif self.header['sh_type'] == 'SHT_RELA': - expected_size = self.structs.Elf_Rela.sizeof() - self.entry_struct = self.structs.Elf_Rela + + 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: - elf_assert(False, 'Unknown relocation type section') + self.entry_struct = self._elfstructs.Elf_Rel - elf_assert( - self.header['sh_entsize'] == expected_size, - 'Expected sh_entsize of SHT_REL section to be %s' % expected_size) + self.entry_size = self.entry_struct.sizeof() def is_RELA(self): """ Is this a RELA relocation section? If not, it's REL. """ - return self.header['sh_type'] == 'SHT_RELA' + return self._is_rela def num_relocations(self): """ Number of relocations in the section """ - return self['sh_size'] // self['sh_entsize'] + 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['sh_offset'] + n * self['sh_entsize'] + entry_offset = self._offset + n * self.entry_size entry = struct_parse( self.entry_struct, - self.stream, + self._stream, stream_pos=entry_offset) - return Relocation(entry, self.elffile) + return Relocation(entry, self._elffile) def iter_relocations(self): """ Yield all the relocations in the section @@ -89,6 +90,21 @@ class RelocationSection(Section): 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. """ diff --git a/test/test_relocations.py b/test/test_relocations.py new file mode 100644 index 0000000..f1c8f10 --- /dev/null +++ b/test/test_relocations.py @@ -0,0 +1,48 @@ +import os +import sys +import unittest + +from elftools.common.py3compat import BytesIO +from elftools.elf.elffile import ELFFile +from elftools.elf.dynamic import DynamicSegment, DynamicSection + + +class TestRelocation(unittest.TestCase): + def test_dynamic_segment(self): + """Verify that we can process relocations on the PT_DYNAMIC segment without section headers""" + + test_dir = os.path.join('test', 'testfiles_for_unittests') + with open(os.path.join(test_dir, 'x64_bad_sections.elf'), 'rb') as f: + elff = ELFFile(f) + + for seg in elff.iter_segments(): + if isinstance(seg, DynamicSegment): + relos = seg.get_relocation_tables() + self.assertEqual(set(relos), {'JMPREL', 'RELA'}) + + def test_dynamic_section(self): + """Verify that we can parse relocations from the .dynamic section""" + + test_dir = os.path.join('test', 'testfiles_for_unittests') + with open(os.path.join(test_dir, 'sample_exe64.elf'), 'rb') as f: + elff = ELFFile(f) + + for sect in elff.iter_sections(): + if isinstance(sect, DynamicSection): + relos = sect.get_relocation_tables() + self.assertEqual(set(relos), {'JMPREL', 'RELA'}) + + def test_dynamic_section_solaris(self): + """Verify that we can parse relocations from the .dynamic section""" + + test_dir = os.path.join('test', 'testfiles_for_unittests') + with open(os.path.join(test_dir, 'exe_solaris32_cc.elf'), 'rb') as f: + elff = ELFFile(f) + + for sect in elff.iter_sections(): + if isinstance(sect, DynamicSection): + relos = sect.get_relocation_tables() + self.assertEqual(set(relos), {'JMPREL', 'REL'}) + +if __name__ == '__main__': + unittest.main() diff --git a/test/testfiles_for_unittests/x64_bad_sections.elf b/test/testfiles_for_unittests/x64_bad_sections.elf new file mode 100644 index 0000000000000000000000000000000000000000..6a30111fc304d1da86c9d69f22389b2a7c3bf987 GIT binary patch literal 12333 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