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

868 lines
30 KiB
Python

import queue
import logging
import traceback
import threading
import collections
import envi
import envi.bits as e_bits
import envi.memory as e_mem
import envi.pagelookup as e_page
import envi.codeflow as e_codeflow
import vstruct.cparse as vs_cparse
import vstruct.builder as vs_builder
import vstruct.constants as vs_const
import vivisect.exc as v_exc
import vivisect.const as v_const
import vivisect.impapi as viv_impapi
import vivisect.parsers as viv_parsers
import vivisect.analysis as viv_analysis
import vivisect.codegraph as viv_codegraph
from envi.threads import firethread
logger = logging.getLogger(__name__)
"""
Mostly this is a place to scuttle away some of the inner workings
of a workspace, so the outer facing API is a little cleaner.
"""
class VivEventCore(object):
'''
A class to facilitate event monitoring in the viv workspace.
'''
def __init__(self, vw=None, **kwargs):
self._ve_vw = vw
self._ve_ehand = [None for x in range(v_const.VWE_MAX)]
self._ve_thand = [None for x in range(v_const.VTE_MAX)]
self._ve_lock = threading.Lock()
# Find and put handler functions into the list
for name in dir(self):
if name.startswith('VWE_'):
idx = getattr(v_const, name, None)
self._ve_ehand[idx] = getattr(self, name)
if name.startswith('VTE_'):
idx = getattr(v_const, name, None)
self._ve_thand[idx] = getattr(self, name)
def _ve_fireEvent(self, event, edata):
hlist = self._ve_ehand
if event & v_const.VTE_MASK:
event ^= v_const.VTE_MASK
hlist = self._ve_thand
h = hlist[event]
if h is not None:
try:
h(self._ve_vw, event, edata)
except Exception:
logger.error(traceback.format_exc())
@firethread
def _ve_fireListener(self):
chanid = self._ve_vw.createEventChannel()
try:
etup = self._ve_vw.waitForEvent(chanid)
while etup is not None:
with self._ve_lock:
self._ve_fireEvent(*etup)
etup = self._ve_vw.waitForEvent(chanid)
finally:
self._ve_vw.deleteEventChannel(chanid)
vaset_xlate = {
int: v_const.VASET_ADDRESS,
str: v_const.VASET_STRING,
}
class VivEventDist(VivEventCore):
'''
Similar to an event core, but does optimized distribution
to a set of sub eventcore objects (think GUI windows...)
'''
def __init__(self, vw, **kwargs):
if vw is None:
raise Exception("VivEventDist requires a vw argument")
VivEventCore.__init__(self, vw)
self._ve_subs = [[] for x in range(v_const.VWE_MAX)]
self._ve_tsubs = [[] for x in range(v_const.VTE_MAX)]
self.addEventCore(self)
# event distributors pretty much always need a thread
self._ve_fireListener()
def addEventCore(self, core):
for i in range(v_const.VWE_MAX):
h = core._ve_ehand[i]
if h is not None:
self._ve_subs[i].append(h)
for i in range(v_const.VTE_MAX):
h = core._ve_thand[i]
if h is not None:
self._ve_tsubs[i].append(h)
def delEventCore(self, core):
for i in range(v_const.VWE_MAX):
h = core._ve_ehand[i]
if h is not None:
self._ve_subs[i].remove(h)
for i in range(v_const.VTE_MAX):
h = core._ve_thand[i]
if h is not None:
self._ve_tsubs[i].remove(h)
def _ve_fireEvent(self, event, edata):
'''
We don't have events of our own, we just hand them down.
'''
subs = self._ve_subs
if event & v_const.VTE_MASK:
event ^= v_const.VTE_MASK
subs = self._ve_tsubs
hlist = subs[event]
for h in hlist:
try:
h(self._ve_vw, event, edata)
except Exception:
logger.error(traceback.format_exc())
VivEventCore._ve_fireEvent(self, event, edata)
def ddict():
return collections.defaultdict(dict)
class VivWorkspaceCore(viv_impapi.ImportApi):
'''
A base class that the VivWorkspace inherits from that defines a lot of the event handlers
for things like the creation of the various location types.
'''
def __init__(self):
viv_impapi.ImportApi.__init__(self)
self.loclist = []
self.bigend = False
self.locmap = e_page.MapLookup()
self.blockmap = e_page.MapLookup()
self._mods_loaded = False
self.parsedbin = None
# Storage for function local symbols
self.localsyms = ddict()
self._call_graph = viv_codegraph.CallGraph()
# Just in case of the GUI... :)
self._call_graph.setMeta('bgcolor', '#000')
self._call_graph.setMeta('nodecolor', '#00ff00')
self._call_graph.setMeta('edgecolor', '#00802b')
self._event_list = []
self._event_saved = 0 # The index of the last "save" event...
# Give ourself a structure namespace!
self.vsbuilder = vs_builder.VStructBuilder()
self.vsconsts = vs_const.VSConstResolver()
# Follow the Leader data
self.leaders = {} # existing "leader" sessions
self.leaderloc = {} # last known location for each session
def _snapInAnalysisModules(self):
'''
Snap in the analysis modules which are appropriate for the
format/architecture/platform of this workspace by calling
'''
if self._mods_loaded:
return
viv_analysis.addAnalysisModules(self)
self._mods_loaded = True
def _createSaveMark(self):
'''
Update the index of the most recent saved event to the current
length of the event list (called after successful save)..
'''
self._event_saved = len(self._event_list)
def _handleADDLOCATION(self, loc):
lva, lsize, ltype, linfo = loc
self.locmap.setMapLookup(lva, lsize, loc)
self.loclist.append(loc)
# A few special handling cases...
if ltype == v_const.LOC_IMPORT:
# Check if the import is registered in NoReturnApis
if self.getMeta('NoReturnApis', {}).get(linfo.lower()):
self.cfctx.addNoReturnAddr(lva)
def _handleDELLOCATION(self, loc):
# FIXME delete xrefs
lva, lsize, ltype, linfo = loc
self.locmap.setMapLookup(lva, lsize, None)
self.loclist.remove(loc)
def _handleADDSEGMENT(self, einfo):
self.segments.append(einfo)
def _handleADDRELOC(self, einfo):
if len(einfo) == 4:
fname, ptroff, rtype, data = einfo
size = None
else:
fname, ptroff, rtype, data, size = einfo
if size is None:
size = self.psize
imgbase = self.getFileMeta(fname, 'imagebase')
rva = imgbase + ptroff
self.reloc_by_va[rva] = rtype
self.relocations.append((fname, ptroff, rtype, data, size))
# RTYPE_BASERELOC assumes the memory is already accurate (eg. PE's unless rebased)
if rtype in v_const.REBASE_TYPES:
# add imgbase and offset to pointer in memory
# 'data' arg must be 'offset' number
ptr = imgbase + data
if ptr != (ptr & e_bits.u_maxes[size]):
logger.warning('Relocations calculated a bad pointer: 0x%x (imgbase: 0x%x) (relocation: %d)', ptr, imgbase, rtype)
# writes are costly, especially on larger binaries
if ptr != self.readMemValue(rva, size):
with self.getAdminRights():
self.writeMemValue(rva, ptr, size)
if rtype == v_const.RTYPE_BASEPTR:
# make it like a pointer (but one that could move with each load)
# self.addXref(va, tova, REF_PTR)
# ploc = self.addLocation(va, psize, LOC_POINTER)
# don't follow. handle it later, once "known code" is analyzed
ptr, reftype, rflags = self.arch.archModifyXrefAddr(ptr, None, None)
self._handleADDXREF((rva, ptr, v_const.REF_PTR, 0))
self._handleADDLOCATION((rva, size, v_const.LOC_POINTER, ptr))
def _handleDELRELOC(self, einfo):
fname, rva, rtyp, full = einfo
imgbase = self.getFileMeta(fname, 'imagebase')
ptroff = rva - imgbase
self.reloc_by_va.pop(rva, None)
delidx = -1
for idx, (fn, off, typ, data, size) in enumerate(self.relocations):
if fn == fname and off == ptroff and typ == rtyp:
delidx = idx
break
if delidx >= 0:
self.relocations.pop(delidx)
if full:
if rtyp == v_const.RTYPE_BASEPTR:
ptr = imgbase + data
ptr, reftype, rflags = self.arch.archModifyXrefAddr(ptr, None, None)
self._handleDELXREF((rva, ptr, v_const.REF_PTR, 0))
self._handleDELLOCATION((rva, self.psize, v_const.LOC_POINTER, ptr))
def _handleADDMODULE(self, einfo):
logger.warning('DEPRECATED (ADDMODULE) ignored: %s', einfo)
def _handleDELMODULE(self, einfo):
logger.warning('DEPRECATED (DELMODULE) ignored: %s', einfo)
def _handleADDFMODULE(self, einfo):
logger.warning('DEPRECATED (ADDFMODULE) ignored: %s', einfo)
def _handleDELFMODULE(self, einfo):
logger.warning('DEPRECATED (DELFMODULE) ignored: %s', einfo)
def _handleADDFUNCTION(self, einfo):
va, meta = einfo
self._initFunction(va)
# node = self._call_graph.addNode( nid=va, repr=self.getName( va ) ) #, color='#00ff00' )
# node = self._call_graph.getFunctionNode(va, repr=self.getName( va ) )
node = self._call_graph.getFunctionNode(va)
self._call_graph.setNodeProp(node, 'repr', self.getName(va))
# Tell the codeflow subsystem about this one!
calls_from = meta.get('CallsFrom')
self.cfctx.addFunctionDef(va, calls_from)
self.funcmeta[va] = meta
for name, value in meta.items():
mcbname = "_fmcb_%s" % name.split(':')[0]
mcb = getattr(self, mcbname, None)
if mcb is not None:
mcb(va, name, value)
def _handleDELFUNCTION(self, einfo):
# clear funcmeta, func_args, codeblocks_by_funcva, update codeblocks, blockgraph, locations, etc...
fva = einfo
# not every codeblock identifying as this function is stored in funcmeta
for cb in self.getCodeBlocks():
if cb[v_const.CB_FUNCVA] == fva:
self._handleDELCODEBLOCK(cb)
self.funcmeta.pop(fva)
self.func_args.pop(fva, None)
self.codeblocks_by_funcva.pop(fva)
node = self._call_graph.getNode(fva)
self._call_graph.delNode(node)
self.cfctx.flushFunction(fva)
# FIXME: do we want to now seek the function we *should* be in?
# if xrefs_to, look for non-PROC code xrefs and take their function
# if the previous instruction falls through, take its function
# run codeblock analysis on that function to reassociate the blocks
# with that function
def _handleSETFUNCMETA(self, einfo):
funcva, name, value = einfo
m = self.funcmeta.get(funcva)
if m is not None:
m[name] = value
mcbname = "_fmcb_%s" % name.split(':')[0]
mcb = getattr(self, mcbname, None)
if mcb is not None:
mcb(funcva, name, value)
def _handleADDCODEBLOCK(self, einfo):
va,size,funcva = einfo
self.blockmap.setMapLookup(va, size, einfo)
self.codeblocks_by_funcva.get(funcva).append(einfo)
self.codeblocks.append(einfo)
def _handleDELCODEBLOCK(self, cb):
va,size,funcva = cb
self.codeblocks.remove(cb)
self.codeblocks_by_funcva.get(cb[v_const.CB_FUNCVA]).remove(cb)
self.blockmap.setMapLookup(va, size, None)
def _handleADDXREF(self, einfo):
fromva, tova, reftype, rflags = einfo
xr_to = self.xrefs_by_to.get(tova, None)
xr_from = self.xrefs_by_from.get(fromva, None)
if xr_to is None:
xr_to = []
self.xrefs_by_to[tova] = xr_to
if xr_from is None:
xr_from = []
self.xrefs_by_from[fromva] = xr_from
if einfo not in xr_to: # Just check one for now
xr_to.append(einfo)
xr_from.append(einfo)
self.xrefs.append(einfo)
def _handleDELXREF(self, einfo):
fromva, tova, reftype, refflags = einfo
self.xrefs_by_to[tova].remove(einfo)
self.xrefs_by_from[fromva].remove(einfo)
def _handleSETNAME(self, einfo):
va, name = einfo
if name is None:
oldname = self.name_by_va.pop(va, None)
self.va_by_name.pop(oldname, None)
else:
curname = self.name_by_va.get(va)
if curname is not None:
logger.debug('replacing 0x%x: %r -> %r', va, curname, name)
self.va_by_name.pop(curname)
self.va_by_name[name] = va
self.name_by_va[va] = name
if self.isFunction(va):
fnode = self._call_graph.getFunctionNode(va)
if name is None:
self._call_graph.delNodeProp(fnode, 'repr')
else:
self._call_graph.setNodeProp(fnode, 'repr', name)
def _handleADDMMAP(self, einfo):
if len(einfo) == 5:
# new "alignment-friendly" event
va, perms, fname, mbytes, align = einfo
else:
# DEPRECATED (21-09-13) - old event style, to support older .viv's
va, perms, fname, mbytes = einfo
align = None
blen = e_mem.MemoryObject.addMemoryMap(self, va, perms, fname, mbytes, align)
self.locmap.initMapLookup(va, blen)
self.blockmap.initMapLookup(va, blen)
# On loading a new memory map, we need to crush a few
# transmeta items...
self.transmeta.pop('findPointers',None)
def _handleDELMMAP(self, mapva):
e_mem.MemoryObject.delMemoryMap(self, mapva)
self.locmap.delMapLookup(mapva)
self.blockmap.delMapLookup(mapva)
# wipe the opcode cache in case we have cached deleted locations
self.clearOpcache()
def _handleADDEXPORT(self, einfo):
va, etype, name, filename = einfo
self.exports.append(einfo)
self.exports_by_va[va] = einfo
def _handleSETMETA(self, einfo):
name, value = einfo
# See if there's a callback handler for this meta set.
# For "meta namespaces" use the first part to find the
# callback name....
mcbname = "_mcb_%s" % name.split(':')[0]
mcb = getattr(self, mcbname, None)
if mcb is not None:
mcb(name, value)
self.metadata[name] = value
def _handleCOMMENT(self, einfo):
va,comment = einfo
if comment is None:
self.comments.pop(va, None)
else:
self.comments[va] = comment
def _handleENDIAN(self, einfo):
self.bigend = einfo
for idx, arch in self.imem_archs.items():
if not arch:
continue
arch.setEndian(self.bigend)
def _handleADDFILE(self, einfo):
normname, imagebase, md5sum = einfo
self.filemeta[normname] = {"md5sum":md5sum,"imagebase":imagebase}
def _handleSETFILEMETA(self, einfo):
fname, key, value = einfo
self.filemeta.get(fname)[key] = value
def _handleADDCOLOR(self, coltup):
mapname, colmap = coltup
self.colormaps[mapname] = colmap
def _handleDELCOLOR(self, mapname):
self.colormaps.pop(mapname)
def _handleADDVASET(self, argtup):
name, defs, rows = argtup
# NOTE: legacy translation for vaset column types...
defs = [ (cname,vaset_xlate.get(ctype,ctype)) for (cname,ctype) in defs ]
self.vasetdefs[name] = defs
vals = {}
for row in rows:
vals[row[0]] = row
self.vasets[name] = vals
def _handleDELVASET(self, setname):
self.vasetdefs.pop(setname)
self.vasets.pop(setname)
def _handleADDFREF(self, frtup):
va, idx, val = frtup
self.frefs[(va,idx)] = val
def _handleDELFREF(self, frtup):
va, idx, val = frtup
self.frefs.pop((va,idx), None)
def _handleSETVASETROW(self, argtup):
name, row = argtup
self.vasets[name][row[0]] = row
def _handleDELVASETROW(self, argtup):
name, va = argtup
self.vasets[name].pop(va, None)
def _handleADDFSIG(self, einfo):
raise NotImplementedError("FSIG is deprecated and should not be used")
def _handleCHAT(self, msgtup):
# FIXME make a GUI window for this...
user, msg = msgtup
self.vprint('%s: %s' % (user, msg))
def _handleSYMHINT(self, msgtup):
va, idx, hint = msgtup
if hint is None:
self.symhints.pop((va, idx), None)
else:
self.symhints[(va, idx)] = hint
def _handleSETFUNCARGS(self, einfo):
fva, args = einfo
self.func_args[fva] = args
def _handleAUTOANALFIN(self, einfo):
'''
This event is more for the storage subsystem than anything else. It
marks the end of autoanalysis. Any event beyond this is due to the
end user or analysis modules they've executed.
'''
pass
def _handleWRITEMEM(self, einfo):
'''
Handle permanent writes to a memory map after initialization
(fname, off, bytez, supv) where supv is supervisor mode...
'''
va, bytez, oldbytes = einfo
with self.getAdminRights():
e_mem.MemoryObject.writeMemory(self, va, bytez)
def _initEventHandlers(self):
self.ehand = [None for x in range(v_const.VWE_MAX)]
self.ehand[v_const.VWE_ADDLOCATION] = self._handleADDLOCATION
self.ehand[v_const.VWE_DELLOCATION] = self._handleDELLOCATION
self.ehand[v_const.VWE_ADDSEGMENT] = self._handleADDSEGMENT
self.ehand[v_const.VWE_DELSEGMENT] = None
self.ehand[v_const.VWE_ADDRELOC] = self._handleADDRELOC
self.ehand[v_const.VWE_DELRELOC] = self._handleDELRELOC
self.ehand[v_const.VWE_ADDMODULE] = self._handleADDMODULE
self.ehand[v_const.VWE_DELMODULE] = self._handleDELMODULE
self.ehand[v_const.VWE_ADDFMODULE] = self._handleADDFMODULE
self.ehand[v_const.VWE_DELFMODULE] = self._handleDELFMODULE
self.ehand[v_const.VWE_ADDFUNCTION] = self._handleADDFUNCTION
self.ehand[v_const.VWE_DELFUNCTION] = self._handleDELFUNCTION
self.ehand[v_const.VWE_SETFUNCARGS] = self._handleSETFUNCARGS
self.ehand[v_const.VWE_SETFUNCMETA] = self._handleSETFUNCMETA
self.ehand[v_const.VWE_ADDCODEBLOCK] = self._handleADDCODEBLOCK
self.ehand[v_const.VWE_DELCODEBLOCK] = self._handleDELCODEBLOCK
self.ehand[v_const.VWE_ADDXREF] = self._handleADDXREF
self.ehand[v_const.VWE_DELXREF] = self._handleDELXREF
self.ehand[v_const.VWE_SETNAME] = self._handleSETNAME
self.ehand[v_const.VWE_ADDMMAP] = self._handleADDMMAP
self.ehand[v_const.VWE_DELMMAP] = self._handleDELMMAP
self.ehand[v_const.VWE_ADDEXPORT] = self._handleADDEXPORT
self.ehand[v_const.VWE_DELEXPORT] = None
self.ehand[v_const.VWE_SETMETA] = self._handleSETMETA
self.ehand[v_const.VWE_COMMENT] = self._handleCOMMENT
self.ehand[v_const.VWE_ADDFILE] = self._handleADDFILE
self.ehand[v_const.VWE_DELFILE] = None
self.ehand[v_const.VWE_SETFILEMETA] = self._handleSETFILEMETA
self.ehand[v_const.VWE_ADDCOLOR] = self._handleADDCOLOR
self.ehand[v_const.VWE_DELCOLOR] = self._handleDELCOLOR
self.ehand[v_const.VWE_ADDVASET] = self._handleADDVASET
self.ehand[v_const.VWE_DELVASET] = self._handleDELVASET
self.ehand[v_const.VWE_SETVASETROW] = self._handleSETVASETROW
self.ehand[v_const.VWE_DELVASETROW] = self._handleDELVASETROW
self.ehand[v_const.VWE_ADDFSIG] = self._handleADDFSIG
self.ehand[v_const.VWE_ADDFREF] = self._handleADDFREF
self.ehand[v_const.VWE_DELFREF] = self._handleDELFREF
self.ehand[v_const.VWE_FOLLOWME] = self._handleEventFOLLOWME
self.ehand[v_const.VWE_CHAT] = self._handleCHAT
self.ehand[v_const.VWE_SYMHINT] = self._handleSYMHINT
self.ehand[v_const.VWE_AUTOANALFIN] = self._handleAUTOANALFIN
self.ehand[v_const.VWE_WRITEMEM] = self._handleWRITEMEM
self.ehand[v_const.VWE_ENDIAN] = self._handleENDIAN
self.thand = [None for x in range(v_const.VTE_MAX)]
self.thand[v_const.VTE_IAMLEADER] = self._handleIAMLEADER
self.thand[v_const.VTE_FOLLOWME] = self._handleTransFOLLOWME
self.thand[v_const.VTE_KILLLEADER] = self._handleKILLLEADER
self.thand[v_const.VTE_MODLEADER] = self._handleMODLEADER
def _handleEventFOLLOWME(self, einfo):
pass
def _handleTransFOLLOWME(self, event, einfo):
uuid, expr = einfo
logger.debug("_handleFOLLOWME(%r, %r)", event, einfo)
self.leaderloc[uuid] = expr
def _handleKILLLEADER(self, event, einfo):
logger.debug("_handleKILLLEADER(%r, %r)", event, einfo)
uuid = einfo
user, fname = self.leaders.pop(uuid)
self.vprint("*Ended: %s's session '%s' (%r)" % (user,fname,uuid))
def _handleMODLEADER(self, event, einfo):
uuid, user, fname = einfo
self.vprint('*%s changed leader session name to "%s" (%r)' % (user,fname,uuid))
self.leaders[uuid] = (user, fname)
def _handleIAMLEADER(self, event, einfo):
uuid, user, fname, locexpr = einfo
logger.debug("_handleIAMLEADER(%r, (%r, %r, %r, %r))", event, user, uuid, fname, locexpr)
self.vprint('*%s invites everyone to follow "%s" (%r)' % (user,fname,uuid))
self.leaders[uuid] = (user, fname)
self.leaderloc[uuid] = locexpr
def _fireEvent(self, event, einfo, local=False, skip=None):
'''
Fire an event down the hole. "local" specifies that this is
being called on a client (self.server is not None) but we got it
from the server in the first place so no need to send it back.
skip is used to tell the server to bypass our channelid when
putting the event into channel queues (we took care of our own).
'''
try:
if event & v_const.VTE_MASK:
return self._fireTransEvent(event, einfo)
# Do our main event processing
self.ehand[event](einfo)
# If we're supposed to call a server, do that.
if self.server is not None and not local:
self.server._fireEvent(event, einfo, skip=self.rchan)
# FIXME perhaps we should only process events *via* our server
# if we have one? Just to confirm it works before we apply it...
self._event_list.append((event, einfo))
for id, q in self.chan_lookup.items():
if id == skip:
continue
try:
q.put_nowait((event, einfo))
except queue.Full:
logger.warning('Queue is full!')
except Exception:
logger.error(traceback.format_exc())
def _fireTransEvent(self, event, einfo):
for q in self.chan_lookup.values():
q.put((event, einfo))
return self.thand[event ^ v_const.VTE_MASK](event, einfo)
def _initFunction(self, funcva):
# Internal function to initialize all datastructures necessary for
# a function, but only if they haven't been done already.
if self.funcmeta.get(funcva) is None:
self.funcmeta[funcva] = {} # His metadata
self.codeblocks_by_funcva[funcva] = [] # Init code block list
#def _loadImportApi(self, apidict):
#self._imp_api.update( apidict )
def getEndian(self):
return self.bigend
def notifyLoadEvent(self):
'''
There are a couple components required before we the "load event" is
complete. Each component calls this function, and based on the state
of other components, the _load_event is set
'''
if self.getMeta('Architecture') is None:
return
if self.getMeta('Platform') is None:
return
self._load_event.set()
#################################################################
#
# setMeta key callbacks
#
def _mcb_Architecture(self, name, value):
archid = envi.getArchByName(value)
try:
# Some of the ENVI archs defined may have architecture modules that
# are still in progress
self.setMemArchitecture(archid)
except IndexError:
raise v_exc.ArchModDefException(value) from None
# This is for legacy stuff...
#self.arch = envi.getArchModule(value)
self.arch = self.getMemArchModule()
self.psize = self.arch.getPointerSize()
# Default calling convention for architecture
# This will be superceded by Platform and Parser settings
defcall = self.arch.getArchDefaultCall()
if defcall:
self.setMeta('DefaultCall', defcall)
self.notifyLoadEvent()
def _mcb_bigend(self, name, value):
self.setEndian(bool(value))
def _mcb_Platform(self, name, value):
# Default calling convention for platform
# This supercedes Architecture's setting and should make
# parser settings obsolete
defcall = self.arch.getPlatDefaultCall(value)
if defcall:
self.setMeta('DefaultCall', defcall)
self.notifyLoadEvent()
def _mcb_FileBytes(self, name, value):
if not self.parsedbin:
byts = viv_parsers.uncompressBytes(value)
fmt = viv_parsers.guessFormat(byts)
parser = viv_parsers.getBytesParser(fmt)
if parser:
self.parsedbin = parser(byts)
def _mcb_ustruct(self, name, ssrc):
# All meta values in the "ustruct" namespace are user defined
# structure defintions in C.
sname = name.split(':')[1]
ctor = vs_cparse.ctorFromCSource(ssrc)
self.vsbuilder.addVStructCtor(sname, ctor)
def _mcb_GUID(self, name, guid):
self._load_guid.set()
def _mcb_WorkspaceServer(self, name, wshost):
self.vprint('Workspace was Saved to Server: %s' % wshost)
self.vprint('(You must close this local copy and work from the server to stay in sync.)')
def _fmcb_Thunk(self, funcva, th, thunkname):
# If the function being made a thunk is registered
# in NoReturnApis, update codeflow...
if self.getMeta('NoReturnApis').get(thunkname.lower()):
self.cfctx.addNoReturnAddr(funcva)
def _fmcb_CallsFrom(self, funcva, th, callsfrom):
for va in callsfrom:
f2va = self.getFunction(va)
if f2va is not None:
self._call_graph.getCallEdge( funcva, f2va )
def _fmcb_LocalSymbol(self, fva, mname, locsym):
fva,spdelta,symtype,syminfo = locsym
self.localsyms[fva][spdelta] = locsym
def trackDynBranches(cfctx, op, vw, bflags, branches):
'''
track dynamic branches
'''
# FIXME: do we want to filter anything out?
# jmp edx
# jmp dword [ebx + 68]
# call eax
# call dword [ebx + eax * 4 - 228]
# if we have any xrefs from here, we have already been analyzed. nevermind.
if len(vw.getXrefsFrom(op.va)):
return
logger.info("0x%x: Dynamic Branch found: %s" % (op.va, op))
vw.setVaSetRow('DynamicBranches', (op.va, repr(op), bflags))
class VivCodeFlowContext(e_codeflow.CodeFlowContext):
def __init__(self, mem, persist=False, exptable=True, recurse=True):
e_codeflow.CodeFlowContext.__init__(self, mem, persist=persist, exptable=exptable, recurse=recurse)
self.addDynamicBranchHandler(trackDynBranches)
def _cb_noflow(self, srcva, dstva):
vw = self._mem
loc = vw.getLocation(srcva)
if loc is None:
return
lva, lsize, ltype, linfo = loc
if ltype != v_const.LOC_OP:
return
# Update the location def for NOFALL bit
vw.delLocation(lva)
vw.addLocation(lva, lsize, ltype, linfo | envi.IF_NOFALL)
vw.setVaSetRow('NoReturnCalls', (lva,))
# NOTE: self._mem is the viv workspace...
def _cb_opcode(self, va, op, branches):
'''
callback for each OPCODE in codeflow analysis
must return list of branches, modified for our purposes
'''
loc = self._mem.getLocation(va)
if loc is None:
# dont code flow through import calls
branches = [br for br in branches if not self._mem.isLocType(br[0], v_const.LOC_IMPORT)]
self._mem.makeOpcode(op.va, op=op)
# TODO: future home of makeOpcode branch/xref analysis
if not self._mem.isNoReturnVa(op.va):
return branches
elif loc[v_const.L_LTYPE] != v_const.LOC_OP:
locrepr = self._mem.reprLocation(loc)
logger.warning("_cb_opcode(0x%x): LOCATION ALREADY EXISTS: loc: %r", va, locrepr)
return ()
def _cb_function(self, fva, fmeta):
vw = self._mem
if vw.isFunction(fva):
return
# This may be possible if an export/symbol was mistaken for
# a function...
if not vw.isLocType(fva, v_const.LOC_OP):
return
# If the function doesn't have a name, make one
if vw.getName(fva) is None:
vw.makeName(fva, "sub_%.8x" % fva)
vw._fireEvent(v_const.VWE_ADDFUNCTION, (fva, fmeta))
# Go through the function analysis modules in order
vw.analyzeFunction(fva)
fname = vw.getName(fva)
if vw.getMeta('NoReturnApis').get( fname.lower() ):
self._cf_noret[fva] = True
if len( vw.getFunctionBlocks(fva)) == 1:
return
fmeta = vw.getFunctionMetaDict(fva)
for lva in vw.getVaSetRows('NoReturnCalls'):
va = lva[0]
ctup = vw.getCodeBlock(va)
if ctup and fva == ctup[2] and vw.getFunctionMeta(fva, 'BlockCount', default=0) == 1:
self._cf_noret[fva] = True
break
def _cb_branchtable(self, tablebase, tableva, destva):
if tablebase != tableva and self._mem.getXrefsTo(tableva):
return False
if self._mem.getLocation(tableva) is None:
self._mem.makePointer(tableva, tova=destva, follow=False)
return True