File: dynamic.py

package info (click to toggle)
python-pyelftools 0.32-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 68,964 kB
  • sloc: python: 15,903; ansic: 298; asm: 86; makefile: 24; cpp: 18; sh: 4
file content (358 lines) | stat: -rw-r--r-- 14,189 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#-------------------------------------------------------------------------------
# elftools: elf/dynamic.py
#
# ELF Dynamic Tags
#
# Mike Frysinger (vapier@gentoo.org)
# This code is in the public domain
#-------------------------------------------------------------------------------
import itertools

from collections import defaultdict
from .hash import ELFHashTable, GNUHashTable
from .sections import Section, Symbol
from .enums import ENUM_D_TAG
from .segments import Segment
from .relocation import RelocationTable, RelrRelocationTable
from ..common.exceptions import ELFError
from ..common.utils import elf_assert, struct_parse, parse_cstring_from_stream


class _DynamicStringTable(object):
    """ Bare string table based on values found via ELF dynamic tags and
        loadable segments only.  Good enough for get_string() only.
    """
    def __init__(self, stream, table_offset):
        self._stream = stream
        self._table_offset = table_offset

    def get_string(self, offset):
        """ Get the string stored at the given offset in this string table.
        """
        s = parse_cstring_from_stream(self._stream, self._table_offset + offset)
        return s.decode('utf-8') if s else ''


class DynamicTag(object):
    """ Dynamic Tag object - representing a single dynamic tag entry from a
        dynamic section.

        Allows dictionary-like access to the dynamic structure. For special
        tags (those listed in the _HANDLED_TAGS set below), creates additional
        attributes for convenience. For example, .soname will contain the actual
        value of DT_SONAME (fetched from the dynamic symbol table).
    """
    _HANDLED_TAGS = frozenset(
        ['DT_NEEDED', 'DT_RPATH', 'DT_RUNPATH', 'DT_SONAME',
         'DT_SUNW_FILTER'])

    def __init__(self, entry, stringtable):
        if stringtable is None:
            raise ELFError('Creating DynamicTag without string table')
        self.entry = entry
        if entry.d_tag in self._HANDLED_TAGS:
            setattr(self, entry.d_tag[3:].lower(),
                    stringtable.get_string(self.entry.d_val))

    def __getitem__(self, name):
        """ Implement dict-like access to entries
        """
        return self.entry[name]

    def __repr__(self):
        return '<DynamicTag (%s): %r>' % (self.entry.d_tag, self.entry)

    def __str__(self):
        if self.entry.d_tag in self._HANDLED_TAGS:
            s = '"%s"' % getattr(self, self.entry.d_tag[3:].lower())
        else:
            s = '%#x' % self.entry.d_ptr
        return '<DynamicTag (%s) %s>' % (self.entry.d_tag, s)


class Dynamic(object):
    """ Shared functionality between dynamic sections and segments.
    """
    def __init__(self, stream, elffile, stringtable, position, empty):
        """
        stream:
            The file-like object from which to load data

        elffile:
            The parent elffile object

        stringtable:
            A stringtable reference to use for parsing string references in
            entries

        position:
            The file offset of the dynamic segment/section

        empty:
            Whether this is a degenerate case with zero entries. Normally, every
            dynamic table will have at least one entry, the DT_NULL terminator.
        """
        self.elffile = elffile
        self.elfstructs = elffile.structs
        self._stream = stream
        self._num_tags = -1 if not empty else 0
        self._offset = position
        self._tagsize = self.elfstructs.Elf_Dyn.sizeof()
        self._empty = empty

        # Do not access this directly yourself; use _get_stringtable() instead.
        self._stringtable = stringtable

    def get_table_offset(self, tag_name):
        """ Return the virtual address and file offset of a dynamic table.
        """
        ptr = None
        for tag in self._iter_tags(type=tag_name):
            ptr = tag['d_ptr']
            break

        # If we found a virtual address, locate the offset in the file
        # by using the program headers.
        offset = None
        if ptr:
            offset = next(self.elffile.address_offsets(ptr), None)

        return ptr, offset

    def _get_stringtable(self):
        """ Return a string table for looking up dynamic tag related strings.

            This won't be a "full" string table object, but will at least
            support the get_string() function.
        """
        if self._stringtable:
            return self._stringtable

        # If the ELF has stripped its section table (which is unusual, but
        # perfectly valid), we need to use the dynamic tags to locate the
        # dynamic string table.
        _, table_offset = self.get_table_offset('DT_STRTAB')
        if table_offset is not None:
            self._stringtable = _DynamicStringTable(self._stream, table_offset)
            return self._stringtable

        # That didn't work for some reason.  Let's use the section header
        # even though this ELF is super weird.
        self._stringtable = self.elffile.get_section_by_name('.dynstr')
        return self._stringtable

    def _iter_tags(self, type=None):
        """ Yield all raw tags (limit to |type| if specified)
        """
        if self._empty:
            return
        for n in itertools.count():
            tag = self._get_tag(n)
            if type is None or tag['d_tag'] == type:
                yield tag
            if tag['d_tag'] == 'DT_NULL':
                break

    def iter_tags(self, type=None):
        """ Yield all tags (limit to |type| if specified)
        """
        for tag in self._iter_tags(type=type):
            yield DynamicTag(tag, self._get_stringtable())

    def _get_tag(self, n):
        """ Get the raw tag at index #n from the file
        """
        if self._num_tags != -1 and n >= self._num_tags:
            raise IndexError(n)
        offset = self._offset + n * self._tagsize
        return struct_parse(
            self.elfstructs.Elf_Dyn,
            self._stream,
            stream_pos=offset)

    def get_tag(self, n):
        """ Get the tag at index #n from the file (DynamicTag object)
        """
        return DynamicTag(self._get_tag(n), self._get_stringtable())

    def num_tags(self):
        """ Number of dynamic tags in the file, including the DT_NULL tag
        """
        if self._num_tags != -1:
            return self._num_tags

        for n in itertools.count():
            tag = self.get_tag(n)
            if tag.entry.d_tag == 'DT_NULL':
                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,
            RELR, 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_RELR')):
            result['RELR'] = RelrRelocationTable(self.elffile,
                self.get_table_offset('DT_RELR')[1],
                next(self.iter_tags('DT_RELRSZ'))['d_val'],
                next(self.iter_tags('DT_RELRENT'))['d_val'])

        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.
    """
    def __init__(self, header, name, elffile):
        Section.__init__(self, header, name, elffile)
        stringtable = elffile.get_section(header['sh_link'], ('SHT_STRTAB', 'SHT_NOBITS'))
        Dynamic.__init__(self, self.stream, self.elffile, stringtable,
            self['sh_offset'], self['sh_type'] == 'SHT_NOBITS')


class DynamicSegment(Segment, Dynamic):
    """ ELF dynamic table segment.  Knows how to process the list of tags.
    """
    def __init__(self, header, stream, elffile):
        # The string table section to be used to resolve string names in
        # the dynamic tag array is the one pointed at by the sh_link field
        # of the dynamic section header.
        # So we must look for the dynamic section contained in the dynamic
        # segment, we do so by searching for the dynamic section whose content
        # is located at the same offset as the dynamic segment
        stringtable = None
        for section in elffile.iter_sections():
            if (isinstance(section, DynamicSection) and
                    section['sh_offset'] == header['p_offset']):
                stringtable = elffile.get_section(section['sh_link'])
                break
        Segment.__init__(self, header, stream)
        Dynamic.__init__(self, stream, elffile, stringtable, self['p_offset'],
             self['p_filesz'] == 0)
        self._symbol_size = self.elfstructs.Elf_Sym.sizeof()
        self._num_symbols = None
        self._symbol_name_map = None

    def num_symbols(self):
        """ Number of symbols in the table recovered from DT_SYMTAB
        """
        if self._num_symbols is not None:
            return self._num_symbols

        # Check if a DT_GNU_HASH tag exists and recover the number of symbols
        # from the corresponding hash table
        _, gnu_hash_offset = self.get_table_offset('DT_GNU_HASH')
        if gnu_hash_offset is not None:
            hash_section = GNUHashTable(self.elffile, gnu_hash_offset, self)
            self._num_symbols = hash_section.get_number_of_symbols()

        # If DT_GNU_HASH did not exist, maybe we can use DT_HASH
        if self._num_symbols is None:
            _, hash_offset = self.get_table_offset('DT_HASH')
            if hash_offset is not None:
                # Get the hash table from the DT_HASH offset
                hash_section = ELFHashTable(self.elffile, hash_offset, self)
                self._num_symbols = hash_section.get_number_of_symbols()

        if self._num_symbols is None:
            # Find closest higher pointer than tab_ptr. We'll use that to mark
            # the end of the symbol table.
            tab_ptr, tab_offset = self.get_table_offset('DT_SYMTAB')
            if tab_ptr is None or tab_offset is None:
                raise ELFError('Segment does not contain DT_SYMTAB.')
            nearest_ptr = None
            for tag in self.iter_tags():
                tag_ptr = tag['d_ptr']
                if tag['d_tag'] == 'DT_SYMENT':
                    if self._symbol_size != tag['d_val']:
                        # DT_SYMENT is the size of one symbol entry. It must be
                        # the same as returned by Elf_Sym.sizeof.
                        raise ELFError('DT_SYMENT (%d) != Elf_Sym (%d).' %
                                       (tag['d_val'], self._symbol_size))
                if (tag_ptr > tab_ptr and
                        (nearest_ptr is None or nearest_ptr > tag_ptr)):
                    nearest_ptr = tag_ptr

            if nearest_ptr is None:
                # Use the end of segment that contains DT_SYMTAB.
                for segment in self.elffile.iter_segments():
                    if (segment['p_vaddr'] <= tab_ptr and
                            tab_ptr <= (segment['p_vaddr'] + segment['p_filesz'])):
                        nearest_ptr = segment['p_vaddr'] + segment['p_filesz']

            end_ptr = nearest_ptr
            self._num_symbols = (end_ptr - tab_ptr) // self._symbol_size

        if self._num_symbols is None:
            raise ELFError('Cannot determine the end of DT_SYMTAB.')

        return self._num_symbols

    def get_symbol(self, index):
        """ Get the symbol at index #index from the table (Symbol object)
        """
        tab_ptr, tab_offset = self.get_table_offset('DT_SYMTAB')
        if tab_ptr is None or tab_offset is None:
            raise ELFError('Segment does not contain DT_SYMTAB.')

        symbol = struct_parse(
            self.elfstructs.Elf_Sym,
            self._stream,
            stream_pos=tab_offset + index * self._symbol_size)

        string_table = self._get_stringtable()
        symbol_name = string_table.get_string(symbol["st_name"])

        return Symbol(symbol, symbol_name)

    def get_symbol_by_name(self, name):
        """ Get a symbol(s) by name. Return None if no symbol by the given name
            exists.
        """
        # The first time this method is called, construct a name to number
        # mapping
        #
        if self._symbol_name_map is None:
            self._symbol_name_map = defaultdict(list)
            for i, sym in enumerate(self.iter_symbols()):
                self._symbol_name_map[sym.name].append(i)
        symnums = self._symbol_name_map.get(name)
        return [self.get_symbol(i) for i in symnums] if symnums else None

    def iter_symbols(self):
        """ Yield all symbols in this dynamic segment. The symbols are usually
            the same as returned by SymbolTableSection.iter_symbols. However,
            in stripped binaries, SymbolTableSection might have been removed.
            This method reads from the mandatory dynamic tag DT_SYMTAB.
        """
        for i in range(self.num_symbols()):
            yield(self.get_symbol(i))