mirror of
https://github.com/vivisect/vivisect
synced 2026-06-08 18:04:23 +00:00
1050 lines
36 KiB
Python
1050 lines
36 KiB
Python
"""
|
|
Kenshoto's Elf parser
|
|
|
|
This package will let you use programatic ninja-fu
|
|
when trying to parse Elf binaries. The API is based
|
|
around several objects representing constructs in the
|
|
Elf binary format. The Elf object itself contains
|
|
parsed metadata and lists of things like section headers
|
|
and relocation entries. Additionally, most of the
|
|
objects implement repr() in some form or another which
|
|
allows you a bunch of readelf-like functionality.
|
|
|
|
*Eventually* this API will allow you to modify Elf binaries
|
|
and spit them back out in working order (not complete, you
|
|
may notice some of the initial code).
|
|
|
|
Send bug reports to rakuyo or at1as in the issue tracker
|
|
"""
|
|
|
|
# Copyright (C) 2007 Invisigoth - See LICENSE file for details
|
|
import io
|
|
import logging
|
|
|
|
from stat import *
|
|
from Elf.elf_lookup import *
|
|
import vstruct
|
|
import vstruct.defs.elf as vs_elf
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
HAS_STRING = [DT_NEEDED, DT_SONAME]
|
|
|
|
class Elf(vs_elf.Elf32, vs_elf.Elf64):
|
|
def __init__(self, fd, inmem=False):
|
|
'''
|
|
Parse data from 'fd' and create an Elf object.
|
|
|
|
This process attempts to get as much information from DYNAMICS as
|
|
possible, then adds in data from SECTIONS.
|
|
'''
|
|
|
|
# Grab a 32bit header to use to check for other
|
|
# machine types...
|
|
e = vs_elf.Elf32()
|
|
fd.seek(0)
|
|
bytes = fd.read(len(e))
|
|
e.vsParse(bytes)
|
|
|
|
# if e_data == 1, then 32 bit, if e_data == 2, 64bit
|
|
bigend = (e.e_data == ELFDATA2MSB)
|
|
|
|
# Parse 32bit header
|
|
if e.e_class == ELFCLASS32:
|
|
vs_elf.Elf32.__init__(self, bigend=bigend)
|
|
self.bits = 32
|
|
self.psize = 4
|
|
|
|
self._cls_reloc = vs_elf.Elf32Reloc
|
|
self._cls_reloca = vs_elf.Elf32Reloca
|
|
self._cls_symbol = vs_elf.Elf32Symbol
|
|
self._cls_section = vs_elf.Elf32Section
|
|
|
|
# Parse 64bit header
|
|
elif e.e_class == ELFCLASS64:
|
|
vs_elf.Elf64.__init__(self, bigend=bigend)
|
|
self.bits = 64
|
|
self.psize = 8
|
|
|
|
self._cls_reloc = vs_elf.Elf64Reloc
|
|
self._cls_reloca = vs_elf.Elf64Reloca
|
|
self._cls_symbol = vs_elf.Elf64Symbol
|
|
self._cls_section = vs_elf.Elf64Section
|
|
else:
|
|
raise Exception('Unrecognized e_class: %d' % e.e_class)
|
|
|
|
self.fd = fd
|
|
self.inmem = inmem
|
|
self.bigend = bigend
|
|
|
|
bytes = self.readAtOffset(0, len(self))
|
|
self.vsParse(bytes)
|
|
|
|
if self.e_machine == EM_386:
|
|
self.r_types = r_types_386
|
|
elif self.e_machine == EM_X86_64:
|
|
self.r_types = r_types_amd64
|
|
else:
|
|
self.r_types = {}
|
|
|
|
self.dyns = {}
|
|
self.pheaders = []
|
|
self.sections = []
|
|
self.secnames = {}
|
|
self.symbols = []
|
|
self.relocs = []
|
|
self.relocvas = set([])
|
|
self.symbols_by_name = {}
|
|
self.symbols_by_addr = {}
|
|
self.dynamics = [] # deprecated - 2019-10-21
|
|
self.dynamic_symbols = []
|
|
self.dynstrtabmeta = (None, None)
|
|
self.dynstrtab = []
|
|
self.dynsymtabct = None # populated by _parseDynStrs()
|
|
|
|
try:
|
|
logger.info('self._parsePheaders')
|
|
self._parsePheaders()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Program Headers: %r" % e, exc_info=1)
|
|
|
|
try:
|
|
logger.info('self._parseDynLinkInfo')
|
|
self._parseDynLinkInfo()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Program Headers: %r" % e, exc_info=1)
|
|
|
|
|
|
try:
|
|
logger.info('self._parseSections')
|
|
self._parseSections()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Sections: %r" % e, exc_info=1)
|
|
|
|
try:
|
|
logger.info('self._parseDynamicsFromSections')
|
|
self._parseDynamicsFromSections()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Dynamics from Sections: %r" % e, exc_info=1)
|
|
|
|
|
|
# load symbols and relocs from DYNAMICS
|
|
try:
|
|
logger.info('self._parseDynStrs')
|
|
self._parseDynStrs()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing String Table (via dynamics): %r" % e, exc_info=1)
|
|
|
|
try:
|
|
logger.info('self._parseDynSyms')
|
|
self._parseDynSyms()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Symbols (via dynamics): %r" % e, exc_info=1)
|
|
|
|
try:
|
|
logger.info('self._parseDynRelocs')
|
|
self._parseDynRelocs()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Relocations (via dynamics): %r" % e, exc_info=1)
|
|
|
|
|
|
# load symbols and relocs from SECTIONS
|
|
try:
|
|
logger.info('self._parseDynSymsFromSections')
|
|
self._parseDynSymsFromSections()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Dynamic Symbols (via Sections): %r" % e, exc_info=1)
|
|
|
|
try:
|
|
logger.info('self._parseSectionSymbols')
|
|
self._parseSectionSymbols()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Symbols (via Sections): %r" % e, exc_info=1)
|
|
|
|
try:
|
|
logger.info('self._parseSectionRelocs')
|
|
self._parseSectionRelocs()
|
|
except Exception as e:
|
|
logger.warning("Exception parsing Relocations (via Sections): %r" % e, exc_info=1)
|
|
|
|
logger.info('done parsing ELF')
|
|
|
|
def __del__(self):
|
|
try:
|
|
self.fd.close()
|
|
except:
|
|
pass # whatever. we're tearing down anyway
|
|
|
|
def getFileBytes(self):
|
|
'''
|
|
Return the bytes of the file as they currently exist from the view of the file descriptor-like object
|
|
|
|
But keeping in mind not to smash over the old location of the fd
|
|
'''
|
|
self.fd.flush()
|
|
old = self.fd.tell()
|
|
self.fd.seek(0)
|
|
byts = self.fd.read()
|
|
self.fd.seek(old)
|
|
return byts
|
|
|
|
def getRelocTypeName(self, rtype):
|
|
'''
|
|
Because relocation type names are decided based on the
|
|
arch, only the Elf knows for sure...
|
|
'''
|
|
return self.r_types.get(rtype)
|
|
|
|
def _parsePheaders(self):
|
|
# Load up any program headers we find
|
|
if self.e_phoff:
|
|
pbase = self.e_phoff
|
|
plen = self.e_phentsize
|
|
for i in range(self.e_phnum):
|
|
if self.bits == 32:
|
|
pgm = vs_elf.Elf32Pheader(bigend=self.bigend)
|
|
elif self.bits == 64:
|
|
pgm = vs_elf.Elf64Pheader(bigend=self.bigend)
|
|
else:
|
|
raise Exception('Platform not supported: %d' % (self.bits))
|
|
|
|
bytes = self.readAtOffset(pbase, plen)
|
|
pgm.vsParse(bytes)
|
|
|
|
self.pheaders.append(pgm)
|
|
pbase += plen
|
|
|
|
def _parseSections(self):
|
|
# Load up all the section headers
|
|
if self.e_shoff:
|
|
# Load up the sections
|
|
secs = vstruct.VArray()
|
|
[secs.vsAddElement(self._cls_section(bigend=self.bigend)) for i in range(self.e_shnum)]
|
|
|
|
sbase = self.e_shoff
|
|
slen = self.e_shentsize
|
|
if len(secs[0]) != slen:
|
|
raise Exception('Invalid Section Header Size: %d' % slen)
|
|
|
|
secbytes = self.readAtOffset(sbase, self.e_shnum * slen)
|
|
|
|
secslen = slen * self.e_shnum
|
|
secs.vsParse(secbytes, fast=True)
|
|
if secslen != len(secs):
|
|
logger.warning('Invalid Section-Headers Size: should be: %d retrieved: %d', secslen, len(secbytes))
|
|
|
|
self.sections.extend([s[1] for s in secs])
|
|
|
|
# Populate the section names
|
|
strsec = self.sections[self.e_shstrndx]
|
|
names = self.readAtOffset(strsec.sh_offset, strsec.sh_size)
|
|
for sec in self.sections:
|
|
name = names[sec.sh_name:].split(b"\x00")[0]
|
|
if len(name) > 0:
|
|
name = name.decode('utf-8')
|
|
sec.setName(name)
|
|
self.secnames[name] = sec
|
|
|
|
def getDynPHdr(self):
|
|
'''
|
|
Returns the "Dynamics" ProgramHeader
|
|
'''
|
|
phdr = None
|
|
for phdr in self.getPheaders():
|
|
if phdr.p_type == PT_DYNAMIC:
|
|
return phdr
|
|
return None
|
|
|
|
def getDynBytes(self):
|
|
'''
|
|
Finds and returns the bytes from a Program Header of type PT_DYNAMIC
|
|
'''
|
|
phdr = self.getDynPHdr()
|
|
if phdr is None:
|
|
return None
|
|
|
|
dynbytes = self.readAtOffset(phdr.p_offset, phdr.p_filesz)
|
|
return dynbytes
|
|
|
|
def _parseDynSymsFromSections(self):
|
|
'''
|
|
if by some strange chance, the DYNAMCS PHDR doesn't exist but we have this section...
|
|
'''
|
|
symtab = self.getSectionBytes('.dynsym')
|
|
if symtab is None:
|
|
return
|
|
|
|
ssymtabva = self.getSection('.dynsym').sh_addr
|
|
dsymtabva = self.dyns.get(DT_SYMTAB)
|
|
if ssymtabva != dsymtabva:
|
|
logger.info("Section headers and Dynamics disagree on Symbol Table: sec: 0x%x, dyn: 0x%x", ssymtabva, dsymtabva)
|
|
|
|
# only parse the symbols that are not already accounted for.
|
|
# symbols are ordered, so existence of index Y is always the same
|
|
sym = self._cls_symbol(bigend=self.bigend)
|
|
count = len(symtab) // len(sym)
|
|
diff = count - len(self.dynamic_symbols)
|
|
if diff == 0:
|
|
return
|
|
|
|
offset = len(self.dynamic_symbols) * len(sym)
|
|
|
|
syms = sym * diff
|
|
vstruct.VArray(elems=syms).vsParse(symtab[offset:], fast=True)
|
|
|
|
logger.warning("_parseDynSymsFromSections: current_count: %d\tdiff: %d\toffset: %d\t", count, diff, offset)
|
|
for sym in syms:
|
|
if not sym.st_name:
|
|
continue
|
|
name = self.getStrtabString(sym.st_name, ".dynstr")
|
|
sym.setName(name)
|
|
|
|
if sym in self.dynamic_symbols:
|
|
continue
|
|
self.dynamic_symbols.append(sym)
|
|
|
|
def _parseDynamicsFromSections(self):
|
|
'''
|
|
if by some strange chance, the DYNAMCS PHDR doesn't exist but we have this section...
|
|
'''
|
|
dynbytes = self.getSectionBytes('.dynamic')
|
|
while dynbytes:
|
|
if self.bits == 32:
|
|
dyn = vs_elf.Elf32Dynamic(bigend=self.bigend)
|
|
elif self.bits == 64:
|
|
dyn = vs_elf.Elf64Dynamic(bigend=self.bigend)
|
|
else:
|
|
raise Exception('Platform not supported: %d' % (self.bits))
|
|
dyn.vsParse(dynbytes)
|
|
|
|
if dyn.d_tag in HAS_STRING:
|
|
name = self.getStrtabString(dyn.d_value, ".dynstr")
|
|
dyn.setName(name)
|
|
|
|
# don't add a second entry
|
|
if dyn not in self.dynamics:
|
|
logger.debug("dynamic: %r: 0x%x", dt_names.get(dyn.d_tag), dyn.d_value)
|
|
self.dynamics.append(dyn)
|
|
|
|
if dyn.d_tag == DT_NULL: # Represents the end
|
|
break
|
|
dynbytes = dynbytes[len(dyn):]
|
|
|
|
def _parseDynLinkInfo(self):
|
|
'''
|
|
Parse the Dynamics segment and populate both self.dynamics (legacy) and self.dyns
|
|
This must be run before most Dynamic-data accessors like getDynStrtabString(),
|
|
getDynSymTabInfo(), etc..
|
|
'''
|
|
dynbytes = self.getDynBytes()
|
|
if dynbytes is None:
|
|
return
|
|
|
|
while dynbytes:
|
|
if self.bits == 32:
|
|
dyn = vs_elf.Elf32Dynamic(bigend=self.bigend)
|
|
elif self.bits == 64:
|
|
dyn = vs_elf.Elf64Dynamic(bigend=self.bigend)
|
|
else:
|
|
raise Exception('Platform not supported: %d' % (self.bits))
|
|
|
|
dyn.vsParse(dynbytes)
|
|
|
|
# dump the tag/value pairs into the "dyns" dictionary. if multiples, create a tuple
|
|
curdyn = self.dyns.get(dyn.d_tag)
|
|
if curdyn is not None:
|
|
self.dyns[dyn.d_tag] = (curdyn, dyn.d_value)
|
|
else:
|
|
self.dyns[dyn.d_tag] = dyn.d_value
|
|
logger.debug('dynamic: %r: 0x%x', dt_names.get(dyn.d_tag), dyn.d_value)
|
|
|
|
# DEPRECATED: storing info in both dyns{} and dynamics[].
|
|
# 2019-10-21: dynamics will go away sometime in the future
|
|
self.dynamics.append(dyn)
|
|
if dyn.d_tag == DT_NULL: # Represents the end
|
|
break
|
|
dynbytes = dynbytes[len(dyn):]
|
|
|
|
def _parseDynStrs(self):
|
|
# setup STRTAB for string recovery:
|
|
dynstrtabva = self.dyns.get(DT_STRTAB)
|
|
strsz = self.dyns.get(DT_STRSZ)
|
|
if dynstrtabva is None or strsz is None:
|
|
logger.info('no dynamic string tableinfo found: DT_STRTAB: %r DT_STRSZ: %r', dynstrtabva, strsz)
|
|
return
|
|
|
|
if self.dynstrtabmeta != (None, None):
|
|
curtab = self.dynstrtabmeta[0]
|
|
logger.warning('wtf? multiple dynamic string tables? old: 0x%x new: 0x%x', curtab, rva)
|
|
|
|
strtabbytes = self.readAtRva(dynstrtabva, strsz)
|
|
|
|
self.dynstrtabmeta = (dynstrtabva, strsz)
|
|
self.dynstrtab = strtabbytes.split(b'\0')
|
|
|
|
# since our string table should certainly end in '\0', we'll have an empty string
|
|
# at the end. since this array is used to determine the number of symbols, we
|
|
# need to clean it up.
|
|
if len(self.dynstrtab) and not len(self.dynstrtab[-1]):
|
|
self.dynstrtab.pop()
|
|
|
|
# setup names for the dynamics table entries
|
|
for dyn in self.dynamics:
|
|
if dyn.d_tag in HAS_STRING:
|
|
name = self.getDynStrtabString(dyn.d_value)
|
|
dyn.setName(name)
|
|
|
|
def _parseDynSyms(self):
|
|
'''
|
|
Parses the Symbol Table and sets up Dynamic String Table
|
|
Using Dynamics instead of ELF Sections
|
|
|
|
This relies on the DYNAMICS section having DT_SYMTAB and DT_SYMENT
|
|
|
|
Because ELF has no DT_SYMTABSZ, "symtabsz" as returned from
|
|
getDynSymTabInfo() cannot be fully trusted. Therefore, we run a few
|
|
sanity heuristics.
|
|
|
|
This is only a prep run to identify symbols. If getDynSymbol() is
|
|
called with an index not currently in dynamic_symbols, dynamic_symbols
|
|
is expanded to fill the need (albeit, without these sanity checks, so
|
|
be cautious).
|
|
'''
|
|
# fyi: '.dynsym' section == DT_SYMTAB
|
|
# '.dynstr' section == DT_STRTAB
|
|
|
|
# parse Dynamic Symbol Table
|
|
if len(self.dynamic_symbols):
|
|
logger.warning('_parseDynSyms() cannot run: dynamic_symbols is not empty')
|
|
return
|
|
|
|
symtabrva, symsz, symtabsz = self.getDynSymTabInfo()
|
|
if symtabrva is None:
|
|
return
|
|
|
|
dsoff = 0
|
|
while True:
|
|
syment = self.readAtRva(symtabrva + dsoff, symsz)
|
|
sym = self._cls_symbol(bigend=self.bigend)
|
|
sym.vsParse(syment)
|
|
if sym.getInfoType() not in st_info_type:
|
|
break
|
|
|
|
if sym.getInfoBind() not in st_info_bind:
|
|
break
|
|
|
|
name = self.getDynStrtabString(sym.st_name)
|
|
if name is None:
|
|
break
|
|
|
|
sym.setName(name)
|
|
self.dynamic_symbols.append(sym)
|
|
|
|
dsoff += symsz
|
|
|
|
# FIXME: wrap in VERDEF and SYMINFO into the analysis.
|
|
def _parseSectionSymbols(self):
|
|
"""
|
|
Parse out the symbols that this elf binary has for us.
|
|
"""
|
|
for sec in self.sections:
|
|
if sec.sh_type == SHT_SYMTAB:
|
|
sym = self._cls_symbol(bigend=self.bigend)
|
|
symtab = self.readAtOffset(sec.sh_offset, sec.sh_size)
|
|
|
|
count, remain = divmod(sec.sh_size, len(sym))
|
|
|
|
syms = vstruct.VArray()
|
|
[syms.vsAddElement(self._cls_symbol(bigend=self.bigend)) for i in range(count)]
|
|
syms.vsParse(symtab, fast=True)
|
|
|
|
for idx, sym in syms:
|
|
if sym.st_name:
|
|
name = self.getStrtabString(sym.st_name, ".strtab")
|
|
sym.setName(name)
|
|
# logger.info('SHT_SYMTAB: %r', sym)
|
|
|
|
self.addSymbol(sym)
|
|
|
|
def _parseDynRelocs(self):
|
|
"""
|
|
Parse all the relocation entries out of Dyn table entries based at
|
|
* REL
|
|
* RELA
|
|
* JMPREL
|
|
"""
|
|
rel, relent, relsz = self.getDynRelInfo()
|
|
if rel is not None:
|
|
cls = self._cls_reloc
|
|
self._doDynRelocs(rel, relsz, cls)
|
|
|
|
rela, relaent, relasz = self.getDynRelaInfo()
|
|
if rela is not None:
|
|
cls = self._cls_reloca
|
|
self._doDynRelocs(rela, relasz, cls)
|
|
|
|
jmprel, pltrel, pltrelsz = self.getDynPltRelInfo()
|
|
if jmprel is not None:
|
|
cls = (self._cls_reloc, self._cls_reloca)[pltrel==DT_RELA]
|
|
self._doDynRelocs(jmprel, pltrelsz, cls)
|
|
|
|
def _doDynRelocs(self, rva, relsz, cls=None):
|
|
syms = self.getDynSyms()
|
|
|
|
if cls is None:
|
|
cls = self._cls_reloc
|
|
|
|
reloc = cls(bigend=self.bigend)
|
|
relbytes = self.readAtRva(rva, relsz)
|
|
count, remain = divmod(relsz, len(reloc))
|
|
|
|
relocs = reloc * count
|
|
vstruct.VArray(elems=relocs).vsParse(relbytes,fast=True)
|
|
|
|
for reloc in relocs:
|
|
index = reloc.getSymTabIndex()
|
|
sym = self.getDynSymbol(index)
|
|
if sym is not None:
|
|
reloc.setName( sym.getName() )
|
|
self.relocs.append((None, reloc))
|
|
self.relocvas.add((None, reloc.r_offset))
|
|
|
|
def _parseSectionRelocs(self):
|
|
"""
|
|
Parse all the relocation entries out of any sections with
|
|
sh_type == SHT_REL or SHT_RELA
|
|
|
|
Ignores repeat relocs (ie. those already parsed from DYNAMICS)
|
|
"""
|
|
# could it ever be interesting? perhaps if dynamic relocs fail?
|
|
rel, relent, relsz = self.getDynRelInfo()
|
|
rela, relaent, relasz = self.getDynRelaInfo()
|
|
jmprel, pltrel, pltrelsz = self.getDynPltRelInfo()
|
|
dynrels = (rel, rela, jmprel)
|
|
|
|
for secidx, sec in enumerate(self.sections):
|
|
if sec.sh_type not in (SHT_REL, SHT_RELA):
|
|
continue
|
|
|
|
if sec.sh_offset not in dynrels:
|
|
logger.warning('_parseSectionRelocs: Reloc section differs from Dynamics: 0x%x', sec.sh_offset)
|
|
|
|
reloccls = self._cls_reloc
|
|
if sec.sh_type == SHT_RELA:
|
|
reloccls = self._cls_reloca
|
|
|
|
secbytes = self.readAtOffset(sec.sh_offset, sec.sh_size)
|
|
reloc = reloccls(bigend=self.bigend)
|
|
count, remain = divmod(len(secbytes), len(reloc))
|
|
|
|
relocs = reloc * count
|
|
vstruct.VArray(elems=relocs).vsParse(secbytes, fast=True)
|
|
|
|
for reloc in relocs:
|
|
index = reloc.getSymTabIndex()
|
|
if index < len(self.dynamic_symbols):
|
|
sym = self.dynamic_symbols[index]
|
|
reloc.setName(sym.getName())
|
|
|
|
# key on the symbol table index because relocatable elf files use r_offset
|
|
# as an actual offset and not a virtual address like executable images
|
|
# For all other binaries, let dynamics win
|
|
# Ref: https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-54839.html
|
|
key = (index, reloc.r_offset)
|
|
if key in self.relocvas or (None, reloc.r_offset) in self.relocvas:
|
|
logger.debug('duplicate relocation (section): %s', reloc)
|
|
continue
|
|
|
|
logger.info('section reloc: %s', reloc)
|
|
self.relocs.append((secidx, reloc))
|
|
self.relocvas.add(key)
|
|
|
|
def getBaseAddress(self):
|
|
"""
|
|
For prelinked and main-exe elf binaries, return the
|
|
value for the loaded base address...
|
|
"""
|
|
shrd = self.isSharedObject()
|
|
plnk = self.isPreLinked()
|
|
|
|
# If it's a shared object and *not* prelinked,
|
|
# we need to select a base address for it
|
|
# FIXME find non-colliding addr in workspace
|
|
if shrd and not plnk:
|
|
return 0x02000000
|
|
|
|
# Find the best base address from the list of
|
|
# section addresses...
|
|
base = None
|
|
for pgm in self.getPheaders():
|
|
|
|
if pgm.p_vaddr == 0:
|
|
continue
|
|
|
|
if base is None:
|
|
base = pgm.p_vaddr
|
|
continue
|
|
|
|
if pgm.p_vaddr < base:
|
|
base = pgm.p_vaddr
|
|
|
|
if base is None:
|
|
if self.isRelocatable():
|
|
base = 0
|
|
else:
|
|
base = 0x20000000
|
|
|
|
base &= 0xfffff000
|
|
return base
|
|
|
|
def readAtRva(self, rva, size):
|
|
'''
|
|
Calculate the file offset for the given RVA and
|
|
read from it...
|
|
'''
|
|
return self.readAtOffset(self.rvaToOffset(rva), size)
|
|
|
|
def rvaToOffset(self, rva):
|
|
'''
|
|
Convert an RVA for this ELF binary to a file offset.
|
|
'''
|
|
baseaddr = 0
|
|
#if self.isPreLinked() or not self.isSharedObject():
|
|
#if not self.isSharedObject():
|
|
#logger.info('SUBTRACTING CALCULATED BASE')
|
|
#baseaddr = self.getBaseAddress()
|
|
|
|
for pgm in self.pheaders:
|
|
if pgm.p_type != PT_LOAD:
|
|
continue
|
|
phrva = pgm.p_vaddr - baseaddr
|
|
if rva < phrva:
|
|
continue
|
|
if rva >= phrva+pgm.p_memsz:
|
|
continue
|
|
# We are inside this pgrm header!
|
|
rvaoff = rva - phrva
|
|
return pgm.p_offset + rvaoff
|
|
|
|
raise Exception(f"Couldn't convert rva: {rva} to a file offset")
|
|
|
|
def readAtOffset(self, off, size):
|
|
'''
|
|
Read from the given file offset.
|
|
'''
|
|
self.fd.seek(off)
|
|
return self.fd.read(size)
|
|
|
|
def getEndian(self):
|
|
'''
|
|
Is architecture BigEndian?
|
|
Returns True for MSB, False for LSB
|
|
|
|
This works with Vivisect's definitions of ENDIAN_MSB/ENDIAN_LSB constants:
|
|
(defined in envi/const.py)
|
|
ENDIAN_LSB = 0
|
|
ENDIAN_MSB = 1
|
|
|
|
'''
|
|
return self.e_data == ELFDATA2MSB
|
|
|
|
def getDynRelInfo(self):
|
|
'''
|
|
Returns startva, size, and Entity size for any REL records
|
|
'''
|
|
rel = self.dyns.get(DT_REL)
|
|
relent = self.dyns.get(DT_RELENT)
|
|
relsz = self.dyns.get(DT_RELSZ)
|
|
return rel, relent, relsz
|
|
|
|
def getDynRelaInfo(self):
|
|
'''
|
|
Returns startva, size, and Entity size for any RELA records
|
|
'''
|
|
rela = self.dyns.get(DT_RELA)
|
|
relaent = self.dyns.get(DT_RELAENT)
|
|
relasz = self.dyns.get(DT_RELASZ)
|
|
return rela, relaent, relasz
|
|
|
|
def getDynPltRelInfo(self):
|
|
'''
|
|
Returns startva, size, and Entity size for any RELA records
|
|
'''
|
|
jmprel = self.dyns.get(DT_JMPREL)
|
|
pltrel = self.dyns.get(DT_PLTREL)
|
|
pltrelsz = self.dyns.get(DT_PLTRELSZ)
|
|
return jmprel, pltrel, pltrelsz
|
|
|
|
def getSection(self, secname):
|
|
return self.secnames.get(secname, None)
|
|
|
|
def getSectionByIndex(self, idx):
|
|
if idx >= len(self.sections):
|
|
return None
|
|
return self.sections[idx]
|
|
|
|
def getSections(self):
|
|
"""
|
|
Return the array of sections for this Elf
|
|
"""
|
|
return list(self.sections)
|
|
|
|
def getSectionBytes(self, secname):
|
|
sec = self.getSection(secname)
|
|
if sec is None:
|
|
return None
|
|
return self.readAtOffset(sec.sh_offset, sec.sh_size)
|
|
|
|
def getStrtabString(self, offset, section=".strtab"):
|
|
sec = self.getSection(section)
|
|
bytes = self.readAtOffset(sec.sh_offset, sec.sh_size)
|
|
index = bytes.find(b"\x00", offset)
|
|
return bytes[offset:index].decode('utf-8')
|
|
|
|
def getNotes(self):
|
|
'''
|
|
Retrieve a list of the ElfNote vstructs from any
|
|
sections of type SHT_NOTE.
|
|
|
|
Example:
|
|
for note in e.getNotes():
|
|
print('%s : %d' % (e.name, e.ntype))
|
|
'''
|
|
for sec in self.getSections():
|
|
if sec.sh_type != SHT_NOTE:
|
|
continue
|
|
|
|
try:
|
|
notebytes = self.readAtOffset(sec.sh_offset, sec.sh_size)
|
|
offset = 0
|
|
notebyteslen = len(notebytes)
|
|
while offset < notebyteslen:
|
|
note = vs_elf.ElfNote(bigend=self.getEndian())
|
|
if notebyteslen - offset < len(note):
|
|
logger.warning("""\nNOTES section length mismatch!\n\t%s
|
|
\tSection Bytes: %s\n\tStranded bytes: %s\n""",
|
|
sec, repr(notebytes), repr(notebytes[offset:]))
|
|
break
|
|
|
|
offset = note.vsParse(notebytes, offset=offset)
|
|
yield note
|
|
except Exception as e:
|
|
logger.warning('Elf.getNotes() Exception: %s', e)
|
|
|
|
def getPlatform(self):
|
|
'''
|
|
Return a "best effort" platform guess (envi platform name).
|
|
( and platform specific details if any )
|
|
|
|
Example:
|
|
plat = e.getPlatform()
|
|
'''
|
|
for note in self.getNotes():
|
|
if note.name == b'GNU\x00' and note.ntype == 1:
|
|
desc0 = int(note.desc[0])
|
|
return osnotes.get(desc0, 'unknown')
|
|
|
|
if self.getSection('.comment'):
|
|
sec = self.getSection('.comment')
|
|
if b'FreeBSD' in self.readAtOffset(sec.sh_offset, sec.sh_size):
|
|
return 'freebsd'
|
|
|
|
if self.getSection('QNX_info'):
|
|
return 'qnx'
|
|
|
|
return 'unknown'
|
|
|
|
def getDynamics(self):
|
|
'''
|
|
Return a list of the dynamics.
|
|
'''
|
|
return list(self.dynamics)
|
|
|
|
def getDynSyms(self):
|
|
'''
|
|
Return a list of dynamic symbol objects.
|
|
'''
|
|
return self.dynamic_symbols
|
|
|
|
def getRelocs(self):
|
|
'''
|
|
Get the list of relocations.
|
|
'''
|
|
return list(self.relocs)
|
|
|
|
def isPreLinked(self):
|
|
'''
|
|
Returns True if the Elf binary is prelinked.
|
|
'''
|
|
for dyn in self.dynamics:
|
|
if dyn.d_tag == DT_GNU_PRELINKED:
|
|
return True
|
|
if dyn.d_tag == DT_GNU_CONFLICTSZ:
|
|
return True
|
|
return False
|
|
|
|
def isSharedObject(self):
|
|
'''
|
|
Returns true if the given Elf binary is a dynamically shared
|
|
object.
|
|
'''
|
|
if self.e_type == ET_DYN:
|
|
return True
|
|
return False
|
|
|
|
def hasInterpreter(self):
|
|
'''
|
|
Returns true if the Elf binary has an PT_INTERP program header.
|
|
'''
|
|
for phdr in self.getPheaders():
|
|
if phdr.p_type == PT_INTERP:
|
|
return True
|
|
|
|
return False
|
|
|
|
def isExecutable(self):
|
|
'''
|
|
Returns true if the given Elf binary is an executable file type.
|
|
Either the Elf header specifies e_type of ET_EXEC or
|
|
e_type == ET_DYN and has an interpreter designated in the pheaders.
|
|
'''
|
|
if self.e_type == ET_EXEC:
|
|
return True
|
|
|
|
if self.e_type == ET_DYN and self.hasInterpreter():
|
|
return True
|
|
|
|
return False
|
|
|
|
def isRelocatable(self):
|
|
'''
|
|
Returns true if the given Elf binary is marked as a relocatable file.
|
|
isRelocatable() helps determine if this ELF is a Kernel Module (.ko)
|
|
or Object file (.o), *not* a Shared Object (.so) or executable.
|
|
'''
|
|
return self.e_type == ET_REL
|
|
|
|
def __repr__(self, verbose=False):
|
|
"""
|
|
Returns a string summary of this ELF.
|
|
"""
|
|
mystr = 'Elf Binary:'
|
|
mystr+= "\n= Intimate Details:"
|
|
mystr+= "\n==Magic:\t\t\t\t%r" % self.e_ident.decode('utf-8')
|
|
mystr+= "\n==Type:\t\t\t\t\t" + e_types.get(self.e_type)
|
|
mystr+= "\n==Machine Arch:\t\t\t\t" + e_machine_types.get(self.e_machine)
|
|
mystr+= "\n==Version:\t\t\t\t%d" % (self.e_version)
|
|
mystr+= "\n==Entry:\t\t\t\t0x%.8x" % (self.e_entry)
|
|
mystr+= "\n==Program Headers(offset):\t\t%d (0x%x) bytes" % (self.e_phoff, self.e_phoff)
|
|
mystr+= "\n==Section Headers(offset):\t\t%d (0x%x) bytes" % (self.e_shoff, self.e_shoff)
|
|
mystr+= "\n==Flags:\t\t\t\t" + repr(self.e_flags) + " "
|
|
mystr+= "\n==Elf Header Size:\t\t\t" + repr(self.e_ehsize) + " (" + hex(self.e_ehsize) + " bytes)"
|
|
mystr+= "\n==Program Header Size:\t\t\t" + repr(self.e_phentsize) + " (" + hex(self.e_phentsize) + " bytes)"
|
|
mystr+= "\n==Program Header Count:\t\t\t" + repr(self.e_phnum) + " (" + hex(self.e_phnum)+ ")"
|
|
mystr+= "\n==Section Header Size:\t\t\t" + repr(self.e_shentsize) + " (" + hex(self.e_shentsize) + " bytes)"
|
|
mystr+= "\n==Section Header Count:\t\t\t" + repr(self.e_shnum) + " (" + hex(self.e_shnum) + ")"
|
|
mystr+= "\n==Section Header String Index\t\t" + repr(self.e_shstrndx) + " (" + hex(self.e_shstrndx) + " bytes)"
|
|
|
|
mystr+= "\n\n= Sections:"
|
|
for sec in self.sections:
|
|
mystr+= "\n" + repr(sec)
|
|
|
|
mystr+= "\n\n= Program Headers:"
|
|
for ph in self.pheaders:
|
|
mystr+= "\n" + repr(ph)
|
|
|
|
return mystr
|
|
|
|
def verbrepr(self):
|
|
mystr = repr(self)
|
|
|
|
mystr+= "\n\n= Symbols table:"
|
|
for sym in self.symbols:
|
|
mystr+= "\n"+repr(sym)
|
|
|
|
mystr+= "\n\n= Relocation table:"
|
|
for _, reloc in self.relocs:
|
|
mystr+= "\n"+repr(reloc)
|
|
|
|
mystr+= "\n\n= Dynamics table:"
|
|
for dyn in self.dynamics:
|
|
mystr+= "\n"+repr(dyn)
|
|
|
|
mystr+= "\n\n= Dynamic Symbols table:"
|
|
for dyn in self.dynamic_symbols:
|
|
mystr+= "\n"+repr(dyn)
|
|
|
|
return mystr
|
|
|
|
def lookupSymbolName(self, name):
|
|
"""
|
|
Lookup symbol entries in this elf binary by name. The result is
|
|
a long representing the address for the given symbol. Or None if
|
|
it's not found.
|
|
"""
|
|
return self.symbols_by_name.get(name, None)
|
|
|
|
def lookupSymbolAddr(self, address):
|
|
"""
|
|
lookup symbols from this elf binary by address.
|
|
This returns the name for the given symbol or None for not found
|
|
"""
|
|
return self.symbols_by_addr.get(address, None)
|
|
|
|
def getPheaders(self):
|
|
"""
|
|
Return a list of the program headers for this elf
|
|
"""
|
|
return list(self.pheaders)
|
|
|
|
def addSymbol(self, symbol):
|
|
'''
|
|
Add a symbol to the Symbols table, along with caching symbols_by_name and symbols_by_addr
|
|
These symbols are from ELF Sections of type SHT_SYMTAB
|
|
'''
|
|
self.symbols.append(symbol)
|
|
self.symbols_by_name[symbol.getName()] = symbol
|
|
self.symbols_by_addr[symbol.st_value] = symbol
|
|
|
|
def getSymbols(self):
|
|
'''
|
|
Returns discovered Symbols (from ELF Sections)
|
|
'''
|
|
return self.symbols
|
|
|
|
def getSymbol(self, symidx):
|
|
if symidx is None or symidx >= len(self.symbols):
|
|
return None
|
|
return self.symbols[symidx]
|
|
|
|
def getDynSymbol(self, symidx):
|
|
'''
|
|
Returns the DT_SYMTAB entry at index "symidx".
|
|
|
|
Checks self.dynamic_symbols first.
|
|
If the index hasn't been loaded into self.dynamic_symbols, it is.
|
|
'''
|
|
symlen = len(self.dynamic_symbols)
|
|
if symidx >= symlen:
|
|
# dynamic_symbols is too small, grow
|
|
logger.info('getDynSymbol(%d): expanding dynamic_symbols from %d', symidx, symlen)
|
|
newspace = [self._getDynSymbol(x) for x in range(symlen, symidx + 1)]
|
|
self.dynamic_symbols.extend(newspace)
|
|
|
|
sym = self.dynamic_symbols[symidx]
|
|
return sym
|
|
|
|
def _getDynSymbol(self, symidx):
|
|
'''
|
|
Parse the Dynamics entries for SYMTAB and STRTAB, and return the
|
|
symidx indexed symbol.
|
|
'''
|
|
symtabrva, symsz, symtabsz = self.getDynSymTabInfo()
|
|
|
|
symrva = symtabrva + (symidx * symsz)
|
|
# DON'T trust symtabsz. it's often smaller than the '.dynsym' section
|
|
# but, if we attempt to parse outside the binary, we'll throw an error.
|
|
sym = self._cls_symbol(bigend=self.bigend)
|
|
sym.vsParse(self.readAtRva(symrva, symsz))
|
|
|
|
name = self.getDynStrtabString(sym.st_name)
|
|
sym.setName(name)
|
|
return sym
|
|
|
|
def getDynStrTabInfo(self):
|
|
return self.dynstrtabmeta
|
|
|
|
def getDynSymTabInfo(self):
|
|
'''
|
|
Returns Symbol Table information (as obtained through Dynamics only)
|
|
Assumes _parseDynSyms has run (populating self.dynstrtab)
|
|
returns (symtabva, symbolsz, symtabsz)
|
|
|
|
Because there is no DT_SYMTABSZ, we can't be certain how many dynamic
|
|
symbols to expect. Supposedly there is a 1:1 relationship between
|
|
DynSyms and DynStrs, but that can be misleading. Still, based on the
|
|
number of DynStrs parsed in _parseDynStrs() we use that to roughly
|
|
determine the number, which is estimated in _parseDynStrs() and stored
|
|
in self.dynsymtabct. Perhaps this is horrible and should be stricken
|
|
from the code.
|
|
'''
|
|
# if DT_SYMTAB doesn't exist, we don't have a symbol table defined by DYNAMICS
|
|
symtabva = self.dyns.get(DT_SYMTAB)
|
|
if symtabva is None:
|
|
return None, None, None
|
|
|
|
symsz = self.dyns.get(DT_SYMENT)
|
|
|
|
# calculate/reuse symbol count
|
|
if not self.dynsymtabct: # check if we've already got this value
|
|
# calculate the Dynamic Symbol Table Size
|
|
self.dynsymtabct = len(self.dynstrtab)
|
|
if not self.dynsymtabct:
|
|
# if we haven't set up dynsymtabct, call _parseDynStrs and create one)
|
|
self._parseDynStrs()
|
|
self.dynsymtabct = len(self.dynstrtab)
|
|
|
|
# if "DT_SONAME" is within this string table, there are no symbols to match that or thereafter:
|
|
soname = self.dyns.get(DT_SONAME)
|
|
if soname is not None and soname != -1:
|
|
strsz = self.dyns.get(DT_STRSZ)
|
|
if soname < strsz:
|
|
dynstrtabva = self.dyns.get(DT_STRTAB)
|
|
strtabbytes = self.readAtRva(dynstrtabva, soname)
|
|
dynsymstrs = strtabbytes.split(b'\0')
|
|
self.dynsymtabct = len(dynsymstrs) - 1
|
|
|
|
# if any of the other Dyns (which are addresses) come after this, don't parse past them
|
|
for dyntype in dt_rebase:
|
|
dyn = self.dyns.get(dyntype)
|
|
if dyn is None:
|
|
continue
|
|
|
|
if dyn > symtabva:
|
|
if dyn < symtabva + (self.dynsymtabct * symsz):
|
|
# we have a new winner!
|
|
delta = dyn - symtabva
|
|
self.dynsymtabct = delta // symsz
|
|
|
|
count = self.dynsymtabct
|
|
symtabsz = count * symsz
|
|
|
|
return symtabva, symsz, symtabsz
|
|
|
|
def getDynStrtabString(self, stroff):
|
|
'''
|
|
Returns a string starting at stroff
|
|
'''
|
|
if self.dynstrtabmeta == (None, None):
|
|
logger.info("no dyn strtabs!")
|
|
return ''
|
|
|
|
dynstrtabva, strsz = self.dynstrtabmeta
|
|
strings = self.readAtRva(dynstrtabva, strsz)
|
|
strend = strings.find(b'\0', stroff)
|
|
if stroff > len(strings):
|
|
return None
|
|
|
|
return strings[stroff:strend].decode('utf-8')
|
|
|
|
|
|
def elfFromFileName(fname):
|
|
return Elf(open(fname, 'rb'))
|
|
|
|
|
|
def elfFromBytes(fbytes):
|
|
fd = io.BytesIO(fbytes)
|
|
return Elf(fd)
|
|
|
|
|
|
def elfFromMemoryObject(memobj, baseaddr):
|
|
fd = vstruct.MemObjFile(memobj, baseaddr)
|
|
return Elf(fd)
|
|
|
|
# TODO: Consider deprecating these. We have methods on the ElfReloc objects specifically for these two
|
|
def getRelocType(val):
|
|
return val & 0xff
|
|
|
|
def getRelocSymTabIndex(val):
|
|
return val >> 8
|