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))
|