mirror of
https://github.com/vivisect/vivisect
synced 2026-06-08 18:04:23 +00:00
88e3fafbde
* vivbin -S now starts the VivServer. -P sets the listening port. modifying VivServer comms to break up massive event dumps into max chunk sizes, and favor sending Memory Maps individually. this helps reduce timeouts while loading massive remote workspaces (working on a 500MB workspace right now). tweaking the timeouts and chunk sizes still. was taking 35 minutes to load, now down to ~16minutes over Wifi. still not happy, looking for other ways to enhance performance. i believe there are unnecessary delays in master, so i'm looking for them. * dramatic improvements on performance. now for a little cleanup. * some cleanup * cleaned up. should be gtg. * updates to cover unittests and other non-huge things * kill unused variable * better documentation for VivChunkQueue * more comment * more comment * solidly avoid race condition between Arch and Platform meta's being set before _snapInAnalysisModules() * metrics/monitoring on VivServer comms performance * changes per Rakuy0. configurable timeouts for Cobra and VivServer * Update vivisect/remote/server.py * timeo_* to timeout_* per Rakuy0 :) makes sense to me. * Apply suggestions from code review per rakuy0 * remove "block" options from getServerWorkspace. it's no longer part of the solution.
880 lines
30 KiB
Python
880 lines
30 KiB
Python
import queue
|
|
import base64
|
|
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.const as viv_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
|
|
|
|
from vivisect.exc import *
|
|
from vivisect.const import *
|
|
|
|
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(VWE_MAX)]
|
|
self._ve_thand = [None for x in range(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(viv_const, name, None)
|
|
self._ve_ehand[idx] = getattr(self, name)
|
|
if name.startswith('VTE_'):
|
|
idx = getattr(viv_const, name, None)
|
|
self._ve_thand[idx] = getattr(self, name)
|
|
|
|
def _ve_fireEvent(self, event, edata):
|
|
hlist = self._ve_ehand
|
|
if event & VTE_MASK:
|
|
event ^= VTE_MASK
|
|
hlist = self._ve_thand
|
|
|
|
h = hlist[event]
|
|
if h is not None:
|
|
try:
|
|
h(self._ve_vw, event, edata)
|
|
except Exception as e:
|
|
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:
|
|
self._ve_lock.acquire()
|
|
self._ve_lock.release()
|
|
|
|
self._ve_fireEvent(*etup)
|
|
|
|
etup = self._ve_vw.waitForEvent(chanid)
|
|
|
|
finally:
|
|
self._ve_vw.deleteEventChannel(chanid)
|
|
|
|
def _ve_freezeEvents(self):
|
|
self._ve_lock.acquire()
|
|
|
|
def _ve_thawEvents(self):
|
|
self._ve_lock.release()
|
|
|
|
vaset_xlate = {
|
|
int:VASET_ADDRESS,
|
|
str: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=None, **kwargs):
|
|
if vw is None:
|
|
raise Exception("VivEventDist requires a vw argument")
|
|
|
|
VivEventCore.__init__(self, vw)
|
|
self._ve_subs = [ [] for x in range(VWE_MAX) ]
|
|
self._ve_tsubs = [ [] for x in range(VTE_MAX) ]
|
|
|
|
self.addEventCore(self)
|
|
|
|
# event distributors pretty much always need a thread
|
|
self._ve_fireListener()
|
|
|
|
def addEventCore(self, core):
|
|
for i in range(VWE_MAX):
|
|
h = core._ve_ehand[i]
|
|
if h is not None:
|
|
self._ve_subs[i].append(h)
|
|
|
|
for i in range(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(VWE_MAX):
|
|
h = core._ve_ehand[i]
|
|
if h is not None:
|
|
self._ve_subs[i].remove(h)
|
|
|
|
for i in range(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 & VTE_MASK:
|
|
event ^= 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 == 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 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 == 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, REF_PTR, 0))
|
|
self._handleADDLOCATION((rva, size, 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 == RTYPE_BASEPTR:
|
|
ptr = imgbase + data
|
|
ptr, reftype, rflags = self.arch.archModifyXrefAddr(ptr, None, None)
|
|
self._handleDELXREF((rva, ptr, REF_PTR, 0))
|
|
self._handleDELLOCATION((rva, self.psize, 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[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[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 arch in self.imem_archs:
|
|
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 _handleFOLLOWME(self, va):
|
|
pass
|
|
|
|
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(VWE_MAX)]
|
|
self.ehand[VWE_ADDLOCATION] = self._handleADDLOCATION
|
|
self.ehand[VWE_DELLOCATION] = self._handleDELLOCATION
|
|
self.ehand[VWE_ADDSEGMENT] = self._handleADDSEGMENT
|
|
self.ehand[VWE_DELSEGMENT] = None
|
|
self.ehand[VWE_ADDRELOC] = self._handleADDRELOC
|
|
self.ehand[VWE_DELRELOC] = self._handleDELRELOC
|
|
self.ehand[VWE_ADDMODULE] = self._handleADDMODULE
|
|
self.ehand[VWE_DELMODULE] = self._handleDELMODULE
|
|
self.ehand[VWE_ADDFMODULE] = self._handleADDFMODULE
|
|
self.ehand[VWE_DELFMODULE] = self._handleDELFMODULE
|
|
self.ehand[VWE_ADDFUNCTION] = self._handleADDFUNCTION
|
|
self.ehand[VWE_DELFUNCTION] = self._handleDELFUNCTION
|
|
self.ehand[VWE_SETFUNCARGS] = self._handleSETFUNCARGS
|
|
self.ehand[VWE_SETFUNCMETA] = self._handleSETFUNCMETA
|
|
self.ehand[VWE_ADDCODEBLOCK] = self._handleADDCODEBLOCK
|
|
self.ehand[VWE_DELCODEBLOCK] = self._handleDELCODEBLOCK
|
|
self.ehand[VWE_ADDXREF] = self._handleADDXREF
|
|
self.ehand[VWE_DELXREF] = self._handleDELXREF
|
|
self.ehand[VWE_SETNAME] = self._handleSETNAME
|
|
self.ehand[VWE_ADDMMAP] = self._handleADDMMAP
|
|
self.ehand[VWE_DELMMAP] = self._handleDELMMAP
|
|
self.ehand[VWE_ADDEXPORT] = self._handleADDEXPORT
|
|
self.ehand[VWE_DELEXPORT] = None
|
|
self.ehand[VWE_SETMETA] = self._handleSETMETA
|
|
self.ehand[VWE_COMMENT] = self._handleCOMMENT
|
|
self.ehand[VWE_ADDFILE] = self._handleADDFILE
|
|
self.ehand[VWE_DELFILE] = None
|
|
self.ehand[VWE_SETFILEMETA] = self._handleSETFILEMETA
|
|
self.ehand[VWE_ADDCOLOR] = self._handleADDCOLOR
|
|
self.ehand[VWE_DELCOLOR] = self._handleDELCOLOR
|
|
self.ehand[VWE_ADDVASET] = self._handleADDVASET
|
|
self.ehand[VWE_DELVASET] = self._handleDELVASET
|
|
self.ehand[VWE_SETVASETROW] = self._handleSETVASETROW
|
|
self.ehand[VWE_DELVASETROW] = self._handleDELVASETROW
|
|
self.ehand[VWE_ADDFSIG] = self._handleADDFSIG
|
|
self.ehand[VWE_ADDFREF] = self._handleADDFREF
|
|
self.ehand[VWE_DELFREF] = self._handleDELFREF
|
|
self.ehand[VWE_FOLLOWME] = self._handleFOLLOWME
|
|
self.ehand[VWE_CHAT] = self._handleCHAT
|
|
self.ehand[VWE_SYMHINT] = self._handleSYMHINT
|
|
self.ehand[VWE_AUTOANALFIN] = self._handleAUTOANALFIN
|
|
self.ehand[VWE_WRITEMEM] = self._handleWRITEMEM
|
|
self.ehand[VWE_ENDIAN] = self._handleENDIAN
|
|
|
|
self.thand = [None for x in range(VTE_MAX)]
|
|
self.thand[VTE_IAMLEADER] = self._handleIAMLEADER
|
|
self.thand[VTE_FOLLOWME] = self._handleFOLLOWME
|
|
self.thand[VTE_KILLLEADER] = self._handleKILLLEADER
|
|
self.thand[VTE_MODLEADER] = self._handleMODLEADER
|
|
|
|
def _handleFOLLOWME(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 & 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 local == False:
|
|
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 as e:
|
|
logger.warning('Queue is full!')
|
|
|
|
except Exception as e:
|
|
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 ^ 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 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 != 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], 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[L_LTYPE] != 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, 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(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
|
|
|