Forked from psifertex/1_Snippet_Instructions.txt
Last active
September 28, 2022 18:17
-
-
Save galenbwill/553626eb0db8e45d1b5b55edd03a2dd7 to your computer and use it in GitHub Desktop.
my current collection of snippets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Welcome to Jordan's grab-bag of common Binary Ninja Snippets. | |
These snippest are meant to run with the Binary Ninja Snippets Plugin | |
(http://github.com/Vector35/snippets) though they can all also be pasted | |
directly into the python console or turned into stand-alone plugins if needed. | |
To install the entire collection at once, just install the Snippets plugin via | |
the plugin manager (CMD/CTL-SHIFT-M), confirm the Snippet Editor works | |
(Tool/Snippets/Snippet Editor), and unzip this bundle (Download ZIP above) into | |
your Snippets folder. | |
Alternatively, the `update_snippets` snippet can be used to automatically | |
download/update this entire collection. | |
You can access the snippets folder by using the "Browse Snippets" button in the | |
Snippet Editor itself. | |
Also, appologies for the weird file name, trying to fight gist's auto-sorting. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Since these are starting to get a bit more complex and there's a lot of code gathered I figured I should be explicit that these are released under a public domain license (CC0): | |
To the extent possible under law, Jordan Wiens (@psifertex) has waived all copyright and related or neighboring rights to this Binary Ninja Snippets Collection (https://gist.github.com/psifertex/6fbc7532f536775194edd26290892ef7). This work is published from: United States. | |
For more information on a CC0 license, see: https://creativecommons.org/publicdomain/zero/1.0/deed.en |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# append file contents to view | |
# | |
# Will append a file's raw contents to the existing view (useful for working with firmware or memory dumps). Thanks to @mariomain for the snippet. | |
def load_file(infilename): | |
f = open(infilename, "rb") | |
g = f.read() | |
f.close() | |
faddr = None | |
while (faddr is not None): | |
faddr = interaction.get_int_input("Load address", "Load address") | |
fsize = len(g) | |
return g, faddr, fsize | |
def overlap(reg1, reg1sz, reg2, reg2sz): | |
if ((reg1 >= reg2) and (reg1 < (reg2 + reg2sz))): | |
return True | |
if (((reg1 + reg1sz) > reg2) and ((reg1 + reg1sz) < (reg2 + reg2sz))): | |
return True | |
if ((reg1 < reg2) and ((reg1 + reg1sz) >= (reg2 + reg2sz))): | |
return True | |
return False | |
def add_some_file_sec(file_name): | |
rv = bv.get_view_of_type("Raw") | |
fblob, faddr, fsize = load_file(file_name) | |
prev_sec = bv.get_segment_at(bv.start) | |
succ_sec = None | |
new_succ_blob = None | |
new_succ_blob_addr = 0 | |
if (overlap(faddr, fsize, prev_sec.start, len(prev_sec)) == False) and (faddr < prev_sec.start): | |
succ_sec = prev_sec | |
else: | |
for i in bv.sections: | |
i = bv.get_section_by_name(i) | |
if overlap(faddr, fsize, i.start, len(i)) == True: | |
log_info("overlapping with region {}".format(hex(i.start))) | |
return False | |
if prev_sec.start != i.start: | |
if (faddr >= (prev_sec.start + len(prev_sec))) and (faddr < i.start): | |
succ_sec = i | |
break | |
prev_sec = i | |
if succ_sec != None: | |
follow_seg = bv.get_segment_at(succ_sec.start) | |
the_rest = rv.read(follow_seg.data_offset, rv.length - follow_seg.data_offset) | |
new_succ_blob = fblob + the_rest | |
new_succ_blob_addr = follow_seg.data_offset | |
else: | |
new_succ_blob = fblob | |
new_succ_blob_addr = rv.length | |
rv.write(new_succ_blob_addr, new_succ_blob) | |
if succ_sec != None: | |
for i in bv.segments: | |
if i.start > faddr: | |
temp_start = i.start | |
temp_off = i.data_offset | |
temp_len = i.data_length | |
bv.remove_auto_segment(i.start, len(i)) | |
temp_off += fsize | |
bv.add_auto_segment(start=temp_start, length=temp_len, data_offset=temp_off, data_length=temp_len, | |
flags=(SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable)) | |
bv.add_auto_section(os.path.basename(file_name), start=faddr, length=fsize, | |
semantics=SectionSemantics.ReadOnlyCodeSectionSemantics) | |
bv.add_auto_segment(start=faddr, length=fsize, data_offset=new_succ_blob_addr, data_length=fsize, | |
flags=(SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# auto strings | |
# | |
# automatically create string types at all detected strings | |
count = 0 | |
for s in bv.strings: | |
bv.define_user_data_var(s.start, Type.array(Type.char(), s.length)) | |
if bv.get_symbol_at(s.start) is None: | |
sym = Symbol(types.SymbolType.DataSymbol, s.start, "str_{}".format(s.value)) | |
bv.define_user_symbol(sym) | |
count += 1 | |
interaction.show_message_box( | |
"Auto-Rename strings", | |
f"Completed renaming variables based {count} strings!" | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import time | |
# 1. IL dests | |
def find_calls_by_mlil(): | |
start = time.time() | |
calls = {} | |
for f in bv.functions: | |
for c in f.call_sites: | |
call_mlil = c.mlil | |
if call_mlil is None: | |
print(f'DBG: No MLIL @ {hex(c.address)}') | |
continue | |
try: | |
call_dest_addr = call_mlil.dest.value.value | |
except: | |
print(f'Exception @ {hex(c.address)} : {c.mlil}') | |
continue | |
if call_dest_addr is None: | |
#print(f'DBG: No IL value @ {hex(c.address)} : {c.mlil}') | |
continue | |
funcs = bv.get_function_at(call_dest_addr) | |
if not funcs: | |
#print(f'DBG: No funcs @ dest {hex(call_dest_addr)}: {hex(c.address)} : {c.mlil}') | |
continue | |
call_dest = funcs[0] | |
calls[c.address] = (f, call_dest) | |
duration = time.time() - start | |
print(f'[*] 1. MLIL dest: Found {len(calls)} in {duration:.02f} seconds') | |
return calls | |
def find_calls_by_llil(): | |
start = time.time() | |
calls = {} | |
for f in bv.functions: | |
for c in f.call_sites: | |
call_llil = c.llil | |
if call_llil is None: | |
#print(f'DBG: No llil @ {hex(c.address)}') | |
continue | |
try: | |
call_dest_addr = call_llil.dest.value.value | |
except: | |
#print(f'Exception @ {hex(c.address)} : {c.llil}') | |
continue | |
if call_dest_addr is None: | |
#print(f'DBG: No IL value @ {hex(c.address)} : {c.mlil}') | |
continue | |
funcs = bv.get_function_at(call_dest_addr) | |
if not funcs: | |
#print(f'DBG: No funcs @ dest {hex(call_dest_addr)}: {hex(c.address)} : {c.mlil}') | |
continue | |
call_dest = funcs[0] | |
calls[c.address] = (f, call_dest) | |
duration = time.time() - start | |
print(f'[*] 1.5. LLIL dest: Found {len(calls)} in {duration:.02f} seconds') | |
return calls | |
# 2. code refs | |
def find_calls_by_xref(): | |
start = time.time() | |
calls = {} | |
for f in bv.functions: | |
for c in f.call_sites: | |
xrefs = bv.get_code_refs_from(c.address) | |
if not xrefs: | |
#print(f'DBG: No xrefs @ {hex(c.address)} : {c.mlil}') | |
continue | |
call_dest_addr = xrefs[0] | |
funcs = bv.get_function_at(call_dest_addr) | |
if not funcs: | |
#print(f'DBG: No funcs @ dest {hex(call_dest_addr)}: {hex(c.address)} : {c.mlil}') | |
continue | |
call_dest = funcs[0] | |
calls[c.address] = (f, call_dest) | |
duration = time.time() - start | |
print(f'[*] 2. code refs: Found {len(calls)} in {duration:.02f} seconds') | |
return calls | |
# 3. Intersection of call_sites and caller_sites | |
def find_calls_by_caller_sites(): | |
start = time.time() | |
calls = {} | |
for f in bv.functions: | |
call_destinations = {} | |
for callee in f.callees: | |
calls_to_callee = {c.address: callee for c in callee.caller_sites if c.function == f} | |
call_destinations.update(calls_to_callee) | |
for c in f.call_sites: | |
call_dest = call_destinations.get(c.address) | |
if not call_dest: | |
#print(f'DBG: {hex(c.address)} not in caller_sites') | |
continue | |
calls[c.address] = (f, call_dest) | |
duration = time.time() - start | |
print(f'[*] 3. Intersection: Found {len(calls)} in {duration:.02f} seconds') | |
return calls | |
mlil_calls = find_calls_by_mlil() | |
llil_calls = find_calls_by_llil() | |
xref_calls = find_calls_by_xref() | |
caller_calls = find_calls_by_caller_sites() | |
for k, v in caller_calls.items(): | |
if k not in llil_calls: | |
print(f'{hex(k)} -> {v} not in llil_calls') | |
for k, v in llil_calls.items(): | |
if k not in xref_calls: | |
print(f'{hex(k)} -> {v} not in xref_calls') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# copy location as offset | |
# | |
# copies the currently selected value as an offset from the base to the clipboard | |
# Was used to include offset as a module name, not needed now | |
# import os | |
# modulename=os.path.basename(bv.file.original_filename) | |
s = bv.get_segment_at(here) | |
fileoffset=here - s.start + s.data_offset | |
# Alternate implementation that copies as a file offset (requires above lines | |
#offset=s.data_offset + offset | |
clip = PySide2.QtGui.QGuiApplication.clipboard() | |
clip.setText('%x' % (fileoffset)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# IL instruction counts for a given function | |
# | |
# Walks all IL instructions at different layers, counting unique operations | |
from binaryninja.lowlevelil import LowLevelILInstruction | |
from binaryninja.mediumlevelil import MediumLevelILInstruction | |
from binaryninja.highlevelil import HighLevelILInstruction | |
def visit(illest, expr, operations): | |
for field in operations[expr.operation]: | |
if field[1] == "expr": | |
visit(illest, getattr(expr, field[0]), operations) | |
illest.add(expr.operation) | |
llillest = set(()) | |
mlillest = set(()) | |
hlillest = set(()) | |
if current_llil: | |
fnname = current_function.name | |
for ins in current_llil.instructions: | |
visit(llillest, ins, LowLevelILInstruction.ILOperations) | |
if current_mlil: | |
for ins in current_mlil.instructions: | |
visit(mlillest, ins, MediumLevelILInstruction.ILOperations) | |
if current_hlil: | |
for ins in current_hlil.instructions: | |
visit(hlillest, ins, HighLevelILInstruction.ILOperations) | |
log_info("%s LLIL (%d): " % (fnname, len(llillest))) | |
log_info(str(llillest)) | |
log_info("%s MLIL (%d): " % (fnname, len(mlillest))) | |
log_info(str(mlillest)) | |
log_info("%s HLIL (%d): " % (fnname, len(hlillest))) | |
log_info(str(hlillest)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# creates executable sections | |
# | |
# Useful snippet for binaries without sections but executable (and writable) | |
# segments. Also demonstrates triggering an analysis module after load. | |
# | |
# Note that the last line is unnecessary in the python console since the UI | |
# triggers it after each command entered anyway. | |
counter=0 | |
for seg in bv.segments: | |
if seg.executable and seg.writable: | |
bv.add_user_section("section_%d"%counter, seg.start, seg.end-seg.start, SectionSemantics.ReadOnlyCodeSectionSemantics) | |
bv.add_analysis_option("linearsweep") | |
counter += 1 | |
bv.update_analysis() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# create a symbol | |
# | |
# Used to create a symbol when you don't want to define a type at a particular location | |
sym_name = get_text_line_input("Symbol name:", "Symbol Name") | |
if sym_name not in [None, b'']: | |
bv.define_user_symbol(Symbol(SymbolType.DataSymbol, here, sym_name)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# demangle gnu pe | |
# | |
# Will attempt to demangle GNU mangled C++ names in a PE file since that is not automatically done | |
for func_sym in bv.get_symbols_of_type(SymbolType.FunctionSymbol): | |
log_debug(f"Attempting to demangle {func_sym}") | |
symtype, symname = demangle_gnu3(Architecture['x86'], func_sym.name) | |
if symname != func_sym.name: | |
log_info(f"Successfully demangled {func_sym.name}") | |
if type(symname) == str: | |
full_name = symname | |
else: | |
full_name = '::'.join(map(str, symname)) | |
new_sym = binaryninja.types.Symbol(SymbolType.FunctionSymbol, func_sym.address, short_name=symname[-1], full_name=full_name, raw_name=func_sym.name) | |
bv.define_user_symbol(new_sym) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# dump IL | |
# | |
# Dump the current function's IL in a verbose and structured text representation. Kinda what I want repr to do for | |
# HLIL AST's, but also works on other ILs. Also supports SSA forms. Edit the last couple lines to control which ILs | |
# get dumped. Needs some improvement. | |
def dump_hlil(hlil, indent=0, op_num=None, name=None, kind='hlil', ssa=False, offset=0): | |
if kind.endswith('_ssa'): | |
ssa = True | |
kind = kind[:-4] | |
llil = hlil | |
if hasattr(llil, 'mlil'): | |
llil = llil.mlil | |
if hasattr(llil, 'llil'): | |
llil = llil.llil | |
if hasattr(llil, 'address'): | |
offset = llil.address | |
def _print(msg): | |
idx = f'[{op_num}] ' if op_num is not None else '' | |
n = f'{name}: ' if name is not None else '' | |
_ = print(f'{kind}:0x{offset:08x}:{" "*indent}{idx}{n}{msg}') | |
if type(hlil) in [list, HighLevelILFunction, LowLevelILFunction, MediumLevelILFunction]: | |
if hasattr(hlil, 'instructions'): | |
if ssa: | |
hlil = hlil.ssa_form | |
hlil = hlil.instructions | |
for i, h in enumerate(hlil): | |
dump_hlil(h, indent, op_num=i, kind=kind, ssa=ssa, offset=offset) | |
return | |
if hasattr(hlil, kind): | |
dump_hlil(getattr(hlil, kind), indent=indent, kind=kind, ssa=ssa, offset=offset) | |
return | |
if hasattr(hlil, 'operation'): | |
try: | |
rendered = str(hlil) | |
if hlil.operation.name.endswith('_PTR'): | |
value = hlil.constant | |
symbols = bv.get_symbols(value) | |
if symbols: | |
rendered = f'{symbols[0].name} @ 0x{value:08x}' | |
_ = _print(f'<{type(hlil).__name__.split(".")[-1]}:{hlil.operation.name}> {rendered!r}') | |
except Exception as e: | |
_ = _print(e) | |
else: | |
_ = _print(f'<{type(hlil).__name__.split(".")[-1]}> {hlil}') | |
if hasattr(hlil, 'operands'): | |
if hasattr(hlil, 'operation'): | |
instruction_type = { | |
'hlil': HighLevelILInstruction, | |
'mlil': MediumLevelILInstruction, | |
'llil': LowLevelILInstruction, | |
}[kind] | |
op_names = instruction_type.ILOperations[hlil.operation] | |
else: | |
op_names = [] | |
for i, op in enumerate(hlil.operands): | |
if True or op is not None: | |
n = None | |
if len(op_names) > i: | |
n = op_names[i][0] | |
dump_hlil(op, indent+4, i, n, kind, ssa=ssa, offset=offset) | |
#dump_hlil(current_function, kind='llil') | |
print(' ') | |
dump_hlil(current_function, kind='mlil_ssa') | |
print(' ') | |
dump_hlil(current_function, kind='hlil_ssa') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# generate official settings documentation | |
# | |
# Snippet that generates https://docs.binary.ninja/getting-started.html#all-settings | |
import json | |
from PySide2.QtGui import QGuiApplication | |
settings = json.loads(binaryninja.Settings().serialize_schema()) | |
table = """|Category|Setting|Description|Type|Default|Scope|Key| | |
|---|---|---|---|---|---|---| | |
""" | |
excludeEnum = [ "analysis.unicode.blocks", "python.interpreter", "ui.theme"] | |
allscope = set(["SettingsProjectScope", "SettingsUserScope", "SettingsResourceScope"]) | |
for category in settings: | |
for setting in settings[category]['settings']: | |
title = settings[category]['settings'][setting]['title'] | |
description = settings[category]['settings'][setting]['description'] | |
typ = settings[category]['settings'][setting]['type'] | |
key = settings[category]['settings'][setting]['key'] | |
default = settings[category]['settings'][setting]['default'] | |
if isinstance(default, list): | |
default = "[" + ', '.join(["`%s`" % x for x in default]) + "]" | |
else: | |
default = f"`{str(default)}`" | |
if default == "``": | |
default = " "; | |
print(settings[category]['settings'][setting]) | |
if 'ignore' in settings[category]['settings'][setting].keys(): | |
scope = allscope - set(settings[category]['settings'][setting]['ignore']) | |
else: | |
scope = allscope | |
scope = "[" + ', '.join(["`%s`" % x for x in scope]) + "]" | |
table += f"|{category}|{title}|{description}|`{typ}`|{default}|{scope}|<a id='{key}'>{key}</a>|\n" | |
if settings[category]['settings'][setting].get("enum") and key not in excludeEnum: | |
for idx, enum in enumerate(settings[category]['settings'][setting]["enum"]): | |
if settings[category]['settings'][setting].get("enumDescriptions"): | |
description = " enum: " + settings[category]['settings'][setting]["enumDescriptions"][idx] | |
else: | |
description = " " | |
table += f"| | |{description}|`enum`|`{enum}`| | |\n" | |
show_markdown_report("Settings Documentation", "Below table added to the clipboard:\n\n"+table) | |
log_info("Saving result to the clipboard.") | |
clip = QGuiApplication.clipboard() | |
clip.setText(table) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# export to text | |
# | |
# Can export IL and assembly forms for the current function or the entire binary | |
import os | |
import io | |
def valid_filename(s): | |
s = s.strip().replace(' ', '_') | |
return re.sub(r'(?u)[^-\w.]', '', s) | |
def filtername(name): | |
return "".join(x for x in name if x.isalnum() or x in ["_", "-"]) | |
def fnsource(fn, form): | |
if form == "HLIL": | |
return ''.join(["\t" + x + "\n" for x in map(str, fn.hlil.root.lines)]) | |
if form == "MLIL": | |
return ''.join(["\t" + x + "\n" for x in map(str, fn.mlil.instructions)]) | |
if form == "LLIL": | |
return ''.join(["\t" + x + "\n" for x in map(str, fn.llil.instructions)]) | |
if form == "Assembly": | |
return ''.join(["\t" + "".join(map(str, x[0])) + "\n" for x in fn.instructions]) | |
if form == "Assembly with offset": | |
return ''.join([f'\t{x[1]:#04x}: {"".join(map(str, x[0]))}\n' for x in fn.instructions]) | |
def overwrite(fname): | |
if show_message_box("File exists", "File exists, delete and overwrite?", buttons=MessageBoxButtonSet.YesNoButtonSet) == MessageBoxButtonResult.YesButton: | |
os.unlink(fname) | |
return True | |
else: | |
return False | |
def main(): | |
#Maybe eventually add "Whole Binary" to include data from linear view | |
all_or_func = ChoiceField("Scope?", ["All functions", "Current function"]) | |
asm_or_il = ChoiceField("Which form?", ["Assembly with offset", "Assembly", "LLIL", "MLIL", "HLIL"]) | |
folder = DirectoryNameField("Folder to save result", default_name=os.path.dirname(bv.file.filename)) | |
choices = get_form_input(["Which would you like to export?\n\nNote that \"whole binary\" will only dump IL contained in functions when IL is selected", all_or_func, asm_or_il, folder], "Export to text") | |
if choices: | |
current_only = all_or_func.result == 1 | |
form = asm_or_il.choices[asm_or_il.result] | |
fname = os.path.splitext(os.path.basename(bv.file.filename))[0] | |
if folder.result: | |
if current_only: | |
outputname = f"{os.path.join(folder.result, fname)}.{valid_filename(current_function.name)}.txt" | |
if os.path.isfile(outputname): | |
if not overwrite(outputname): | |
log_warn("Stopping export to text due to existing file.") | |
return | |
log_info(f"Dumping {current_function.name} to {outputname}") | |
with io.open(outputname, mode='w', encoding="utf-8") as f: | |
f.write(fnsource(current_function, form)) | |
else: | |
outputname = f"{os.path.join(folder.result, fname)}.txt" | |
if os.path.isfile(outputname): | |
if not overwrite(outputname): | |
log_warn("Stopping export to text due to existing file.") | |
return | |
with io.open(outputname, mode='w', encoding="utf-8") as f: | |
for fn in bv.functions: | |
log_info(f"Writing {fn.name}") | |
f.write(f"\n{fn.name}: \n") | |
f.write(fnsource(fn, form)) | |
log_info(f"Done dumping whole binary to {outputname}") | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Find a variable definition from current selection | |
# | |
if uicontext.token.localVarValid: | |
log_info("Found a localvar") | |
varname = uicontext.token.token.text | |
log_info(str(dir(uicontext))) | |
log_info("-----\n") | |
instrIndex = 0 | |
else: | |
log_warn("No variable selected") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Basic sample flowgrpah | |
# | |
# Creates a flow graph, showing some basic functionality | |
graph = FlowGraph() | |
node_a = FlowGraphNode(graph) | |
node_a.lines = ["Node A"] | |
node_b = FlowGraphNode(graph) | |
node_b.lines = ["Node B"] | |
node_c = FlowGraphNode(graph) | |
node_c.lines = ["Node C"] | |
graph.append(node_a) | |
graph.append(node_b) | |
graph.append(node_c) | |
node_a.add_outgoing_edge(BranchType.UnconditionalBranch, node_b) | |
node_a.add_outgoing_edge(BranchType.UnconditionalBranch, node_c) | |
show_graph_report("In order", graph) | |
graph2 = FlowGraph() | |
node2_a = FlowGraphNode(graph) | |
node2_a.lines = ["Node A"] | |
node2_b = FlowGraphNode(graph) | |
node2_b.lines = ["Node B"] | |
node2_c = FlowGraphNode(graph) | |
node2_c.lines = ["Node C"] | |
graph2.append(node2_b) | |
graph2.append(node2_c) | |
graph2.append(node2_a) | |
node2_a.add_outgoing_edge(BranchType.UnconditionalBranch, node2_b) | |
node2_a.add_outgoing_edge(BranchType.UnconditionalBranch, node2_c) | |
show_graph_report("Out of order", graph) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# function comment | |
# | |
# The UI currently lacks a way to add a "function" based comment that will be repeated when the function is called | |
newcomment = get_text_line_input("Current value: " + current_function.comment, "Function plate comment") | |
if (newcomment): | |
current_function.comment = newcomment |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# get action context | |
# | |
# Useful for quering what is selected from a snippet or plugin instead of using various register_for APIs | |
def get_current_context() -> Optional[Any]: | |
ctx = UIContext.activeContext() | |
if not ctx: | |
ctx = UIContext.allContexts()[0] | |
if not ctx: | |
binaryninja.log_warn(f'No UI Context available') | |
return None | |
handler = ctx.contentActionHandler() | |
if not handler: | |
binaryninja.log_warn(f'No Action Context available') | |
return None | |
return handler.actionContext() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# highlight dangerous calls | |
# | |
# Highlight with theme-aware colors any "dangerous" functions: | |
# Note that prefixed versions like "_system" will not be highlighted using this sample code. | |
dangerous = ["strcpy", "gets"] | |
sus = ["printf", "system", "exec"] | |
for fnname in dangerous + sus: | |
if fnname in dangerous: | |
color = HighlightStandardColor.RedHighlightColor | |
if fname in sus: | |
color = HighlightStandardColor.OrangeHighlightColor | |
for sym in bv.get_symbols_by_name(fnname): | |
for ref in bv.get_code_refs(sym.address): | |
ref.function.set_user_instr_highlight(ref.address, color) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# debug HLIL decompilation | |
# | |
# trigger a debug report to show all intermediate stages of analysis creating HLIL | |
current_function.request_debug_report("high_level_il") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# interesting units | |
# | |
# Log interesting components like the function and basic blocks with most incoming or outgoing edges | |
log_info("Most connected function: " + repr(max(bv.functions, key=lambda x: len(x.callees) + len(x.callers)))) | |
log_info("Most incoming callers: " + repr(max(bv.functions, key=lambda x: len(x.callers)))) | |
log_info("Most connected bblock: " + repr(max(bv.basic_blocks, key=lambda x: len(x.incoming_edges) + len(x.outgoing_edges)))) | |
log_info("Highest xrefs: " + repr(max(bv.functions, key=lambda x: len(x.callers)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# list plugins | |
# | |
# Produce an HTML report showing how to use a sortable HTML table to make a slightly more useful UI. Appologies for the awful intermixing of code and HTMl, think it's actually more readable this way. | |
from binaryninjaui import getThemeColor, ThemeColor | |
r = RepositoryManager() | |
repos = ["community", "official"] | |
html = '''<html> | |
<head> | |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> | |
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/jq-3.3.1/dt-1.10.22/datatables.min.css"/> | |
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/jq-3.3.1/dt-1.10.22/datatables.min.js"></script> | |
<script>''' | |
for repo in repos: | |
html += f'''$(document).ready( function () {{ | |
$('#{repo}').DataTable({{ | |
"paging": false, | |
"info": false, | |
"searching": false, | |
"order": [[2, "desc"], [0, "asc"]] | |
}}); | |
}} ); | |
''' | |
html += f''' </script> | |
<style> | |
tr[data-state="Update Available"]{{ | |
color: {getThemeColor(ThemeColor.CyanStandardHighlightColor).name()}; | |
}} | |
tr[data-state="Disabled"]{{ | |
color: {getThemeColor(ThemeColor.FalseBranchColor).name()}; | |
}} | |
tr[data-state="Enabled"]{{ | |
color: {getThemeColor(ThemeColor.TrueBranchColor).name()}; | |
}} | |
</style> | |
</head> | |
<body> | |
<div style="margin: 50px"> | |
''' | |
for repo in ["community", "official"]: | |
html += f'''<h3>{repo.capitalize()} Plugins</h3> | |
<table id="{repo}" class="sortable" cellspacing="0" width="100%"> | |
<thead> | |
<tr> | |
<th>Plugin Name</th> | |
<th>Version</th> | |
<th>Status</th> | |
<th>Short Description</th> | |
</tr> | |
</thead> | |
<tbody> | |
''' | |
for plugin in r.plugins[repo]: | |
if plugin.update_available: | |
status = "Update Available" | |
elif plugin.installed and not plugin.enabled: | |
status = "Disabled" | |
elif plugin.installed: | |
status = "Enabled" | |
else: | |
continue | |
html += f'<tr data-state="{status}"><td>{plugin.name}</td><td>{plugin.version}</td><td>{status}</td><td>{plugin.description}</td></tr>' | |
html += f'''</tbody> | |
</table> | |
<hr /> | |
''' | |
html += ''' | |
</div> | |
</body> | |
</html> | |
''' | |
show_html_report('Plugin List', html) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# load borland map | |
# | |
# loads function and data variable symbols from Borland MAP files (https://community.embarcadero.com/article/technical-articles/149-tools/14524-understanding-linker-generated-32bit-map-files) | |
import re | |
import os | |
#Load symbols from a MAP file | |
mapfile = get_open_filename_input("filename:", "All Files (*)") | |
if mapfile is not None and os.access(mapfile, os.R_OK): | |
with open(mapfile, "r+", encoding="utf-8") as f: | |
data = f.readlines() | |
else: | |
log_error("Unable to parse specified map file.") | |
data = [] | |
mylog = log_debug | |
#uncomment to enable debugging even if BN debugging is off | |
#mylog = log_info | |
segments = {} | |
symcount = 0 | |
for line in data: | |
line = line.strip() | |
if line.startswith("0"): | |
index = line.split(":")[0] | |
if index in segments.keys(): #this is a record for a segment we know about | |
offset = int(line.split(" ")[0].split(":")[1], 16) | |
addr = offset + segments[index][0] | |
symbol = line.split(" ")[-1] | |
symtype=SymbolType.DataSymbol | |
if symbol.endswith("()"): | |
symbol = symbol[0:-2] | |
if symbol.startswith("<-"): | |
symbol = symbol[2:] | |
symtype=SymbolType.FunctionSymbol | |
contain = bv.get_functions_containing(addr) | |
makenew = True | |
for fn in contain: | |
if fn.start == addr: #there should not be other functions around this | |
makenew = False | |
else: | |
mylog(f'Removed bogus prior function at {hex(fn.start)}') | |
bv.remove_user_function(fn) | |
if makenew: | |
mylog(f'Created function at {hex(addr)}') | |
bv.create_user_function(addr) | |
if symbol.startswith("->:"): #call to a function, name destination | |
symbol = symbol[3:] | |
symtype=SymbolType.FunctionSymbol | |
dest = bv.get_callees(addr) | |
if len(dest) == 0: #current function hasn't been analyzed yet, extract destination from disasssembly and create destination function and symbol | |
destaddr = int(bv.get_disassembly(addr).split(' ')[-1], 16) | |
bv.create_user_function(destaddr) | |
bv.define_user_symbol(Symbol(symtype, destaddr, symbol)) | |
mylog(f'Created function at {hex(destaddr)}') | |
continue | |
else: | |
destfn = bv.get_function_at(dest[0]) | |
destfn.name = symbol | |
mylog(f'Renamed function {symbol} as destination of call at {hex(addr)}') | |
if symbol.startswith("->"): | |
symbol = symbol[2:] | |
continue #just a pointer to an import, skip | |
if symbol.startswith("*"): | |
symbol = symbol[1:] | |
bv.define_user_symbol(Symbol(symtype, addr, symbol)) | |
mylog(f'Creating symbol {symbol} at {hex(addr)}') | |
symcount += 1 | |
else: #new memory segment | |
records = re.split('\s+', line[5:]) | |
base = int(records[0], 16) | |
size = int(records[1][:-1], 16) | |
try: | |
name = records[2] | |
except IndexError: | |
name = 'name' | |
try: | |
cls = records[3] | |
except IndexError: | |
cls = 'class' | |
if name.endswith("END") or name.endswith("END_"): | |
continue | |
segments[index] = [ base, size, name, cls ] | |
log_info(f'Updated {symcount} total symbols') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# make functions in selection | |
# | |
# Will create multiple functions in a background thread across the selected region using the default architecture | |
class FunctionTask(BackgroundTaskThread): | |
def __init__(self, bv, selection): | |
BackgroundTaskThread.__init__(self, "Finding functions...", False) | |
self.bv = bv | |
self.selection = selection | |
def run(self): | |
self.bv.begin_undo_actions() | |
for addr in range(self.selection[0], self.selection[1]): | |
if len(self.bv.get_functions_containing(addr)) == 0: | |
self.bv.create_user_function(addr) | |
self.bv.update_analysis_and_wait() | |
self.bv.commit_undo_actions() | |
FunctionTask(bv, current_selection).start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# new file from selection | |
# | |
# Opens the current selections contents into a new file | |
import tempfile | |
from binaryninjaui import UIContext | |
def get_selected_data(): | |
#remove when snippets implements this first-class | |
return bv.read(current_selection[0], current_selection[1]-current_selection[0]) | |
def openFile(filename): | |
ctx = UIContext.activeContext() | |
ctx.openFilename(filename) | |
temp = tempfile.NamedTemporaryFile(delete=False) | |
buf = get_selected_data() | |
log_info(f"Writing {len(buf)} bytes to {temp.name}") | |
temp.write(get_selected_data()) | |
temp.close() | |
execute_on_main_thread_and_wait(lambda: openFile(temp.name)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Paste this def into the console, then run it, and then paste the output into the_types down below: | |
# | |
def dump_types(clip=True, void=True, verbose=False): | |
def make_call_type(name, type): | |
return f'{type.return_value} {name}({",".join(map(str,type.parameters))})' | |
_print = print | |
if not verbose: | |
_print = lambda *a, **kw: None | |
fun_dump = [] | |
for f in bv.functions: | |
adjustments = [] | |
# for ref in f.caller_sites: | |
for ref in sorted(f.call_sites,key=lambda ref:ref.address): | |
# _print(f'{f = } {ref = }') | |
adjustment = ref.function.get_call_type_adjustment(ref.address) | |
if adjustment is not None: | |
#adjustments.append((str(ref.hlil.dest), ref.function.name, str(adjustment))) | |
dest = str(ref.hlil.dest) | |
adjustments.append((dest, ref.function.name, make_call_type(dest, adjustment))) | |
# _print(f'{adjustments = !r}') | |
tag = f'{bv.arch.name}:{f.start:08X}' | |
fun_dump.append(f'{tag!r}:(0x{f.start:08X},{bv.arch.name!r},"fun",({bv.get_sections_at(f.start)[0].name!r},{f.name!r},{str(f)!r},{str(f.clobbered_regs.regs)},{str(f.return_regs.regs)},{adjustments})),') | |
_ = _print(fun_dump[-1]) | |
_ = _print() | |
for v in bv.data_vars: | |
f = bv.get_data_var_at(v) | |
if f.type.type_class == TypeClass.FunctionTypeClass: | |
sections = bv.get_sections_at(f.address) | |
if not sections: | |
sec_name = '' | |
else: | |
sec_name = sections[0].name | |
#t = f'{f.type.return_value} {f.name}({",".join(map(str,f.type.parameters))})' | |
t = make_call_type(f.name, f.type) | |
tag = f'{bv.arch.name}:{f.address:08X}' | |
fun_dump.append(f'{tag!r}:(0x{f.address:08X},{bv.arch.name!r},"var",({sec_name!r},{f.name!r},{t!r})),') | |
_ = _print(fun_dump[-1]) | |
_ = _print() | |
if clip: | |
clipboard.text = '\n\t' + '\n\t'.join(fun_dump) | |
if not void: | |
return fun_dump | |
the_types = { | |
} | |
bv.begin_undo_actions() | |
for item in the_types.items(): | |
tag, (address, arch_name, fun_var, rest) = item | |
print(f'0x{address:08x}:{item[1]}') | |
if arch_name != bv.arch.name: | |
continue | |
sec_name, name, type_str = rest[:3] | |
the_type = bv.parse_type_string(type_str) | |
if not the_type: | |
log_warn(f'failed to parse function type for {name} found 0x{address:08x}: {type_str!r}') | |
continue | |
is_fun = fun_var == "fun" | |
if not is_fun: | |
v = None | |
v = bv.get_data_var_at(address) | |
if v and sec_name: | |
s = bv.get_sections_at(v.address) | |
if s and s[0].name != sec_name: | |
v = None | |
if not v: | |
for symbol in bv.get_symbols_by_name(name): | |
s = bv.get_sections_at(symbol.address) | |
log_warn(f'0x{symbol.address:08x} {symbol.name} {symbol.type.name} {s}') | |
if s and s[0].name != sec_name: | |
continue | |
if symbol.type in [SymbolType.FunctionSymbol, SymbolType.ExternalSymbol, SymbolType.ImportedFunctionSymbol, SymbolType.DataSymbol]: | |
v = bv.get_data_var_at(symbol.address) | |
if v: | |
log_warn(f'0x{v.address:08X}:({bv.arch.name!r},"var",{v.name!r},{str(v.type)!r},[],[]),') | |
break | |
if v is not None: | |
print(f'0x{v.address:08X}:({bv.arch.name!r},"var",{v.name!r},{str(v.type)!r},[],[]),') | |
v.name = name | |
v.type = the_type[0] | |
v = bv.get_data_var_at(v.address) | |
print(f'0x{v.address:08X}:({bv.arch.name!r},"var",{v.name!r},{str(v.type)!r},[],[]),') | |
else: | |
log_warn(f'no appropriate var {name} found at 0x{address:08x} or in section {sec_name}') | |
continue | |
clobbered_regs, return_regs, adjustments = rest[3:] | |
f = bv.get_function_at(address) | |
if not f or f.name != name: | |
fs = bv.get_functions_by_name(name) | |
if fs: | |
f = fs[0] | |
if not f: | |
log_warn(f'no function {name} found at 0x{address:08x}') | |
continue | |
if the_type: | |
f.set_user_type(the_type[0]) | |
else: | |
log_warn(f'failed to parse function type for {name} found 0x{address:08x}: {type_str!r}') | |
continue | |
if clobbered_regs: | |
f.clobbered_regs = RegisterSet(clobbered_regs, 255) | |
if return_regs: | |
f.return_regs = RegisterSet(return_regs, 255) | |
if f.name != name: | |
f.name = name | |
bv.update_analysis_and_wait() | |
if adjustments: | |
call_sites = sorted(f.call_sites,key=lambda ref:ref.address) | |
for target, source, adjustment in adjustments: | |
while call_sites: | |
call_site = call_sites.pop(0) | |
hlil = call_site.hlil | |
if hlil.operation != HighLevelILOperation.HLIL_CALL: | |
log_warn(f'0x{call_site.address:08x}: call site {hlil} is not HLIL_CALL') | |
for op in hlil.operands: | |
if hasattr(op, 'operation') and op.operation == HighLevelILOperation.HLIL_CALL: | |
hlil = op | |
break | |
else: | |
log_warn(f'0x{call_site.address:08x}: operand {op} is not HLIL_CALL') | |
if hlil.operation != HighLevelILOperation.HLIL_CALL: | |
log_warn(f'0x{call_site.address:08x}: call {hlil} is not a call') | |
dest = str(hlil.dest) | |
print(f'0x{call_site.address:08x}: call {dest!r} init: {(target,source,adjustment)}') | |
if dest == target: | |
f.set_call_type_adjustment(call_site.address, adjustment) | |
#adjustment_type = bv.parse_type_string(adjustment) | |
#if adjustment_type: | |
# f.set_call_type_adjustment(call_site.address, adjustment) | |
adjustment = f.get_call_type_adjustment(call_site.address) | |
print(f'0x{call_site.address:08x}: call {str(call_site.hlil.dest)!r} set: {adjustment}') | |
break | |
for call_site in sorted(f.call_sites,key=lambda ref:ref.address): | |
adjustment = f.get_call_type_adjustment(call_site.address) | |
print(f'0x{call_site.address:08x}: call {str(call_site.hlil.dest)!r} final: {adjustment}') | |
f = bv.get_function_at(f.start) | |
print(f'0x{f.start:08X}:({f.name!r},{str(f)!r},{str(f.clobbered_regs.regs)},{str(f.return_regs.regs)}),') | |
bv.commit_undo_actions() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# print current function to pseudo c | |
# | |
# Adapt as necessary to save to file for example, though File / Export will also work | |
def c_source(bv, func): | |
lines = '' | |
settings = DisassemblySettings() | |
settings.set_option(DisassemblyOption.ShowAddress, False) | |
obj = lineardisassembly.LinearViewObject.language_representation(bv, settings) | |
cursor_end = lineardisassembly.LinearViewCursor(obj) | |
cursor_end.seek_to_address(func.highest_address) | |
body = bv.get_next_linear_disassembly_lines(cursor_end) | |
cursor_end.seek_to_address(func.highest_address) | |
header= bv.get_previous_linear_disassembly_lines(cursor_end) | |
for line in header: | |
lines += str(line) + '\n' | |
for line in body: | |
lines += str(line) + '\n' | |
return lines | |
for line in c_source(bv, current_function): | |
print(line) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# PDB PTR Downloader | |
# | |
# Can be adapted to work together with the built in PDB support to fetch UNC paths from debug server file.ptr files. Also a good example of how to access GlobalArea widgets. | |
import binaryninjaui | |
import os | |
USER="myusername" | |
PASS="mypassword" #change to an interactive popup for more security but be aware os.system may leave history | |
DOMAIN="mydomain" | |
globalArea=uicontext.context.globalArea() | |
logWidget=globalArea.widget("Log") | |
for child in logWidget.children(): | |
if isinstance(child, binaryninjaui.LogListModel): | |
if child.hasSelectedItems(): | |
items = child.getSelectedItems() | |
if items: | |
lastLine = items[0].text | |
lineSplit = lastLine.split(" -> ") | |
if len(lineSplit) == 2: | |
unc = lineSplit[0] | |
pdbPath = lineSplit[1] | |
unc = unc.replace("\\","\/") | |
os.system(f"smbget -U {USER}%{PASS} -w {DOMAIN} -o {pdbPath} {unc}") | |
else: | |
log_warn("Invalid PTR log message.") | |
else: | |
log_info("No selected PTR log message.") | |
else: | |
log_info("No selected PTR log message.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# random jump | |
# | |
# jump to a random function in the current binaryview | |
import random | |
randomIndex = random.randint(0, len(bv.functions)-1) | |
destination = bv.functions[randomIndex].start | |
log_info("Jumping to: 0x%x" % destination) | |
here = destination |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# read dword | |
# | |
# Simple example showing how to read a dword at the current location | |
dword = int.from_bytes(bv.read(here, 4), "big" if bv.endianness == Endianness.BigEndian else "little") | |
clip = PySide2.QtGui.QGuiApplication.clipboard() | |
clip.setText('%x' % (dword)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# run python file | |
# | |
# Run a file from disk with the current python context | |
filename = get_open_filename_input("Python File:") | |
if filename: | |
exec(open(filename).read()) | |
else: | |
show_message_box("Invalid python file", "Invalid python file selected.", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# save HLIL | |
# | |
# DEPRECATED IN FAVOR OF "export to text" snippet | |
def filtername(name): | |
return "".join(x for x in name if x.isalnum() or x in ["_", "-"]) | |
choice = get_choice_input("Save all functions or just current?", "choices", ["All", "Current"]) | |
if (choice == 0): | |
import os | |
fname = os.path.splitext(os.path.basename(bv.file.filename))[0] | |
folder = get_directory_name_input("Where to save decompilation:").decode('utf-8') | |
for fn in bv.functions: | |
source = '\n'.join(map(str, fn.hlil.root.lines)) | |
output = os.path.join(folder, filtername(fn.name) + ".txt") | |
try: | |
with open(output, 'w') as f: | |
f.write(source) | |
log_info(f"Dumped {fn.name} to {output}") | |
except: | |
log_error(f"Unable to save {output}") | |
if (choice == 1): | |
source = '\n'.join(map(str, current_function.hlil.root.lines)) | |
while True: | |
output = get_save_filename_input("Source filename:", "txt", "%s.txt" % filtername(current_function.name)) | |
if output == None: | |
msg = "No file specified." | |
interaction.show_message_box(msg, msg) | |
break | |
try: | |
with open(output, "w") as f: | |
f.write(source) | |
except: | |
msg = "Save failed. Try again?" | |
if not interaction.show_message_box(msg, msg, buttons=MessageBoxButtonSet.YesNoButtonSet): | |
break | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# save search | |
# | |
# Save search results to a specified file | |
targets = [FunctionGraphType.NormalFunctionGraph, FunctionGraphType.LowLevelILFunctionGraph, FunctionGraphType.MediumLevelILFunctionGraph, FunctionGraphType.HighLevelILFunctionGraph, FunctionGraphType.HighLevelLanguageRepresentationFunctionGraph] | |
search_text = TextLineField("Search text") | |
search_target = ChoiceField("Search target?", ["Assembly", "LLIL", "MLIL", "HLIL", "Pseudo C"]) | |
search_addresses = ChoiceField("Include addresses in output?", ["No", "Yes"]) | |
output_file = SaveFileNameField("Output filename: ", default_name=bv.file.filename + ".txt") | |
choices = get_form_input(["Saved Search Plugin", search_text, search_target, output_file, search_addresses], "Saved Search") | |
if choices and search_text.result and search_target.result and output_file.result: | |
with open(output_file.result, 'wb') as f: | |
target = targets[search_target.result] | |
for result in bv.find_all_text(bv.start, bv.end, search_text.result, graph_type=target): | |
if search_addresses.result and search_addresses.result == 1: | |
addr = bytes(hex(result[0]) + "\t", 'utf8') | |
else: | |
addr = b"" | |
f.write(addr + bytes(str(result[2]), 'utf8') + b"\n") | |
log_info(f"Search results saved to {output_file.result}") | |
else: | |
log_warn("Search not saved, dialog cancelled or missing selection.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# get the selected variable | |
# | |
# Uses the UIContext to get the variable the user has currently selected, copied from https://twitter.com/josh_watson/status/1352319354663178240 | |
ctx = UIContext.activeContext() | |
h = ctx.contentActionHandler() | |
a = h.actionContext() | |
token_state = a.token | |
selectedVar = Variable.from_identifier(current_function, token_state.token.value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# show file modifications | |
# | |
# Use the get_modification API to show all places where bytes were modified in the file | |
for offset in range(bv.start, len(bv) + bv.start): | |
mod = bv.get_modification(offset,1)[0] | |
if mod != ModificationStatus.Original: | |
b = "0x" + bv.read(offset, 1).hex() | |
if mod == ModificationStatus.Inserted: | |
print(f"Looks like {b} was inserted at {hex(offset)}") | |
else: | |
print(f"Looks like {b} was written at {hex(offset)}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Simple PySide UI elements/testing | |
# | |
import binaryninjaui | |
from PySide2 import QtWidgets, QtGui, QtWidgets, QtCore | |
def basic(): | |
popout = QtWidgets.QDialog() | |
popout.setWindowTitle("test popout1") | |
popout.exec_() | |
def qpixmap(): | |
pixmap = QtGui.QPixmap("play-and-pause-button.png") | |
mask = pixmap.createMaskFromColor(QtGui.QColor('black'), QtGui.Qt.MaskOutColor) | |
pixmap.fill((QtGui.QColor('red'))) | |
pixmap.setMask(mask) | |
window = QtWidgets.QDialog() | |
window.setWindowTitle("View Image") | |
label = QtWidgets.QLabel(window) | |
label.setPixmap(pixmap) | |
label.setGeometry(QtCore.QRect(10, 40, pixmap.width(), pixmap.height())) | |
window.resize(pixmap.width()+20, pixmap.height()+100) | |
window.exec_() | |
def qpixmap2(): | |
icon = QtGui.QIcon("play-and-pause-button.svg") | |
button = QtWidgets.QPushButton(icon) | |
msg_box = QtWidgets.QMessageBox() | |
msg_box.setWindowTitle("Testing Icons") | |
msg_box.exec_() | |
def colorize(img, color): | |
pixmap = QtGui.QPixmap(img) | |
mask = pixmap.createMaskFromColor(QtGui.QColor('black'), QtGui.Qt.MaskOutColor) | |
pixmap.fill(color) | |
pixmap.setMask(mask) | |
return pixmap | |
def qicon(): | |
ico = QtGui.QIcon("play-and-pause-button.svg") | |
ico2 = QtGui.QIcon(colorize(ico.pixmap(ico.actualSize(QtCore.QSize(1024, 1024))), QtGui.QColor('red'))) | |
msg_box = QtWidgets.QMessageBox() | |
msg_box.setWindowTitle("Show Icon") | |
button = QtWidgets.QPushButton(msg_box) | |
button.setIcon(ico2) | |
button.setText("PlayPause") | |
msg_box.exec_() | |
#qpixmap2() | |
#qpixmap() | |
#basic() | |
qicon() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# annotate inline strings that are assembled via one byte moves/pushes | |
# | |
annotation="" | |
for instruction in current_basic_block.get_disassembly_text(): | |
if instruction.address >= current_selection[0] and instruction.address < current_selection[1]: | |
address = instruction.address | |
value = instruction.tokens[-1].value | |
operand = instruction.tokens[-1].operand | |
type = IntegerDisplayType.CharacterConstantDisplayType | |
current_function.set_int_display_type(address, value, operand, type) | |
while (value > 0): | |
annotation += chr(value % 256) | |
value = value >> 8 | |
log_info("Adding comment for string: %s" % annotation) | |
current_function.set_comment_at(current_selection[0], annotation) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# shell out to swift-demangle to handle swift symbol names | |
# | |
import subprocess | |
result = subprocess.run(['/usr/bin/xcrun', '--find', 'swift-demangle'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) | |
if result.returncode == 0: | |
demangle_str = result.stdout.decode('utf-8') | |
for f in bv.functions: | |
result = subprocess.run([demangle_str, '-simplified', '-compact', symbol], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) | |
if result.returncode == 0: | |
f.name = demangle(f.name) | |
else: | |
log_error('Unable to find swift-demangle.') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# toggle arm thumb | |
# | |
# ARM/Thumb have an odd quirk where the same architecture module handles both and | |
# uses the lowest bit of the address to determine the correct architecture, so | |
# the normal API usage of specifing the optional platform will not work. Here's a | |
# quick snippet to show how to toggle the architecture of the current function by | |
# removing/creating it | |
func = current_function | |
if not func and (current_address & 1) == 0: | |
address = current_address + 1 | |
funcs = bv.get_functions_at(address) | |
func = funcs[0] if funcs else None | |
if not func: | |
log_error(f'Cannot find a function at current_address {current_address:#x}') | |
else: | |
address = func.start | |
if func.arch == Architecture['armv7']: | |
new_platform = Platform['thumb2'] | |
address |= 1 | |
elif func.arch == Architecture['thumb2']: | |
new_platform = Platform['armv7'] | |
address &= ~3 | |
else: | |
raise AttributeError("This snippet only works on thumb or armv7 functions") | |
bv.remove_user_function(func) | |
bv.create_user_function(address, new_platform) | |
platform_name = str(new_platform) | |
article = 'an' if platform_name[0].lower() in 'aeiou' else 'a' | |
log_info(f"Creating {article} {str(new_platform)} function at {(address - (address % 2)):#x}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# trigger actions | |
# | |
# Trigger actions in the UI Action system via a plugin or snippet | |
# Use the command-palette (CMD/CTL-P) to find action descriptions | |
# Not compatible with headless of course | |
from binaryninjaui import UIActionHandler, DockHandler | |
def triggerAction(action): | |
handler = UIActionHandler().actionHandlerFromWidget(DockHandler.getActiveDockHandler().parent()) | |
handler.executeAction(action) | |
action="About..." | |
execute_on_main_thread_and_wait(lambda: triggerAction(action)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# uidf example | |
# | |
# This snippet graciously provided by https://twitter.com/xmppwocky as an | |
# example of a workflow leveraging the User-Informed-Data-Flow system | |
# (https://binary.ninja/2020/09/10/user-informed-dataflow.html). | |
# | |
# Used to handle C++ classes with virtual methods but no subclasses | |
vt_ty_field = TextLineField("vtable type") | |
vt_addr_field = AddressField("vtable address") | |
assert get_form_input([vt_ty_field, vt_addr_field], "hello") | |
vt_type = bv.get_type_by_name(vt_ty_field.result) | |
vt_addr = vt_addr_field.result | |
assert vt_type is not None | |
def proc_fn(fn): | |
candidates = [] | |
for var in fn.vars: | |
if var.type.target == vt_type: | |
candidates.append((fn, var)) | |
return candidates | |
candidates = [] | |
for fn in bv.functions: | |
candidates += proc_fn(fn) | |
if get_choice_input( | |
"Set value of {} variables?".format(len(candidates)), | |
"Confirm", ["Yes", "No"]) == 1: | |
raise Exception("cancelled") | |
for fn, var in candidates: | |
for defn in fn.mlil.get_var_definitions(var): | |
# could double-check that this comes from a vtable | |
# field here, if we wanted... | |
fn.set_user_var_value(var, defn.address, | |
PossibleValueSet.constant_ptr(vt_addr)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# unimplemented instructions | |
# | |
# Create a report showing unimplemented instructions | |
from collections import defaultdict | |
insts = defaultdict(lambda:0) | |
addrs = {} | |
for ins in bv.llil_instructions: | |
if ins.operation == LowLevelILOperation.LLIL_UNIMPL: | |
mnem = bv.get_disassembly(ins.address, ins.il_basic_block.arch).split()[0] | |
insts[mnem + '-' + ins.il_basic_block.arch.name] = insts[mnem + '-' + ins.il_basic_block.arch.name] + 1 | |
addrs[mnem + '-' + ins.il_basic_block.arch.name] = ins.address | |
lines = list(insts.items()) | |
lines = sorted(lines, key=lambda x: x[1], reverse=True) | |
contents = "| Memonic-Arch | Count | Example Address |\n|---|---|---|\n" | |
for mnem, count in lines: | |
contents += f"|{mnem}|{count}|[{hex(addrs[mnem])}](binaryninja://?expr={hex(addrs[mnem])})|\n" | |
bv.show_markdown_report("Unimplemented counts", contents, contents) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# update snippets | |
# | |
# Automatically download and update this collection of snippets to your local snippet folder | |
from zipfile import ZipFile | |
from tempfile import TemporaryFile | |
import os | |
#TODO: Merge remote with local description or hotkey changes (AKA: if filename matches, skip the first two lines, truncate, re-write the rest) | |
domain = b'https://gist.github.com' | |
path = b'/psifertex/6fbc7532f536775194edd26290892ef7' # Feel free to adapt to your own setup | |
subfolder = 'default' # Change to save the snippets to a different sub-folder | |
tab2space = False | |
width = 4 | |
def download(url): | |
# Can also use 'CoreDownloadProvider' or 'PythonDownloadProvider' as keys here | |
provider = DownloadProvider[Settings().get_string('network.downloadProviderName')].create_instance() | |
code, data = provider.get_response(url) | |
if code == 0: | |
return data | |
else: | |
raise ConnectionError("Unsuccessful download of %s" % url) | |
def update_snippets(): | |
if not interaction.show_message_box('Warning', "Use at your own risk. Do you want to automatically overwrite local snippets from gist?", buttons=MessageBoxButtonSet.YesNoButtonSet): | |
return | |
snippetPath = os.path.realpath(os.path.join(user_plugin_path(), '..', 'snippets', subfolder)) | |
if not os.path.isdir(snippetPath): | |
os.makedirs(snippetPath) | |
url = domain + path | |
log_info("Downloading from: %s" % url) | |
source = download(url) | |
zipPath = [s for s in source.split(b'\"') if s.endswith(b'.zip')] | |
if len(zipPath) != 1: | |
log_error("Update failed.") | |
return | |
url = domain + zipPath[0] | |
log_info("Downloading from: %s" % url) | |
zip = download(url) | |
with TemporaryFile() as f: | |
f.write(zip) | |
with ZipFile(f, 'r') as zip: | |
for item in zip.infolist(): | |
if item.filename[-1] == '/': | |
continue | |
basename = os.path.basename(item.filename) | |
with open(os.path.join(snippetPath, basename), 'wb') as f: | |
if tab2space: | |
f.write(zip.read(item).replace(b'\t', b' ' * width)) | |
else: | |
f.write(zip.read(item)) | |
log_info("Extracting %s" % item.filename) | |
update_snippets() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment