mirror of
https://github.com/vivisect/vivisect
synced 2026-06-08 18:04:23 +00:00
4979ea079f
* lotsa work making it all work, with segments and maps and collapsing adjacent maps and bugfixes and... * pagemap and memorymap deletion * update unittests * memory-related updates (and unittests for delMemoryMap) * unittests and MapNotFoundException * update unittest to test appropriately * change in symstore fix * more bugfixes for vdb/win32. symbols are a bit of a mess and may need to be refactored in the future. * make syms work (do_syms and do_bp) on Win32 * unittest mods per @rakuy0 * aaaaand this will make unittests break. unittest discovered a bug in symstore about storing subresolvers... still need to fix the bug. * lockstep emulator class and some improvements for Win32 PEB/TEB capture * enhancements for envitools.LockstepEmulator class * cleanup and import bugfix * collapseMemoryMaps::strict * bugfix: deleting symbols and tests * bugfix: Win32 detaching * updates per @rakuy0 * touchups per @rakuy0 * tweaking the test to account for different versions of Linux and python * updates per @rakuy0 and improvements from i386_emu_... PR. we're housing the refugee code here while the cull takes place in PR#405 * update to identify FreeBSD ELF files (OS matters on some of these vtrace and emu changes) * a few bugfixes for Elf and Vtrace handling of Elfs. * Break On Library Init (and stub for Library Load) and bugfix for clicking EFLAGS gui buttons. * modified flaky unittest * removed prints * bugfix for event/threading for notify/breakpoints * Finally! LockStepper Class moving into vtrace.envitools. Raw move here... updates to follow (for easy diffing between commits) * bugfix * revamped the LockStepper class (still have to remove LockStepEmulator class and revamp "lockStepEmulator" when we're done) * oops * bugfix * change a few INS_SYSTEM x86/x64 instruction opcodes to be unique * unify @rakuy0's and my lockstep emu classes and clean up * de-x86ify * bsd commented code removal .gitignore to ignore docs build files * bugfix: vwFromTrace() call to addFile was handing in the md5 object, not a serializable string (hexdigest) bugfix: vdb snapshot wasn't capturing TEB's from a trace object, vw/emuFromTrace wasn't allowing for PEB/TEBs tracking from snapshot * bugfix: str versus bytes * cleanup 'Comparing' print/log message * updates per @rakuy0 * finish the docstr (per rakuy0) * finish the docstr (per rakuy0) (for realz this time, last commit was actually the @idlethread change) * document user interface for InteractiveLSMon * MM_* removed from envi.memory and left only in envi.const * allow --LL and --LI settings to persist between different traces in the same session (instead of only for the first run and only if provided "-c /path/to/proggy" at the command line. in the future, make this more cohesive by setting vdbbin to create the `db` and use `db.newTrace()` instead of punching low-level into vtrace.getTrace. this will have to include handling of `platargs` which is beyond the scope of this PR currently and would unnecessarily delay review/merging * cleanup per rakuy0 * cleanups per rakuy0 * bugfix (unittests have been screaming about a typo) and minor cleanup. * kwargs to get new trace able to handle platform magic. logging of --LI and --LL effects. * yes, @rakuy0, that should do something ;) * windows debugging privs (tested on Win7 and Win10) debugging print statements that need to be cut/converted to logs * Break on Library Load/Init bugfixes * make POSIX (Linux) catch Library Loads and raise the correct notifiers. this requires hooking a function in `ld` which causes libraries to be rediscovered and unresolved breakpoints to be resolved if possible. * outdated code causing Python warnings. * decouping Vtrace from VDB, where only the latter has a trace.db.config (or trace.db, for that matter) * no prints! * equaling out the unresolved breakpoint warnings. * update vtrace unittest * remove need for pywin32 (in README) * remove dependency on pywin32 and winadmin * vprint on both VdbTrace and Trace objects. and string/bytes bugfixes. * add to the BP execution context * don't need to hand in **kwargs to `self.getTrace()` in fact, that's bad. * REMOVE collapseMaps functionality. no longer important since we can emulate/read across memory maps now. there are many other bugfixes in the branch that should be merged, and this was always a little wonky. * lost an import somewhere. * test_privs was getting caught by unittests :) * remove tests for the collapseMaps that i just yanked out of this PR. * update checkprivs to use only ctypes instead of pywin32. TODO: wrap this ability into indicating that Windows users don't have sufficient rights for debug. * updates per @rakuy0 --------- Co-authored-by: James Gross <45212823+rakuy0@users.noreply.github.com>
302 lines
9.9 KiB
Python
302 lines
9.9 KiB
Python
# Copyright (C) 2007 Invisigoth - See LICENSE file for details
|
|
|
|
import logging
|
|
import hashlib
|
|
import collections
|
|
|
|
import vtrace
|
|
import vtrace.notifiers as v_notifiers
|
|
import vtrace.rmi as v_rmi
|
|
|
|
import envi
|
|
import envi.const as e_const
|
|
import envi.archs.i386 as e_i386
|
|
import envi.archs.amd64 as e_amd64
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TraceManager:
|
|
"""
|
|
A trace-manager is a utility class to extend from when you may be dealing
|
|
with multiple tracer objects. It allows for persistant mode settings and
|
|
persistent metadata as well as bundling a DistributedNotifier. You may also
|
|
extend from this to get auto-magic remote stuff for your managed traces.
|
|
"""
|
|
def __init__(self, trace=None):
|
|
self.trace = trace
|
|
self.dnotif = v_notifiers.DistributedNotifier()
|
|
self.modes = {} # See docs for trace modes
|
|
self.metadata = {} # Like traces, but persistant
|
|
|
|
def manageTrace(self, trace):
|
|
"""
|
|
Set all the modes/meta/notifiers in this trace for management
|
|
by this TraceManager.
|
|
"""
|
|
self.trace = trace
|
|
if vtrace.remote:
|
|
trace.registerNotifier(vtrace.NOTIFY_ALL, v_rmi.getCallbackProxy(trace, self.dnotif))
|
|
else:
|
|
trace.registerNotifier(vtrace.NOTIFY_ALL, self.dnotif)
|
|
|
|
for name, val in self.modes.items():
|
|
trace.setMode(name, val)
|
|
|
|
for name, val in self.metadata.items():
|
|
trace.setMeta(name, val)
|
|
|
|
def unManageTrace(self, trace):
|
|
"""
|
|
Untie this trace manager from the trace.
|
|
"""
|
|
if vtrace.remote:
|
|
trace.deregisterNotifier(vtrace.NOTIFY_ALL, v_rmi.getCallbackProxy(trace, self.dnotif))
|
|
else:
|
|
trace.deregisterNotifier(vtrace.NOTIFY_ALL, self.dnotif)
|
|
|
|
def setMode(self, name, value):
|
|
if self.trace is not None:
|
|
self.trace.setMode(name, value)
|
|
self.modes[name] = value
|
|
|
|
def getMode(self, name, default=False):
|
|
if self.trace is not None:
|
|
return self.trace.getMode(name, default)
|
|
return self.modes.get(name, default)
|
|
|
|
def setMeta(self, name, value):
|
|
if self.trace is not None:
|
|
self.trace.setMeta(name, value)
|
|
self.metadata[name] = value
|
|
|
|
def getMeta(self, name, default=None):
|
|
if self.trace is not None:
|
|
return self.trace.getMeta(name, default)
|
|
return self.metadata.get(name, default)
|
|
|
|
def registerNotifier(self, event, notif):
|
|
self.dnotif.registerNotifier(event, notif)
|
|
|
|
def deregisterNotifier(self, event, notif):
|
|
self.dnotif.deregisterNotifier(event, notif)
|
|
|
|
def fireLocalNotifiers(self, event, trace):
|
|
"""
|
|
Deliver a local event to the DistributedNotifier managing
|
|
the traces. (used to locally bump notifiers)
|
|
"""
|
|
self.dnotif.notify(event, trace)
|
|
|
|
|
|
def emuFromTrace(trace):
|
|
'''
|
|
Produce an envi emulator for this tracer object.
|
|
'''
|
|
arch = trace.getMeta('Architecture')
|
|
plat = trace.getMeta('Platform')
|
|
amod = envi.getArchModule(arch)
|
|
emu = amod.getEmulator()
|
|
[emu.setMeta(key, val) for key, val in trace.metadata.items()]
|
|
|
|
# could use {get,set}MemorySnap if trace inherited from MemoryObject
|
|
for va, size, perms, fname in trace.getMemoryMaps():
|
|
try:
|
|
# So linux maps in a PROT_NONE page for efficient library sharing, so we have to take that into account
|
|
if (not perms & e_const.MM_READ):
|
|
continue
|
|
if plat == 'linux' and fname in ['[vvar]']:
|
|
continue
|
|
bytez = trace.readMemory(va, size)
|
|
emu.addMemoryMap(va, perms, fname, bytez)
|
|
except vtrace.PlatformException:
|
|
logger.warning('failed to map: 0x{:x} into emu'.format(va, size))
|
|
continue
|
|
|
|
rsnap = trace.getRegisterContext().getRegisterSnap()
|
|
emu.setRegisterSnap(rsnap)
|
|
|
|
if plat == 'windows':
|
|
psize = trace.getPointerSize()
|
|
# capture PEB and TIB
|
|
peb = trace.getMeta('PEB')
|
|
if hasattr(trace, 'win32threads'):
|
|
tebs = dict(trace.win32threads)
|
|
vw.setMeta('TEBs', tebs)
|
|
else:
|
|
metatebs = trace.getMeta('TEBs')
|
|
if metatebs:
|
|
vw.setMeta('TEBs', tebs)
|
|
|
|
emu.setMeta('PEB', peb)
|
|
|
|
seginfo = trace.getThreads()[trace.getMeta('ThreadId')]
|
|
if psize == 4:
|
|
emu.setSegmentInfo(e_i386.SEG_FS, seginfo, 0xffffffff)
|
|
elif psize == 8:
|
|
emu.setSegmentInfo(e_amd64.SEG_GS, seginfo, 0xffffffffffff)
|
|
|
|
return emu
|
|
|
|
|
|
def vwFromTrace(trace, storagename='binary_workspace_from_vsnap.viv', filefmt=None, collapse=True, strict=True):
|
|
'''
|
|
Produce an envi emulator for this tracer object.
|
|
|
|
If filefmt is None, it will be auto-determined
|
|
|
|
If collapse, join adjacent maps
|
|
If strict, only join maps with the same permissions
|
|
'''
|
|
import vivisect
|
|
vw = vivisect.VivWorkspace()
|
|
arch = trace.getMeta('Architecture')
|
|
plat = trace.getMeta('Platform')
|
|
psize = trace.getPointerSize()
|
|
|
|
# determine file format (if not specified above)
|
|
if filefmt is None:
|
|
if 'win' in plat.lower():
|
|
filefmt = 'pe'
|
|
from vivisect.parsers.pe import archcalls
|
|
else:
|
|
filefmt = 'elf'
|
|
from vivisect.parsers.elf import archcalls
|
|
|
|
vw.setMeta("Architecture", arch)
|
|
vw.setMeta("Platform", plat)
|
|
vw.setMeta('Format', filefmt)
|
|
vw.setMeta('DefaultCall', archcalls.get(arch,'unknown'))
|
|
vw.setMeta('StorageName', storagename)
|
|
|
|
if 'win' in plat.lower():
|
|
ossep = '\\'
|
|
exts = ('exe', 'dll')
|
|
|
|
else:
|
|
ossep = '/'
|
|
exts = ('so')
|
|
|
|
# could use {get,set}MemorySnap if trace inherited from MemoryObject
|
|
maps = []
|
|
fnames = collections.defaultdict(int)
|
|
filemeta = collections.defaultdict(hashlib.md5)
|
|
|
|
for va, size, perms, fname in trace.getMemoryMaps():
|
|
# strip off unwanted parts
|
|
trimfname = fname.split(ossep)[-1]
|
|
stripfname = trimfname
|
|
for ext in exts:
|
|
if trimfname.endswith('.' + ext):
|
|
stripfname = trimfname[:-(len(ext) + 1)]
|
|
|
|
# add map to the workspace
|
|
try:
|
|
# So linux maps in a PROT_NONE page for efficient library sharing, so we have to take that into account
|
|
if (not perms & e_const.MM_READ):
|
|
continue
|
|
if plat == 'linux' and stripfname in ['[vvar]']:
|
|
continue
|
|
bytez = trace.readMemory(va, size)
|
|
maps.append((va, perms, stripfname, bytez))
|
|
|
|
except vtrace.PlatformException:
|
|
logger.warning('failed to map: 0x{:x} into emu'.format(va, size))
|
|
continue
|
|
|
|
# filter maps
|
|
if collapse:
|
|
maps = collapseMemoryMaps(maps, strict=strict)
|
|
|
|
# add maps
|
|
for midx, (va, perms, fname, bytez) in enumerate(maps):
|
|
count = fnames.get(fname, 0)
|
|
fnames[fname] = count + 1
|
|
|
|
vw.addMemoryMap(va, perms, fname, bytez)
|
|
vw.addSegment(va, len(bytez), "%s_%d" % (fname, count), fname)
|
|
filemeta[fname].update(bytez)
|
|
|
|
# now actually add the files
|
|
for fname in filemeta.keys():
|
|
# find first va:
|
|
for va, perms, mnm, btz in maps:
|
|
if mnm == fname:
|
|
break
|
|
|
|
vw.addFile(fname, va, filemeta[fname].hexdigest())
|
|
|
|
# windows stuff
|
|
if plat == 'windows':
|
|
# capture PEB and TIB
|
|
peb = trace.getMeta('PEB')
|
|
if hasattr(trace, 'win32threads'):
|
|
tebs = dict(trace.win32threads)
|
|
vw.setMeta('TEBs', tebs)
|
|
else:
|
|
metatebs = trace.getMeta('TEBs')
|
|
if metatebs:
|
|
vw.setMeta('TEBs', metatebs)
|
|
|
|
vw.setMeta('PEB', peb)
|
|
|
|
return vw
|
|
|
|
|
|
def collapseMemoryMaps(oldmaps, strict=True):
|
|
'''
|
|
Sort through a list of memory maps and collapse any which abutt.
|
|
If strict, only collapse if the permissions are the same.
|
|
Otherwise, collapse them and or the permissions together.
|
|
|
|
TODO: make all map bytes collapsed and make maps start at an offset?
|
|
or poss
|
|
'''
|
|
# if we have no maps, skip the whole process
|
|
if not len(oldmaps):
|
|
return
|
|
|
|
oldmaps.sort()
|
|
|
|
# start off with the current map as the first oldmap, and add it
|
|
newmaps = [oldmaps[0]]
|
|
curva, curperms, curfname, curbytez = oldmaps[0]
|
|
cursz = len(curbytez)
|
|
curvamax = curva + cursz
|
|
logger.debug("initial map: 0x%x, perms:%x, %r, %d-bytes, curvamax: 0x%x", curva, curperms, curfname, cursz, curvamax)
|
|
|
|
for omidx in range(1, len(oldmaps)):
|
|
ova, operms, ofname, obytez = oldmaps[omidx]
|
|
omsz = len(obytez)
|
|
ovamax = ova + omsz
|
|
logger.debug("next map: 0x%x, perms:%x, %r, %d-bytes, curvamax: 0x%x", ova, operms, ofname, omsz, ovamax)
|
|
if ova == curvamax and curfname == ofname and (not strict or curperms == operms):
|
|
# collapse this into previous and update curvamax and curbytes if perms or not strict
|
|
curvamax = ovamax
|
|
newfname = None
|
|
if len(curfname):
|
|
if len(ofname) and ofname != curfname:
|
|
newfname = '%s + %s' % (curfname, ofname)
|
|
else:
|
|
newfname = curfname
|
|
else:
|
|
newfname = ofname
|
|
|
|
curbytez += obytez
|
|
curfname = newfname
|
|
newmaps[-1] = curva, curperms, newfname, curbytez
|
|
logger.debug("collapsing: initial map: 0x%x, perms:%x, %r, %d-bytes, curvamax: 0x%x", \
|
|
curva, curperms, curfname, cursz, curvamax)
|
|
|
|
else:
|
|
#logger.debug("ova (0x%x) != curvamax (0x%x) or curperms (%r) != operms (%r)", ova, curvamax, curperms, operms)
|
|
# add this map to newmaps and update cur*
|
|
newmaps.append(oldmaps[omidx])
|
|
curva, curperms, curfname, curbytez = oldmaps[omidx]
|
|
cursz = len(curbytez)
|
|
curvamax = curva + cursz
|
|
|
|
return newmaps
|
|
|