Files
2026-05-29 09:41:06 -04:00

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