Source code for envi.cli

"""
Unified CLI code for things like vivisect and vdb.
"""

import code
import traceback
import threading

import envi.bits as e_bits
import envi.memory as e_mem
import envi.config as e_config
import envi.resolver as e_resolv
import envi.memcanvas as e_canvas
import envi.expression as e_expr

from cmd import Cmd
from getopt import getopt

import re                                                                                                          

[docs]def splitargs(cmdline): cmdline = cmdline.replace('\\\\"', '"').replace('\\"', '') patt = re.compile('\".+?\"|\S+') for item in cmdline.split('\n'): return [s.strip('"') for s in patt.findall(item)]
[docs]def columnstr(slist): msize = 0 for s in slist: if len(s) > msize: msize = len(s) return [x.ljust(msize) for x in slist]
[docs]class CliExtMeth: """ This is used to work around the difference between functions and bound methods for extended command modules """ def __init__(self, cli, func): self.cli = cli self.func = func self.__doc__ = func.__doc__ def __call__(self, line): return self.func(self.cli, line)
cfgdefs = """ [Aliases] """
[docs]class EnviCli(Cmd): def __init__(self, memobj, config=None, symobj=None): self.extcmds = {} Cmd.__init__(self, stdout=self) self.shutdown = threading.Event() # If they didn't give us a resolver, make one. if symobj == None: symobj = e_resolv.SymbolResolver() if config == None: config = e_config.EnviConfig(defaults=cfgdefs) self.config = config self.memobj = memobj self.symobj = symobj self.canvas = e_canvas.MemoryCanvas(memobj, syms=symobj)
[docs] def setCanvas(self, canvas): """ Set a new canvas for the CLI and add all the current renderers to the new one. """ for name in self.canvas.getRendererNames(): canvas.addRenderer(name, self.canvas.getRenderer(name)) self.canvas = canvas
[docs] def write(self, data): # For stdout/stderr self.canvas.write(data)
[docs] def get_names(self): ret = [] ret.extend(Cmd.get_names(self)) ret.extend(self.extcmds.keys()) return ret
[docs] def getExpressionLocals(self): """ Over-ride this to have things like the eval command and the python command use more locals than the sybolic defaults. """ return e_expr.MemoryExpressionLocals(self.memobj, symobj=self.symobj)
[docs] def registerCmdExtension(self, func): self.extcmds["do_%s" % func.__name__] = CliExtMeth(self, func)
[docs] def vprint(self, msg, addnl=True): ''' Print output to the CLI's output handler. This allows routines to print to the terminal or the GUI depending on which mode we're in. Example: vprint('hi mom!') ''' if addnl: msg = msg+"\n" self.canvas.write(msg)
def __getattr__(self, name): func = self.extcmds.get(name, None) if func == None: raise AttributeError(name) return func
[docs] def doAlias(self, line): for opt in self.config.options("Aliases"): if line.startswith(opt): line = line.replace(opt, self.config.get("Aliases", opt), 1) return line
[docs] def cmdloop(self, intro=None): if intro != None: self.vprint(intro) while not self.shutdown.isSet(): try: Cmd.cmdloop(self, intro=intro) except: traceback.print_exc()
[docs] def onecmd(self, line): lines = line.split("&&") try: for line in lines: line = self.doAlias(line) Cmd.onecmd(self, line) except SystemExit: raise except Exception, msg: self.vprint(traceback.format_exc()) self.vprint("\nERROR: (%s) %s" % (msg.__class__.__name__,msg)) if self.shutdown.isSet(): return True
[docs] def do_EOF(self, line): self.vprint("Use quit")
[docs] def do_quit(self, line): """ Quit Usage: quit """ self.shutdown.set()
[docs] def do_config(self, line): """ Show or edit a config option from the command line Usage: config [-S section] [option=value] """ argv = splitargs(line) secname = None try: opts,args = getopt(argv, "S:") for opt,optarg in opts: if opt == "-S": secname = optarg except Exception, e: print e return self.do_help("config") if len(args) > 1: return self.do_help("config") if len(args) == 0: if secname != None: secs = [secname,] else: secs = self.config.sections() for secname in secs: self.vprint("") self.vprint("[%s]" % secname) for oname in self.config.options(secname): val = self.config.get(secname, oname) self.vprint("%s=%s" % (oname, val)) else: if secname == None: secname = "" key,val = args[0].split("=",1) self.config.set(secname, key, val) self.vprint("[%s] %s = %s" % (secname,key,val))
[docs] def do_alias(self, line): """ Add an alias to the command line interpreter's aliases dictionary Usage: alias <alias_word> rest of the alias command To delete an alias: Usage: alias <alias_word> """ if len(line): row = line.split(None, 1) if len(row) == 1: self.config.remove_option("Aliases",row[0]) else: self.config.set("Aliases",row[0],row[1]) self.vprint("Current Aliases:\n") opts = self.config.options("Aliases") opts.sort() for opt in opts: val = self.config.get("Aliases", opt) self.vprint("%s -> %s" % (opt,val)) self.vprint("") return
[docs] def do_python(self, line): """ Start an interactive python interpreter. The namespace of the interpreter is updated with expression nicities. You may also specify a line of python code as an argument to be exec'd without beginning an interactive python interpreter on the controlling terminal. Usage: python [pycode] """ locals = self.getExpressionLocals() if len(line) != 0: cobj = compile(line, 'cli_input', 'exec') exec(cobj, locals) else: code.interact(local=locals)
[docs] def parseExpression(self, expr): l = self.getExpressionLocals() return long(e_expr.evaluate(expr, l))
[docs] def do_binstr(self, line): ''' Display a binary representation of the given value expression (padded to optional width in bits) Usage: binstr <val_expr> [<bitwidth_expr>] ''' argv = splitargs(line) if len(argv) == 0: return self.do_help('binstr') bitwidth = None value = self.parseExpression(argv[0]) if len(argv) > 1: bitwidth = self.parseExpression(argv[1]) binstr = e_bits.binrepr(value, bitwidth=bitwidth) self.canvas.addText("0x%.8x (%d) %s\n" % (value, value, binstr))
[docs] def do_eval(self, line): """ Evaluate an expression on the CLI to show it's value. Usage: eval (ecx+edx)/2 """ if not line: return self.do_help("eval") value = self.parseExpression(line) self.canvas.addText("%s = " % line) if self.memobj.isValidPointer(value): self.canvas.addVaText("0x%.8x" % value, value) sym = self.symobj.getSymByAddr(value, exact=False) if sym != None: self.canvas.addText(" ") self.canvas.addVaText("%s + %d" % (repr(sym),value-long(sym)), value) else: self.canvas.addText("0x%.8x (%d)" % (value, value)) self.canvas.addText("\n")
[docs] def do_script(self, line): """ Execute a python file. The script file is arbitrary python code which is run with the full compliment of expression extensions mapped in as locals. NOTE: additional command line arguments may be passed in and will appear as the list "argv" in the script namespace! (They will all be strings) Usage: script <scriptfile> [<argv[0]>, ...] """ if len(line) == 0: return self.do_help("script") argv = splitargs(line) locals = self.getExpressionLocals() locals['argv'] = argv script = file(argv[0]).read() cobj = compile(script, argv[0], "exec") exec(cobj, locals)
[docs] def do_maps(self, line): """ Display either a list of all the memory maps or the memory map details for the given address expression. Usage: maps [addr_expression] """ argv = splitargs(line) if len(argv): expr = " ".join(argv) va = self.parseExpression(expr) map = self.memobj.getMemoryMap(va) if map == None: self.vprint("Memory Map Not Found For: 0x%.8x"%va) else: addr,size,perm,fname = map pname = e_mem.reprPerms(perm) self.canvas.addText("Memory Map For: ") self.canvas.addVaText("0x%.8x" % va, va) self.canvas.addText("\n") self.canvas.addVaText("0x%.8x" % addr, addr) self.canvas.addText("\t%d\t%s\t%s\n" % (size,pname,fname)) else: totsize = 0 self.vprint("[ address ] [ size ] [ perms ] [ File ]") for addr,size,perm,fname in self.memobj.getMemoryMaps(): pname = e_mem.reprPerms(perm) totsize += size self.canvas.addVaText("0x%.8x" % addr, addr) sizestr = ("%dK" % (size/1024,)).rjust(8) self.canvas.addText("%s\t%s\t%s\n" % (sizestr,pname,fname)) self.vprint("Total Virtual Memory: %.2f MB" % ((float(totsize)/1024)/1024))
[docs] def reprPointer(self, va): """ Do your best to create a humon readable name for the value of this pointer. """ if va == 0: return "NULL" mbase,msize,mperm,mfile = self.memobj.getMemoryMap(va) ret = mfile sym = self.symobj.getSymByAddr(va, exact=False) if sym != None: ret = "%s + %d" % (repr(sym),va-long(sym)) return ret
[docs] def do_memdump(self, line): """ Dump memory out to a file. Usage: memdump <addr_expression> <size_expression> <filename> """ if len(line) == 0: return self.do_help("memdump") argv = splitargs(line) if len(argv) != 3: return self.do_help("memdump") addr = self.parseExpression(argv[0]) size = self.parseExpression(argv[1]) mem = self.memobj.readMemory(addr, size) file(argv[2], "wb").write(mem) self.vprint("Wrote %d bytes!" % len(mem))
[docs] def do_memcmp(self, line): ''' Compare memory at the given locations. Outputs a set of differences showing bytes at their given offsets.... Usage: memcmp <addr_expr1> <addr_expr2> <size_expr> ''' if len(line) == 0: return self.do_help('memcmp') argv = splitargs(line) if len(argv) != 3: return self.do_help('memcmp') addr1 = self.parseExpression(argv[0]) addr2 = self.parseExpression(argv[1]) size = self.parseExpression(argv[2]) bytes1 = self.memobj.readMemory(addr1, size) bytes2 = self.memobj.readMemory(addr2, size) res = e_mem.memdiff(bytes1, bytes2) if len(res) == 0: self.vprint('No Differences!') return for offset, offsize in res: diff1 = addr1+offset diff2 = addr2+offset self.canvas.addText('==== %d byte difference at offset %d\n' % (offsize,offset)) self.canvas.addVaText("0x%.8x" % diff1, diff1) self.canvas.addText(":") self.canvas.addText(bytes1[offset:offset+offsize].encode('hex')) self.canvas.addText('\n') self.canvas.addVaText("0x%.8x" % diff2, diff2) self.canvas.addText(":") self.canvas.addText(bytes2[offset:offset+offsize].encode('hex')) self.canvas.addText('\n')
[docs] def do_mem(self, line): """ Show some memory (with optional formatting and size) Usage: mem [-F <format>] <addr expression> [size] NOTE: use -F ? for a list of the formats """ fmtname = "bytes" if len(line) == 0: return self.do_help("mem") argv = splitargs(line) try: opts,args = getopt(argv, "F:") except: return self.do_help("mem") for opt,optarg in opts: if opt == "-F": fmtname = optarg fnames = self.canvas.getRendererNames() if fmtname == "?": self.vprint("Registered renderers:") for name in fnames: self.vprint(name) return if fmtname not in fnames: self.vprint("Unknown renderer: %s" % fmtname) return if len(args) == 0: return self.do_help("mem") size = 256 addr = self.parseExpression(args[0]) if len(args) == 2: size = self.parseExpression(args[1]) self.canvas.setRenderer(fmtname) self.canvas.renderMemory(addr, size)
[docs]class EnviMutableCli(EnviCli): """ Cli extensions which require a mutable memory object (emulator/trace) rather than a static one (viv workspace) """
[docs] def do_memcpy(self, line): ''' Copy memory from one location to another... Usage: memcpy <dest_expr> <src_expr> <size_expr> ''' argv = splitargs(line) if len(argv) != 3: return self.do_help('memcpy') dst = self.parseExpression(argv[0]) src = self.parseExpression(argv[1]) siz = self.parseExpression(argv[2]) mem = self.memobj.readMemory(src, siz) self.memobj.writeMemory(dst, mem)
[docs] def do_memprotect(self, line): """ Change the memory permissions of a given page/map. Usage: memprotect [options] <addr_expr> <perms> -S <size> Specify the size of the region to change (default == whole memory map) <perms> = "rwx" string "rw", "rx" "rwx" etc... """ if len(line) == 0: return self.do_help("memprotect") size = None argv = splitargs(line) try: opts, args = getopt(argv, "S:") except Exception, e: return self.do_help("memprotect") for opt,optarg in opts: if opt == "-S": size = self.parseExpression(optarg) if len(args) != 2: return self.do_help("memprotect") addr = self.parseExpression(args[0]) perm = e_mem.parsePerms(args[1]) if size == None: map = self.memobj.getMemoryMap(addr) if map == None: raise Exception("Unknown memory map for 0x%.8x" % addr) size = map[1] self.memobj.protectMemory(addr, size, perm)
[docs] 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 = 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 = memstr.decode('hex') if douni: memstr = ("\x00".join(memstr)) + "\x00" addr = self.parseExpression(exprstr) self.memobj.writeMemory(addr, memstr)