Files

165 lines
4.9 KiB
Python

"""
A function analysis module that will find code blocks in
functions by flow and xrefs. This is basically a mandatory
module which should be snapped in *very* early by parsers.
"""
import logging
import collections
import envi
import envi.const as e_const
from vivisect.const import REF_CODE, LOC_POINTER, LOC_OP
logger = logging.getLogger(__name__)
def analyzeFunction(vw, funcva):
blocks = {}
done = {}
mnem = collections.defaultdict(int)
todo = [funcva, ]
brefs = []
size = 0
opcount = 0
while len(todo):
start = todo.pop()
# If we've hit code we've already done, keep going.
if done.get(start):
continue
done[start] = True
blocks[start] = 0
brefs.append((start, True))
va = start
op = None
arch = envi.ARCH_DEFAULT
# Walk forward through instructions until a branch edge
while True:
loc = vw.getLocation(va)
# If it's not a location, terminate
if loc is None:
blocks[start] = va - start
brefs.append((va, False))
break
lva, lsize, ltype, linfo = loc
if ltype == LOC_POINTER:
# pointer analysis mis-identified a pointer,
# so clear and re-analyze instructions.
vw.delLocation(lva)
# assume we're adding a valid instruction, which is most likely.
if op is not None:
arch = op.iflags & envi.ARCH_MASK
vw.makeCode(va, arch=arch, fva=funcva)
loc = vw.getLocation(va)
if loc is None:
blocks[start] = va - start
brefs.append((va, False))
break
lva, lsize, ltype, linfo = loc
# If it's not an op, terminate
if ltype != LOC_OP:
blocks[start] = va - start
brefs.append((va, False))
break
try:
op = vw.parseOpcode(va)
mnem[op.mnem] += 1
except Exception as e:
logger.warning('Codeblock bad opcode at 0x%x, breaking on error %s', va, e)
break
size += lsize
opcount += 1
nextva = va + lsize
# For each of our code xrefs, create a new target.
branch = False
xrefs = vw.getXrefsFrom(va, REF_CODE)
for fromva, tova, rtype, rflags in xrefs:
# We don't handle procedural branches here...
if rflags & envi.BR_PROC:
continue
# For now, we'll skip jmp [import] thunks...
if rflags & envi.BR_DEREF:
continue
mmap = vw.getMemoryMap(tova)
if mmap:
mva, msize, mperm, mname = mmap
if mperm & e_const.MM_UNINIT:
continue
branch = True
todo.append(tova)
# If it doesn't fall through, terminate (at nextva)
if linfo & envi.IF_NOFALL:
blocks[start] = nextva - start
brefs.append((nextva, False))
break
# If we hit a branch, we are the end of a block...
if branch:
blocks[start] = nextva - start
todo.append(nextva)
break
if vw.getXrefsTo(nextva, REF_CODE):
blocks[start] = nextva - start
todo.append(nextva)
break
va = nextva
oldblocks = {va: size for (va, size, fva) in vw.getFunctionBlocks(funcva)}
# we now have an ordered list of block references!
brefs.sort()
brefs.reverse()
bcnt = 0
while len(brefs):
bva, isbegin = brefs.pop()
if not isbegin:
continue
if len(brefs) == 0:
break
# So we don't add a codeblock if we're re-analyzing a function
# (like during dynamic branch analysis)
try:
bsize = blocks[bva]
if bsize == 0:
continue
tmpcb = vw.getCodeBlock(bva)
# sometimes codeblocks can be deleted if owned by multiple functions
if bva not in oldblocks or tmpcb is None:
vw.addCodeBlock(bva, bsize, funcva)
elif bsize != oldblocks[bva]:
vw.delCodeBlock(bva)
vw.addCodeBlock(bva, bsize, funcva)
bcnt += 1
except Exception as e:
logger.warning('Codeblock analysis for 0x%.8x hit exception: %s', funcva, e)
break
vw.setFunctionMeta(funcva, 'Size', size)
vw.setFunctionMeta(funcva, 'BlockCount', bcnt)
vw.setFunctionMeta(funcva, 'InstructionCount', opcount)
vw.setFunctionMeta(funcva, 'MnemDist', dict(mnem))