Source code for PE

import os
import struct

from cStringIO import StringIO

import vstruct
import vstruct.defs.pe as vs_pe

import ordlookup

IMAGE_DLLCHARACTERISTICS_RESERVED_1      = 1
IMAGE_DLLCHARACTERISTICS_RESERVED_2      = 2
IMAGE_DLLCHARACTERISTICS_RESERVED_4      = 4
IMAGE_DLLCHARACTERISTICS_RESERVED_8      = 8
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE    = 0x0040 # The DLL can be relocated at load time.
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080 # Code integrity checks are forced. If you set this flag and a section contains only uninitialized data, set the PointerToRawData member of IMAGE_SECTION_HEADER for that section to zero; otherwise, the image will fail to load because the digital signature cannot be verified.
IMAGE_DLLCHARACTERISTICS_NX_COMPAT       = 0x0100 # The image is compatible with data execution prevention (DEP).
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION    = 0x0200 # The image is isolation aware, but should not be isolated.
IMAGE_DLLCHARACTERISTICS_NO_SEH          = 0x0400 # The image does not use structured exception handling (SEH). No handlers can be called in this image.
IMAGE_DLLCHARACTERISTICS_NO_BIND         = 0x0800 # Do not bind the image.
IMAGE_DLLCHARACTERISTICS_RESERVED_1000   = 0x1000 # Reserved
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER      = 0x2000 # A WDM driver.
IMAGE_DLLCHARACTERISTICS_RESERVED_4000   = 0x4000 # Reserved
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE  = 0x8000

IMAGE_FILE_MACHINE_I386  = 0x014c
IMAGE_FILE_MACHINE_IA64  = 0x0200
IMAGE_FILE_MACHINE_AMD64 = 0x8664

machine_names = {
    IMAGE_FILE_MACHINE_I386: 'i386',
    IMAGE_FILE_MACHINE_IA64: 'ia64',
    IMAGE_FILE_MACHINE_AMD64: 'amd64',
}

IMAGE_REL_BASED_ABSOLUTE              = 0
IMAGE_REL_BASED_HIGH                  = 1
IMAGE_REL_BASED_LOW                   = 2
IMAGE_REL_BASED_HIGHLOW               = 3
IMAGE_REL_BASED_HIGHADJ               = 4
IMAGE_REL_BASED_MIPS_JMPADDR          = 5
IMAGE_REL_BASED_IA64_IMM64            = 9
IMAGE_REL_BASED_DIR64                 = 10

IMAGE_DIRECTORY_ENTRY_EXPORT          =0   # Export Directory
IMAGE_DIRECTORY_ENTRY_IMPORT          =1   # Import Directory
IMAGE_DIRECTORY_ENTRY_RESOURCE        =2   # Resource Directory
IMAGE_DIRECTORY_ENTRY_EXCEPTION       =3   # Exception Directory
IMAGE_DIRECTORY_ENTRY_SECURITY        =4   # Security Directory
IMAGE_DIRECTORY_ENTRY_BASERELOC       =5   # Base Relocation Table
IMAGE_DIRECTORY_ENTRY_DEBUG           =6   # Debug Directory
IMAGE_DIRECTORY_ENTRY_COPYRIGHT       =7   # (X86 usage)
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    =7   # Architecture Specific Data
IMAGE_DIRECTORY_ENTRY_GLOBALPTR       =8   # RVA of GP
IMAGE_DIRECTORY_ENTRY_TLS             =9   # TLS Directory
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    =10   # Load Configuration Directory
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   =11   # Bound Import Directory in headers
IMAGE_DIRECTORY_ENTRY_IAT            =12   # Import Address Table
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   =13   # Delay Load Import Descriptors
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR =14   # COM Runtime descriptor

IMAGE_DEBUG_TYPE_UNKNOWN          =0
IMAGE_DEBUG_TYPE_COFF             =1
IMAGE_DEBUG_TYPE_CODEVIEW         =2
IMAGE_DEBUG_TYPE_FPO              =3
IMAGE_DEBUG_TYPE_MISC             =4
IMAGE_DEBUG_TYPE_EXCEPTION        =5
IMAGE_DEBUG_TYPE_FIXUP            =6
IMAGE_DEBUG_TYPE_OMAP_TO_SRC      =7
IMAGE_DEBUG_TYPE_OMAP_FROM_SRC    =8
IMAGE_DEBUG_TYPE_BORLAND          =9
IMAGE_DEBUG_TYPE_RESERVED10       =10
IMAGE_DEBUG_TYPE_CLSID            =11

IMAGE_SCN_CNT_CODE                  = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA      = 0x00000040
IMAGE_SCN_CNT_UNINITIALIZED_DATA    = 0x00000080
IMAGE_SCN_LNK_OTHER                 = 0x00000100
IMAGE_SCN_LNK_INFO                  = 0x00000200
IMAGE_SCN_LNK_REMOVE                = 0x00000800
IMAGE_SCN_LNK_COMDAT                = 0x00001000
IMAGE_SCN_MEM_FARDATA               = 0x00008000
IMAGE_SCN_MEM_PURGEABLE             = 0x00020000
IMAGE_SCN_MEM_16BIT                 = 0x00020000
IMAGE_SCN_MEM_LOCKED                = 0x00040000
IMAGE_SCN_MEM_PRELOAD               = 0x00080000
IMAGE_SCN_ALIGN_1BYTES              = 0x00100000
IMAGE_SCN_ALIGN_2BYTES              = 0x00200000
IMAGE_SCN_ALIGN_4BYTES              = 0x00300000
IMAGE_SCN_ALIGN_8BYTES              = 0x00400000
IMAGE_SCN_ALIGN_16BYTES             = 0x00500000
IMAGE_SCN_ALIGN_32BYTES             = 0x00600000
IMAGE_SCN_ALIGN_64BYTES             = 0x00700000
IMAGE_SCN_ALIGN_128BYTES            = 0x00800000
IMAGE_SCN_ALIGN_256BYTES            = 0x00900000
IMAGE_SCN_ALIGN_512BYTES            = 0x00A00000
IMAGE_SCN_ALIGN_1024BYTES           = 0x00B00000
IMAGE_SCN_ALIGN_2048BYTES           = 0x00C00000
IMAGE_SCN_ALIGN_4096BYTES           = 0x00D00000
IMAGE_SCN_ALIGN_8192BYTES           = 0x00E00000
IMAGE_SCN_ALIGN_MASK                = 0x00F00000
IMAGE_SCN_LNK_NRELOC_OVFL           = 0x01000000
IMAGE_SCN_MEM_DISCARDABLE           = 0x02000000
IMAGE_SCN_MEM_NOT_CACHED            = 0x04000000
IMAGE_SCN_MEM_NOT_PAGED             = 0x08000000
IMAGE_SCN_MEM_SHARED                = 0x10000000
IMAGE_SCN_MEM_EXECUTE               = 0x20000000
IMAGE_SCN_MEM_READ                  = 0x40000000
IMAGE_SCN_MEM_WRITE                 = 0x80000000

# Flags for the UNWIND_INFO flags field from
# RUNTIME_FUNCTION defs
UNW_FLAG_NHANDLER   = 0x0
UNW_FLAG_EHANDLER   = 0x1
UNW_FLAG_UHANDLER   = 0x2
UNW_FLAG_CHAININFO  = 0x4

# Resource Types
RT_CURSOR           = 1
RT_BITMAP           = 2
RT_ICON             = 3
RT_MENU             = 4
RT_DIALOG           = 5
RT_STRING           = 6
RT_FONTDIR          = 7
RT_FONT             = 8
RT_ACCELERATOR      = 9
RT_RCDATA           = 10
RT_MESSAGETABLE     = 11
RT_GROUP_CURSOR     = 12
RT_GROUP_ICON       = 14
RT_VERSION          = 16
RT_DLGINCLUDE       = 17
RT_PLUGPLAY         = 19
RT_VXD              = 20
RT_ANICURSOR        = 21
RT_ANIICON          = 22
RT_HTML             = 23
RT_MANIFEST         = 24

[docs]class VS_VERSIONINFO: ''' A simple (read-only) VS_VERSIONINFO parser ''' def __init__(self, bytes): self._version_info = {} self._parseBytes(bytes)
[docs] def getVersionValue(self, key, default=None): ''' Retrieve a key from the VS_VERSIONINFO data. Example: vs.getVersionValue('FileVersion') ''' return self._version_info.get(key, default)
[docs] def getVersionKeys(self): ''' Return a list of the keys in this VS_VERSIONINFO struct. Example: for keyname in vs.getVersionKeys(): print keyname ''' return self._version_info.keys()
[docs] def getVersionItems(self): ''' Return dictionary style key,val tuples for the version keys in this VS_VERSIONINFO structure. Example: for vskey,vsdata in vs.getVersionItems(): print vskey,vsdata ''' return self._version_info.items()
def _parseBytes(self, bytes): offset = 0 mysize, valsize, vstype = struct.unpack('<HHH', bytes[:6]) offset += 6 offset, vinfosig = self._eatStringAndAlign(bytes, offset) if vinfosig != 'VS_VERSION_INFO': Exception('Invalid VS_VERSION_INFO signature!: %s' % repr(vinfosig)) if valsize: ffinfo = vs_pe.VS_FIXEDFILEINFO() ffinfo.vsParse(bytes[offset:offset+valsize]) offset += valsize offmod = offset % 4 if offmod: offset += (4 - offmod) xmax = min(mysize, len(bytes)) i = 0 while offset < xmax and i < 2: offset = self._stringFileInfo(bytes, offset) i += 1 def _eatStringAndAlign(self, bytes, offset): ret = '' blen = len(bytes) while bytes[offset:offset+2] != '\x00\x00': ret += bytes[offset:offset+2] offset += 2 if offset >= blen: break # Add 2 for the null terminator offset += 2 offmod = offset % 4 if offmod: offset += (4 - offmod) return offset, ret.decode('utf-16le') def _stringFileInfo(self, bytes, offset): xoffset = offset mysize, valsize, valtype = struct.unpack('<HHH', bytes[xoffset:xoffset+6]) xoffset += 6 xoffset, sigstr = self._eatStringAndAlign(bytes, xoffset) if sigstr not in ('VarFileInfo','StringFileInfo'): raise Exception('Invalid StringFileInfo Key!: %s' % repr(sigstr)) xmax = offset + mysize if sigstr == 'StringFileInfo': while xoffset < xmax: xoffset = self._stringTable(bytes, xoffset, mysize - (xoffset-offset)) elif sigstr == 'VarFileInfo': while xoffset < xmax: xoffset = self._varTable(bytes, xoffset, mysize - (xoffset-offset)) xmod = xoffset % 4 if xmod: xoffset += (4 - xmod) return xoffset def _varTable(self, bytes, offset, size): xmax = offset + size xoffset = offset mysize, valsize, valtype = struct.unpack('<HHH', bytes[xoffset:xoffset+6]) xoffset += 6 xoffset, varname = self._eatStringAndAlign(bytes, xoffset) varval = struct.unpack('<I', bytes[xoffset:xoffset+4])[0] xoffset += 4 self._version_info[varname] = varval return offset + size def _stringTable(self, bytes, offset, size): xmax = offset + size xoffset = offset mysize, valsize, valtype = struct.unpack('<HHH', bytes[offset:offset+6]) xoffset += 6 xoffset, hexcpage = self._eatStringAndAlign(bytes, xoffset) while xoffset < xmax: xoffset = self._stringData(bytes, xoffset) xmod = xoffset % 4 if xmod: xoffset += (4 - xmod) return offset + size def _stringData(self, bytes, offset): ''' Parse out a "String" structure... ''' xoffset = offset mysize, valsize, stype = struct.unpack('<HHH', bytes[offset:offset+6]) if mysize == 0: raise Exception() xoffset += 6 xoffset, strkey = self._eatStringAndAlign(bytes, xoffset) # valsize is in words... valsize *= 2 value = bytes[xoffset : xoffset + valsize ] # Do utf16le decode if we're "textual data" if stype == 1: value = value.decode('utf-16le','ignore') value = value.strip('\x00') #print 'VALSIZE',valsize,'MYSIZE',mysize #print 'Key: ->%s<-, ->%s<-' % (strkey,repr(value)) self._version_info[strkey] = value # No matter what we parse, believe the headers... return offset + mysize
[docs]class ResourceDirectory: ''' Resources are sorted into a hierarchy which begins with "type" and then "name/id" which still points to another directory entry which has 1 child (id 1033) with data. ''' def __init__(self): self._rsrc_subdirs = {} self._rsrc_data = []
[docs] def addRsrcDirectory(self, name_id): r = ResourceDirectory() self._rsrc_subdirs[name_id] = r return r
[docs] def addRsrcData(self, rva, size, codepage): self._rsrc_data.append( (rva, size, codepage) )
[docs] def getDirById(self, name_id): return self._rsrc_subdirs.get(name_id) #todo = [ self, ] #while len(todo): #curdir = todo.pop() #for rname_id, resdir in curdir._rsrc_subdirs.items(): #if rname_id == name_id: ##return resdir #yield resdir #todo.append(resdir)
[docs] def getResourceDef(self, restype, name_id): ''' This should *only* be called on the root node! ''' typedir = self._rsrc_subdirs.get(restype) if typedir == None: return None datadir = typedir._rsrc_subdirs.get(name_id) if datadir == None: return None # The first entry in the datadir's data is the one return datadir._rsrc_data[0]
[docs] def getDataEntries(self): return self._rsrc_data
[docs]class PE(object): def __init__(self, fd, inmem=False): """ Construct a PE object. use inmem=True if you are using a MemObjFile or other "memory like" image. """ object.__init__(self) self.inmem = inmem fd.seek(0) self.fd = fd self.pe32p = False self.psize = 4 self.high_bit_mask = 0x80000000 self.IMAGE_DOS_HEADER = vstruct.getStructure("pe.IMAGE_DOS_HEADER") dosbytes = self.readAtOffset(0, len(self.IMAGE_DOS_HEADER)) self.IMAGE_DOS_HEADER.vsParse(dosbytes) nt = self.readStructAtOffset(self.IMAGE_DOS_HEADER.e_lfanew, "pe.IMAGE_NT_HEADERS") # Parse in a default 32 bit, and then check for 64... if nt.FileHeader.Machine in [ IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_IA64 ]: nt = self.readStructAtOffset(self.IMAGE_DOS_HEADER.e_lfanew, "pe.IMAGE_NT_HEADERS64") self.pe32p = True self.psize = 8 self.high_bit_mask = 0x8000000000000000 self.IMAGE_NT_HEADERS = nt
[docs] def getPdataEntries(self): sec = self.getSectionByName('.pdata') if sec == None: return () ret = [] rbytes = self.readAtRva(sec.VirtualAddress, sec.VirtualSize) while len(rbytes): f = vs_pe.IMAGE_RUNTIME_FUNCTION_ENTRY() f.vsParse(rbytes) rbytes = rbytes[len(f):] ret.append(f) return ret
[docs] def getDllName(self): ''' Return the "dll name" from the Name field of the IMAGE_EXPORT_DIRECTORY if one is present. If not, return None. ''' if self.IMAGE_EXPORT_DIRECTORY != None: rawname = self.readAtRva(self.IMAGE_EXPORT_DIRECTORY.Name, 32) return rawname.split('\x00')[0] return None
[docs] def getImports(self): """ Return the list of import tuples for this PE. The tuples are in the format (rva, libname, funcname). """ return self.imports
[docs] def getExports(self): """ Return the list of exports in this PE. The list contains tuples in the format; (rva, ord, name). """ return self.exports
[docs] def getForwarders(self): """ [ (rva, name, forwardname), ... ] """ return self.forwarders
[docs] def getSections(self): return self.sections
[docs] def rvaToOffset(self, rva): if self.inmem: return rva for s in self.sections: sbase = s.VirtualAddress ssize = s.VirtualSize if rva >= sbase and rva < (sbase + ssize): return s.PointerToRawData + (rva - sbase) return 0
[docs] def getSectionByName(self, name): for s in self.getSections(): if s.Name.split("\x00", 1)[0] == name: return s return None
[docs] def readStructAtRva(self, rva, structname, check=False): s = vstruct.getStructure(structname) slen = len(s) if check and not self.checkRva(rva, size=slen): return None bytes = self.readAtRva(rva, len(s)) s.vsParse(bytes) return s
[docs] def readStructAtOffset(self, offset, structname): s = vstruct.getStructure(structname) sbytes = self.readAtOffset(offset, len(s)) #print "%s [%s]: %s" % (structname, offset, sbytes.encode('hex')) s.vsParse(sbytes) return s
[docs] def getDataDirectory(self, idx): return self.IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[idx]
[docs] def getResourceDef(self, rtype, name_id): ''' Get the (rva, size, codepage) tuple for the specified resource type/id combination. Returns None if not found. ''' return self.ResourceRoot.getResourceDef(rtype, name_id)
[docs] def getResources(self): ''' Get the (rtype, nameid, (rva, size, codepage)) tuples for each resource in the PE. ''' ret = [] for rtype,subdir in self.ResourceRoot._rsrc_subdirs.items(): for nameid, subsubdir in subdir._rsrc_subdirs.items(): ret.append( (rtype, nameid, subsubdir._rsrc_data[0]) ) return ret
[docs] def readResource(self, rtype, name_id): ''' Return the bytes which define the specified resource. Returns None if not found. ''' rsdef = self.getResourceDef(rtype, name_id) if rsdef == None: return None rsrva, rssize, rscpage = rsdef return self.readAtRva(rsrva, rssize)
[docs] def getVS_VERSIONINFO(self): ''' Get a VS_VERSIONINFO object for this PE. (returns None if version resource is not found) ''' vbytes = self.readResource(RT_VERSION, 1) if vbytes == None: return None return VS_VERSIONINFO(vbytes)
[docs] def parseResources(self): self.ResourceRoot = ResourceDirectory() # RP BUG FIX - Binaries can have a .rsrc section it doesn't mean that the .rsrc section contains the resource data we think it does # validate .rsrc == RESOURCE Section by checking data directory entries... dresc = self.getDataDirectory(IMAGE_DIRECTORY_ENTRY_RESOURCE) if not dresc.VirtualAddress: return rsrc_todo = [ (dresc.VirtualAddress, self.ResourceRoot), ] while len(rsrc_todo): rsrva, rsdirobj = rsrc_todo.pop() rsdir = self.readStructAtRva( rsrva, 'pe.IMAGE_RESOURCE_DIRECTORY', check=True ) if rsdir == None: continue totcount = rsdir.NumberOfIdEntries + rsdir.NumberOfNamedEntries offset = len(rsdir) for i in xrange(totcount): dentrva = rsrva + offset dirent = self.readStructAtRva( dentrva, 'pe.IMAGE_RESOURCE_DIRECTORY_ENTRY', check=True ) if dirent == None: break # We use name/id interchangably in the python dict... name_id = None if dirent.Name & 0x80000000: # If high bit is set, it's a string! namerva = dresc.VirtualAddress + (dirent.Name & 0x7fffffff) namelen_bytes = self.readAtRva(namerva, 2) namelen = struct.unpack('<H', namelen_bytes)[0] name_id = self.readAtRva(namerva + 2, namelen * 2).decode('utf-16le', 'ignore') else: name_id = dirent.Name # if OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY then we have another directory if dirent.OffsetToData & 0x80000000: # This points to a subdirectory subdir = rsdirobj.addRsrcDirectory(name_id) rsrc_todo.append( (dresc.VirtualAddress + (dirent.OffsetToData & 0x7fffffff), subdir) ) else: subdata = self.readStructAtRva( dresc.VirtualAddress + dirent.OffsetToData, 'pe.IMAGE_RESOURCE_DATA_ENTRY') # RP BUG FIX - sanity check the subdata if self.checkRva(subdata.OffsetToData, size=subdata.Size): rsdirobj.addRsrcData(subdata.OffsetToData, subdata.Size, subdata.CodePage) #print 'Data %s : 0x%.8x (%d)' % (name_id, sec.VirtualAddress + subdata.OffsetToData, subdata.Size) #print repr(self.readAtRva(subdata.OffsetToData, min(subdata.Size, 40) )) offset += len(dirent) #print dirent.tree()
[docs] def parseSections(self): self.sections = [] off = self.IMAGE_DOS_HEADER.e_lfanew + len(self.IMAGE_NT_HEADERS) secsize = len(vstruct.getStructure("pe.IMAGE_SECTION_HEADER")) sbytes = self.readAtOffset(off, secsize * self.IMAGE_NT_HEADERS.FileHeader.NumberOfSections) while sbytes: s = vstruct.getStructure("pe.IMAGE_SECTION_HEADER") s.vsParse(sbytes[:secsize]) self.sections.append(s) sbytes = sbytes[secsize:]
[docs] def readRvaFormat(self, fmt, rva): size = struct.calcsize(fmt) fbytes = self.readAtRva(rva, size) return struct.unpack(fmt, fbytes)
[docs] def readAtRva(self, rva, size): offset = self.rvaToOffset(rva) return self.readAtOffset(offset, size)
[docs] def readAtOffset(self, offset, size): ret = "" self.fd.seek(offset) while len(ret) != size: rlen = size - len(ret) x = self.fd.read(rlen) if x == "": raise Exception("EOF In readAtOffset()") ret += x return ret
[docs] def parseLoadConfig(self): self.IMAGE_LOAD_CONFIG = None cdir = self.getDataDirectory(IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG) rva = cdir.VirtualAddress # RP BUG FIX - validate config directory if self.checkRva(rva, size=cdir.Size): self.IMAGE_LOAD_CONFIG = self.readStructAtRva(rva, "pe.IMAGE_LOAD_CONFIG_DIRECTORY")
[docs] def readPointerAtOffset(self, off): fmt = "<L" if self.psize == 8: fmt = "<Q" return struct.unpack(fmt, self.readAtOffset(off, self.psize))[0]
[docs] def readPointerAtRva(self, rva): off = self.rvaToOffset(rva) return self.readPointerAtOffset(off)
[docs] def getMaxRva(self): return self.IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage
[docs] def checkRva(self, rva, size=None): ''' Make sure an RVA falls inside the valid mapped range for the file. (also make sure it's not 0...) ''' if rva == 0: return False isize = self.getMaxRva() if rva > isize: #raise Exception('too high! %d > %d' % (rva, isize)) return False if size != None and (rva + size) > isize: #raise Exception('too big! %d > %d' % (rva+size, isize)) return False return True
[docs] def readStringAtRva(self, rva, maxsize=None): ret = '' while True: if maxsize and maxsize <= len(ret): break x = self.readAtRva(rva, 1) if x == '\x00': break ret += x rva += 1 return ret
[docs] def parseImports(self): self.imports = [] idir = self.getDataDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT) # RP BUG FIX - invalid IAT entry will point of range of file irva = idir.VirtualAddress x = self.readStructAtRva(irva, 'pe.IMAGE_IMPORT_DIRECTORY', check=True) if x == None: return isize = len(x) while self.checkRva(x.Name): # RP BUG FIX - we can't assume that we have 256 bytes to read libname = self.readStringAtRva(x.Name, maxsize=256) idx = 0 imp_by_name = x.OriginalFirstThunk if imp_by_name == 0: imp_by_name = x.FirstThunk if not self.checkRva(imp_by_name): break while True: arrayoff = self.psize * idx ibn_rva = self.readPointerAtRva(imp_by_name+arrayoff) if ibn_rva == 0: break if ibn_rva & self.high_bit_mask: funcname = ordlookup.ordLookup(libname, ibn_rva & 0x7fffffff) else: # RP BUG FIX - we can't use this API on this call because we can have binaries that put their import table # right at the end of the file, statically saying the imported function name is 128 will cause use to potentially # over run our read and traceback... diff = self.getMaxRva() - ibn_rva - 2 ibn = vstruct.getStructure("pe.IMAGE_IMPORT_BY_NAME") ibn.vsGetField('Name').vsSetLength( min(diff, 128) ) bytes = self.readAtRva(ibn_rva, len(ibn)) ibn.vsParse(bytes) funcname = ibn.Name self.imports.append((x.FirstThunk+arrayoff,libname,funcname)) idx += 1 irva += isize # RP BUG FIX - if the import table is at the end of the file we can't count on the ending to be null if not self.checkRva(irva, size=isize): break x.vsParse(self.readAtRva(irva, isize))
[docs] def getRelocations(self): """ Return the list of RVA base-relocations in this PE. """ return self.relocations
[docs] def parseRelocations(self): self.relocations = [] edir = self.getDataDirectory(IMAGE_DIRECTORY_ENTRY_BASERELOC) rva = edir.VirtualAddress rsize = edir.Size # RP BUG FIX - don't watn to read past the end of the file if not self.checkRva(rva): return reloff = self.rvaToOffset(rva) relbytes = self.readAtOffset(reloff, rsize) while relbytes: pageva, chunksize = struct.unpack("<LL", relbytes[:8]) relcnt = (chunksize - 8) / 2 # RP BUG FIX - when the reloc section has no fixups but the directory exists.. if not chunksize or not pageva: return # RP BUG FIX - sometimes the chunksize is invalid we do a quick check to make sure we dont overrun the buffer if chunksize > len(relbytes): return rels = struct.unpack("<%dH" % relcnt, relbytes[8:chunksize]) for r in rels: rtype = r >> 12 roff = r & 0xfff self.relocations.append((pageva+roff, rtype)) relbytes = relbytes[chunksize:]
[docs] def getExportName(self): ''' Return the name of this file acording to it's export entry. (if there are no exports, return None) ''' e = self.IMAGE_EXPORT_DIRECTORY if e == None: return None return self.readAtRva(e.Name, 128).split('\x00')[0]
[docs] def parseExports(self): # Initialize our required locals. self.exports = [] self.forwarders = [] self.IMAGE_EXPORT_DIRECTORY = None edir = self.getDataDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT) poff = self.rvaToOffset(edir.VirtualAddress) if poff == 0: # No exports... return self.IMAGE_EXPORT_DIRECTORY = self.readStructAtOffset(poff, "pe.IMAGE_EXPORT_DIRECTORY") funcoff = self.rvaToOffset(self.IMAGE_EXPORT_DIRECTORY.AddressOfFunctions) funcsize = 4 * self.IMAGE_EXPORT_DIRECTORY.NumberOfFunctions nameoff = self.rvaToOffset(self.IMAGE_EXPORT_DIRECTORY.AddressOfNames) namesize = 4 * self.IMAGE_EXPORT_DIRECTORY.NumberOfNames ordoff = self.rvaToOffset(self.IMAGE_EXPORT_DIRECTORY.AddressOfOrdinals) ordsize = 2 * self.IMAGE_EXPORT_DIRECTORY.NumberOfNames # RP BUG FIX - sanity check the exports before reading if not funcoff or not ordoff and not nameoff or funcsize > 0x7FFF: return funcbytes = self.readAtOffset(funcoff, funcsize) namebytes = self.readAtOffset(nameoff, namesize) ordbytes = self.readAtOffset(ordoff, ordsize) funclist = struct.unpack("%dI" % (len(funcbytes) / 4), funcbytes) namelist = struct.unpack("%dI" % (len(namebytes) / 4), namebytes) ordlist = struct.unpack("%dH" % (len(ordbytes) / 2), ordbytes) #for i in range(len(funclist)): for i in range(len(namelist)): ord = ordlist[i] nameoff = self.rvaToOffset(namelist[i]) funcoff = funclist[ord] ffoff = self.rvaToOffset(funcoff) name = None if nameoff != 0: name = self.readAtOffset(nameoff, 256).split("\x00", 1)[0] else: name = "ord_%.4x" % ord if ffoff >= poff and ffoff < poff + edir.Size: fwdname = self.readAtOffset(ffoff, 260).split("\x00", 1)[0] self.forwarders.append((funclist[ord],name,fwdname)) else: self.exports.append((funclist[ord], ord, name))
def __getattr__(self, name): """ Use a getattr over-ride to allow "on demand" parsing of particular sections. """ if name == "exports": self.parseExports() return self.exports elif name == "IMAGE_IMPORT_DIRECTORY": self.parseImports() return self.IMAGE_IMPORT_DIRECTORY elif name == "imports": self.parseImports() return self.imports elif name == "IMAGE_EXPORT_DIRECTORY": self.parseExports() return self.IMAGE_EXPORT_DIRECTORY elif name == "forwarders": self.parseExports() return self.forwarders elif name == "sections": self.parseSections() return self.sections elif name == "ResourceRoot": self.parseResources() return self.ResourceRoot elif name == "relocations": self.parseRelocations() return self.relocations elif name == "IMAGE_LOAD_CONFIG": self.parseLoadConfig() return self.IMAGE_LOAD_CONFIG else: raise AttributeError
[docs]class MemObjFile: """ A file like object that wraps a MemoryObject (envi) compatable object with a file-like object where seek == VA. """ def __init__(self, memobj, baseaddr): self.baseaddr = baseaddr self.offset = baseaddr self.memobj = memobj
[docs] def seek(self, offset): self.offset = self.baseaddr + offset
[docs] def read(self, size): ret = self.memobj.readMemory(self.offset, size) self.offset += size return ret
[docs] def write(self, bytes): self.memobj.writeMemory(self.offset, bytes) self.offset += len(bytes)
[docs]def peFromMemoryObject(memobj, baseaddr): fd = MemObjFile(memobj, baseaddr) return PE(fd, inmem=True)
[docs]def peFromFileName(fname): """ Utility helper that assures that the file is opened in binary mode which is required for proper functioning. """ f = file(fname, "rb") return PE(f)
[docs]def peFromBytes(fbytes): fd = StringIO(fbytes) return PE(fd)