Files
atlas0fd00m 44075feab2 Various bugfixes mostly cli oriented (#467)
* 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.
2021-11-11 15:43:57 -05:00

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)