mirror of
https://github.com/vivisect/vivisect
synced 2026-06-08 18:04:23 +00:00
80de840881
A lot of cleanup things in prep for a python 3 transition. Getting rid of the old exception syntax, converting prints over to logging, cutting random scraps of code to be proper unit tests, cut away some older bits of code, etc. This still works in python2. It's just a lot of tidying up. There are no major functionality changes.
225 lines
6.5 KiB
Python
225 lines
6.5 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):
|
|
self.res.append( (op.va, argv) )
|
|
|
|
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)
|
|
|
|
|