Enable parsing of relocations pointed to by DYNAMIC. (#135)
authorRobert Xiao <nneonneo@gmail.com>
Sat, 16 Mar 2019 13:48:47 +0000 (06:48 -0700)
committerEli Bendersky <eliben@users.noreply.github.com>
Sat, 16 Mar 2019 13:48:47 +0000 (06:48 -0700)
elftools/elf/dynamic.py
elftools/elf/relocation.py
test/test_relocations.py [new file with mode: 0644]
test/testfiles_for_unittests/x64_bad_sections.elf [new file with mode: 0644]

index e75c16ed935dfd4c4bf9bebb4202547fbb653b1f..202a1f495569322265502f7b71a67a54e4644334 100644 (file)
@@ -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.
index e245df43ae96e912bf60a651dc38da7abdd3fb54..be0316532f38fad1fdbbb7d9bbae6067474e91c9 100644 (file)
@@ -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 (file)
index 0000000..f1c8f10
--- /dev/null
@@ -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 (file)
index 0000000..6a30111
Binary files /dev/null and b/test/testfiles_for_unittests/x64_bad_sections.elf differ