mirror of
https://github.com/vivisect/vivisect
synced 2026-06-08 18:04:23 +00:00
2415 lines
76 KiB
Python
2415 lines
76 KiB
Python
import os
|
|
import re
|
|
import sys
|
|
import shlex
|
|
import pprint
|
|
import signal
|
|
import logging
|
|
import binascii
|
|
import threading
|
|
import traceback
|
|
from queue import Queue
|
|
from collections import defaultdict, UserDict
|
|
|
|
from cmd import *
|
|
from struct import *
|
|
from getopt import getopt
|
|
|
|
import vtrace
|
|
import vtrace.util as v_util
|
|
import vtrace.snapshot as vs_snap
|
|
import vtrace.notifiers as v_notif
|
|
|
|
import vdb
|
|
import vdb.stalker as v_stalker
|
|
import vdb.extensions as v_ext
|
|
|
|
import envi
|
|
import envi.cli as e_cli
|
|
import envi.bits as e_bits
|
|
import envi.common as e_common
|
|
import envi.config as e_config
|
|
import envi.memory as e_memory
|
|
import envi.symstore.resolver as e_resolv
|
|
|
|
import vstruct.primitives as vs_prims
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
vdb.basepath = vdb.__path__[0] + '/'
|
|
|
|
class VdbLookup(UserDict):
|
|
'''
|
|
Used for lookups by key or value.
|
|
'''
|
|
def __init__(self, initdict=None):
|
|
UserDict.__init__(self)
|
|
if initdict is None:
|
|
return
|
|
|
|
for key, val in initdict.items():
|
|
self.__setitem__(self, key, val)
|
|
|
|
def __setitem__(self, key, item):
|
|
UserDict.__setitem__(self, key, item)
|
|
UserDict.__setitem__(self, item, key)
|
|
|
|
class ScriptThread(threading.Thread):
|
|
def __init__(self, cobj, locals):
|
|
threading.Thread.__init__(self, daemon=True)
|
|
self.cobj = cobj
|
|
self.locals = locals
|
|
|
|
def run(self):
|
|
try:
|
|
exec(self.cobj, self.locals)
|
|
except Exception as e:
|
|
logger.warning('Script Error: %s', e)
|
|
|
|
def setupBreakOnEntry(trace):
|
|
'''
|
|
Sets a one time breakpoint at the __entry symbol. Removes itself as a
|
|
notifier after a single NOTIFY_BREAK event.
|
|
'''
|
|
exefile = trace.normFileName(trace.getExe())
|
|
exesym = trace.getSymByName(exefile)
|
|
if exesym is not None:
|
|
entrySym = exesym.getSymByName('__entry')
|
|
if entrySym is not None:
|
|
entrySymExpr = '%s.__entry' % (exefile,)
|
|
otb = vtrace.OneTimeBreak(None, expression=entrySymExpr)
|
|
trace.addBreakpoint(otb)
|
|
|
|
class VdbTrace(object):
|
|
"""
|
|
Used to hand thing that need a persistant reference to a trace
|
|
when using vdb to manage tracers.
|
|
"""
|
|
def __init__(self, db):
|
|
self.db = db
|
|
|
|
def attach(self, pid):
|
|
# Create a new tracer for the debugger and attach.
|
|
trace = self.db.newTrace()
|
|
trace.attach(pid)
|
|
|
|
# Take over all notifier registration
|
|
def registerNotifier(self, event, notif):
|
|
self.db.registerNotifier(event, notif)
|
|
|
|
def deregisterNotifier(self, event, notif):
|
|
self.db.deregisterNotifier(event, notif)
|
|
|
|
#FIXME should we add modes to this?
|
|
|
|
def selectThread(self, threadid):
|
|
#FIXME perhaps a thread selected LOCAL event?
|
|
trace = self.db.getTrace()
|
|
trace.selectThread(threadid)
|
|
self.db.fireLocalNotifiers(vtrace.NOTIFY_BREAK, trace)
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self.db.getTrace(), name)
|
|
|
|
def vprint(self, msg, addnl=True):
|
|
return self.db.vprint(msg, addnl)
|
|
|
|
defconfig = {
|
|
|
|
'vdb':{
|
|
'BreakOnEntry':False,
|
|
'BreakOnMain':False,
|
|
'BreakOnLibraryLoad':False,
|
|
'BreakOnLibraryInit':False,
|
|
|
|
'SymbolCacheActive':True,
|
|
'SymbolCachePath':e_config.gethomedir('.envi','symcache'),
|
|
|
|
'KillOnQuit': False,
|
|
},
|
|
|
|
'cli':{
|
|
'verbose':False,
|
|
'aliases': {
|
|
'<f1>':'stepi',
|
|
'<f2>':'go -I 1',
|
|
'<f5>':'go',
|
|
}
|
|
},
|
|
|
|
}
|
|
|
|
docconfig = {
|
|
'vdb':{
|
|
'BreakOnMain':'Should the debugger break on main() if known?',
|
|
'BreakOnEntry':'Should the debugger break on the entry to the main module? (only works if you exec (and not attach to) the process)',
|
|
'BreakOnLibraryLoad':"Should the debugger break when a new library is loaded?",
|
|
'BreakOnLibraryInit':"Should the debugger break on new library init routines?",
|
|
|
|
'SymbolCacheActive':'Should we cache symbols for subsequent loads?',
|
|
'SymbolCachePaths':'Path elements ( ; seperated) to search/cache symbols (filepath,cobra)',
|
|
}
|
|
}
|
|
|
|
class WrapExcThread(threading.Thread):
|
|
'''
|
|
Places the return value or exception information into a queue that can
|
|
be checked by the caller.
|
|
If the method calls exit(), then nothing will be in the queue.
|
|
'''
|
|
def __init__(self, target=None, args=tuple(), kwargs={}):
|
|
threading.Thread.__init__(self)
|
|
self.queue = Queue()
|
|
self.target = target
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
def run(self):
|
|
try:
|
|
retval = self.target(*self.args, **self.kwargs)
|
|
self.queue.put((retval,))
|
|
except Exception as e:
|
|
tb = traceback.format_exc()
|
|
self.queue.put((e, tb))
|
|
|
|
class Vdb(e_cli.EnviMutableCli, v_notif.Notifier, v_util.TraceManager):
|
|
'''
|
|
A VDB object is a debugger object which may be used to embed full
|
|
debugger like functionality into a python application. The
|
|
Vdb object contains a CLI impelementation which extends envi.cli>
|
|
'''
|
|
|
|
def __init__(self, trace=None):
|
|
v_notif.Notifier.__init__(self)
|
|
v_util.TraceManager.__init__(self)
|
|
|
|
self._extensions = {}
|
|
self._ext_ctxmenu_hooks = {}
|
|
|
|
if trace is None:
|
|
trace = vtrace.getTrace()
|
|
|
|
arch = trace.getMeta('Architecture')
|
|
self.arch = envi.getArchModule(arch)
|
|
|
|
self.bpcmds = {}
|
|
self.waitlib = None
|
|
self.difftracks = {}
|
|
|
|
self.runcache = {}
|
|
self.runcachectors = {}
|
|
|
|
self.server = None
|
|
self.autoscript = None
|
|
|
|
self.runagain = False # A one-time thing for the cli
|
|
self.windows_jit_event = None
|
|
|
|
# We hang on to an opcode renderer instance
|
|
self.opcoderend = None
|
|
|
|
# If a VdbGui instance is present it will set this.
|
|
self.gui = None
|
|
|
|
self.setMode('NonBlocking', True)
|
|
|
|
self.manageTrace(trace)
|
|
self.registerNotifier(vtrace.NOTIFY_ALL, self)
|
|
|
|
# FIXME if config verbose
|
|
#self.registerNotifier(vtrace.NOTIFY_ALL, vtrace.VerboseNotifier())
|
|
|
|
self.vdbhome = e_config.gethomedir('.vdb')
|
|
|
|
# Load up the config
|
|
cfgfile = os.path.join(self.vdbhome, 'vdb.json')
|
|
self.config = e_config.EnviConfig(filename=cfgfile, defaults=defconfig)
|
|
|
|
self.setupSignalLookups()
|
|
|
|
# Ok... from here down we're handing everybody the crazy
|
|
# on-demand-resolved trace object.
|
|
trace = vdb.VdbTrace(self)
|
|
e_cli.EnviMutableCli.__init__(self, trace, self.config, symobj=trace)
|
|
|
|
self.prompt = 'vdb > '
|
|
self.banner = 'Welcome To VDB!\n'
|
|
self.addScriptPathEnvVar('VDB_SCRIPT_PATH')
|
|
|
|
self.loadDefaultRenderers(trace)
|
|
self.loadExtensions(trace)
|
|
|
|
def addCtxMenuHook(self, name, handler):
|
|
'''
|
|
Extensions can add Context Menu hooks to modify the menu as they wish.
|
|
This would most often happen from the Extension's vivExtension() init function.
|
|
see vivisect.qt.ctxmenu for more details
|
|
|
|
handler should have the following prototype (inc. example code):
|
|
|
|
|
|
from vqt.common import ACT
|
|
def myExtCtxMenuHandler(vw, menu):
|
|
toymenu = menu.addMenu('myToys')
|
|
toymenu.addAction('Voodoo Wizbang ZeroDay Finder Thingy', ACT(doCoolShit, vw, va))
|
|
|
|
Currently, this should live in a loaded module, not in your Viv Extension's main py file.
|
|
'''
|
|
self._ext_ctxmenu_hooks[name] = handler
|
|
|
|
def delCtxMenuHook(self, name):
|
|
'''
|
|
Remove a context-menu hook that has been installed by an extension
|
|
'''
|
|
self._ext_ctxmenu_hooks.pop(name, None)
|
|
|
|
def addExtension(self, name, extmod):
|
|
'''
|
|
Add extension module to a list of extensions.
|
|
This keeps a list of installed extension modules, with the added value
|
|
of keeping the loaded module in memory.
|
|
'''
|
|
self._extensions[name] = extmod
|
|
|
|
def delExtension(self, name):
|
|
'''
|
|
Remove's extension module from the list of extensions.
|
|
'''
|
|
self._extensions.pop(name, None)
|
|
|
|
def addRunCacheCtor(self, name, ctor):
|
|
'''
|
|
Add a "run cache constructor" which will be used if a RunCacheVar
|
|
is requested that is not currently cached. *All* RunCacheVar
|
|
entries are flushed automagically on run...
|
|
|
|
( Allows db caching of critical structs likely to be parsed
|
|
more than once by extensions )
|
|
'''
|
|
self.runcachectors[name] = ctor
|
|
|
|
def getRunCacheVar(self, cname):
|
|
'''
|
|
Retrieve a variable from the vdb "runcache". If not currently
|
|
cached, the object will be constructed and added to the cache
|
|
so that future references are fast.
|
|
'''
|
|
ret = self.runcache.get(cname)
|
|
if ret is None:
|
|
ret = self.runcachectors.get(cname)(self)
|
|
self.runcache[cname] = ret
|
|
return ret
|
|
|
|
def loadDefaultRenderers(self, trace):
|
|
import envi.memcanvas.renderers as e_render
|
|
import vdb.renderers as v_rend
|
|
# FIXME check endianness
|
|
self.canvas.addRenderer("bytes", e_render.ByteRend())
|
|
self.canvas.addRenderer("u_int_16", e_render.ShortRend())
|
|
self.canvas.addRenderer("u_int_32", e_render.LongRend())
|
|
self.canvas.addRenderer("u_int_64", e_render.QuadRend())
|
|
self.opcoderend = v_rend.OpcodeRenderer(self.trace)
|
|
self.canvas.addRenderer("asm", self.opcoderend)
|
|
|
|
stackrend = v_rend.StackRenderer(self.trace)
|
|
self.canvas.addRenderer('Stack View', stackrend)
|
|
drend = v_rend.DerefRenderer(self.trace)
|
|
self.canvas.addRenderer("Deref View", drend)
|
|
srend = v_rend.SymbolRenderer(self.trace)
|
|
self.canvas.addRenderer('Symbols View', srend)
|
|
|
|
for arch in envi.getArchModules():
|
|
if arch is None: # The "empty" default...
|
|
continue
|
|
archid = arch.getArchId()
|
|
archname = arch.getArchName()
|
|
|
|
archrend = v_rend.OpcodeRenderer( self.trace, arch=archid)
|
|
self.canvas.addRenderer('asm - %s' % archname, archrend)
|
|
|
|
def verror(self, msg, addnl=True):
|
|
if addnl:
|
|
msg += "\n"
|
|
sys.stderr.write(msg)
|
|
|
|
def fatalError(self, exception):
|
|
'''
|
|
Used for platform exceptions. This indicates something in the
|
|
underlying platform failed and continuing to debug is probably not a
|
|
good idea.
|
|
'''
|
|
self.vprint('%s: %s' % ('FATAL ERROR (you probably should restart session', exception))
|
|
|
|
def vdbUIEvent(self, event, einfo=None):
|
|
'''
|
|
Fire a UI event (mostly used by the GUI to force refresh)
|
|
|
|
Do *not* fire this API in a tight loop, rather, fire once when
|
|
changes are complete.
|
|
|
|
NOTE: Events should only be created for notification on
|
|
events *not* already emitted by the tracer.
|
|
'''
|
|
if self.gui is not None:
|
|
self.gui.vdbUIEvent(event, einfo)
|
|
|
|
def loadExtensions(self, trace):
|
|
"""
|
|
Load up any extensions which are relevant for the current tracer's
|
|
platform/arch/etc...
|
|
"""
|
|
v_ext.loadExtensions(self, trace)
|
|
|
|
def getTrace(self):
|
|
return self.trace
|
|
|
|
def newTrace(self, **kwargs):
|
|
"""
|
|
Generate a new trace for this vdb instance. This fixes many of
|
|
the new attach/exec data munging issues because tracer re-use is
|
|
*very* sketchy...
|
|
|
|
**kwargs is handed into the new trace to handle any platform magic
|
|
"""
|
|
oldtrace = self.getTrace()
|
|
if oldtrace.isRunning():
|
|
oldtrace.sendBreak()
|
|
if oldtrace.isAttached():
|
|
oldtrace.detach()
|
|
|
|
self.trace = oldtrace.buildNewTrace(**kwargs)
|
|
oldtrace.release()
|
|
|
|
self.bpcmds = {} # TODO: make these reusable from previous sessions
|
|
self.manageTrace(self.trace)
|
|
|
|
# must be set for each trace
|
|
self.trace.setBreakOnLibraryLoad(self.config.vdb.BreakOnLibraryLoad)
|
|
self.trace.setBreakOnLibraryInit(self.config.vdb.BreakOnLibraryInit)
|
|
return self.trace
|
|
|
|
def setupSignalLookups(self):
|
|
self.siglookup = VdbLookup()
|
|
|
|
self.siglookup[0] = 'None'
|
|
|
|
for name in dir(signal):
|
|
if name[:3] == 'SIG' and '_' not in name:
|
|
self.siglookup[name] = getattr(signal, name)
|
|
|
|
def getSignal(self, sig):
|
|
"""
|
|
If given an int, return the name, for a name, return the int ;)
|
|
"""
|
|
return self.siglookup.get(sig,None)
|
|
|
|
def parseExpression(self, exprstr):
|
|
return self.trace.parseExpression(exprstr)
|
|
|
|
def getExpressionLocals(self):
|
|
trace = vdb.VdbTrace(self)
|
|
r = vtrace.VtraceExpressionLocals(trace)
|
|
r['db'] = self
|
|
r['vprint'] = self.vprint
|
|
return r
|
|
|
|
def reprPointer(self, address):
|
|
"""
|
|
Return a string representing the best known name for
|
|
the given address
|
|
"""
|
|
if not address:
|
|
return "NULL"
|
|
|
|
# Do we have a symbol?
|
|
sym = self.trace.getSymByAddr(address, exact=False)
|
|
if sym is not None:
|
|
return "%s + %d" % (repr(sym),address-int(sym))
|
|
|
|
# Check if it's a thread's stack
|
|
for tid,tinfo in self.trace.getThreads().items():
|
|
ctx = self.trace.getRegisterContext(tid)
|
|
sp = ctx.getStackCounter()
|
|
|
|
smap = self.trace.getMemoryMap(sp)
|
|
if not smap:
|
|
continue
|
|
|
|
stack,size,perms,fname = smap
|
|
if address >= stack and address < (stack+size):
|
|
off = address - sp
|
|
op = "+"
|
|
if off < 0:
|
|
op = "-"
|
|
off = abs(off)
|
|
return "tid:%d sp%s%s (stack)" % (tid,op,off)
|
|
|
|
map = self.trace.getMemoryMap(address)
|
|
if map:
|
|
return map[3]
|
|
|
|
return "Who knows?!?!!?"
|
|
|
|
def notify(self, event, trace):
|
|
|
|
pid = trace.getPid()
|
|
tid = trace.getCurrentThread()
|
|
|
|
# Any kind of event resets the runcache
|
|
self.runcache = {}
|
|
|
|
if event == vtrace.NOTIFY_ATTACH:
|
|
self.vprint("Attached to : %d" % pid)
|
|
self.waitlib = None
|
|
self.difftracks = {}
|
|
|
|
if self.windows_jit_event:
|
|
trace._winJitEvent(self.windows_jit_event)
|
|
self.windows_jit_event = None
|
|
|
|
# Initialize the tracer's symbol cache path
|
|
if self.config.vdb.SymbolCacheActive:
|
|
trace.setSymCachePath(self.config.vdb.SymbolCachePath)
|
|
|
|
# only respect BreakOnEntry if we exec'd something
|
|
if self.config.vdb.BreakOnEntry and trace.hasMeta('ExecCommand'):
|
|
self.runagain = True # skip initial break
|
|
|
|
if self.autoscript:
|
|
self.do_script(self.autoscript)
|
|
|
|
elif event == vtrace.NOTIFY_CONTINUE:
|
|
pass
|
|
|
|
elif event == vtrace.NOTIFY_STEP:
|
|
pass
|
|
|
|
elif event == vtrace.NOTIFY_DETACH:
|
|
self.difftracks = {}
|
|
self.vprint("Detached from %d" % pid)
|
|
|
|
elif event == vtrace.NOTIFY_SIGNAL:
|
|
# FIXME move all this code into a bolt on notifier!
|
|
thr = trace.getCurrentThread()
|
|
signo = trace.getCurrentSignal()
|
|
|
|
self.vprint("Process Recieved Signal %d (0x%.8x) (Thread: %d (0x%.8x))" % (signo, signo, thr, thr))
|
|
|
|
faddr,fperm = trace.getMemoryFault()
|
|
if faddr is not None:
|
|
accstr = e_memory.getPermName(fperm)
|
|
self.vprint('Memory Fault: addr: 0x%.8x perm: %s' % (faddr, accstr))
|
|
|
|
elif event == vtrace.NOTIFY_BREAK:
|
|
trace.setMeta('PendingBreak', False)
|
|
bp = trace.getCurrentBreakpoint()
|
|
if bp:
|
|
if not bp.silent:
|
|
self.vprint("Thread: %d Hit Break: %s" % (tid, repr(bp)))
|
|
cmdstr = self.bpcmds.get(bp.id, None)
|
|
if cmdstr is not None:
|
|
self.onecmd(cmdstr)
|
|
|
|
else:
|
|
self.vprint("Thread: %d NOTIFY_BREAK" % tid)
|
|
|
|
if self.runagain: # One-time run-again behavior (for cli option)
|
|
if self.config.vdb.BreakOnEntry:
|
|
setupBreakOnEntry(trace)
|
|
|
|
trace.runAgain()
|
|
self.runagain = False
|
|
|
|
elif event == vtrace.NOTIFY_EXIT:
|
|
ecode = trace.getMeta('ExitCode')
|
|
self.vprint("PID %d exited: %d (0x%.8x)" % (pid,ecode,ecode))
|
|
|
|
elif event == vtrace.NOTIFY_LOAD_LIBRARY:
|
|
self.vprint("Loading Binary: %s" % trace.getMeta("LatestLibrary",None))
|
|
if self.waitlib is not None:
|
|
normname = trace.getMeta('LatestLibraryNorm', None)
|
|
if self.waitlib == normname:
|
|
self.waitlib = None
|
|
trace.runAgain(False)
|
|
|
|
elif event == vtrace.NOTIFY_UNLOAD_LIBRARY:
|
|
self.vprint("Unloading Binary: %s" % trace.getMeta("LatestLibrary",None))
|
|
|
|
elif event == vtrace.NOTIFY_CREATE_THREAD:
|
|
self.vprint("New Thread: %d" % tid)
|
|
|
|
elif event == vtrace.NOTIFY_EXIT_THREAD:
|
|
ecode = trace.getMeta("ExitCode", 0)
|
|
self.vprint("Exit Thread: %d (ecode: 0x%.8x (%d))" % (tid,ecode,ecode))
|
|
|
|
elif event == vtrace.NOTIFY_DEBUG_PRINT:
|
|
s = "<unknown>"
|
|
win32 = trace.getMeta("Win32Event", None)
|
|
if win32:
|
|
s = win32.get("DebugString", "<unknown>")
|
|
self.vprint("DEBUG PRINT: %s" % s)
|
|
|
|
else:
|
|
self.vprint('unhandled event: %d' % event)
|
|
|
|
###################################################################
|
|
#
|
|
# All CLI extension commands start here
|
|
#
|
|
|
|
# FIXME this is duplicate, but... PUNT...
|
|
def do_writemem(self, args):
|
|
"""
|
|
Over-write some memory in the target address space.
|
|
Usage: writemem [options] <addr expression> <string>
|
|
-X The specified string is in hex (ie 414141 = AAA)
|
|
-U The specified string needs to be unicode in mem (AAA -> 410041004100)
|
|
"""
|
|
dohex = False
|
|
douni = False
|
|
|
|
try:
|
|
argv = e_cli.splitargs(args)
|
|
opts,args = getopt(argv, "XU")
|
|
except:
|
|
return self.do_help("writemem")
|
|
|
|
if len(args) != 2:
|
|
return self.do_help("writemem")
|
|
|
|
for opt,optarg in opts:
|
|
if opt == "-X":
|
|
dohex = True
|
|
elif opt == "-U":
|
|
douni = True
|
|
|
|
exprstr, memstr = args
|
|
if dohex:
|
|
memstr = binascii.unhexlify(memstr)
|
|
if douni:
|
|
memstr = (b"\x00".join(memstr)) + b"\x00"
|
|
|
|
memstr = memstr.decode('utf8')
|
|
addr = self.parseExpression(exprstr)
|
|
self.memobj.writeMemory(addr, memstr)
|
|
self.vdbUIEvent('vdb:writemem', (addr,memstr))
|
|
|
|
def do_vstruct(self, line):
|
|
"""
|
|
List the available structure modules and optionally
|
|
structure definitions from a particular module in the
|
|
current vstruct.
|
|
|
|
Usage: vstruct [modname]
|
|
"""
|
|
if len(line) == 0:
|
|
self.vprint("\nVStruct Namespaces:")
|
|
plist = self.trace.getStructNames()
|
|
else:
|
|
self.vprint("\nKnown Structures (from %s):" % line)
|
|
plist = self.trace.getStructNames(namespace=line)
|
|
|
|
plist.sort()
|
|
for n in plist:
|
|
self.vprint(str(n))
|
|
|
|
self.vprint("\n")
|
|
|
|
def do_dis(self, line):
|
|
"""
|
|
Print out the opcodes for a given address expression
|
|
|
|
Usage: dis <address expression> [<size expression>]
|
|
"""
|
|
|
|
argv = e_cli.splitargs(line)
|
|
|
|
size = 20
|
|
argc = len(argv)
|
|
if argc == 0:
|
|
addr = self.trace.getProgramCounter()
|
|
else:
|
|
addr = self.parseExpression(argv[0])
|
|
|
|
if argc > 1:
|
|
size = self.parseExpression(argv[1])
|
|
|
|
self.vprint("Dissassembly:")
|
|
self.canvas.renderMemory(addr, size, rend=self.opcoderend)
|
|
|
|
def do_var(self, line):
|
|
"""
|
|
Set a variable in the expression parsing context. This allows
|
|
for scratchspace names (python compatable names) to be used in
|
|
expressions.
|
|
|
|
Usage: var <name> <addr_expression>
|
|
|
|
NOTE: The address expression *must* resolve at the time you set it.
|
|
"""
|
|
t = self.trace
|
|
|
|
if len(line):
|
|
argv = e_cli.splitargs(line)
|
|
if len(argv) == 1:
|
|
return self.do_help("var")
|
|
name = argv[0]
|
|
expr = " ".join(argv[1:])
|
|
addr = t.parseExpression(expr)
|
|
t.setVariable(name, addr)
|
|
|
|
varz = t.getVariables()
|
|
self.vprint("Current Variables:")
|
|
if not varz:
|
|
self.vprint("None.")
|
|
else:
|
|
vnames = varz.keys()
|
|
vnames.sort()
|
|
for n in vnames:
|
|
val = varz.get(n)
|
|
if isinstance(val, int):
|
|
self.vprint("%20s = 0x%.8x" % (n, val))
|
|
else:
|
|
rstr = repr(val)
|
|
if len(rstr) > 30:
|
|
rstr = rstr[:30] + '...'
|
|
self.vprint("%20s = %s" % (n, rstr))
|
|
|
|
def do_alloc(self, args):
|
|
"""
|
|
Allocate a chunk of memory in the target process. It will be
|
|
allocated with rwx permissions.
|
|
|
|
Usage: alloc <size expr>
|
|
"""
|
|
if len(args) == 0:
|
|
return self.do_help("alloc")
|
|
t = self.trace
|
|
#argv = e_cli.splitargs(args)
|
|
try:
|
|
size = t.parseExpression(args)
|
|
base = t.allocateMemory(size)
|
|
self.vprint("Allocated %d bytes at: 0x%.8x" % (size, base))
|
|
except Exception as e:
|
|
logger.error(traceback.format_exc())
|
|
self.vprint("Allocation Error: %s" % e)
|
|
|
|
def do_autoscript(self, line):
|
|
'''
|
|
Tell vdb to run a python script on every process attach.
|
|
|
|
Usage: autoscript <scriptfile>|clear
|
|
'''
|
|
argv = e_cli.splitargs(line)
|
|
if len(argv) != 1:
|
|
self.vprint('Current Autoscript: %s' % self.autoscript)
|
|
return
|
|
|
|
if argv[0] == 'clear':
|
|
self.vprint('clearing autoscript: %s' % self.autoscript)
|
|
return
|
|
|
|
if not os.path.isfile(argv[0]):
|
|
self.vprint('Error: %s is not a valid file' % argv[0])
|
|
return
|
|
|
|
self.autoscript = argv[0]
|
|
|
|
def do_memload(self, line):
|
|
'''
|
|
Load a file into memory. (straight mapping, no parsing)
|
|
|
|
Usage: memload <filename>
|
|
'''
|
|
argv = e_cli.splitargs(line)
|
|
if len(argv) != 1:
|
|
return self.do_help('memload')
|
|
|
|
fname = argv[0]
|
|
if not os.path.isfile(fname):
|
|
self.vprint('Invalid File: %s' % fname)
|
|
return
|
|
|
|
with open(fname, 'rb') as f:
|
|
fbytes = f.read()
|
|
memva = self.trace.allocateMemory(len(fbytes))
|
|
self.trace.writeMemory(memva, fbytes)
|
|
|
|
self.vprint('Loaded At: 0x%.8x (%d bytes)' % (memva, len(fbytes)))
|
|
|
|
def do_struct(self, line):
|
|
'''
|
|
Show and optionally apply a vstruct definition to memory.
|
|
Use the 'vstruct' command to find and display a structure of interest.
|
|
|
|
Usage: struct <vstruct name> [memory expression]
|
|
'''
|
|
argv = shlex.split(line)
|
|
if len(argv) not in (1, 2):
|
|
return self.do_help('struct')
|
|
|
|
clsname = argv[0]
|
|
expr = None
|
|
va = None
|
|
if len(argv) == 2:
|
|
expr = argv[1]
|
|
va = self.trace.parseExpression(expr)
|
|
|
|
sinfo = self.trace.getStruct(clsname, va=va)
|
|
if sinfo is None:
|
|
self.vprint('%s not found.' % clsname)
|
|
return
|
|
|
|
# yuck.
|
|
if len(argv) == 1:
|
|
va = 0
|
|
|
|
stree = sinfo.tree(va=va)
|
|
self.vprint(stree)
|
|
|
|
def do_signal(self, args):
|
|
"""
|
|
Show the current pending signal/exception code.
|
|
|
|
Usage: signal
|
|
"""
|
|
# FIXME -i do NOT pass the signal on to the target process.
|
|
t = self.trace
|
|
t.requireAttached()
|
|
cursig = t.getCurrentSignal()
|
|
if cursig is None:
|
|
self.vprint('No Pending Signals/Exceptions!')
|
|
else:
|
|
self.vprint("Current signal: %d (0x%.8x)" % (cursig, cursig))
|
|
|
|
def do_snapshot(self, line):
|
|
"""
|
|
Take a process snapshot of the current (stopped) trace and
|
|
save it to the specified file.
|
|
|
|
Usage: snapshot <filename>
|
|
"""
|
|
if len(line) == 0:
|
|
return self.do_help("snapshot")
|
|
alist = e_cli.splitargs(line)
|
|
if len(alist) != 1:
|
|
return self.do_help("snapshot")
|
|
|
|
t = self.trace
|
|
t.requireAttached()
|
|
self.vprint("Taking Snapshot...")
|
|
snap = vs_snap.takeSnapshot(t)
|
|
self.vprint("Saving To File")
|
|
snap.saveToFile(alist[0])
|
|
self.vprint("Done")
|
|
snap.release()
|
|
|
|
def do_ignore(self, args):
|
|
"""
|
|
Add the specified signal id (exception id for windows) to the ignored
|
|
signals list for the current trace. This will make the smallest possible
|
|
performance impact for that particular signal but will also not alert
|
|
you that it has occured.
|
|
|
|
Usage: ignore [options] [-c | <sigcode>...]
|
|
-d - Remove the specified signal codes.
|
|
-c - Include the *current* signal in the sigcode list
|
|
-C - Clear the list of ignored signals
|
|
|
|
Example: ignore -c # Ignore the currently posted signal
|
|
ignore -d 0x80000001 # Remove 0x80000001 from the ignores
|
|
"""
|
|
argv = e_cli.splitargs(args)
|
|
try:
|
|
opts,args = getopt(argv, 'Ccd')
|
|
except Exception as e:
|
|
return self.do_help('ignore')
|
|
|
|
remove = False
|
|
sigs = []
|
|
|
|
for opt,optarg in opts:
|
|
if opt == '-c':
|
|
sig = self.trace.getCurrentSignal()
|
|
if sig is None:
|
|
self.vprint('No current signal to ignore!')
|
|
return
|
|
sigs.append(sig)
|
|
elif opt == '-C':
|
|
self.vprint('Clearing ignore list...')
|
|
self.trace.setMeta('IgnoredSignals', [])
|
|
elif opt == '-d':
|
|
remove = True
|
|
|
|
for arg in args:
|
|
sigs.append(self.trace.parseExpression(arg))
|
|
|
|
for sig in sigs:
|
|
if remove:
|
|
self.vprint('Removing: 0x%.8x' % sig)
|
|
self.trace.delIgnoreSignal(sig)
|
|
else:
|
|
self.vprint('Adding: 0x%.8x' % sig)
|
|
self.trace.addIgnoreSignal(sig)
|
|
|
|
ilist = self.trace.getMeta("IgnoredSignals")
|
|
self.vprint("Currently Ignored Signals/Exceptions:")
|
|
for x in ilist:
|
|
self.vprint("0x%.8x (%d)" % (x, x))
|
|
|
|
def do_exec(self, cmd):
|
|
"""
|
|
Execute a program with the given command line and
|
|
attach to it.
|
|
Usage: exec </some/where and some args>
|
|
"""
|
|
t = self.newTrace()
|
|
t.execute(cmd)
|
|
|
|
def do_threads(self, line):
|
|
"""
|
|
List the current threads in the target process or select
|
|
the current thread context for the target tracer.
|
|
Usage: threads [thread id]
|
|
"""
|
|
self.trace.requireNotRunning()
|
|
if self.trace.isRunning():
|
|
self.vprint("Can't list threads while running!")
|
|
return
|
|
|
|
if len(line) > 0:
|
|
thrid = int(line, 0)
|
|
self.trace.selectThread(thrid)
|
|
self.vdbUIEvent('vdb:setthread', thrid)
|
|
|
|
self.vprint("Current Threads:")
|
|
self.vprint("[thrid] [thrinfo] [pc]")
|
|
|
|
curtid = self.trace.getMeta("ThreadId")
|
|
for tid, tinfo in self.trace.getThreads().items():
|
|
a = " "
|
|
if tid == curtid:
|
|
a = "*"
|
|
|
|
sus = ""
|
|
if self.trace.isThreadSuspended(tid):
|
|
sus = "(suspended)"
|
|
ctx = self.trace.getRegisterContext(tid)
|
|
pc = ctx.getProgramCounter()
|
|
self.vprint("%s%6d 0x%.8x 0x%.8x %s" % (a, tid, tinfo, pc, sus))
|
|
|
|
def do_suspend(self, line):
|
|
"""
|
|
Suspend a thread.
|
|
|
|
Usage: suspend <-A | <tid>[ <tid>...]>
|
|
"""
|
|
argv = e_cli.splitargs(line)
|
|
try:
|
|
opts,args = getopt(argv, "A")
|
|
except Exception as e:
|
|
return self.do_help("suspend")
|
|
|
|
for opt,optarg in opts:
|
|
if opt == "-A":
|
|
# hehe...
|
|
args = [str(tid) for tid in self.trace.getThreads().keys()]
|
|
|
|
if not len(args):
|
|
return self.do_help("suspend")
|
|
|
|
for arg in args:
|
|
tid = int(arg)
|
|
self.trace.suspendThread(tid)
|
|
self.vprint("Suspended Thread: %d" % tid)
|
|
|
|
def do_restart(self, line):
|
|
'''
|
|
Restart the current process.
|
|
|
|
Usage: restart
|
|
|
|
NOTE: This only works if the process was exec'd to begin with!
|
|
|
|
TODO: Plumb options for persisting bp's etc...
|
|
'''
|
|
t = self.trace
|
|
cmdline = t.getMeta('ExecCommand')
|
|
if cmdline is None:
|
|
self.vprint('This trace was not fired with exec! (cannot restart)')
|
|
return
|
|
|
|
if t.isRunning():
|
|
t.setMode("RunForever", False)
|
|
t.sendBreak()
|
|
|
|
if t.isAttached():
|
|
t.kill()
|
|
|
|
t = self.newTrace()
|
|
t.execute(cmdline)
|
|
|
|
def do_resume(self, line):
|
|
"""
|
|
Resume a thread.
|
|
|
|
Usage: resume <-A | <tid>[ <tid>...]>
|
|
"""
|
|
argv = e_cli.splitargs(line)
|
|
try:
|
|
opts,args = getopt(argv, "A")
|
|
except Exception as e:
|
|
return self.do_help("suspend")
|
|
|
|
for opt,optarg in opts:
|
|
if opt == "-A":
|
|
# hehe...
|
|
args = [str(tid) for tid in self.trace.getThreads().keys()]
|
|
|
|
if not len(args):
|
|
return self.do_help("resume")
|
|
|
|
for arg in args:
|
|
tid = int(arg)
|
|
self.trace.resumeThread(tid)
|
|
self.vprint("Resumed Thread: %d" % tid)
|
|
|
|
#def do_inject(self, line):
|
|
|
|
def do_mode(self, args):
|
|
"""
|
|
Set modes in the tracers...
|
|
mode Foo=True/False
|
|
"""
|
|
if args:
|
|
mode,val = args.split("=")
|
|
newmode = eval(val)
|
|
self.setMode(mode, newmode)
|
|
else:
|
|
for key,val in self.trace.modes.items():
|
|
self.vprint("%s -> %d" % (key,val))
|
|
|
|
def do_reg(self, args):
|
|
"""
|
|
Show the current register values. Additionally, you may specify
|
|
name=<expression> to set a register
|
|
|
|
Usage: reg [regname=vtrace_expression]
|
|
"""
|
|
if len(args):
|
|
|
|
if args.find("=") == -1:
|
|
return self.do_help("reg")
|
|
|
|
regname,expr = args.split("=", 1)
|
|
val = self.trace.parseExpression(expr)
|
|
self.trace.setRegisterByName(regname, val)
|
|
self.vprint("%s = 0x%.8x" % (regname, val))
|
|
self.vdbUIEvent('vdb:setregs')
|
|
return
|
|
|
|
regs = self.trace.getRegisters()
|
|
rnames = [reg for reg in regs.keys() if reg is not None]
|
|
rnames.sort()
|
|
final = []
|
|
for r in rnames:
|
|
# Capitol names are used for reg vals that we don't want to see
|
|
# (by default)
|
|
if r.lower() != r:
|
|
continue
|
|
val = regs.get(r)
|
|
vstr = e_bits.hex(val, 4)
|
|
final.append(("%12s:0x%.8x (%d)" % (r,val,val)))
|
|
self.columnize(final)
|
|
|
|
def complete_reg(self, text, line, bigidx, endidx):
|
|
|
|
if '=' in line:
|
|
return []
|
|
|
|
regs = self.trace.getRegisters().keys()
|
|
if not text:
|
|
return regs
|
|
|
|
if text in regs:
|
|
return [ text + '=' ]
|
|
|
|
return [ i for i in regs if i.startswith(text) ]
|
|
|
|
def do_stepi(self, line):
|
|
"""
|
|
Single step the target tracer.
|
|
Usage: stepi [ options ]
|
|
|
|
-A <addr> - Step to <addr>
|
|
-B - Step past the next branch instruction
|
|
-C <count> - Step <count> instructions
|
|
-R - Step to return from this function
|
|
-V - Show operand values during single step (verbose!)
|
|
-U - Remainder of args is "step until" expression (stop on True)
|
|
-Q - Do not output to canvas
|
|
-O - Step Over calls (ie. stay in this function)
|
|
-M - Stay in the same Memory Map (step over calls to other maps)
|
|
"""
|
|
t = self.trace
|
|
argv = e_cli.splitargs(line)
|
|
try:
|
|
opts,args = getopt(argv, "A:BC:RVUOQ")
|
|
except Exception as e:
|
|
return self.do_help("stepi")
|
|
|
|
until = None
|
|
count = None
|
|
taddr = None
|
|
toret = False
|
|
tobrn = False
|
|
showop = False
|
|
quiet = False
|
|
stepover = False
|
|
module = False
|
|
curmap = None
|
|
|
|
for opt, optarg in opts:
|
|
|
|
if opt == '-A':
|
|
taddr = t.parseExpression(optarg)
|
|
|
|
elif opt == '-B':
|
|
tobrn = True
|
|
|
|
elif opt == '-C':
|
|
count = t.parseExpression(optarg)
|
|
|
|
elif opt == '-R':
|
|
toret = True
|
|
|
|
elif opt == '-V':
|
|
showop = True
|
|
|
|
elif opt == '-U':
|
|
until = ' '.join(args)
|
|
|
|
elif opt == '-Q':
|
|
quiet = True
|
|
|
|
elif opt == '-O':
|
|
stepover = True
|
|
|
|
elif opt == '-M':
|
|
module = True
|
|
pc = t.getProgramCounter()
|
|
curmap = t.getMemoryMap(pc)
|
|
|
|
if ( count is None
|
|
and taddr is None
|
|
and until is None
|
|
and toret == False
|
|
and tobrn == False):
|
|
count = 1
|
|
|
|
oldmode = self.getMode('FastStep')
|
|
self.setMode('FastStep', True)
|
|
|
|
hits = 0
|
|
depth = 0
|
|
try:
|
|
while True:
|
|
|
|
pc = t.getProgramCounter()
|
|
|
|
try:
|
|
if pc == taddr:
|
|
break
|
|
|
|
op = t.parseOpcode(pc)
|
|
|
|
sym = t.getSymByAddr(pc)
|
|
|
|
if sym is not None and not quiet:
|
|
self.canvas.addVaText(repr(sym), pc)
|
|
self.canvas.addText(':\n')
|
|
|
|
if not quiet:
|
|
self.canvas.addText(' ' * max(depth,0))
|
|
self.canvas.addVaText('0x%.8x' % pc, pc)
|
|
self.canvas.addText(': ')
|
|
op.render(self.canvas)
|
|
|
|
# these options are really mutually exclusive
|
|
if showop and not quiet:
|
|
self.canvas.addText(' ; ')
|
|
for oper in op.opers:
|
|
try:
|
|
val = oper.getOperValue(op, emu=t)
|
|
self.canvas.addText('0x%.8x ' % val)
|
|
except Exception as e:
|
|
self.canvas.addText(str(e))
|
|
|
|
if not quiet:
|
|
self.canvas.addText('\n')
|
|
|
|
if op.iflags & envi.IF_CALL and not stepover:
|
|
depth += 1
|
|
|
|
elif op.iflags & envi.IF_RET:
|
|
depth -= 1
|
|
except Exception as e:
|
|
print("[E@0x%x] %r" % (pc, e))
|
|
|
|
|
|
# execute the instruction
|
|
if op.iflags & envi.IF_CALL and (stepover or module):
|
|
follow = True
|
|
if module:
|
|
for tgtva in op.getTargets(emu=t):
|
|
# there should be only one?
|
|
tgtmap = t.getMemoryMap(tgtva)
|
|
if tgtmap != curmap:
|
|
follow = False
|
|
break
|
|
|
|
else:
|
|
# if we're here, and not "module-only" mode, step-over
|
|
follow = False
|
|
|
|
if follow:
|
|
# this is standard "lalala execute" mode
|
|
tid = t.getCurrentThread()
|
|
t.stepi()
|
|
|
|
else:
|
|
# if we don't follow (stepo or out-of-module), set a
|
|
# one-time breakpoint at the next fallthrough va and go
|
|
bp = vtrace.breakpoints.OneTimeBreak(op.va + op.size)
|
|
self.trace.addBreakpoint(bp)
|
|
self.trace.run()
|
|
|
|
else:
|
|
# all non-call's just execute
|
|
tid = t.getCurrentThread()
|
|
t.stepi()
|
|
|
|
if until and t.parseExpression(until):
|
|
break
|
|
|
|
# If we get an event from a different thread, get out!
|
|
if t.getCurrentThread() != tid:
|
|
break
|
|
|
|
# Break out if we have returned from the current function
|
|
if toret and depth < 0:
|
|
break
|
|
|
|
if depth < 0:
|
|
depth = 0
|
|
|
|
hits += 1
|
|
|
|
# If we have passed a conditional branch...
|
|
if tobrn == True and hits != 0:
|
|
|
|
if op.iflags & envi.IF_CALL:
|
|
break
|
|
|
|
if op.iflags & envi.IF_RET:
|
|
break
|
|
|
|
getout = False
|
|
for bva, bflags in op.getBranches():
|
|
if bflags & envi.BR_COND:
|
|
getout = True
|
|
break
|
|
if getout:
|
|
break
|
|
|
|
|
|
if count is not None and hits >= count:
|
|
break
|
|
|
|
if t.getCurrentSignal() is not None:
|
|
break
|
|
|
|
if t.getMeta('PendingSignal'):
|
|
break
|
|
|
|
finally:
|
|
self.setMode('FastStep', oldmode)
|
|
# We ate all the events, tell things we have updated...
|
|
t.fireNotifiers(vtrace.NOTIFY_STEP)
|
|
|
|
def do_stepo(self, line):
|
|
'''
|
|
Step over current instruction.
|
|
Executes the current instruction unless it is a procedure call.
|
|
If it is a procedure call, sets a breakpoint on the instruction after
|
|
the call.
|
|
'''
|
|
op = self.trace.parseOpcode(self.trace.getProgramCounter())
|
|
if not op.isCall():
|
|
self.trace.stepi()
|
|
else:
|
|
# should we make this like stepout?
|
|
bp = vtrace.breakpoints.OneTimeBreak(op.va + op.size)
|
|
self.trace.addBreakpoint(bp)
|
|
self.trace.run()
|
|
|
|
def do_stepout(self, line):
|
|
'''
|
|
Step out of the current function. (stepi or stepover until return)
|
|
Single step (stepping over procedure calls) until a return
|
|
instruction. Breaks on the return instruction.
|
|
|
|
Usage: stepout [options]
|
|
-V verbose, print step instructions. (much slower)
|
|
'''
|
|
args = shlex.split(line)
|
|
verbose = False
|
|
if len(args) not in (0, 1):
|
|
return self.do_help('stepout')
|
|
|
|
if len(args) == 1:
|
|
if '-V' != args[0]:
|
|
return self.do_help('stepout')
|
|
verbose = True
|
|
|
|
nb = self.trace.getMode('NonBlocking')
|
|
self.trace.setMode('NonBlocking', False)
|
|
|
|
fs = self.trace.getMode('FastStep')
|
|
self.trace.setMode('FastStep', True)
|
|
|
|
tid = self.trace.getCurrentThread()
|
|
waitva = None
|
|
try:
|
|
while True:
|
|
op = self.trace.parseOpcode(self.trace.getProgramCounter())
|
|
if op.isReturn():
|
|
break
|
|
|
|
if self.trace.getCurrentSignal() is not None:
|
|
self.vprint('do_stepout: received signal, stopping')
|
|
break
|
|
|
|
if self.trace.getMeta('PendingSignal'):
|
|
self.vprint('do_stepout: pending signal, stopping')
|
|
break
|
|
|
|
if self.trace.getCurrentThread() != tid:
|
|
self.vprint('do_stepout: event from different thread, stopping')
|
|
break
|
|
|
|
if op.isCall():
|
|
waitva = op.va + op.size
|
|
self.trace.run(until=waitva)
|
|
|
|
continue
|
|
|
|
if verbose:
|
|
self.do_stepi('')
|
|
else:
|
|
self.trace.stepi()
|
|
|
|
except Exception as e:
|
|
self.vprint('do_stepout: exception %s, stopping' % (str(e)))
|
|
finally:
|
|
self.trace.setMode('NonBlocking', nb)
|
|
self.trace.setMode('FastStep', fs)
|
|
|
|
# make sure waitva is gone
|
|
bpid = self.trace.getBreakpointByAddr(waitva)
|
|
if bpid is not None:
|
|
self.trace.removeBreakpoint(bpid)
|
|
|
|
self.trace.fireNotifiers(vtrace.NOTIFY_STEP)
|
|
|
|
def do_go(self, line):
|
|
'''
|
|
Continue the target tracer.
|
|
-I go icount linear instructions forward (step over style)
|
|
-U go *out* of fcount frames (step out style)
|
|
<until addr> go until explicit address
|
|
|
|
Usage: go [-U <fcount> | -I <icount> | <until addr expression>]
|
|
'''
|
|
until = None
|
|
icount = None
|
|
fcount = None
|
|
|
|
argv = e_cli.splitargs(line)
|
|
try:
|
|
opts, args = getopt(argv, 'U:I:')
|
|
except:
|
|
return self.do_help('go')
|
|
|
|
for opt, optarg in opts:
|
|
if opt == '-U':
|
|
if len(optarg) == 0: return self.do_help('go')
|
|
fcount = self.trace.parseExpression(optarg)
|
|
elif opt == '-I':
|
|
if len(optarg) == 0: return self.do_help('go')
|
|
icount = self.trace.parseExpression(optarg)
|
|
|
|
if icount is not None:
|
|
addr = self.trace.getProgramCounter()
|
|
for i in range(icount):
|
|
addr += len(self.trace.parseOpcode(addr))
|
|
|
|
until = addr
|
|
|
|
elif fcount is not None:
|
|
until = self.trace.getStackTrace()[fcount][0]
|
|
|
|
elif len(args):
|
|
until = self.trace.parseExpression(' '.join(args))
|
|
|
|
if not until:
|
|
self.vprint("Running Tracer (use 'break' to stop it)")
|
|
|
|
self.trace.run(until=until)
|
|
|
|
def do_gui(self, line):
|
|
'''
|
|
Attempt to spawn the VDB gui.
|
|
'''
|
|
if self.gui is not None:
|
|
self.vprint('Gui already running!')
|
|
return
|
|
|
|
import vqt.main as vq_main
|
|
if vq_main.isGuiStarted():
|
|
import vivisect.vdbext as viv_vdbext
|
|
viv_vdbext.runVdb(self._viv_gui)
|
|
|
|
else:
|
|
|
|
import vqt.colors as vq_colors
|
|
import vdb.qt.main as vdb_q_main
|
|
|
|
vq_main.startup(css=vq_colors.qt_matrix)
|
|
qgui = vdb_q_main.VdbWindow(self)
|
|
qgui.show()
|
|
|
|
vq_main.main()
|
|
|
|
def do_waitlib(self, line):
|
|
'''
|
|
Run the target process until the specified library
|
|
(by normalized name such as 'kernel32' or 'libc')
|
|
is loaded. Disable waiting with -D.
|
|
|
|
Usage: waitlib [ -D | <libname> ]
|
|
'''
|
|
t = self.trace
|
|
pid = t.getPid()
|
|
|
|
t.requireAttached()
|
|
|
|
argv = e_cli.splitargs(line)
|
|
try:
|
|
opts,args = getopt(argv, "D")
|
|
except:
|
|
return self.do_help("waitlib")
|
|
|
|
for opt, optarg in opts:
|
|
if opt == '-D':
|
|
self.vprint('Disabling Wait On: %s' % self.waitlib)
|
|
self.waitlib = None
|
|
return
|
|
|
|
if len(args) != 1:
|
|
return self.do_help('waitlib')
|
|
|
|
libname = args[0]
|
|
|
|
if t.getMeta('LibraryBases').get(libname) is not None:
|
|
self.vprint('Library Already Loaded: %s' % libname)
|
|
return
|
|
|
|
self.vprint('Setting Waitlib: %s' % libname)
|
|
self.waitlib = libname
|
|
|
|
def do_server(self, port):
|
|
"""
|
|
Start a vtrace server on the local box. If the server
|
|
is already running, show which processes are being remotely
|
|
debugged.
|
|
|
|
Usage: server
|
|
"""
|
|
if port:
|
|
vtrace.port = int(port)
|
|
|
|
if self.server is None:
|
|
self.vprint('Starting vtrace server!')
|
|
self.server = vtrace.startVtraceServer()
|
|
return
|
|
|
|
self.vprint('Displaying remotely debugged traces:')
|
|
shared = [ t for (n,t) in self.server.getSharedObjects() if isinstance(t, vtrace.Trace) ]
|
|
if not shared:
|
|
self.vprint('None.')
|
|
return
|
|
|
|
for t in shared:
|
|
|
|
if not t.isAttached():
|
|
continue
|
|
|
|
runmsg = 'stopped'
|
|
if t.isRunning():
|
|
runmsg = 'running'
|
|
|
|
pid = t.getPid()
|
|
name = t.getMeta('ExeName', 'Unknown')
|
|
self.vprint('%6d %.8s - %s' % (pid, runmsg, name))
|
|
|
|
def do_syms(self, line):
|
|
'''
|
|
List symbols for loaded libraries. Use 'lm' to see loaded libraries.
|
|
-s <regex> a regular expression (case insensitive search)
|
|
<libname> the library name
|
|
|
|
Usage: syms [-s <regex>] [<libname> ...]
|
|
|
|
Usage: show all symbols for library foobar
|
|
syms foobar
|
|
|
|
Usage: show specific symbols for library foobar and bazfaz
|
|
syms -s .*?barfoo.* foobar bazfaz
|
|
|
|
Usage: shows specific symbols in any library
|
|
syms -s .*?barfoo.*
|
|
'''
|
|
argv = shlex.split(line)
|
|
if len(argv) < 1:
|
|
return self.do_help('syms')
|
|
|
|
rgx = None
|
|
if '-s' in argv:
|
|
idx = argv.index('-s')
|
|
argv.pop(idx)
|
|
rgx = argv.pop(idx)
|
|
|
|
s = set(argv)
|
|
libs = self.trace.getNormalizedLibNames()
|
|
if len(s) > 0:
|
|
libs = [lib for lib in libs if lib in s]
|
|
|
|
if len(libs) == 0 and rgx is None:
|
|
self.vprint('invalid library names: %s' % argv)
|
|
return self.do_help('syms')
|
|
|
|
for lib in sorted(libs):
|
|
for sym in self.trace.getSymsForFile(lib):
|
|
r = repr(sym)
|
|
|
|
if rgx is not None:
|
|
match = re.search(rgx, r, re.IGNORECASE)
|
|
if match is None:
|
|
continue
|
|
|
|
self.vprint('0x%.8x %s' % (sym.value, r))
|
|
|
|
def do_call(self, line):
|
|
"""
|
|
Allows a C-like syntax for calling functions inside
|
|
the target process (from his context).
|
|
Example: call printf("yermom %d", 10)
|
|
"""
|
|
self.trace.requireAttached()
|
|
ind = line.index("(")
|
|
if ind == -1:
|
|
raise Exception('ERROR - call wants c-style syntax: ie call printf("yermom")')
|
|
funcaddr = self.trace.parseExpression(line[:ind])
|
|
|
|
try:
|
|
args = eval(line[ind:])
|
|
except:
|
|
raise Exception('ERROR - call wants c-style syntax: ie call printf("yermom")')
|
|
|
|
self.vprint("calling %s -> 0x%.8x" % (line[:ind], funcaddr))
|
|
self.trace.call(funcaddr, args)
|
|
|
|
def do_bestname(self, args):
|
|
"""
|
|
Return the "best name" string for an address.
|
|
|
|
Usage: bestname <vtrace expression>
|
|
"""
|
|
if len(args) == 0:
|
|
return self.do_help("bestname")
|
|
addr = self.trace.parseExpression(args)
|
|
self.vprint(self.reprPointer(addr))
|
|
|
|
def do_EOF(self, string):
|
|
'''
|
|
Prints how to exit VDB (use quit).
|
|
'''
|
|
self.vprint("No.. this is NOT a python interpreter... use quit ;)")
|
|
|
|
def do_quit(self, args):
|
|
"""
|
|
Quit VDB and terminate the process.
|
|
|
|
use "quit force" to hard-force a quit regardless of everything.
|
|
"""
|
|
|
|
if args == 'force':
|
|
print('Quitting by force!')
|
|
os._exit(0)
|
|
|
|
try:
|
|
if self.trace.isRunning():
|
|
self.trace.setMode('RunForever', False)
|
|
self.trace.sendBreak()
|
|
|
|
if self.trace.isAttached():
|
|
if self.config.vdb.KillOnQuit:
|
|
self.trace.kill()
|
|
else:
|
|
self.trace.detach()
|
|
|
|
self.vprint('Exiting...')
|
|
e_cli.EnviMutableCli.do_quit(self, args)
|
|
|
|
self.trace.release()
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
self.vprint(traceback.format_exc())
|
|
self.vprint('Exception during quit (may need: quit force): %s' % e)
|
|
|
|
def do_detach(self, line):
|
|
'''
|
|
Detach from the current tracer.
|
|
|
|
Detaching using -k terminates the process on detach.
|
|
|
|
Usage: detach [-k]
|
|
'''
|
|
self.trace.requireAttached()
|
|
|
|
argv = e_cli.splitargs(line)
|
|
if '-k' in argv:
|
|
self.trace.kill()
|
|
if not self.trace.isRunning():
|
|
self.trace.run()
|
|
else:
|
|
if self.trace.isRunning():
|
|
self.trace.setMode("RunForever", False)
|
|
self.trace.sendBreak()
|
|
self.trace.detach()
|
|
|
|
def do_attach(self, args):
|
|
"""
|
|
Attach to a process by PID or by process name. In
|
|
the event of more than one process by a given name,
|
|
attach to the last (most recently created) one in
|
|
the list.
|
|
|
|
Usage: attach [<pid>,<name>]
|
|
|
|
NOTE: This is *not* a regular expression. The given
|
|
string must be found as a substring of the process
|
|
name...
|
|
"""
|
|
pid = None
|
|
try:
|
|
pid = int(args)
|
|
except ValueError:
|
|
|
|
for mypid, pname in self.trace.ps():
|
|
if pname.find(args) != -1:
|
|
pid = mypid
|
|
|
|
if pid is None:
|
|
return self.do_help('attach')
|
|
|
|
self.vprint("Attaching to %d" % pid)
|
|
self.newTrace().attach(pid)
|
|
|
|
def complete_attach(self, text, line, begidx, endidx):
|
|
procs = self.trace.ps()
|
|
pidlist = [ str(x) for x,y in procs ]
|
|
proclist = [ y for x,y in procs ]
|
|
if not text:
|
|
return proclist
|
|
if text.isdigit():
|
|
return [ i for i in pidlist if i.startswith(text) ]
|
|
|
|
return [ i for i in proclist if i.find(text) != -1 ]
|
|
|
|
def do_autocont(self, line):
|
|
"""
|
|
Manipulate the auto-continue behavior for the trace. This
|
|
will cause particular event types to automagically continue
|
|
execution.
|
|
|
|
Usage: autocont [event name]
|
|
"""
|
|
argv = e_cli.splitargs(line)
|
|
acnames = ["attach",
|
|
"signal",
|
|
"break",
|
|
"loadlib",
|
|
"unloadlib",
|
|
"createthread",
|
|
"exitthread",
|
|
"dbgprint"]
|
|
|
|
acvals = [ vtrace.NOTIFY_ATTACH,
|
|
vtrace.NOTIFY_SIGNAL,
|
|
vtrace.NOTIFY_BREAK,
|
|
vtrace.NOTIFY_LOAD_LIBRARY,
|
|
vtrace.NOTIFY_UNLOAD_LIBRARY,
|
|
vtrace.NOTIFY_CREATE_THREAD,
|
|
vtrace.NOTIFY_EXIT_THREAD,
|
|
vtrace.NOTIFY_DEBUG_PRINT]
|
|
|
|
c = self.trace.getAutoContinueList()
|
|
|
|
if len(line):
|
|
try:
|
|
index = acnames.index(line)
|
|
except ValueError:
|
|
self.vprint("Unknown event name: %s" % line)
|
|
return
|
|
sig = acvals[index]
|
|
if sig in c:
|
|
self.trace.disableAutoContinue(sig)
|
|
c.remove(sig)
|
|
else:
|
|
self.trace.enableAutoContinue(sig)
|
|
c.append(sig)
|
|
|
|
self.vprint("Auto Continue Status:")
|
|
for i in range(len(acnames)):
|
|
name = acnames[i]
|
|
sig = acvals[i]
|
|
acont = False
|
|
if sig in c:
|
|
acont = True
|
|
self.vprint("%s %s" % (name.rjust(14),repr(acont)))
|
|
|
|
self.vdbUIEvent('vdb:setautocont')
|
|
|
|
def do_bt(self, line):
|
|
"""
|
|
Show a stack backtrace for the currently selected thread.
|
|
|
|
Usage: bt
|
|
"""
|
|
self.vprint(" [ PC ] [ Frame ] [ Location ]")
|
|
idx = 0
|
|
for pc,frame in self.trace.getStackTrace():
|
|
self.vprint("[%3d] 0x%.8x 0x%.8x %s" % (idx,pc,frame,self.reprPointer(pc)))
|
|
idx += 1
|
|
|
|
def do_lm(self, args):
|
|
"""
|
|
Show the loaded libraries and their base addresses.
|
|
|
|
Usage: lm [libname]
|
|
"""
|
|
bases = self.trace.getMeta("LibraryBases")
|
|
paths = self.trace.getMeta("LibraryPaths")
|
|
if len(args):
|
|
base = bases.get(args)
|
|
path = paths.get(base, "unknown")
|
|
if base is None:
|
|
self.vprint("Library %s is not found!" % args)
|
|
else:
|
|
self.vprint("0x%.8x - %s %s" % (base, args, path))
|
|
else:
|
|
self.vprint("Loaded Libraries:")
|
|
names = self.trace.getNormalizedLibNames()
|
|
names.sort()
|
|
names = e_cli.columnstr(names)
|
|
for libname in names:
|
|
base = bases.get(libname.strip(), -1)
|
|
path = paths.get(base, "unknown")
|
|
self.vprint("0x%.8x - %.30s %s" % (base, libname, path))
|
|
|
|
def do_guid(self, line):
|
|
"""
|
|
Parse and display a Global Unique Identifier (GUID) from memory
|
|
(eventually, use GUID db to lookup the name/meaning of the GUID).
|
|
|
|
Usage: guid <addr_exp>
|
|
"""
|
|
self.trace.requireNotRunning()
|
|
if not line:
|
|
return self.do_help("guid")
|
|
|
|
addr = self.parseExpression(line)
|
|
guid = vs_prims.GUID()
|
|
bytes = self.trace.readMemory(addr, len(guid))
|
|
guid.vsSetValue(bytes)
|
|
self.vprint("GUID 0x%.8x %s" % (addr, repr(guid)))
|
|
|
|
def do_bpfile(self, line):
|
|
"""
|
|
Set the python code for a breakpoint from the contents
|
|
of a file.
|
|
|
|
Usage: bpfile <bpid> <filename>
|
|
"""
|
|
argv = e_cli.splitargs(line)
|
|
if len(argv) != 2:
|
|
return self.do_help("bpfile")
|
|
|
|
bpid = int(argv[0])
|
|
with open(argv[1], 'rU') as f:
|
|
pycode = f.read()
|
|
|
|
self.trace.setBreakpointCode(bpid, pycode)
|
|
|
|
def do_bpedit(self, line):
|
|
"""
|
|
Modify a given breakpoint.
|
|
* Manipulate the python code that will be run for a given
|
|
breakpoint by ID.
|
|
* Also the way to view the code
|
|
* Modify characteristics of the breakpoint (eg. FastBreak)
|
|
|
|
Usage: bpedit [options] <id> ["optionally new code"]
|
|
|
|
where options include:
|
|
-F toggles a breakpoint's FastBreak mode
|
|
-S toggles a breakpoint's Silent mode (doesn't print console msg, still runs BP code)
|
|
-V prints more metadata about a given breakpoint (default is just the code)
|
|
|
|
NOTE: Your code must be surrounded by "s and may not
|
|
contain any "s
|
|
"""
|
|
argv = e_cli.splitargs(line)
|
|
verbose = False
|
|
try:
|
|
opts, args = getopt(argv, 'FSV')
|
|
except:
|
|
return self.do_help('bpedit')
|
|
|
|
if len(argv) == 0:
|
|
return self.do_help("bpedit")
|
|
|
|
bpid = int(args[0])
|
|
|
|
bp = self.trace.getBreakpoint(bpid)
|
|
if bp is None:
|
|
self.vprint("Breakpoint %d does not exist!" % bpid)
|
|
return
|
|
|
|
for opt, optarg in opts:
|
|
if opt == '-V':
|
|
# print all the metadata
|
|
verbose = True
|
|
|
|
elif opt == '-S':
|
|
bp.silent = not bp.silent
|
|
|
|
elif opt == '-F':
|
|
bp.fastbreak = not bp.fastbreak
|
|
|
|
if len(args) == 2:
|
|
self.trace.setBreakpointCode(bpid, args[1])
|
|
|
|
# print the breakpoint metadata:
|
|
pystr = self.trace.getBreakpointCode(bpid)
|
|
if verbose:
|
|
self.vprint("%r" % bp)
|
|
self.vprint(" code: %s" % (pystr))
|
|
self.vprint(" FastBreak: %r" % bp.fastbreak)
|
|
self.vprint(" enabled: %r" % bp.enabled)
|
|
self.vprint(" silent: %r" % bp.silent)
|
|
self.vprint(" active: %r" % bp.active)
|
|
self.vprint(" resonce: %r" % bp.resonce)
|
|
if bp.stealthbreak:
|
|
self.vprint(" stealth: %r" % bp.stealthbreak)
|
|
|
|
else:
|
|
self.vprint("[%d] Breakpoint code: %s" % (bpid,pystr))
|
|
|
|
def do_bp(self, line):
|
|
"""
|
|
Show, add, and enable/disable breakpoints
|
|
USAGE: bp [-d <addr>] [-a <addr>] [-o <addr>] [[-c pycode] <address> [vdb cmds]]
|
|
-A - Show *all* breakpoints (including special bp's used for VDB functionality)
|
|
-C - Clear All Breakpoints
|
|
-c "py code" - Set the breakpoint code to the given python string
|
|
-d <id> - Disable Breakpoint
|
|
-e <id> - Enable Breakpoint
|
|
-r <id> - Remove Breakpoint
|
|
-o <addr> - Create a OneTimeBreak
|
|
-L <libname> - Add bp's to all functions in <libname>
|
|
-F <filename> - Load bpcode from file
|
|
-W perms:size - Set a hardware Watchpoint with perms/size (ie -W rw:4)
|
|
-f - Make added breakpoints from this command into "fastbreaks"
|
|
-S <libname>:<regex> - Add bp's to all matching funcs in <libname>
|
|
|
|
<address>... - Create Breakpoint
|
|
|
|
[vdb cmds].. - (optional) vdb cli comand to run on BP hit (seperate
|
|
multiple commands with ;; )
|
|
|
|
NOTE: -c adds python code to the breakpoint. The python code will
|
|
be run with the following objects mapped into it's namespace
|
|
automagically:
|
|
vtrace - the vtrace package
|
|
trace - the tracer
|
|
bp - the breakpoint object
|
|
"""
|
|
self.trace.requireNotRunning()
|
|
|
|
argv = e_cli.splitargs(line)
|
|
try:
|
|
opts,args = getopt(argv, "AfF:e:d:o:r:L:Cc:S:W:")
|
|
except Exception as e:
|
|
return self.do_help('bp')
|
|
|
|
pycode = None
|
|
wpargs = None
|
|
showall = False
|
|
fastbreak = False
|
|
libsearch = None
|
|
|
|
for opt,optarg in opts:
|
|
if opt == "-e":
|
|
self.trace.setBreakpointEnabled(eval(optarg), True)
|
|
|
|
elif opt == "-A":
|
|
showall = True
|
|
|
|
elif opt == "-c":
|
|
pycode = optarg
|
|
test = compile(pycode, "test","exec")
|
|
|
|
elif opt == "-F":
|
|
with open(optarg, 'rU') as f:
|
|
pycode = f.read()
|
|
|
|
elif opt == '-f':
|
|
fastbreak = True
|
|
|
|
elif opt == "-r":
|
|
self.bpcmds.pop(int(optarg), None)
|
|
self.trace.removeBreakpoint(int(optarg))
|
|
self.vdbUIEvent('vdb:delbreak', int(optarg))
|
|
|
|
elif opt == "-C":
|
|
for bp in self.trace.getBreakpoints():
|
|
if bp.stealthbreak:
|
|
continue
|
|
|
|
self.bpcmds.pop(bp.id, None)
|
|
self.trace.removeBreakpoint(bp.id)
|
|
self.vdbUIEvent('vdb:delbreak', bp.id)
|
|
|
|
elif opt == "-d":
|
|
self.trace.setBreakpointEnabled(eval(optarg), False)
|
|
|
|
elif opt == "-o":
|
|
bpid = self.trace.addBreakpoint(vtrace.OneTimeBreak(None, expression=optarg))
|
|
self.vdbUIEvent('vdb:addbreak', bpid)
|
|
|
|
elif opt == "-L":
|
|
for sym in self.trace.getSymsForFile(optarg):
|
|
if not isinstance(sym, e_resolv.FunctionSymbol):
|
|
continue
|
|
try:
|
|
bp = vtrace.Breakpoint(None, expression=str(sym))
|
|
bp.setBreakpointCode(pycode)
|
|
bpid = self.trace.addBreakpoint(bp)
|
|
self.vdbUIEvent('vdb:addbreak', bpid)
|
|
self.vprint("Added: %s" % str(sym))
|
|
except Exception as msg:
|
|
self.vprint("WARNING: %s" % str(msg))
|
|
|
|
elif opt == "-W":
|
|
wpargs = optarg.split(":")
|
|
|
|
elif opt == '-S':
|
|
libname, regex = optarg.split(':')
|
|
|
|
try:
|
|
for sym in self.trace.searchSymbols(regex, libname=libname):
|
|
|
|
symstr = str(sym)
|
|
symval = int(sym)
|
|
if self.trace.getBreakpointByAddr(symval) is not None:
|
|
self.vprint('Duplicate (0x%.8x) %s' % (symval, symstr))
|
|
continue
|
|
bp = vtrace.Breakpoint(None, expression=symstr)
|
|
self.trace.addBreakpoint(bp)
|
|
self.vprint('Added: %s' % symstr)
|
|
|
|
except re.error:
|
|
self.vprint('Invalid Regular Expression: %s' % regex)
|
|
return
|
|
|
|
cmdstr = None
|
|
if len(args) > 1:
|
|
cmdstr = ' '.join(args[1:])
|
|
|
|
if len(args) >= 1:
|
|
arg = args[0]
|
|
|
|
if wpargs is not None:
|
|
size = int(wpargs[1])
|
|
bp = vtrace.Watchpoint(None, expression=arg, size=size, perms=wpargs[0])
|
|
else:
|
|
bp = vtrace.Breakpoint(None, expression=arg)
|
|
|
|
bp.setBreakpointCode(pycode)
|
|
bp.fastbreak = fastbreak
|
|
bpid = self.trace.addBreakpoint(bp)
|
|
self.vdbUIEvent('vdb:addbreak', bpid)
|
|
if cmdstr:
|
|
self.bpcmds[bpid] = cmdstr.replace(';;', '&&')
|
|
|
|
self.vprint(" [ Breakpoints ]")
|
|
for bp in self.trace.getBreakpoints():
|
|
if bp.stealthbreak and not showall:
|
|
# don't list stealthbreak bp's (unless forced)
|
|
continue
|
|
self._print_bp(bp)
|
|
|
|
def _print_bp(self, bp):
|
|
cmdstr = self.bpcmds.get(bp.id, '')
|
|
self.vprint("%s enabled: %s fast: %s %s" % (bp, bp.isEnabled(), bp.fastbreak, cmdstr))
|
|
|
|
def do_fds(self, args):
|
|
"""
|
|
Show all the open Handles/FileDescriptors for the target process.
|
|
The "typecode" shown in []'s is the vtrace typecode for that kind of
|
|
fd/handle.
|
|
|
|
Usage: fds
|
|
"""
|
|
self.trace.requireAttached()
|
|
for id,fdtype,fname in self.trace.getFds():
|
|
self.vprint("0x%.8x [%d] %s" % (id,fdtype,fname))
|
|
|
|
def do_ps(self, args):
|
|
"""
|
|
Show the current process list.
|
|
|
|
Usage: ps
|
|
"""
|
|
self.vprint("[Pid]\t[ Name ]")
|
|
for ps in self.trace.ps():
|
|
self.vprint("%s\t%s" % (ps[0],ps[1]))
|
|
|
|
def do_break(self, args):
|
|
"""
|
|
Send the break signal to the target tracer to stop
|
|
it's execution.
|
|
|
|
Usage: break
|
|
"""
|
|
if self.trace.getMeta('PendingBreak'):
|
|
self.vprint('Break already sent...')
|
|
return
|
|
self.trace.setMeta('PendingBreak', True)
|
|
self.trace.setMode("RunForever", False)
|
|
self.trace.sendBreak()
|
|
|
|
def do_meta(self, line):
|
|
"""
|
|
Show the metadata for the current trace.
|
|
|
|
Usage: meta
|
|
"""
|
|
argv = e_cli.splitargs(line)
|
|
if argv:
|
|
for name in argv:
|
|
mval = self.trace.getMeta(name)
|
|
self.vprint('%s: %r' % (name, mval))
|
|
else:
|
|
meta = self.trace.metadata
|
|
x = pprint.pformat(meta)
|
|
self.vprint(x)
|
|
|
|
def do_memdiff(self, line):
|
|
"""
|
|
Save and compare snapshots of memory to enumerate changes.
|
|
|
|
Usage: memdiff [options]
|
|
-C Clear all current memory diff snapshots.
|
|
-A <va:size> Add the given virtual address to the list.
|
|
-M <va> Add the entire memory map which contains VA to the list.
|
|
-D Compare currently tracked memory with the target process
|
|
and show any differences.
|
|
"""
|
|
argv = e_cli.splitargs(line)
|
|
opts,args = getopt(argv, "A:CDM:")
|
|
|
|
if len(opts) == 0:
|
|
return self.do_help('memdiff')
|
|
|
|
self.trace.requireNotRunning()
|
|
|
|
for opt,optarg in opts:
|
|
|
|
if opt == "-A":
|
|
if optarg.find(':') == -1:
|
|
return self.do_help('memdiff')
|
|
|
|
vastr,sizestr = optarg.split(':')
|
|
va = self.parseExpression(vastr)
|
|
size = self.parseExpression(sizestr)
|
|
bytez = self.trace.readMemory(va,size)
|
|
self.difftracks[va] = bytez
|
|
|
|
elif opt == '-C':
|
|
self.difftracks = {}
|
|
|
|
elif opt == '-D':
|
|
difs = self._getDiffs()
|
|
if len(difs) == 0:
|
|
self.vprint('No Differences!')
|
|
else:
|
|
for va,thenbytes,nowbytes in difs:
|
|
self.vprint('0x%.8x: %s %s' % (va,
|
|
e_common.hexify(thenbytes),
|
|
e_common.hexify(nowbytes)))
|
|
|
|
elif opt == '-M':
|
|
va = self.parseExpression(optarg)
|
|
mmap = self.trace.getMemoryMap(va)
|
|
if mmap is None:
|
|
self.vprint('No Memory Map At: 0x%.8x' % va)
|
|
return
|
|
mva,msize,mperm,mfile = mmap
|
|
bytez = self.trace.readMemory(mva, msize)
|
|
self.difftracks[mva] = bytez
|
|
|
|
|
|
def _getDiffs(self):
|
|
|
|
ret = []
|
|
for va, bytez in self.difftracks.items():
|
|
nowbytez = self.trace.readMemory(va, len(bytez))
|
|
|
|
i = 0
|
|
while i < len(bytez):
|
|
thendiff = ""
|
|
nowdiff = ""
|
|
iva = va+i
|
|
while (i < len(bytez) and
|
|
bytez[i] != nowbytez[i]):
|
|
thendiff += bytez[i]
|
|
nowdiff += nowbytez[i]
|
|
i += 1
|
|
|
|
if thendiff:
|
|
ret.append((iva, thendiff, nowdiff))
|
|
continue
|
|
|
|
i += 1
|
|
|
|
return ret
|
|
|
|
def do_dope(self, line):
|
|
'''
|
|
Cli interface to the "stack doping" api inside recon. *BETA*
|
|
|
|
(Basically, set all un-initialized stack memory to V's to tease
|
|
out uninitialized stack bugs)
|
|
|
|
Usage: dope [ options ]
|
|
-E Enable automagic thread stack doping on all continue events
|
|
-D Disable automagic thread stack doping on all continue events
|
|
-A Dope all current thread stacks
|
|
'''
|
|
import vdb.recon.dopestack as vr_dopestack
|
|
|
|
argv = e_cli.splitargs(line)
|
|
|
|
if len(argv) == 0:
|
|
return self.do_help('dope')
|
|
|
|
opts,args = getopt(argv, 'ADE')
|
|
|
|
if len(opts) == 0:
|
|
return self.do_help('dope')
|
|
|
|
for opt, optarg in opts:
|
|
|
|
if opt == '-A':
|
|
self.vprint('Doping all thread stacks...')
|
|
vr_dopestack.dopeAllThreadStacks(self.trace)
|
|
self.vprint('...complete!')
|
|
|
|
elif opt == '-D':
|
|
self.vprint('Disabling thread doping...')
|
|
vr_dopestack.disableEventDoping(self.trace)
|
|
self.vprint('...complete!')
|
|
|
|
elif opt == '-E':
|
|
self.vprint('Enabling thread doping on CONTINUE events...')
|
|
vr_dopestack.enableEventDoping(self.trace)
|
|
self.vprint('...complete!')
|
|
|
|
|
|
def do_recon(self, line):
|
|
'''
|
|
Cli front end to the vdb recon subsystem which allows runtime
|
|
analysis of known API calls.
|
|
|
|
Usage: recon [options]
|
|
-A <sym_expr>:<recon_fmt> - Add a recon breakpoint with the given format
|
|
-C - Clear the current list of recon breakpoint hits.
|
|
-H - Print the current list of recon breakpoint hits.
|
|
-Q - Toggle "quiet" mode which prints nothing on bp hits.
|
|
-S <sym_expr>:<argidx> - Add a sniper break for arg index
|
|
|
|
NOTE: A "recon format" is a special format sequence which tells the
|
|
recon subsystem how to present the argument data for a given
|
|
breakpoint hit.
|
|
|
|
Recon Format:
|
|
C - A character
|
|
I - A decimal integer
|
|
P - A pointer (display symbol if possible)
|
|
S - An ascii string (up to 260 chars)
|
|
U - A unicode string (up to 260 chars)
|
|
X - A hex number
|
|
|
|
'''
|
|
import vdb.recon as v_recon
|
|
import vdb.recon.sniper as v_sniper
|
|
argv = e_cli.splitargs(line)
|
|
|
|
if len(argv) == 0:
|
|
return self.do_help('recon')
|
|
|
|
if self.trace.getMeta('Architecture') != 'i386':
|
|
self.vprint('FIXME: recon only works on i386 right now...')
|
|
return
|
|
|
|
opts,args = getopt(argv, 'A:CHQS:')
|
|
for opt, optarg in opts:
|
|
if opt == '-A':
|
|
symname, reconfmt = optarg.split(':', 1)
|
|
v_recon.addReconBreak(self.trace, symname, reconfmt)
|
|
|
|
elif opt == '-C':
|
|
v_recon.clearReconHits(self.trace)
|
|
|
|
elif opt == '-H':
|
|
self.vprint('Recon Hits:')
|
|
hits = v_recon.getReconHits(self.trace)
|
|
for hit in hits:
|
|
thrid, savedeip, symname, args, argrep = hit
|
|
argstr = '(%s)' % ', '.join(argrep)
|
|
self.vprint('[%6d] 0x%.8x %s%s' % (thrid, savedeip, symname, argstr))
|
|
self.vprint('%d total hits' % len(hits))
|
|
|
|
elif opt == '-Q':
|
|
newval = not self.trace.getMeta('recon_quiet', False)
|
|
self.trace.setMeta('recon_quiet', newval)
|
|
self.vprint('Recon Quiet: %s' % newval)
|
|
|
|
elif opt == '-S':
|
|
symname, idxstr = optarg.split(':')
|
|
argidx = self.trace.parseExpression(idxstr)
|
|
v_sniper.snipeDynArg(self.trace, symname, argidx)
|
|
|
|
def do_stalker(self, line):
|
|
'''
|
|
Cli front end to the VDB code coverage subsystem. FIXME MORE DOCS!
|
|
|
|
Usage: stalker [options]
|
|
-C - Cleanup stalker breaks and hit info
|
|
-c - Clear the current hits (so you can make more ;)
|
|
-E <addr_expr> - Add the specified entry point for tracking
|
|
-H - Show the current hits
|
|
-L <lib>:<regex> - Add stalker breaks to all matching library symbols
|
|
-R - Reset all breakpoints to enabled and clear hit info
|
|
'''
|
|
|
|
argv = e_cli.splitargs(line)
|
|
|
|
if len(argv) == 0:
|
|
return self.do_help('stalker')
|
|
|
|
try:
|
|
opts,args = getopt(argv, 'cCE:HIL:R')
|
|
except Exception as e:
|
|
return self.do_help('stalker')
|
|
|
|
trace = self.trace
|
|
for opt, optarg in opts:
|
|
if opt == '-c':
|
|
v_stalker.clearStalkerHits(trace)
|
|
self.vprint('Clearing Stalker Hits...')
|
|
|
|
elif opt == '-C':
|
|
v_stalker.clearStalkerBreaks(trace)
|
|
v_stalker.clearStalkerHits(trace)
|
|
self.vprint('Cleaning up stalker breaks and hits')
|
|
|
|
|
|
elif opt == '-E':
|
|
addr = trace.parseExpression(optarg)
|
|
v_stalker.addStalkerEntry(trace, addr)
|
|
self.vprint('Added 0x%.8x' % addr)
|
|
|
|
elif opt == '-H':
|
|
self.vprint('Current Stalker Hits:')
|
|
for hitva in v_stalker.getStalkerHits(trace):
|
|
self.vprint('0x%.8x' % hitva)
|
|
|
|
elif opt == '-L':
|
|
libname, regex = optarg.split(':', 1)
|
|
for sym in trace.searchSymbols(regex, libname=libname):
|
|
v_stalker.addStalkerEntry(trace, int(sym))
|
|
self.vprint('Stalking %s' % str(sym))
|
|
|
|
elif opt == '-R':
|
|
self.vprint('Resetting all breaks and hit info')
|
|
v_stalker.clearStalkerHits(trace)
|
|
v_stalker.resetStalkerBreaks(trace)
|
|
|
|
def do_status(self, line):
|
|
'''
|
|
Print out the status of the debugger / trace...
|
|
'''
|
|
t = self.getTrace()
|
|
if not t.isAttached():
|
|
self.vprint('Trace Not Attached...')
|
|
return
|
|
|
|
runmsg = 'stopped'
|
|
if t.isRunning():
|
|
runmsg = 'running'
|
|
pid = t.getPid()
|
|
self.vprint('Attached to pid: %d (%s)' % (pid, runmsg))
|
|
|
|
def _getFirstLine(self, line):
|
|
'''
|
|
Returns the first non-empty line in a (potentially) empty or
|
|
multiline string. An empty line is returned for a None string or
|
|
if all lines are empty.
|
|
'''
|
|
if line is None:
|
|
return ''
|
|
|
|
lines = line.split('\n')
|
|
lines = [line.strip() for line in lines]
|
|
|
|
for line in lines:
|
|
if line != '':
|
|
return line
|
|
return ''
|
|
|
|
def _getCommandHelp(self):
|
|
'''
|
|
Returns a list of command name, doc first line, and doc string tuples.
|
|
(sorted by command name)
|
|
We'll need this later anyway when we implement our own groups of
|
|
commands.
|
|
'''
|
|
# commands can be docstrings or have help_<cmd> methods.
|
|
HELP_DOCS = 0 # help docstring
|
|
HELP_FUNC = 1 # help function (precedence over docstring)
|
|
cmds = defaultdict(list)
|
|
for name in dir(self):
|
|
|
|
if name.startswith('help_'):
|
|
hstr = getattr(self, name)()
|
|
cmds[name[5:]].append( (HELP_FUNC, hstr) )
|
|
|
|
elif name.startswith('do_'):
|
|
hstr = getattr(self, name).__doc__
|
|
cmds[name[3:]].append( (HELP_DOCS, hstr) )
|
|
|
|
else:
|
|
pass
|
|
|
|
rcmds = []
|
|
for cname, clist in cmds.items():
|
|
if len(clist) > 2:
|
|
raise Exception('how do we handle inherited overridden help')
|
|
|
|
# find the right help string.
|
|
# pull out HELP_FUNC if it exists, otherwise use HELP_DOCS
|
|
if len(clist) == 2:
|
|
chelp = max(clist, key=lambda x: x[0])[1].strip()
|
|
else:
|
|
chelp = clist[0][1].strip()
|
|
|
|
# maybe change to first line or first sentence in line?
|
|
fline = self._getFirstLine(chelp)
|
|
|
|
rcmds.append( (cname, fline, chelp) )
|
|
|
|
rcmds.sort()
|
|
return rcmds
|
|
|
|
def do_help(self, line):
|
|
'''
|
|
Prints a list of commands and further help depending on the options.
|
|
|
|
Usage: help [options] [string]
|
|
Usage: ? [options] [string]
|
|
|
|
no opts/args prints all commands
|
|
<command> help for the command
|
|
-s one line of help for each command
|
|
-s <string> one line of help per command for commands that contain
|
|
string
|
|
-k <string> all help per command for commands that contain <string>
|
|
'''
|
|
argv = shlex.split(line)
|
|
if len(argv) == 0:
|
|
return e_cli.EnviMutableCli.do_help(self, line)
|
|
|
|
if argv[0] not in ('-k', '-s'):
|
|
return e_cli.EnviMutableCli.do_help(self, line)
|
|
|
|
# this gets a tad messy.
|
|
if argv[0] == '-s' and len(argv) == 1:
|
|
ctups = self._getCommandHelp()
|
|
for ctup in ctups:
|
|
self.vprint('%15s: %s' % (ctup[0], ctup[1]))
|
|
|
|
elif argv[0] == '-s' and len(argv) == 2:
|
|
ctups = self._getCommandHelp()
|
|
for ctup in ctups:
|
|
if (argv[1] in ctup[0]) or (argv[1] in ctup[2]):
|
|
self.vprint('%s: %s' % (ctup[0], ctup[1]))
|
|
|
|
elif argv[0] == '-k' and len(argv) == 2:
|
|
ctups = self._getCommandHelp()
|
|
for ctup in ctups:
|
|
if (argv[1] in ctup[0]) or (argv[1] in ctup[2]):
|
|
self.vprint('> help %s\n' % ctup[0])
|
|
self.vprint(' %s\n' % ctup[2])
|
|
else:
|
|
self.vprint(self.do_help.__doc__)
|
|
|
|
def FIXME_do_remote(self, line):
|
|
"""
|
|
Act as a remote debugging client to the server running on
|
|
the specified host/ip.
|
|
|
|
Usage: remote <host>
|
|
"""
|
|
vtrace.remote = line
|
|
# FIXME how do we re-init the debugger?
|
|
|
|
# Some helper functions for tab completion
|
|
def _complete_libname(self, text, line, begidx, endidx):
|
|
libnames = self.trace.getNormalizedLibNames()
|
|
if not text:
|
|
return libnames
|
|
return [ i for i in libnames if i.startswith( text ) ]
|
|
|
|
##############################################################################
|
|
# The following are touched during the release process by bump2version.
|
|
# You should have no reason to modify these yourself
|
|
version = (1, 2, 1)
|
|
verstring = '.'.join([str(x) for x in version])
|
|
commit = ''
|