def plasma_file(filename, symbol, options): gctx = GlobalContext() gctx.color = False gctx.filename = filename gctx.entry = symbol gctx.quiet = True gctx.db_path = None for o in options: if o == "--raw x86": gctx.raw_type = "x86" elif o == "--raw x64": gctx.raw_type = "x64" elif o.startswith("--rawbase"): gctx.raw_base = int(o.split(" ")[1], 16) if not gctx.load_file(): die() gctx.api = Api(gctx, None) sio = StringIO() with redirect_stdout(sio): o = gctx.get_addr_context(gctx.entry).decompile() if o is not None: o.print() postfix = '{0}.rev'.format('' if symbol is None else '_' + symbol) with open(filename.replace('.bin', postfix)) as f: assert_equal(sio.getvalue(), f.read())
def console_entry(): gctx = GlobalContext() gctx.parse_args() if gctx.filename is None: die() if not gctx.load_file(): die() if gctx.interactive_mode: from plasma.lib.ui.console import Console gctx.is_interactive = True Console(gctx) else: gctx.api = Api(gctx, None) if gctx.list_sections: for s in gctx.dis.binary.iter_sections(): s.print_header() sys.exit(0) if gctx.syms: gctx.dis.print_symbols() sys.exit(0) ctx = gctx.get_addr_context(gctx.entry) if ctx is None: sys.exit(0) if gctx.do_dump: ctx.dump_asm(gctx.nb_lines).print() sys.exit(0) o = ctx.decompile() if gctx.graph: ctx.gph.dot_graph(gctx.dis.jmptables) if o is not None: if gctx.vim: base = os.path.basename(gctx.filename) + "_" + gctx.entry # re-assign if no colors gctx.libarch.process_ast.assign_colors(ctx, ctx.ast) gctx.color = False generate_vim_syntax(ctx, base + ".vim") sys.stdout = open(base + ".rev", "w+") o.print() if gctx.vim: print("run : vim {0}.rev -S {0}.vim".format(base), file=sys.stderr)
def __init__(self, gctx): self.gctx = gctx self.db = gctx.db gctx.vim = False self.visual_previous_idx = 0 self.visual_last_widgets = [] # A hack to allow window resizing os.environ['LINES']="blah" del os.environ['LINES'] os.environ['COLUMNS']="blah" del os.environ['COLUMNS'] self.COMMANDS = { "analyzer": Command( 0, 0, self.__exec_analyzer, None, [ "", "Analyzer status.", ] ), "push_analyze_symbols": Command( 0, 0, self.push_analyze_symbols, None, [ "", "Force to analyze the entry point, symbols and a memory scan will be done.", ] ), "help": Command( 0, 0, self.__exec_help, None, [ "", "Display this help." ] ), "history": Command( 0, 0, self.__exec_history, None, [ "", "Display the command history.", ] ), "save": Command( 0, 0, self.__exec_save, None, [ "", "Save the database.", ] ), "x": Command( 1, 0, self.__exec_x, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Decompile and print on stdout. By default it will be main.", "The decompilation is forced, it dosn't check if addresses", "are defined as code." ] ), "v": Command( 1, 0, self.__exec_v, self.__complete_x, [ "[SYMBOL|0xXXXX|EP|%VISUAL]", "Visual mode: if no address is given, previous visual is", "reopen. You can keep up to 3 visuals. Use %1, %2 or %3", "to select the visual.", "", "Main shortcuts:", "c create code", "b/w/d/Q create byte/word/dword/qword", "a create ascii string", "p create function", "o set [d|q]word as an offset", "* create an array", "x show xrefs", "r rename", "space highlight current word (ctrl-k to clear)", "; edit inline comment (enter/escape to validate/cancel)", "U undefine", "", "Options:", "I switch to traditional instruction string output (3 modes)", "M show/hide mangling", "B show/hide bytes", "", "Navigation:", "| split the window", "j jump to an address or a symbol", "/ binary search: if the first char is ! you can put an", " hexa string example: /!ab 13 42", " the search is case sensitive.", "n/N next/previous search occurence", "g top", "G bottom", "z set current line on the middle", "% goto next bracket", "{ } previous/next paragraph", "tab switch between dump/decompilation", "enter follow address", "escape go back", "u re-enter", "q quit", ] ), "hexdump": Command( 2, 1, self.__exec_hexdump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Dump memory in hexa." ] ), # by default it will be gctx.nb_lines "dump": Command( 2, 1, self.__exec_dump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Print contents at the specified address.", ] ), "sym": Command( 3, 0, self.__exec_sym, self.__complete_x, [ "[SYMBOL 0xXXXX] [| FILTER]", "Print all symbols or set a new symbol.", "You can filter symbols by searching the word FILTER.", "If FILTER starts with -, the match is inversed." ] ), "rename": Command( 2, 2, self.__exec_rename, self.__complete_x, [ "OLD_SYM NEW_SYM", "Rename a symbol." ] ), "exit": Command( 0, 0, self.__exec_exit, None, [ "", "Exit" ] ), "sections": Command( 0, 0, self.__exec_sections, None, [ "", "Print all sections.", ] ), "info": Command( 0, 0, self.__exec_info, None, [ "", "Information about the current binary." ] ), "jmptable": Command( 4, 4, self.__exec_jmptable, None, [ "INST_ADDR TABLE_ADDR NB_ENTRIES SIZE_ENTRY", "Create a jump table referenced at TABLE_ADDR and called", "from INST_ADDR." ] ), "py": Command( -1, 0, self.__exec_py, self.__complete_file, [ "[!][FILE]", "Run an interactive python shell or execute a script.", "Global variables api and args will be passed to the script.", "The character ! is an alias to the scripts directory." ] ), "mips_set_gp": Command( 1, 1, self.__exec_mips_set_gp, None, [ "ADDR", "Set the register $gp to a fixed value. Note that it will", "erase all defined memory." ] ), "functions": Command( 0, 0, self.__exec_functions, None, [ "", "Print the function list." ] ), "xrefs": Command( 1, 1, self.__exec_xrefs, self.__complete_x, [ "SYMBOL|0xXXXX|EP", "Print cross references to the specified address." ] ), "memmap": Command( 0, 0, self.__exec_memmap, None, [ "", "Open a qt window to display the memory." ] ), "frame_size": Command( 2, 2, self.__exec_frame_size, self.__complete_x, [ "[SYMBOL|0xXXXX|EP] frame_size", "Change the frame size of a function, the function will be re-analyzed." ] ), } if gctx.dis.is_x86: import plasma.lib.arch.x86.analyzer as arch_analyzer elif gctx.dis.is_mips: import plasma.lib.arch.mips.analyzer as arch_analyzer elif gctx.dis.is_arm: import plasma.lib.arch.arm.analyzer as arch_analyzer self.analyzer = Analyzer() self.analyzer.init() self.analyzer.start() self.api = Api(gctx, self.analyzer) gctx.api = self.api self.analyzer.set(gctx, arch_analyzer) self.gctx.dis.binary.api = self.api if gctx.dis.is_mips and not gctx.dis.mips_gp: if sys.stdin.isatty(): print("please run first these commands :") print("mips_set_gp 0xADDRESS") print("push_analyze_symbols") else: # If false it means that the first analysis was already done if gctx.autoanalyzer and len(self.db.mem) == 0: print("analyzer is running... check the command analyzer to see the status") self.push_analyze_symbols(None) self.comp = Completer(self) self.comp.set_history(self.db.history) while 1: self.comp.loop() if SHOULD_EXIT: break if not self.check_db_modified(): break self.analyzer.msg.put("exit")
class Console(): COMMANDS = None TAB = " " def __init__(self, gctx): self.gctx = gctx self.db = gctx.db gctx.vim = False self.visual_previous_idx = 0 self.visual_last_widgets = [] # A hack to allow window resizing os.environ['LINES']="blah" del os.environ['LINES'] os.environ['COLUMNS']="blah" del os.environ['COLUMNS'] self.COMMANDS = { "analyzer": Command( 0, 0, self.__exec_analyzer, None, [ "", "Analyzer status.", ] ), "push_analyze_symbols": Command( 0, 0, self.push_analyze_symbols, None, [ "", "Force to analyze the entry point, symbols and a memory scan will be done.", ] ), "help": Command( 0, 0, self.__exec_help, None, [ "", "Display this help." ] ), "history": Command( 0, 0, self.__exec_history, None, [ "", "Display the command history.", ] ), "save": Command( 0, 0, self.__exec_save, None, [ "", "Save the database.", ] ), "x": Command( 1, 0, self.__exec_x, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Decompile and print on stdout. By default it will be main.", "The decompilation is forced, it dosn't check if addresses", "are defined as code." ] ), "v": Command( 1, 0, self.__exec_v, self.__complete_x, [ "[SYMBOL|0xXXXX|EP|%VISUAL]", "Visual mode: if no address is given, previous visual is", "reopen. You can keep up to 3 visuals. Use %1, %2 or %3", "to select the visual.", "", "Main shortcuts:", "c create code", "b/w/d/Q create byte/word/dword/qword", "a create ascii string", "p create function", "o set [d|q]word as an offset", "* create an array", "x show xrefs", "r rename", "space highlight current word (ctrl-k to clear)", "; edit inline comment (enter/escape to validate/cancel)", "U undefine", "", "Options:", "I switch to traditional instruction string output (3 modes)", "M show/hide mangling", "B show/hide bytes", "", "Navigation:", "| split the window", "j jump to an address or a symbol", "/ binary search: if the first char is ! you can put an", " hexa string example: /!ab 13 42", " the search is case sensitive.", "n/N next/previous search occurence", "g top", "G bottom", "z set current line on the middle", "% goto next bracket", "{ } previous/next paragraph", "tab switch between dump/decompilation", "enter follow address", "escape go back", "u re-enter", "q quit", ] ), "hexdump": Command( 2, 1, self.__exec_hexdump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Dump memory in hexa." ] ), # by default it will be gctx.nb_lines "dump": Command( 2, 1, self.__exec_dump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Print contents at the specified address.", ] ), "sym": Command( 3, 0, self.__exec_sym, self.__complete_x, [ "[SYMBOL 0xXXXX] [| FILTER]", "Print all symbols or set a new symbol.", "You can filter symbols by searching the word FILTER.", "If FILTER starts with -, the match is inversed." ] ), "rename": Command( 2, 2, self.__exec_rename, self.__complete_x, [ "OLD_SYM NEW_SYM", "Rename a symbol." ] ), "exit": Command( 0, 0, self.__exec_exit, None, [ "", "Exit" ] ), "sections": Command( 0, 0, self.__exec_sections, None, [ "", "Print all sections.", ] ), "info": Command( 0, 0, self.__exec_info, None, [ "", "Information about the current binary." ] ), "jmptable": Command( 4, 4, self.__exec_jmptable, None, [ "INST_ADDR TABLE_ADDR NB_ENTRIES SIZE_ENTRY", "Create a jump table referenced at TABLE_ADDR and called", "from INST_ADDR." ] ), "py": Command( -1, 0, self.__exec_py, self.__complete_file, [ "[!][FILE]", "Run an interactive python shell or execute a script.", "Global variables api and args will be passed to the script.", "The character ! is an alias to the scripts directory." ] ), "mips_set_gp": Command( 1, 1, self.__exec_mips_set_gp, None, [ "ADDR", "Set the register $gp to a fixed value. Note that it will", "erase all defined memory." ] ), "functions": Command( 0, 0, self.__exec_functions, None, [ "", "Print the function list." ] ), "xrefs": Command( 1, 1, self.__exec_xrefs, self.__complete_x, [ "SYMBOL|0xXXXX|EP", "Print cross references to the specified address." ] ), "memmap": Command( 0, 0, self.__exec_memmap, None, [ "", "Open a qt window to display the memory." ] ), "frame_size": Command( 2, 2, self.__exec_frame_size, self.__complete_x, [ "[SYMBOL|0xXXXX|EP] frame_size", "Change the frame size of a function, the function will be re-analyzed." ] ), } if gctx.dis.is_x86: import plasma.lib.arch.x86.analyzer as arch_analyzer elif gctx.dis.is_mips: import plasma.lib.arch.mips.analyzer as arch_analyzer elif gctx.dis.is_arm: import plasma.lib.arch.arm.analyzer as arch_analyzer self.analyzer = Analyzer() self.analyzer.init() self.analyzer.start() self.api = Api(gctx, self.analyzer) gctx.api = self.api self.analyzer.set(gctx, arch_analyzer) self.gctx.dis.binary.api = self.api if gctx.dis.is_mips and not gctx.dis.mips_gp: if sys.stdin.isatty(): print("please run first these commands :") print("mips_set_gp 0xADDRESS") print("push_analyze_symbols") else: # If false it means that the first analysis was already done if gctx.autoanalyzer and len(self.db.mem) == 0: print("analyzer is running... check the command analyzer to see the status") self.push_analyze_symbols(None) self.comp = Completer(self) self.comp.set_history(self.db.history) while 1: self.comp.loop() if SHOULD_EXIT: break if not self.check_db_modified(): break self.analyzer.msg.put("exit") def check_db_modified(self): if self.db is not None and self.db.modified: print("the database was modified, run save or exit to force") return True return False def __complete_file(self, nth_arg, last_tok): if nth_arg != 1: return [] results = [] if last_tok.startswith("!"): basename = last_tok[1:] dirname = PLASMA_SCRIPTS_DIR else: basename = os.path.basename(last_tok) dirname = os.path.dirname(last_tok) if not dirname: dirname = "." try: i = 0 for f in os.listdir(dirname): if f.startswith(basename): f_backslahed = f.replace(" ", "\\ ") if last_tok.startswith("!"): s = "!%s " % f_backslahed else: if os.path.isdir(os.path.join(dirname, f)): if dirname == "/": s = "/%s/" % f_backslahed elif dirname == ".": s = "%s/" % f_backslahed else: s = "%s/%s/" % (dirname, f_backslahed) else: if dirname == ".": s = "%s " % f_backslahed else: s = "%s/%s " % (dirname, f_backslahed) results.append(s) i += 1 if i == MAX_PRINT_COMPLETE: return None return results except FileNotFoundError: return [] def __complete_x(self, nth_arg, last_tok): if nth_arg != 1 or self.gctx.dis is None: return [] return self.__find_symbol(nth_arg, last_tok) def __find_symbol(self, nth_arg, last_tok): results = [] i = 0 for sect in self.gctx.dis.binary.section_names: if sect.startswith(last_tok): results.append((sect + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None for sym in self.db.symbols: if sym.startswith(last_tok): results.append((sym + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None for sym in self.db.demangled: if sym.startswith(last_tok): results.append((sym + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None return results def exec_command(self, line): try: args = shlex.split(line) except Exception as e: print("error:", e) return if not args: return if args[0] not in self.COMMANDS: error("unknown command") return c = self.COMMANDS[args[0]] if c.max_args != -1 and len(args) - 1 > c.max_args: error("%s takes max %d args" % (args[0], c.max_args)) return if len(args) - 1 < c.min_args: error("%s takes at least %d args" % (args[0], c.min_args)) return if c.callback_exec is not None: try: c.callback_exec(args) except: traceback.print_exc() def __exec_exit(self, args): global SHOULD_EXIT self.analyzer.msg.put("exit") SHOULD_EXIT = True def __exec_dump(self, args): nb_lines = self.gctx.nb_lines if len(args) == 3: try: nb_lines = int(args[2]) except: pass ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: ctx.dump_asm(nb_lines).print() def __exec_hexdump(self, args): nb_lines = self.gctx.nb_lines if len(args) == 3: try: nb_lines = int(args[2]) except: pass ctx = self.gctx.get_addr_context(args[1]) if ctx: self.gctx.dis.hexdump(ctx, nb_lines) def push_analyze_symbols(self, args): # Analyze all imports (it checks if functions return or not) for ad in self.db.imports: if ad in self.db.functions and self.db.functions[ad] is None: self.analyzer.msg.put((ad, True, True, False, None)) # Analyze entry point ep = self.gctx.dis.binary.get_entry_point() if ep is not None: self.analyzer.msg.put((ep, True, True, False, None)) self.analyzer.msg.put("rename_entry_point") # Analyze static functions for ad in self.db.reverse_symbols: if ad not in self.db.imports and \ ad in self.db.functions and self.db.functions[ad] is None: self.analyzer.msg.put((ad, True, False, False, None)) self.analyzer.msg.put("pass_scan_mem") def __exec_rename(self, args): if args[1] == args[2]: return ad = self.api.get_addr_from_symbol(args[1]) if ad == -1: print("symbol %s not found" % args[1]) return self.api.add_symbol(ad, args[2]) self.db.modified = True def __exec_sym(self, args): if len(args) == 1: self.gctx.dis.print_symbols() return if args[1][0] == "|": if len(args) == 2 or len(args) > 3: error("bad arguments (warn: need spaces between |)") return self.gctx.dis.print_symbols(args[2]) return if len(args) == 2: error("an address is required to save the symbol") return if not args[2].startswith("0x"): error("the address should starts with 0x") return if args[1].startswith("loc_"): error("loc_ is a reserved prefix") return # Save new symbol try: if not self.api.add_symbol(int(args[2], 16), args[1]): error("cannot rename") return self.db.modified = True except: error("there was an error when creating a symbol") def __exec_x(self, args): ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: try: o = ctx.decompile() if o is not None: o.print() except: traceback.print_exc() def __exec_v(self, args): ad = 0 if len(args) == 2: if args[1][0] == "%": if len(args[1]) != 2: print("error: bad visual number") return i = int(args[1][1]) - 1 if i < 0 or i >= len(self.visual_last_widgets): print("error: bad visual number, there are only %d opened visual" % len(self.visual_last_widgets)) return wdgt = self.visual_last_widgets[i] else: ad = args[1] wdgt = None i = None else: if not self.visual_last_widgets: ad = None # will open the visual at EP or main i = None wdgt = None else: i = self.visual_previous_idx wdgt = self.visual_last_widgets[i] v = Visual(self.gctx, ad, self.analyzer, self.api, wdgt) if v.error_occurs: return # Only %1, %2, %3 actually if i is None: self.visual_last_widgets.append(v.widgets) n = len(self.visual_last_widgets) print("visual saved to %%%d" % n) self.visual_previous_idx = n - 1 else: self.visual_last_widgets[i] = v.widgets print("visual saved to %%%d" % (i + 1)) self.visual_previous_idx = i if len(self.visual_last_widgets) == 4: self.visual_last_widgets = self.visual_last_widgets[1:] def __exec_help(self, args): for name in COMMANDS_ALPHA: cmd = self.COMMANDS[name] if cmd.callback_exec is not None: print_no_end(color(name, 2)) print_no_end(" ") for i, line in enumerate(cmd.desc): if i > 0: print_no_end(self.TAB) print(line) def __exec_history(self, args): for line in self.comp.get_history(): print(line) def __exec_sections(self, args): print_no_end("NAME".ljust(20)) print(" [ START - END - VIRTUAL_SIZE - RAW_SIZE ]") for s in self.gctx.dis.binary.iter_sections(): s.print_header() def __exec_info(self, args): print("File:", self.gctx.filename) statinfo = os.stat(self.gctx.filename) print("Size: %.2f ko" % (statinfo.st_size/1024.)) print_no_end("Type: ") ty = self.gctx.dis.binary.type if ty == T_BIN_PE: print("PE") elif ty == T_BIN_ELF: print("ELF") elif ty == T_BIN_RAW: print("RAW") print("Arch:", self.gctx.dis.binary.arch) if self.gctx.dis.binary.is_big_endian(): print("Endianess: big endian") else: print("Endianess: little endian") def __exec_save(self, args): self.db.save(self.comp.get_history()) print("database saved to", self.db.path) self.db.modified = False def __exec_jmptable(self, args): try: inst_addr = int(args[1], 16) table_addr = int(args[2], 16) nb_entries = int(args[3]) entry_size = int(args[4]) except: error("one parameter is invalid, be sure that addresses start with 0x") return if entry_size not in [2, 4, 8]: error("error the entry size should be in [2, 4, 8]") return self.db.modified = True self.api.create_jmptable(inst_addr, table_addr, nb_entries, entry_size) def __exec_py(self, args): ns = {"api": self.api, "args": args[1:], "analyzer": self.analyzer} ns.update(EXPORTED_SYMBOLS) if len(args) > 1: if args[1].startswith("!"): args[1] = "%s/%s" % (PLASMA_SCRIPTS_DIR, args[1][1:]) exec(open(args[1]).read(), ns) else: readline.set_completer(rlcompleter.Completer(ns).complete) code.interact(local=ns) readline.set_completer(self.comp.complete) def __exec_mips_set_gp(self, args): self.gctx.dis.mips_gp = int(args[1], 16) self.db.mips_gp = self.gctx.dis.mips_gp self.db.mem.mm.clear() self.db.xrefs.clear() self.db.data_sub_xrefs.clear() self.db.immediates.clear() self.db.modified = True def __exec_functions(self, args): self.gctx.dis.print_functions(self.api) def __exec_xrefs(self, args): ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx and ctx.entry in self.gctx.db.xrefs or self.gctx.db.data_sub_xrefs: ctx.dump_xrefs().print() def __exec_analyzer(self, args): n = self.analyzer.msg.qsize() + len(self.analyzer.pending) print("addresses remaining to analyze:", n) if self.analyzer.running_second_pass: print("memory scan...") ad = self.analyzer.where s = self.gctx.dis.binary.get_section(ad) percent = int((ad - s.start) * 100 / s.real_size) print(" -> %s %d%% (0x%x)" % (s.name, percent, ad)) def __exec_memmap(self, args): from plasma.lib.memmap import ThreadMemoryMap t = ThreadMemoryMap(self.db, self.gctx.dis.binary) t.start() def __exec_frame_size(self, args): ctx = self.gctx.get_addr_context(args[1]) frame_size = int(args[2]) if ctx: self.api.set_frame_size(ctx.entry, frame_size)
def __init__(self, gctx): self.gctx = gctx self.db = gctx.db gctx.vim = False self.COMMANDS = { "analyzer": Command( 0, self.__exec_analyzer, None, [ "", "Analyzer information", ] ), "push_analyze_symbols": Command( 0, self.push_analyze_symbols, None, [ "", "Force to analyze the entry point, symbols and a memory scan will be done.", ] ), "help": Command( 0, self.__exec_help, None, [ "", "Display this help" ] ), "history": Command( 0, self.__exec_history, None, [ "", "Display the command history", ] ), "save": Command( 0, self.__exec_save, None, [ "", "Save the database (only symbols and history currently).", ] ), "x": Command( 1, self.__exec_x, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Decompile and print on stdout. By default it will be main.", "The decompilation is forced, it dosn't check if addresses", "are defined as code." ] ), "v": Command( 1, self.__exec_v, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Visual mode", "Shortcuts:", "c create code", "b/w/d/Q create byte/word/dword/qword", "a create ascii string", "p create function", "o set [d|q]word as an offset", "x show xrefs", "r rename", "/ binary search: if the first char is ! you can put an", " hexa string example: /!ab 13 42", "n/N next/previous search occurence", "I switch to traditional instruction string output", "M show/hide mangling", "B show/hide bytes", "g top", "G bottom", "z set current line on the middle", "Q quit", "; edit inline comment (enter/escape to validate/cancel)", "% goto next bracket", "* highlight current word (ctrl-k to clear)", "{ } previous/next paragraph", "tab switch between dump/decompilation", "enter follow address", "escape go back", "u re-enter (for undo)", ] ), "hexdump": Command( 2, self.__exec_hexdump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Dump memory in hexa." ] ), # by default it will be gctx.nb_lines "dump": Command( 2, self.__exec_dump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Disassemble only.", ] ), "set": Command( 3, None, None, [ "", "Set options" ] ), "sym": Command( 3, self.__exec_sym, self.__complete_x, [ "[SYMBOL 0xXXXX] [| FILTER]", "Print all symbols or set a new symbol.", "You can filter symbols by searching the word FILTER.", "If FILTER starts with -, the match is inversed." ] ), "rename": Command( 2, self.__exec_rename, self.__complete_x, [ "OLD_SYM NEW_SYM", "Rename a symbol." ] ), "exit": Command( 0, self.__exec_exit, None, [ "", "Exit" ] ), "sections": Command( 0, self.__exec_sections, None, [ "", "Print all sections", ] ), "info": Command( 0, self.__exec_info, None, [ "", "Information about the current binary" ] ), "display.print_section": Command( 0, self.__exec_display_print_section, None, [ "", "Print or not section when an address is found" ] ), "jmptable": Command( 4, self.__exec_jmptable, None, [ "INST_ADDR TABLE_ADDR NB_ENTRIES SIZE_ENTRY", "Create a jump table referenced at TABLE_ADDR and called", "from INST_ADDR." ] ), "py": Command( 1, self.__exec_py, self.__complete_file, [ "[FILE]", "Run an interactive python shell or execute a script.", "The global variable 'api' will be accessible." ] ), "mips_set_gp": Command( 1, self.__exec_mips_set_gp, None, [ "ADDR", "Set the register $gp to a fixed value." ] ), "functions": Command( 1, self.__exec_functions, None, [ "", "Print the function list." ] ), "xrefs": Command( 1, self.__exec_xrefs, self.__complete_x, [ "SYMBOL|0xXXXX|EP", "Print all xrefs." ] ), } self.analyzer = Analyzer() self.analyzer.init() self.analyzer.start() self.api = Api(gctx, self.analyzer) gctx.api = self.api self.analyzer.set(gctx) if gctx.dis.is_mips and gctx.dis.mips_gp == -1: print("please run first these commands :") print("mips_set_gp 0xADDRESS") print("push_analyze_symbols") else: # If false it means that the first analysis was already done if gctx.autoanalyzer and len(self.db.mem) == 0: self.push_analyze_symbols(None) self.comp = Completer(self) self.comp.set_history(self.db.history) if gctx.is_interactive: while 1: self.comp.loop() if SHOULD_EXIT: break if not self.check_db_modified(): break else: self.exec_command("exit") # exit and wait for finish self.analyzer.msg.put("exit") self.analyzer.join()
class Console(): COMMANDS = None TAB = " " def __init__(self, gctx): self.gctx = gctx self.db = gctx.db gctx.vim = False self.COMMANDS = { "analyzer": Command( 0, self.__exec_analyzer, None, [ "", "Analyzer information", ] ), "push_analyze_symbols": Command( 0, self.push_analyze_symbols, None, [ "", "Force to analyze the entry point, symbols and a memory scan will be done.", ] ), "help": Command( 0, self.__exec_help, None, [ "", "Display this help" ] ), "history": Command( 0, self.__exec_history, None, [ "", "Display the command history", ] ), "save": Command( 0, self.__exec_save, None, [ "", "Save the database (only symbols and history currently).", ] ), "x": Command( 1, self.__exec_x, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Decompile and print on stdout. By default it will be main.", "The decompilation is forced, it dosn't check if addresses", "are defined as code." ] ), "v": Command( 1, self.__exec_v, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Visual mode", "Shortcuts:", "c create code", "b/w/d/Q create byte/word/dword/qword", "a create ascii string", "p create function", "o set [d|q]word as an offset", "x show xrefs", "r rename", "/ binary search: if the first char is ! you can put an", " hexa string example: /!ab 13 42", "n/N next/previous search occurence", "I switch to traditional instruction string output", "M show/hide mangling", "B show/hide bytes", "g top", "G bottom", "z set current line on the middle", "Q quit", "; edit inline comment (enter/escape to validate/cancel)", "% goto next bracket", "* highlight current word (ctrl-k to clear)", "{ } previous/next paragraph", "tab switch between dump/decompilation", "enter follow address", "escape go back", "u re-enter (for undo)", ] ), "hexdump": Command( 2, self.__exec_hexdump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Dump memory in hexa." ] ), # by default it will be gctx.nb_lines "dump": Command( 2, self.__exec_dump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Disassemble only.", ] ), "set": Command( 3, None, None, [ "", "Set options" ] ), "sym": Command( 3, self.__exec_sym, self.__complete_x, [ "[SYMBOL 0xXXXX] [| FILTER]", "Print all symbols or set a new symbol.", "You can filter symbols by searching the word FILTER.", "If FILTER starts with -, the match is inversed." ] ), "rename": Command( 2, self.__exec_rename, self.__complete_x, [ "OLD_SYM NEW_SYM", "Rename a symbol." ] ), "exit": Command( 0, self.__exec_exit, None, [ "", "Exit" ] ), "sections": Command( 0, self.__exec_sections, None, [ "", "Print all sections", ] ), "info": Command( 0, self.__exec_info, None, [ "", "Information about the current binary" ] ), "display.print_section": Command( 0, self.__exec_display_print_section, None, [ "", "Print or not section when an address is found" ] ), "jmptable": Command( 4, self.__exec_jmptable, None, [ "INST_ADDR TABLE_ADDR NB_ENTRIES SIZE_ENTRY", "Create a jump table referenced at TABLE_ADDR and called", "from INST_ADDR." ] ), "py": Command( 1, self.__exec_py, self.__complete_file, [ "[FILE]", "Run an interactive python shell or execute a script.", "The global variable 'api' will be accessible." ] ), "mips_set_gp": Command( 1, self.__exec_mips_set_gp, None, [ "ADDR", "Set the register $gp to a fixed value." ] ), "functions": Command( 1, self.__exec_functions, None, [ "", "Print the function list." ] ), "xrefs": Command( 1, self.__exec_xrefs, self.__complete_x, [ "SYMBOL|0xXXXX|EP", "Print all xrefs." ] ), } self.analyzer = Analyzer() self.analyzer.init() self.analyzer.start() self.api = Api(gctx, self.analyzer) gctx.api = self.api self.analyzer.set(gctx) if gctx.dis.is_mips and gctx.dis.mips_gp == -1: print("please run first these commands :") print("mips_set_gp 0xADDRESS") print("push_analyze_symbols") else: # If false it means that the first analysis was already done if gctx.autoanalyzer and len(self.db.mem) == 0: self.push_analyze_symbols(None) self.comp = Completer(self) self.comp.set_history(self.db.history) if gctx.is_interactive: while 1: self.comp.loop() if SHOULD_EXIT: break if not self.check_db_modified(): break else: self.exec_command("exit") # exit and wait for finish self.analyzer.msg.put("exit") self.analyzer.join() def check_db_modified(self): if self.db is not None and self.db.modified: print("the database was modified, run save or exit to force") return True return False def __complete_file(self, nth_arg, last_tok): if nth_arg != 1: return [] results = [] basename = os.path.basename(last_tok) dirname = os.path.dirname(last_tok) if not dirname: dirname = "." try: i = 0 for f in os.listdir(dirname): if f.startswith(basename): f_backslahed = f.replace(" ", "\\ ") if os.path.isdir(os.path.join(dirname, f)): s = "%s/%s/" % (dirname, f_backslahed) else: s = "%s/%s " % (dirname, f_backslahed) results.append(s) i += 1 if i == MAX_PRINT_COMPLETE: return None return results except FileNotFoundError: return [] def __complete_x(self, nth_arg, last_tok): if nth_arg != 1 or self.gctx.dis is None: return [] return self.__find_symbol(nth_arg, last_tok) def __find_symbol(self, nth_arg, last_tok): results = [] i = 0 for sect in self.gctx.dis.binary.section_names: if sect.startswith(last_tok): results.append((sect + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None for sym in self.db.symbols: if sym.startswith(last_tok): results.append((sym + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None for sym in self.db.demangled: if sym.startswith(last_tok): results.append((sym + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None return results def exec_command(self, line): args = shlex.split(line) if args[0] not in self.COMMANDS: error("unknown command") return c = self.COMMANDS[args[0]] if len(args)-1 > c.max_args: error("%s takes max %d args" % (args[0], c.max_args)) return if c.callback_exec is not None: try: c.callback_exec(args) except: traceback.print_exc() def __exec_exit(self, args): global SHOULD_EXIT self.analyzer.msg.put("exit") SHOULD_EXIT = True def __exec_dump(self, args): nb_lines = self.gctx.nb_lines if len(args) == 3: try: nb_lines = int(args[2]) except: pass ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: ctx.dump_asm(nb_lines).print() def __exec_hexdump(self, args): nb_lines = self.gctx.nb_lines if len(args) <= 1: self.gctx.entry = None error("no address in parameter") return if len(args) == 3: try: nb_lines = int(args[2]) except: pass ctx = self.gctx.get_addr_context(args[1]) if ctx: self.gctx.dis.hexdump(ctx, nb_lines) def push_analyze_symbols(self, args): # Analyze all imports (it checks if functions return or not) for ad in self.db.imports: if ad in self.db.functions and self.db.functions[ad] is None: self.analyzer.msg.put((ad, True, True, False, None)) # Analyze entry point ep = self.gctx.dis.binary.get_entry_point() if ep is not None: self.analyzer.msg.put((ep, False, True, False, None)) # Analyze static functions for ad in self.db.reverse_symbols: if ad not in self.db.imports and \ ad in self.db.functions and self.db.functions[ad] is None: self.analyzer.msg.put((ad, True, False, False, None)) self.analyzer.msg.put("pass_scan_mem") def __exec_rename(self, args): if args[1] == args[2]: return ad = self.api.get_addr_from_symbol(args[1]) if ad == -1: print("symbol %s not found" % args[1]) return self.api.add_symbol(ad, args[2]) self.db.modified = True def __exec_sym(self, args): if len(args) == 1: self.gctx.dis.print_symbols(self.gctx.sectionsname) return if args[1][0] == "|": if len(args) == 2 or len(args) > 3: error("bad arguments (warn: need spaces between |)") return self.gctx.dis.print_symbols(self.gctx.sectionsname, args[2]) return if len(args) > 3: error("bad arguments") return if len(args) == 2: error("an address is required to save the symbol") return if not args[2].startswith("0x"): error("the address should starts with 0x") return if args[1].startswith("loc_"): error("loc_ is a reserved prefix") return # Save new symbol try: if not self.api.add_symbol(int(args[2], 16), args[1]): error("cannot rename") return self.db.modified = True except: error("there was an error when creating a symbol") def __exec_x(self, args): ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: try: o = ctx.decompile() if o is not None: o.print() except: traceback.print_exc() def __exec_v(self, args): ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: o = ctx.dump_asm(NB_LINES_TO_DISASM) if o is not None: Visual(self.gctx, ctx, self.analyzer, self.api) def __exec_help(self, args): for name in COMMANDS_ALPHA: cmd = self.COMMANDS[name] if cmd.callback_exec is not None: print_no_end(color(name, 2)) print_no_end(" ") for i, line in enumerate(cmd.desc): if i > 0: print_no_end(self.TAB) print(line) def __exec_history(self, args): for line in self.comp.get_history(): print(line) def __exec_sections(self, args): print_no_end("NAME".ljust(20)) print(" [ START - END - VIRTUAL_SIZE - RAW_SIZE ]") for s in self.gctx.dis.binary.iter_sections(): s.print_header() def __exec_info(self, args): print("File:", self.gctx.filename) statinfo = os.stat(self.gctx.filename) print("Size: %.2f ko" % (statinfo.st_size/1024.)) print_no_end("Type: ") ty = self.gctx.dis.binary.type if ty == T_BIN_PE: print("PE") elif ty == T_BIN_ELF: print("ELF") elif ty == T_BIN_RAW: print("RAW") print("Arch:", self.gctx.dis.binary.arch) if self.gctx.dis.binary.is_big_endian(): print("Endianess: big endian") else: print("Endianess: little endian") def __exec_display_print_section(self, args): if self.gctx.sectionsname: print("now it's off") self.gctx.sectionsname = False else: print("now it's on") self.gctx.sectionsname = True def __exec_save(self, args): self.db.save(self.comp.get_history()) print("database saved to", self.db.path) self.db.modified = False def __exec_jmptable(self, args): try: inst_addr = int(args[1], 16) table_addr = int(args[2], 16) nb_entries = int(args[3]) entry_size = int(args[4]) except: error("one parameter is invalid, be sure that addresses start with 0x") return if entry_size not in [2, 4, 8]: error("error the entry size should be in [2, 4, 8]") return self.db.modified = True self.api.create_jmptable(inst_addr, table_addr, entry_size, nb_entries) def __exec_py(self, args): ns = {"api": self.api} if len(args) == 2: exec(open(args[1]).read(), ns) else: readline.set_completer(rlcompleter.Completer(ns).complete) code.interact(local=ns) readline.set_completer(self.comp.complete) def __exec_mips_set_gp(self, args): try: self.gctx.dis.mips_gp = int(args[1], 16) self.db.mips_gp = self.gctx.dis.mips_gp except: error("bad address") self.db.modified = True def __exec_functions(self, args): self.gctx.dis.print_functions(self.api) def __exec_xrefs(self, args): ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: if ctx.entry not in self.gctx.dis.xrefs: return ctx.dump_xrefs().print() def __exec_analyzer(self, args): print("addresses remaining to analyze:", self.analyzer.msg.qsize()) if self.analyzer.running_second_pass: print("scanning the whole memory...") ad = self.analyzer.where print(" -> %s: 0x%x" % (self.gctx.dis.binary.get_section(ad).name, ad))
def __init__(self, gctx): self.gctx = gctx self.db = gctx.db gctx.vim = False # After exiting the visual mode we copy last addresses we have visited. # Then we can just enter 'v' and we go where we were. self.last_entry = None self.last_stack = [] self.last_saved_stack = [] self.COMMANDS = { "analyzer": Command( 0, self.__exec_analyzer, None, [ "", "Analyzer status.", ] ), "push_analyze_symbols": Command( 0, self.push_analyze_symbols, None, [ "", "Force to analyze the entry point, symbols and a memory scan will be done.", ] ), "help": Command( 0, self.__exec_help, None, [ "", "Display this help." ] ), "history": Command( 0, self.__exec_history, None, [ "", "Display the command history.", ] ), "save": Command( 0, self.__exec_save, None, [ "", "Save the database.", ] ), "x": Command( 1, self.__exec_x, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Decompile and print on stdout. By default it will be main.", "The decompilation is forced, it dosn't check if addresses", "are defined as code." ] ), "v": Command( 1, self.__exec_v, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Visual mode: if no address or symbol is given, you go at the", "previous address before exiting the visual mode.", "", "Main shortcuts:", "c create code", "b/w/d/Q create byte/word/dword/qword", "a create ascii string", "p create function", "o set [d|q]word as an offset", "* create an array", "x show xrefs", "r rename", "space highlight current word (ctrl-k to clear)", "; edit inline comment (enter/escape to validate/cancel)", "U undefine", "", "Options:", "I switch to traditional instruction string output (3 modes)", "M show/hide mangling", "B show/hide bytes", "", "Navigation:", "/ binary search: if the first char is ! you can put an", " hexa string example: /!ab 13 42", "n/N next/previous search occurence", "g top", "G bottom", "z set current line on the middle", "% goto next bracket", "{ } previous/next paragraph", "tab switch between dump/decompilation", "enter follow address", "escape go back", "u re-enter", "q quit", ] ), "hexdump": Command( 2, self.__exec_hexdump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Dump memory in hexa." ] ), # by default it will be gctx.nb_lines "dump": Command( 2, self.__exec_dump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Print contents at the specified address.", ] ), "sym": Command( 3, self.__exec_sym, self.__complete_x, [ "[SYMBOL 0xXXXX] [| FILTER]", "Print all symbols or set a new symbol.", "You can filter symbols by searching the word FILTER.", "If FILTER starts with -, the match is inversed." ] ), "rename": Command( 2, self.__exec_rename, self.__complete_x, [ "OLD_SYM NEW_SYM", "Rename a symbol." ] ), "exit": Command( 0, self.__exec_exit, None, [ "", "Exit" ] ), "sections": Command( 0, self.__exec_sections, None, [ "", "Print all sections.", ] ), "info": Command( 0, self.__exec_info, None, [ "", "Information about the current binary." ] ), "jmptable": Command( 4, self.__exec_jmptable, None, [ "INST_ADDR TABLE_ADDR NB_ENTRIES SIZE_ENTRY", "Create a jump table referenced at TABLE_ADDR and called", "from INST_ADDR." ] ), "py": Command( -1, self.__exec_py, self.__complete_file, [ "[!][FILE]", "Run an interactive python shell or execute a script.", "Global variables api and args will be passed to the script.", "The character ! is an alias to the scripts directory." ] ), "mips_set_gp": Command( 1, self.__exec_mips_set_gp, None, [ "ADDR", "Set the register $gp to a fixed value." ] ), "functions": Command( 1, self.__exec_functions, None, [ "", "Print the function list." ] ), "xrefs": Command( 1, self.__exec_xrefs, self.__complete_x, [ "SYMBOL|0xXXXX|EP", "Print cross references to the specified address." ] ), "memmap": Command( 0, self.__exec_memmap, None, [ "", "Open a qt window to display the memory." ] ), } if gctx.dis.is_x86: import plasma.lib.arch.x86.analyzer as arch_analyzer elif gctx.dis.is_mips: import plasma.lib.arch.mips.analyzer as arch_analyzer elif gctx.dis.is_arm: import plasma.lib.arch.arm.analyzer as arch_analyzer self.analyzer = Analyzer() self.analyzer.init() self.analyzer.start() self.api = Api(gctx, self.analyzer) gctx.api = self.api self.analyzer.set(gctx, arch_analyzer) if gctx.dis.is_mips and gctx.dis.mips_gp == -1: print("please run first these commands :") print("mips_set_gp 0xADDRESS") print("push_analyze_symbols") else: # If false it means that the first analysis was already done if gctx.autoanalyzer and len(self.db.mem) == 0: self.push_analyze_symbols(None) self.comp = Completer(self) self.comp.set_history(self.db.history) while 1: self.comp.loop() if SHOULD_EXIT: break if not self.check_db_modified(): break self.analyzer.msg.put("exit")
def console_entry(): gctx = GlobalContext() gctx.parse_args() if gctx.color and plasma.lib.colors.VERSION < plasma.lib.colors.CURR_VERSION: info("There is a new version of custom_colors.py. If you did any") info("modifications you can delete it. Otherwise you can copy it") info("somewhere, run again your command then merge the file at hand.") die() if gctx.filename is None: die() if not gctx.load_file(): die() if gctx.interactive_mode: from plasma.lib.ui.console import Console gctx.is_interactive = True Console(gctx) else: gctx.api = Api(gctx, None) if gctx.list_sections: for s in gctx.dis.binary.iter_sections(): s.print_header() sys.exit(0) if gctx.syms: gctx.dis.print_symbols() sys.exit(0) ctx = gctx.get_addr_context(gctx.entry) if ctx is None: sys.exit(0) if gctx.do_dump: ctx.dump_asm(gctx.nb_lines).print() sys.exit(0) o = ctx.decompile() if gctx.graph: ctx.gph.dot_graph(gctx.dis.jmptables) if o is not None: if gctx.vim: base = os.path.basename(gctx.filename) + "_" + gctx.entry # re-assign if no colors gctx.libarch.process_ast.assign_colors(ctx, ctx.ast) gctx.color = False generate_vim_syntax(ctx, base + ".vim") sys.stdout = open(base + ".rev", "w+") o.print() if gctx.vim: print("run : vim {0}.rev -S {0}.vim".format(base), file=sys.stderr)
class Console(): COMMANDS = None TAB = " " def __init__(self, gctx): self.gctx = gctx self.db = gctx.db gctx.vim = False # After exiting the visual mode we copy last addresses we have visited. # Then we can just enter 'v' and we go where we were. self.last_entry = None self.last_stack = [] self.last_saved_stack = [] # A hack to allow window resizing os.environ['LINES']="blah" del os.environ['LINES'] os.environ['COLUMNS']="blah" del os.environ['COLUMNS'] self.COMMANDS = { "analyzer": Command( 0, self.__exec_analyzer, None, [ "", "Analyzer status.", ] ), "push_analyze_symbols": Command( 0, self.push_analyze_symbols, None, [ "", "Force to analyze the entry point, symbols and a memory scan will be done.", ] ), "help": Command( 0, self.__exec_help, None, [ "", "Display this help." ] ), "history": Command( 0, self.__exec_history, None, [ "", "Display the command history.", ] ), "save": Command( 0, self.__exec_save, None, [ "", "Save the database.", ] ), "x": Command( 1, self.__exec_x, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Decompile and print on stdout. By default it will be main.", "The decompilation is forced, it dosn't check if addresses", "are defined as code." ] ), "v": Command( 1, self.__exec_v, self.__complete_x, [ "[SYMBOL|0xXXXX|EP]", "Visual mode: if no address or symbol is given, you go at the", "previous address before exiting the visual mode.", "", "Main shortcuts:", "c create code", "b/w/d/Q create byte/word/dword/qword", "a create ascii string", "p create function", "o set [d|q]word as an offset", "* create an array", "x show xrefs", "r rename", "space highlight current word (ctrl-k to clear)", "; edit inline comment (enter/escape to validate/cancel)", "U undefine", "", "Options:", "I switch to traditional instruction string output (3 modes)", "M show/hide mangling", "B show/hide bytes", "", "Navigation:", "/ binary search: if the first char is ! you can put an", " hexa string example: /!ab 13 42", " the search is case sensitive.", "n/N next/previous search occurence", "g top", "G bottom", "z set current line on the middle", "% goto next bracket", "{ } previous/next paragraph", "tab switch between dump/decompilation", "enter follow address", "escape go back", "u re-enter", "q quit", ] ), "hexdump": Command( 2, self.__exec_hexdump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Dump memory in hexa." ] ), # by default it will be gctx.nb_lines "dump": Command( 2, self.__exec_dump, self.__complete_x, [ "SYMBOL|0xXXXX|EP [NB_LINES]", "Print contents at the specified address.", ] ), "sym": Command( 3, self.__exec_sym, self.__complete_x, [ "[SYMBOL 0xXXXX] [| FILTER]", "Print all symbols or set a new symbol.", "You can filter symbols by searching the word FILTER.", "If FILTER starts with -, the match is inversed." ] ), "rename": Command( 2, self.__exec_rename, self.__complete_x, [ "OLD_SYM NEW_SYM", "Rename a symbol." ] ), "exit": Command( 0, self.__exec_exit, None, [ "", "Exit" ] ), "sections": Command( 0, self.__exec_sections, None, [ "", "Print all sections.", ] ), "info": Command( 0, self.__exec_info, None, [ "", "Information about the current binary." ] ), "jmptable": Command( 4, self.__exec_jmptable, None, [ "INST_ADDR TABLE_ADDR NB_ENTRIES SIZE_ENTRY", "Create a jump table referenced at TABLE_ADDR and called", "from INST_ADDR." ] ), "py": Command( -1, self.__exec_py, self.__complete_file, [ "[!][FILE]", "Run an interactive python shell or execute a script.", "Global variables api and args will be passed to the script.", "The character ! is an alias to the scripts directory." ] ), "mips_set_gp": Command( 1, self.__exec_mips_set_gp, None, [ "ADDR", "Set the register $gp to a fixed value. Note that it will", "erase all defined memory." ] ), "functions": Command( 1, self.__exec_functions, None, [ "", "Print the function list." ] ), "xrefs": Command( 1, self.__exec_xrefs, self.__complete_x, [ "SYMBOL|0xXXXX|EP", "Print cross references to the specified address." ] ), "memmap": Command( 0, self.__exec_memmap, None, [ "", "Open a qt window to display the memory." ] ), } if gctx.dis.is_x86: import plasma.lib.arch.x86.analyzer as arch_analyzer elif gctx.dis.is_mips: import plasma.lib.arch.mips.analyzer as arch_analyzer elif gctx.dis.is_arm: import plasma.lib.arch.arm.analyzer as arch_analyzer self.analyzer = Analyzer() self.analyzer.init() self.analyzer.start() self.api = Api(gctx, self.analyzer) gctx.api = self.api self.analyzer.set(gctx, arch_analyzer) self.gctx.dis.binary.api = self.api if gctx.dis.is_mips and not gctx.dis.mips_gp: print("please run first these commands :") print("mips_set_gp 0xADDRESS") print("push_analyze_symbols") else: # If false it means that the first analysis was already done if gctx.autoanalyzer and len(self.db.mem) == 0: self.push_analyze_symbols(None) print("new feature: an instruction preceded by ! means that the analyzer") print("has computed an immediate value. In the visual mode, use the shortcut") print("I to show origin instructions.") self.comp = Completer(self) self.comp.set_history(self.db.history) while 1: self.comp.loop() if SHOULD_EXIT: break if not self.check_db_modified(): break self.analyzer.msg.put("exit") def check_db_modified(self): if self.db is not None and self.db.modified: print("the database was modified, run save or exit to force") return True return False def __complete_file(self, nth_arg, last_tok): if nth_arg != 1: return [] results = [] if last_tok.startswith("!"): basename = last_tok[1:] dirname = PLASMA_SCRIPTS_DIR else: basename = os.path.basename(last_tok) dirname = os.path.dirname(last_tok) if not dirname: dirname = "." try: i = 0 for f in os.listdir(dirname): if f.startswith(basename): f_backslahed = f.replace(" ", "\\ ") if last_tok.startswith("!"): s = "!%s " % f_backslahed else: if os.path.isdir(os.path.join(dirname, f)): if dirname == "/": s = "/%s/" % f_backslahed elif dirname == ".": s = "%s/" % f_backslahed else: s = "%s/%s/" % (dirname, f_backslahed) else: if dirname == ".": s = "%s " % f_backslahed else: s = "%s/%s " % (dirname, f_backslahed) results.append(s) i += 1 if i == MAX_PRINT_COMPLETE: return None return results except FileNotFoundError: return [] def __complete_x(self, nth_arg, last_tok): if nth_arg != 1 or self.gctx.dis is None: return [] return self.__find_symbol(nth_arg, last_tok) def __find_symbol(self, nth_arg, last_tok): results = [] i = 0 for sect in self.gctx.dis.binary.section_names: if sect.startswith(last_tok): results.append((sect + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None for sym in self.db.symbols: if sym.startswith(last_tok): results.append((sym + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None for sym in self.db.demangled: if sym.startswith(last_tok): results.append((sym + " ")) i += 1 if i == MAX_PRINT_COMPLETE: return None return results def exec_command(self, line): args = shlex.split(line) if not args: return if args[0] not in self.COMMANDS: error("unknown command") return c = self.COMMANDS[args[0]] if c.max_args != -1 and len(args) - 1 > c.max_args: error("%s takes max %d args" % (args[0], c.max_args)) return if c.callback_exec is not None: try: c.callback_exec(args) except: traceback.print_exc() def __exec_exit(self, args): global SHOULD_EXIT self.analyzer.msg.put("exit") SHOULD_EXIT = True def __exec_dump(self, args): nb_lines = self.gctx.nb_lines if len(args) == 3: try: nb_lines = int(args[2]) except: pass ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: ctx.dump_asm(nb_lines).print() def __exec_hexdump(self, args): nb_lines = self.gctx.nb_lines if len(args) <= 1: self.gctx.entry = None error("no address in parameter") return if len(args) == 3: try: nb_lines = int(args[2]) except: pass ctx = self.gctx.get_addr_context(args[1]) if ctx: self.gctx.dis.hexdump(ctx, nb_lines) def push_analyze_symbols(self, args): # Analyze all imports (it checks if functions return or not) for ad in self.db.imports: if ad in self.db.functions and self.db.functions[ad] is None: self.analyzer.msg.put((ad, True, True, False, None)) # Analyze entry point ep = self.gctx.dis.binary.get_entry_point() if ep is not None: self.analyzer.msg.put((ep, True, True, False, None)) self.analyzer.msg.put("rename_entry_point") # Analyze static functions for ad in self.db.reverse_symbols: if ad not in self.db.imports and \ ad in self.db.functions and self.db.functions[ad] is None: self.analyzer.msg.put((ad, True, False, False, None)) self.analyzer.msg.put("pass_scan_mem") def __exec_rename(self, args): if args[1] == args[2]: return ad = self.api.get_addr_from_symbol(args[1]) if ad == -1: print("symbol %s not found" % args[1]) return self.api.add_symbol(ad, args[2]) self.db.modified = True def __exec_sym(self, args): if len(args) == 1: self.gctx.dis.print_symbols() return if args[1][0] == "|": if len(args) == 2 or len(args) > 3: error("bad arguments (warn: need spaces between |)") return self.gctx.dis.print_symbols(args[2]) return if len(args) > 3: error("bad arguments") return if len(args) == 2: error("an address is required to save the symbol") return if not args[2].startswith("0x"): error("the address should starts with 0x") return if args[1].startswith("loc_"): error("loc_ is a reserved prefix") return # Save new symbol try: if not self.api.add_symbol(int(args[2], 16), args[1]): error("cannot rename") return self.db.modified = True except: error("there was an error when creating a symbol") def __exec_x(self, args): ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx: try: o = ctx.decompile() if o is not None: o.print() except: traceback.print_exc() def __exec_v(self, args): if len(args) != 1: ad = args[1] else: ad = self.last_entry ctx = self.gctx.get_addr_context(ad) if ctx: o = ctx.dump_asm(NB_LINES_TO_DISASM) if o is not None: v = Visual(self.gctx, ctx, self.analyzer, self.api, self.last_stack, self.last_saved_stack) if v.last_curr_line_ad is not None: self.last_entry = v.last_curr_line_ad def __exec_help(self, args): for name in COMMANDS_ALPHA: cmd = self.COMMANDS[name] if cmd.callback_exec is not None: print_no_end(color(name, 2)) print_no_end(" ") for i, line in enumerate(cmd.desc): if i > 0: print_no_end(self.TAB) print(line) def __exec_history(self, args): for line in self.comp.get_history(): print(line) def __exec_sections(self, args): print_no_end("NAME".ljust(20)) print(" [ START - END - VIRTUAL_SIZE - RAW_SIZE ]") for s in self.gctx.dis.binary.iter_sections(): s.print_header() def __exec_info(self, args): print("File:", self.gctx.filename) statinfo = os.stat(self.gctx.filename) print("Size: %.2f ko" % (statinfo.st_size/1024.)) print_no_end("Type: ") ty = self.gctx.dis.binary.type if ty == T_BIN_PE: print("PE") elif ty == T_BIN_ELF: print("ELF") elif ty == T_BIN_RAW: print("RAW") print("Arch:", self.gctx.dis.binary.arch) if self.gctx.dis.binary.is_big_endian(): print("Endianess: big endian") else: print("Endianess: little endian") def __exec_save(self, args): self.db.save(self.comp.get_history()) print("database saved to", self.db.path) self.db.modified = False def __exec_jmptable(self, args): try: inst_addr = int(args[1], 16) table_addr = int(args[2], 16) nb_entries = int(args[3]) entry_size = int(args[4]) except: error("one parameter is invalid, be sure that addresses start with 0x") return if entry_size not in [2, 4, 8]: error("error the entry size should be in [2, 4, 8]") return self.db.modified = True self.api.create_jmptable(inst_addr, table_addr, nb_entries, entry_size) def __exec_py(self, args): ns = {"api": self.api, "args": args[1:], "analyzer": self.analyzer} ns.update(EXPORTED_SYMBOLS) if len(args) > 1: if args[1].startswith("!"): args[1] = "%s/%s" % (PLASMA_SCRIPTS_DIR, args[1][1:]) exec(open(args[1]).read(), ns) else: readline.set_completer(rlcompleter.Completer(ns).complete) code.interact(local=ns) readline.set_completer(self.comp.complete) def __exec_mips_set_gp(self, args): try: self.gctx.dis.mips_gp = int(args[1], 16) self.db.mips_gp = self.gctx.dis.mips_gp self.db.mem.mm.clear() self.db.xrefs.clear() self.db.data_sub_xrefs.clear() self.db.immediates.clear() except: error("bad address") self.db.modified = True def __exec_functions(self, args): self.gctx.dis.print_functions(self.api) def __exec_xrefs(self, args): ad = None if len(args) == 1 else args[1] ctx = self.gctx.get_addr_context(ad) if ctx and ctx.entry in self.gctx.db.xrefs or self.gctx.db.data_sub_xrefs: ctx.dump_xrefs().print() def __exec_analyzer(self, args): n = self.analyzer.msg.qsize() + len(self.analyzer.pending) print("addresses remaining to analyze:", n) if self.analyzer.running_second_pass: print("memory scan...") ad = self.analyzer.where s = self.gctx.dis.binary.get_section(ad) percent = int((ad - s.start) * 100 / s.real_size) print(" -> %s %d%% (0x%x)" % (s.name, percent, ad)) def __exec_memmap(self, args): from plasma.lib.memmap import ThreadMemoryMap t = ThreadMemoryMap(self.db, self.gctx.dis.binary) t.start()