mirror of
https://github.com/vivisect/vivisect
synced 2026-06-08 18:04:23 +00:00
44075feab2
* basic bugfixes to make the Viv CLI work again. not done just yet. savegame. and logging of traceback for "onecmd()" exceptions (duh) * config and argtrack (argtrack hasn't worked since 2013...) * bugfix: vdbbin (FAIL!) bugfix: getEndian() needs to be in IMemory, since other IMemory functions reference it. improvement: Removing non-GUI vprint() which uses logging instead of memory canvas. this is setting us up to make unittests for the CLI functions by patching in a StringMemoryCanvas and comparing the results. * change back VivWorkspace.vprint() and modify VivCli's inheritance so that EnviCli.vprint() wins * thought you might like these... only envi in this test... vw to come * per @rakuy0 * lots of unittests for VivCli * test bugfix * finishing CLI unittests yay! * remove argtrack from active CLI use until it can be fixed (so as to avoid openly broken things from upsetting users and giving them a bad taste) * do_script unittest * re-fix function complexity report and test.
226 lines
6.6 KiB
Python
226 lines
6.6 KiB
Python
|
|
"""
|
|
A module full of utils for vectored input tracking and code
|
|
flow analysis. (when a scalpel finds something you need to be
|
|
able to figure out how to get to it right?)
|
|
"""
|
|
|
|
import vivisect.exc as viv_exc
|
|
import vivisect.tools.graphutil as v_graphutil
|
|
import vivisect.impemu.monitor as viv_imp_monitor
|
|
|
|
import visgraph.pathcore as vg_path
|
|
|
|
from vivisect.const import *
|
|
|
|
import envi
|
|
|
|
class InputMonitor(viv_imp_monitor.EmulationMonitor):
|
|
|
|
def __init__(self):
|
|
viv_imp_monitor.EmulationMonitor.__init__(self)
|
|
self.res = []
|
|
|
|
def apicall(self, emu, op, pc, api, argv):
|
|
magicargs = [(val, emu.getMagic(val)) for val in argv]
|
|
self.res.append( (op.va, magicargs) )
|
|
|
|
def getEmuAtVa(vw, va, maxhit=None):
|
|
"""
|
|
Build and run an emulator to the given virtual address
|
|
from the function entry point.
|
|
|
|
(most useful for state analysis. kinda heavy though...)
|
|
"""
|
|
fva = vw.getFunction(va)
|
|
if fva is None:
|
|
return None
|
|
|
|
cbva,cbsize,cbfva = vw.getCodeBlock(va)
|
|
fgraph = v_graphutil.buildFunctionGraph(vw, fva)
|
|
|
|
# Just take the first one off the iterator...
|
|
for path in v_graphutil.getCodePathsTo(fgraph, cbva):
|
|
|
|
emu = vw.getEmulator()
|
|
opcodes = v_graphutil.getOpsFromPath(vw, fgraph, path)
|
|
for op in opcodes:
|
|
if op.va == va:
|
|
break
|
|
|
|
emu.executeOpcode(op)
|
|
|
|
return emu
|
|
|
|
def trackImportInputs(vw, iname, maxhit=None):
|
|
"""
|
|
Works just like trackFunctionInputs but finds calls to
|
|
imports by name instead...
|
|
"""
|
|
mon = InputMonitor()
|
|
for va in vw.getImportCallers(iname):
|
|
emu = getEmuAtVa(vw, va, maxhit=maxhit)
|
|
if emu is None:
|
|
continue
|
|
|
|
# Set an emulation monitor and step over the call
|
|
emu.setEmulationMonitor(mon)
|
|
emu.stepi()
|
|
|
|
return mon.res
|
|
|
|
def trackFunctionInputs(vw, fva, maxhit=None):
|
|
"""
|
|
Find all the callers to the given function and return a list
|
|
of (callva, [ (argval, magic), ...]) tuples.
|
|
"""
|
|
mon = InputMonitor()
|
|
for va in vw.getCallers(fva):
|
|
|
|
if not vw.getFunction(va):
|
|
vw.vprint('code at 0x%.8x is not part of a function!' % va)
|
|
continue
|
|
|
|
emu = getEmuAtVa(vw, va, maxhit=maxhit)
|
|
# Set an emulation monitor and step over the call
|
|
emu.setEmulationMonitor(mon)
|
|
emu.stepi()
|
|
|
|
return mon.res
|
|
|
|
def trackArgOrigin(vw, fva, argidx):
|
|
"""
|
|
Return an input tree (visgraph path tree) of the trackable inputs
|
|
to the specified function.
|
|
|
|
Each node in the list will be a leaf node for a path leading
|
|
down toward a call to the target function. Each node will have
|
|
the following path node properties:
|
|
|
|
fva - The function
|
|
argidx - The index of the argument input with this call
|
|
cva - The address of the call (to our next) (None on root node)
|
|
argv - A list of (<val>,<magic>) tuples for the call args (None on root node)
|
|
"""
|
|
|
|
rootpath = vg_path.newPathNode(fva=fva, cva=None, trackidx=argidx, argidx=None, argv=None)
|
|
|
|
todo = [rootpath, ]
|
|
|
|
while len(todo):
|
|
|
|
path = todo.pop()
|
|
|
|
fva = vg_path.getNodeProp(path, 'fva')
|
|
trackidx = vg_path.getNodeProp(path, 'trackidx')
|
|
|
|
# Get all of our callers and their arguments to us
|
|
for callva, argv in trackFunctionInputs(vw, fva):
|
|
|
|
newfva = vw.getFunction(callva)
|
|
pargs = dict(parent=path, fva=newfva, cva=callva, argidx=trackidx, argv=argv)
|
|
newpath = vg_path.newPathNode(**pargs)
|
|
|
|
aval, amagic = argv[trackidx]
|
|
if isinstance(amagic, viv_magic.StackArg) and newfva:
|
|
vg_path.setNodeProp(newpath, 'trackidx', amagic.index)
|
|
todo.append(newpath)
|
|
|
|
return vg_path.getLeafNodes(rootpath)
|
|
|
|
def getCodeFlow(vw, cbva):
|
|
"""
|
|
Get a list of the code blocks which are known to flow
|
|
into this one. This *will* cross function boundaries.
|
|
"""
|
|
ret = []
|
|
# Get our actual xrefs
|
|
for fromva, tova, xtype, xdata in vw.getXrefsTo(cbva, REF_CODE):
|
|
xcb = vw.getCodeBlock(fromva)
|
|
if xcb is not None:
|
|
ret.append(xcb)
|
|
|
|
# Lets see if the instruction before this was a fallthrough
|
|
ploc = vw.getPrevLocation(cbva)
|
|
if ploc is not None:
|
|
pva, psize, ptype, pinfo = ploc
|
|
# If it's an opcode with fallthrough, count this one too...
|
|
if ptype == LOC_OP and not pinfo & envi.IF_NOFALL:
|
|
pblock = vw.getCodeBlock(pva)
|
|
if pblock is not None:
|
|
ret.append(pblock)
|
|
|
|
return ret
|
|
|
|
def getCodePaths(vw, fromva, tova, trim=True):
|
|
"""
|
|
Return a list of paths, where each path is a list
|
|
of code blocks from fromva to tova.
|
|
|
|
Usage: getCodePaths(vw, <fromva>, <tova>) -> [ [frblock, ..., toblock], ...]
|
|
|
|
NOTE: "trim" causes an optimization which may not reveal *all* the paths,
|
|
but is much faster to run. It will never return no paths when there
|
|
are some, but may not return all of them... (based on path overlap)
|
|
"""
|
|
|
|
done = {}
|
|
res = []
|
|
|
|
frcb = vw.getCodeBlock(fromva)
|
|
tocb = vw.getCodeBlock(tova)
|
|
if frcb is None:
|
|
raise viv_exc.InvalidLocation(fromva)
|
|
if tocb is None:
|
|
raise viv_exc.InvalidLocation(tova)
|
|
|
|
frva = frcb[0] # For compare speed
|
|
|
|
root = vg_path.newPathNode(cb=tocb, cbva=tocb[0])
|
|
todo = [root, ]
|
|
done[tova] = tocb
|
|
|
|
cbcache = {}
|
|
|
|
while len(todo):
|
|
|
|
path = todo.pop()
|
|
cbva = vg_path.getNodeProp(path, 'cbva')
|
|
|
|
codeblocks = cbcache.get(cbva)
|
|
if codeblocks is None:
|
|
codeblocks = getCodeFlow(vw, cbva)
|
|
cbcache[cbva] = codeblocks
|
|
|
|
for cblock in codeblocks:
|
|
|
|
bva,bsize,bfva = cblock
|
|
|
|
# Don't follow loops...
|
|
if vg_path.isPathLoop(path, 'cbva', bva):
|
|
continue
|
|
|
|
# If we have been here before and it's *not* the answer,
|
|
# skip out...
|
|
if trim and done.get(bva) is not None:
|
|
continue
|
|
|
|
done[bva] = cblock
|
|
|
|
newpath = vg_path.newPathNode(parent=path, cb=cblock, cbva=bva)
|
|
|
|
# If this one is a match, we don't need to
|
|
# track past it. Also, put it in the results list
|
|
# so we don't have to do it later....
|
|
if bva == frva:
|
|
#res.append(newpath)
|
|
fullpath = vg_path.getPathToNode(newpath)
|
|
|
|
# We actually do it by inbound references, so reverse the result!
|
|
fullpath.reverse()
|
|
yield [vg_path.getNodeProp(path, 'cb') for path in fullpath]
|
|
else:
|
|
todo.append(newpath)
|
|
|
|
|