Files
atlas0fd00m 4979ea079f Vtrace conversion and bugfixes (#406)
* 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>
2023-05-01 12:06:32 -04:00

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