def add_function(self, f): bn.log_info('[+] Adding function') b, i, c = 0, 0, 0 x = len(self.bv.get_code_refs(f.start)) for block in f.low_level_il: b += 1 for inst in block: i += 1 if inst.operation == bn.LowLevelILOperation.LLIL_CALL: c += 1 # naivly determine function size if b == 1 or i < 10: size = 'small' elif i < 100: size = 'medium' else: size = 'large' self.function_data[f.name] = { 'start': f.start, 'blocks': b, 'instructions': i, 'calls': c, 'xrefs': x, 'size': size }
def port_symbols(self, i): if self.role == None: return entry = self.match_model.entries[i] target_index = self.role source_index = 1 if target_index == 0 else 0 source_name = entry["name{}".format(source_index + 1)] target_address = entry["address{}".format(target_index + 1)] old_sym = self.bv.get_symbol_at(target_address) target_name = None if old_sym: target_name = old_sym.name target_text = target_name if target_name else "<unnamed>" if not source_name: bn.log_warn("Port symbols: {} @ {:x} has no source name, skipping".format(target_text, target_address)) return if old_sym and not old_sym.auto: bn.log_warn("Port symbols: {} @ {:x} is already named, skipping".format(target_text, target_address)) return bn.log_info("Port symbols: {} @ {:x} -> {}".format(target_text, target_address, source_name)) new_sym = bn.Symbol(bn.SymbolType.FunctionSymbol, target_address, source_name) self.bv.define_user_symbol(new_sym)
def rename_all_functions(bv): init_cache() for function in bv.functions: if function.name.startswith("0x"): log_info("performing 4byte lookup for '{}'".format(function.name)) try: # sig = "0x" + function.name[1:].strip() sig = function.name sigs = lookup_hash(sig) if len(sigs) >= 1: new_name, comment = format_comment(sigs) function.name = new_name # imm = int(sig, 16) # hash_value = "#{:0=8x}".format(imm) # reset_symbol(bv, imm, hash_value, new_name) if function.comment: function.comment += "\n------\n" function.comment += comment log_info( "found {} text sigs for hash {} renamed function to {}". format(len(sigs), sig, function.name)) except AssertionError: raise except Exception as e: log_error( "4byte lookup failed for function '{}' reason ({}): {}". format(function.name, type(e), e)) save_4byte_cache() return 0
def save_single_function_hash(self, bv, search_index, function, write_meta=True): """ Save the hash of a given function into a given search index. """ # TODO: detect if we are opening database instead of binary exec_id = self.get_exec_id(bv.file.filename) h1, h2 = self.extract_flowgraph_hash(function) if h1 and h2: search_index.add_function(h1, h2, exec_id, function.start) bn.log_info( '[+] Added function <{:x}:0x{:x} {:x}-{:x}> to search index.'. format(exec_id, function.start, h1, h2)) self.metadata.add(exec_id, function.start, bv.file.filename, function.name) if write_meta: self.metadata.__save__() else: bn.log_info( '[-] Did not add function <{:x}:0x{:x}> to search index.'. format(exec_id, function.start))
def save_hash(self, bv, current_function): """ Save hash of current function into search index. """ if not self.sim_hash_location: self.init_db() # Supported platform check if bv.platform.name not in supported_arch: bn.log_error('[!] Right now this plugin supports only the following architectures: ' + str(supported_arch)) return -1 h1, h2 = self.extract_flowgraph_hash(current_function) if os.path.isfile(self.sim_hash_location): create_index = False else: create_index = True search_index = fss.SimHashSearchIndex(self.sim_hash_location, create_index, 28) # TODO: detect if we are opening database instead of binary exec_id = self.get_exec_id(bv.file.filename) search_index.add_function(h1, h2, exec_id, current_function.start) bn.log_info('[+] Added function <{:x}:0x{:x} {:x}-{:x}> to search index.'.format(exec_id, current_function.start, h1, h2)) self.metadata.add(exec_id, current_function.start, bv.file.filename, current_function.name)
def avoid_address(self, bv, addr): if(addr == self.start or addr == self.end): show_message_box("Afl-Unicorn", "Start or End address cannot be avoided !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.WarningIcon) return self.avoid = addr try: blocks = bv.get_basic_blocks_at(addr) for block in blocks: if(addr == self.avoid and addr in self.avoid_addresses): block.function.set_auto_instr_highlight( self.avoid, HighlightStandardColor.NoHighlightColor) self.avoid_addresses = [ x for x in self.avoid_addresses if x != addr] self.avoid = addr else: block.function.set_auto_instr_highlight( addr, HighlightStandardColor.RedHighlightColor) if addr not in self.avoid_addresses: self.avoid_addresses.append(addr) self.avoid = addr binja.log_info("Avoid address: 0x{0:0x}".format(addr)) except: show_message_box("Afl-Unicorn", "Error please open git issue !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
def dumpBlocks(bv, output): module = blocks_pb2.module() for (func_idx, func) in enumerate(bv.functions): pbFunc = module.fuc.add() pbFunc.va = func.start binja.log_info("Function {0}: {1}".format(func_idx, func.start)) for (blk_idx, block) in enumerate(func): blk_start = None pbBB = pbFunc.bb.add() pbBB.va = block.start # can't get the basic block size for now pbBB.parent = pbFunc.va block_start = block.start binja.log_info("\tBasic Block {0:x}: {1:x}".format( blk_idx, block_start)) insn_cur = block_start if not block.can_exit: pbBB.type = 0x20 # ninja potentially non-return type binja.log_info("\t bb 0x%x can exit" % pbBB.va) for insn in block: instruction = pbBB.instructions.add() instruction.va = insn_cur binja.log_info("\t\t{0:x}".format(insn_cur)) insn_cur += insn[1] for (successor_idx, out_edge) in enumerate(block.outgoing_edges): print(out_edge) binja.log_info("\t\tsuccessor {0:x}: {1:x}".format( successor_idx, out_edge.target.start)) child = pbBB.child.add() child.va = out_edge.target.start f = open(output, "wb") f.write(module.SerializeToString()) f.close()
def addr2line(executable, offset): """Returns the line of source like "<file>:<line #>:<function_name>" Returns "ERROR: str(exception)" or "?" on failure.""" addr2line_invocation = "addr2line -e %s -a 0x%x -f" % (executable, offset) child = subprocess.Popen(addr2line_invocation.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = child.communicate() try: if not isinstance(out, str): out = out.decode() output_lines = out.split("\n") #output_address = output_lines[0] # "0x00025ff4" function_name = output_lines[1].strip( ) # e.g. "png_get_current_pass_number" source_line = output_lines[2].strip( ) # e.g. "/home/wintermute/targets/libpng-1.6.36/pngtrans.c:861" except Exception as e: log_warn("[!] Exception encountered in addr2line: %s" % str(e)) log_info(" stdout: %s" % out) log_info(" stderr: %s" % err) return "ERROR: %s" % str(e) if source_line.startswith("??") or source_line.endswith("?"): return "?" return ":".join((source_line, function_name))
def run_plugin(bv, function): # Supported platform check if bv.platform.name not in supported_arch: log_error( '[x] Right now this plugin supports only the following platforms: ' + str(supported_arch)) return -1 r = Report(bv) r.load_template('main', 'functions_report.html') r.load_template('function_row', 'function_table_row.tpl') r.load_template('function_pane', 'function_pane.tpl') r.load_template('function_call_row', 'function_call_row.tpl') r.load_template('function_xref_row', 'function_xref_row.tpl') r.load_template('fingerprint', 'fingerprint_image.tpl') bn.log_info('[*] Scanning functions...') for function in bv.functions: if function.symbol.type != bn.SymbolType.ImportedFunctionSymbol: r.add_function(function) save_filename = bn.interaction.get_save_filename_input( "Save report to ...") if save_filename: with open(save_filename, "w+") as fh: fh.write(r.generate_html())
def do_discover_caller_names(bv, func): param_name = interaction.get_text_line_input( "Please enter the name of the parameter that contains the method name", "Parameter name") # Docs says the above function returns a string with a link to the Python 3 # docs (e.g. a Py3 str), but it actually returns a bytes-object under Py3 param_name = param_name.decode("utf-8") func_params = [ param for param in func.parameter_vars if param.name == param_name ] force = False if len(func_params) != 1: log_error("Unable to determine method name argument") return for name, func in discover_names(func, func_params).items(): # Skip if named correctly if func.symbol.name == name: continue # Skip if we're not forcing and the user has named this already if not func.symbol.auto and not force: log_debug("Skipped %r due to no auto symbol" % name) continue log_info("Renaming %r to %r" % (func, name)) func.view.define_auto_symbol( Symbol(func.symbol.type, func.symbol.address, short_name=name))
def _start_unicorn_emulation(self, harness_file, json_file, dumped_memory, input_file): try: output = subprocess.Popen(['python', harness_file.result, '-d', json_file.result, dumped_memory.result, input_file.result], stdout=subprocess.PIPE).communicate()[0] binja.log_info(output) except TypeError: show_message_box("Afl-Unicorn", "Error please open git issue !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
def test_helper(should_be_found, found, msg_type): nonlocal success for i in should_be_found: if i not in found: log_error(f"'{i}' was not detected as a {msg_type}.") success = False else: log_info(f"'{i}' successfully detected as a {msg_type}.")
def shutdown(self): """ shutdown() => None Cleanly shutdown the XML-RPC service. Example: binaryninja shutdown """ self.server.server_close() log_info("[+] XMLRPC server stopped") setattr(self.server, "shutdown", True) return 0
def show_message(message): """ Originally popped up a message box. Now just logs to console """ global main_window init_gui() log_info(message) if (hasattr(main_window, 'messagebox')): main_window.messagebox.update(message) else: main_window.messagebox = MessageBox()
def init_db(self): # Fetch location location = bn.interaction.get_open_filename_input("Load SimHash database", ".simhash") if not location: bn.log_info("[*] Using default location for SimHash database: {}".format(default_sim_hash_location)) location = default_sim_hash_location # setup metadata class self.sim_hash_location = location self.metadata = Metadata(location+ '.meta')
def load_signature(self, filepath): if os.path.isfile(filepath): try: self.rules.append(yara.compile(filepath)) log_info("Loaded YARA rule: {}".format(filepath)) except yara.SyntaxError: log_error( "Syntax error compiling YARA rule: {}".format(filepath)) else: log_error("YARA rule filepath is invalid: {}".format(filepath))
def cancel_task(self): global process if(process): result = show_message_box("Afl-Unicorn", "Do you want to cancel afl-unicorn fuzzing ?", MessageBoxButtonSet.YesNoButtonSet, MessageBoxIcon.WarningIcon) if result == 1: binja.log_info("Cancel process {0}".format( os.getpgid(process.pid))) os.killpg(os.getpgid(process.pid), signal.SIGTERM) process = None
def _start_afl_fuzz(self, afl_binary, inputs, outputs, harness_file, json_file, dumped_memory): try: global process process = subprocess.Popen([afl_binary.result, '-U', '-m', 'none', '-i', inputs.result, '-o', outputs.result, '--', 'python', harness_file.result, json_file.result, dumped_memory.result, '@@'], preexec_fn=os.setsid) binja.log_info('Process {0} started'.format( os.getpgid(process.pid))) except: show_message_box("Afl-Unicorn", "Error please open git issue !", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
def find_all_hashes(self, bv, current_function): search_index = self.init_index(bv, current_function) report = "" for function in bv.functions: h1, h2 = self.extract_flowgraph_hash(function) if h1 and h2: report = self.find_function_hash(bv, h1, h2, function.start, search_index, report) else: bn.log_info('[-] Did not search for function 0x{:x}.'.format(function.start)) bn.interaction.show_markdown_report('Function Similarity Search Report', report)
def find_hash(self, bv, current_function): """ Find functions similar to the current one. """ search_index = self.init_index(bv, current_function) h1, h2 = self.extract_flowgraph_hash(current_function) if h1 and h2: report = self.find_function_hash(bv, h1, h2, current_function.start, search_index, "") bn.interaction.show_markdown_report('Function Similarity Search Report', report) else: bn.log_info('[-] Did not search for function <{:x}:0x{:x}> to search index.'.format(self.get_exec_id(bv.file.filename), function.start))
def run(self): for function in self.functions: for block in function.low_level_il: for instruction in block: if instruction.operation == bn.LowLevelILOperation.LLIL_SYSCALL: possible_value = instruction.get_reg_value( self.registers[0]) if (possible_value.is_constant): syscall = self.db[str( possible_value.value )] # Get corresponding syscall else: syscall = {'name': 'Unknown Syscall', 'args': []} bn.log_info( "[*] Found syscall {} in function {} at 0x{:x}". format(syscall['name'], function.name, instruction.address)) args = [] # construct arguments for i, arg in enumerate(syscall['args']): possible_arg_value = instruction.get_reg_value( self.registers[i + 1]) if (possible_arg_value.is_constant): arg_value = possible_arg_value.value else: s = '{}: {}'.format(arg['name'], 'Unknown') args.append(s) continue if arg['type'] == 'value': value = arg_value if arg['type'] == 'pointer': value = '*{}'.format(hex(arg_value)) if arg['type'] == 'string': s = self.bv.read( arg_value, self.bv.find_next_data(arg_value, "\x00") - arg_value) if s: value = '<{}>'.format(repr(s)) else: value = '[{}]'.format(hex(arg_value)) s = '{}: {}'.format(arg['name'], value) args.append(s) comment = '{syscall_name}({arguments})'.format( syscall_name=syscall['name'], arguments=", ".join(args)) function.set_comment(instruction.address, comment) args = []
def color(self, visited): with open(visited, 'r') as f: for line in f: index = line.find(':') + 1 addr = line[index:].split()[0] log_info(addr) try: self.color_at(int(addr, 16)) except ValueError: # if thrown by int() pass
def run_vsa(thread, view, function): cfg = function.session_data.cfg log_info(str(cfg)) cfg_function = cfg.get_function_at(function.start - 1 if function.start != 0 else 0) log_info(str(cfg_function)) hash_id = cfg_function.hash_id thread.task.progress = '[VSA] Analyzing...' to_process = [ cfg.get_basic_block_at(function.start - 1 if function.start != 0 else 0) ] seen = set() i = 3 while to_process: thread.task.progress = '[VSA] Processing Basic Blocks{}'.format('.' * i) i += (i + 1) % 4 basic_block = to_process.pop() seen.add(basic_block) end = basic_block.end.pc outgoing_edges = basic_block.outgoing_basic_blocks(hash_id) if outgoing_edges is not None: for outgoing_edge in outgoing_edges: if (view.get_function_at(outgoing_edge.start.pc + 1) is None and outgoing_edge not in seen): to_process.append(outgoing_edge) dest_branches = function.get_indirect_branches_at(end) current_branches = {dest.dest_addr for dest in dest_branches} if outgoing_edge.start.pc not in current_branches: current_branches.add(outgoing_edge.start.pc) function.set_user_indirect_branches( end, [(view.arch, dest) for dest in current_branches if (not basic_block.ends_with_jumpi or outgoing_edge.start.pc != end + 1)]) if function.start == 0: max_function_size, _ = Settings().get_integer_with_scope( 'analysis.limits.maxFunctionSize', scope=SettingsScope.SettingsDefaultScope) if max_function_size: view.max_function_size_for_analysis = max_function_size else: view.max_function_size_for_analysis = 65536
def check_path_substitution(self, path): """Checks for files using path substitutions, going from longest to shortest original path""" sorted_original_paths = sorted(self.path_substitutions.keys(), key=lambda k: len(k), reverse=True) candidate_matches = [] for candidate_path in sorted_original_paths: if candidate_path in path: substitute_pattern = self.path_substitutions[candidate_path] substitute_path = path.replace(candidate_path, substitute_pattern) substitute_path = os.path.expanduser(substitute_path) candidate_matches.append(substitute_path) if os.path.exists(substitute_path): return substitute_path # Only log_warn once per file, and only if the user has tried to add translations if path not in self.failed_substitutions: if len(self.path_substitutions) > 0: log_warn("Failed to find substitution for %s" % path) log_info("Current substitution paths:") for orig_path, sub_path in self.path_substitutions.items(): log_info(" %s => %s" % (orig_path, sub_path)) log_info("Matching patterns' failed substitute paths:") for candidate in candidate_matches: log_info(" %s" % candidate) self.failed_substitutions.append(path) return ""
def plugin_fs_finder(bv): # Find format strings fs_finder = FormatStringFinder( bv, Settings().get_bool(setting_1_should_highlight_variable_trace)) fs_finder.find_format_strings() # Get results and print them in the logs view and in a markdown report md = fs_finder.get_results_string() log_info(md) md = '<span style="color:red">(use the \'Log View\' for clickable addresses)</span>\n' + md title = f"FormatStringFinder results for {os.path.basename(bv.file.filename)}" bv.show_markdown_report(title=title, contents=md)
def explain_llil(bv, llil_instruction): """ Returns the explanation string from explanations_en.json, formatted with the preprocessed LLIL instruction """ if llil_instruction is None: return if llil_instruction.operation.name in explanations: try: # Get the string from the JSON and format it return explanations[llil_instruction.operation.name].format(llil=preprocess(bv, llil_instruction)) except AttributeError: # Usually a bad format string. Shouldn't show up unless something truly weird happens. log_error("Bad Format String in binja_explain_instruction") traceback.print_exc() return llil_instruction.operation.name # If there's anything in the LLIL that doesn't have an explanation, yell about it in the logs log_info("binja_explain_instruction doen't understand " + llil_instruction.operation.name + " yet") return llil_instruction.operation.name
def run_plugin(bv, function): # Supported platform check if bv.platform.name not in supported_arch: log_error( '[x] Right now this plugin supports only the following platforms: ' + str(supported_arch)) return -1 r = Report(bv) r.load_template('functions_report.html') bn.log_info('[*] Scanning functions...') for function in bv.functions: if function.symbol.type != bn.SymbolType.ImportedFunctionSymbol: r.add_function(function) bn.interaction.show_html_report('Functions report', r.generate_html(), 'Not available')
def load_signatures(self, directories): rule_files = [] for directory in directories: if not os.path.isdir(directory) and directory != "": log_error( "YARA rule directory is invalid: {}".format(directory)) else: for f in os.listdir(directory): if f.lower().endswith((".yar", ".yara")): rule_files.append(directory + os.sep + f) for f in rule_files: try: self.rules.append(yara.compile(f)) log_info("Loaded YARA rule: {}".format(f)) except yara.SyntaxError: log_error("Syntax error compiling YARA rule: {}".format(f))
def unret(il: MediumLevelILInstruction): global bb_cache bb_cache = {} function = il.function.source_function # Step 1: find the return ret_addr = il.address log_info(repr(il)) # Step 2: calculate the address to jump to current_esp = function.get_reg_value_at(ret_addr, 'esp') log_info(repr(current_esp)) current_esp = current_esp.offset next_jump_value = function.get_stack_contents_at(ret_addr, current_esp, 4) if next_jump_value.type == RegisterValueType.ConstantValue: next_jump_addr = next_jump_value.value else: print("Return value isn't constant") function.reanalyze() return # Step 3: Identify the start print("Step 3") ret_il_ssa = il.ssa_form mmlil = il.function jump_variable_ssa = ret_il_ssa.dest.src jump_il = mmlil[mmlil.get_ssa_var_definition(jump_variable_ssa)] while jump_il.src.operation != MediumLevelILOperation.MLIL_CONST: new_var_ssa = jump_il.src.left.ssa_form.src jump_il = mmlil[mmlil.get_ssa_var_definition(new_var_ssa)] # Step 4: Patch the binary to jump print("Step 4") patch_addr = jump_il.address view = function.view patch_value = view.arch.assemble(f'jmp 0x{next_jump_addr:x}', patch_addr) if (ret_addr - patch_addr) < len(patch_value): print("Not enough space", hex(patch_addr), len(patch_value)) return view.write(patch_addr, patch_value) return next_jump_addr
def sync(self, off, added, removed): """ Sync(off, added, removed) => None Synchronize debug info with gef. This is an internal function. It is not recommended using it from the command line. """ global g_current_instruction off = int(off, 0) pc = self.base + off if DEBUG: log_info("[*] current_pc=%#x , old_pc=%#x" % (pc, g_current_instruction)) # unhighlight the _current_instruction if g_current_instruction > 0: hl(self.view, g_current_instruction, HL_NO_COLOR) hl(self.view, pc, HL_CUR_INSN_COLOR) # update the _current_instruction g_current_instruction = pc dbg("pre-gdb-add-breakpoints: %s" % (added, )) dbg("pre-gdb-del-breakpoints: %s" % (removed, )) dbg("pre-binja-breakpoints: %s" % (g_breakpoints)) bn_added = [ x - self.base for x in g_breakpoints if x not in self.old_bps ] bn_removed = [ x - self.base for x in self.old_bps if x not in g_breakpoints ] for bp in added: add_gef_breakpoint(self.view, self.base + bp) for bp in removed: delete_gef_breakpoint(self.view, self.base + bp) self.old_bps = copy.deepcopy(g_breakpoints) dbg("post-gdb-add-breakpoints: %s" % (bn_added, )) dbg("post-gdb-del-breakpoints: %s" % (bn_removed, )) dbg("post-binja-breakpoints: %s" % (g_breakpoints, )) return [bn_added, bn_removed]
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. import sys import binaryninja as binja if len(sys.argv) > 1: target = sys.argv[1] bv = binja.BinaryViewType.get_view_of_file(target) binja.log_to_stdout(True) binja.log_info("-------- %s --------" % target) binja.log_info("START: 0x%x" % bv.start) binja.log_info("ENTRY: 0x%x" % bv.entry_point) binja.log_info("ARCH: %s" % bv.arch.name) binja.log_info("\n-------- Function List --------") """ print all the functions, their basic blocks, and their il instructions """ for func in bv.functions: binja.log_info(repr(func)) for block in func.low_level_il: binja.log_info("\t{0}".format(block)) for insn in block: binja.log_info("\t\t{0}".format(insn))