-
-
Save bb33bb/3b6e6878d670f3d87c394d03c4d6e5a1 to your computer and use it in GitHub Desktop.
my current collection of snippets
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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
# batch example | |
# | |
# Intended to be run from the CLI, not as a snippet | |
# Remove these top lines to run headlessly (requires a commercial license) | |
#!/usr/bin/env python3 | |
import binaryninja | |
import glob | |
import filetype | |
import os | |
from tabulate import tabulate | |
from collections import defaultdict | |
binaryninja.disable_default_log() | |
for file in glob.glob("./**/*", recursive=True): | |
if os.path.isfile(file): | |
guess = filetype.guess(file) | |
if guess and guess.extension == "elf": | |
print(f"Filename: {file}") | |
try: | |
with binaryninja.load(file, update_analysis=False) as bv: | |
# Imports don't require analysis, otherwise remove update_analysis param | |
syms = [] | |
for sym in bv.get_symbols_of_type(binaryninja.SymbolType.ImportAddressSymbol): | |
syms.append([sym.name, hex(sym.address)]) | |
print(tabulate(syms, headers=["Import Name", "Offset"])) | |
except: | |
print("Invalid architecture.") | |
print("\n") |
This file contains hidden or 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 = PySide6.QtGui.QGuiApplication.clipboard() | |
clip.setText('%x' % (fileoffset)) |
This file contains hidden or 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
# componentize cpp | |
# | |
# Using the components API to sort CPP symbols into class hierarchies | |
def ___sortCPPSymbols(): | |
bv.begin_undo_actions() | |
cpp_comp = bv.get_component_by_path("C++ Classes") | |
if not cpp_comp: | |
cpp_comp = bv.create_component("C++ Classes") | |
for func in bv.functions: | |
if len(demangle_gnu3(bv.arch, func.name)[1]) < 2 or isinstance(demangle_gnu3(bv.arch, func.name)[1], str): | |
continue | |
namespace = demangle_gnu3(bv.arch, func.name)[1][0] | |
comp = bv.get_component_by_path("C++ Classes/" + namespace) | |
if comp is None: | |
comp = bv.create_component(namespace, cpp_comp) | |
comp.add_function(func) | |
bv.commit_undo_actions() | |
___sortCPPSymbols() |
This file contains hidden or 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
# componentize sections | |
# | |
# Using the components API to sort symbols in different sections into folders | |
def ___sortSymbolsBySectionName(): | |
bv.begin_undo_actions() | |
for func in bv.functions: | |
sects = bv.get_sections_at(func.start) | |
for sect in sects: | |
comp = bv.get_component_by_path(sect.name) | |
if comp is None: | |
comp = bv.create_component(sect.name) | |
comp.add_function(func) | |
bv.commit_undo_actions() | |
___sortSymbolsBySectionName() |
This file contains hidden or 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 types with offsets included | |
# | |
# Requested by a customer who wanted to be able to copy structures simila to how they are shown in the UI with hex offsets | |
import binaryninjaui | |
import PySide6 | |
typeName = interaction.get_text_line_input("Type", "Type to copy:").decode('utf8') | |
typeVar = bv.get_type_by_name(typeName) | |
if not typeVar: | |
# check libraries | |
for lib in bv.type_libraries: | |
typeVar = lib.get_named_type(typeName) | |
if typeVar: | |
break | |
if typeVar: | |
lines = "" | |
for line in typeVar.get_lines(bv, typeName): | |
lines += f"{hex(line.offset)} {str(line)}\n" | |
clip = PySide6.QtGui.QGuiApplication.clipboard() | |
clip.setText(lines) | |
else: | |
log_info(f"Could not find a type with the name {typeName}") |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 PySide6.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 sorted(settings): | |
for setting in sorted(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 sorted(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 sorted(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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 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 fnname in sus: | |
color = HighlightStandardColor.OrangeHighlightColor | |
for sym in bv.get_symbols_by_name(fnname): | |
for ref in bv.get_code_refs(sym.address): | |
log_info(f"Highlighting dangerous call at {hex(ref.address)}") | |
ref.function.set_user_instr_highlight(ref.address, color) |
This file contains hidden or 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
# history dump | |
# | |
# Dump the entire history of the scripting console of this instance of Binary Ninja to the log | |
import binaryninjaui #needed to load PySide | |
from binaryninja import log_info | |
from PySide6.QtCore import QSettings | |
settings = QSettings() | |
history = settings.value("script/history") | |
for line in history: | |
log_info(line) |
This file contains hidden or 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 hidden or 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 hidden or 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/[email protected]/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 hidden or 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 hidden or 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 ida map | |
# | |
# Bare-bones script with no error checking to load a .map file created by IDA | |
import re | |
pattern = re.compile(r"\d+:([0-9A-Fa-f]+)\s+(.+)$") | |
mapfile = get_open_filename_input("map file:", "All Files (*)") | |
if mapfile is not None and os.access(mapfile, os.R_OK): | |
with open(mapfile, "r+", encoding="utf-8") as f: | |
for line in f.readlines(): | |
matching = pattern.search(line) | |
if not matching: | |
continue | |
addr = int(matching.group(1), 16) | |
fnname = matching.group(2) | |
if bv.get_function_at(addr): | |
bv.define_user_symbol(Symbol(SymbolType.FunctionSymbol, addr, fnname)) | |
else: | |
bv.define_user_symbol(Symbol(SymbolType.DataSymbol, addr, fnname)) |
This file contains hidden or 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 const | |
# | |
# Make the selected variable const | |
def make_const(token, fn): | |
if token.localVarValid: | |
var = Variable.from_core_variable(fn, token.localVar) | |
varType = var.type.mutable_copy() | |
if isinstance(varType, PointerBuilder): | |
print("got here") | |
varType.target.const = True | |
else: | |
varType.const = True | |
var.type = varType | |
print(f"Made {repr(var)} have type {repr(var.type)}") | |
elif current_token.type == InstructionTextTokenType.CodeSymbolToken: | |
#TODO | |
pass | |
else: | |
log_warn("Unhandled token") | |
if not current_token or not current_function: | |
log_warn("No valid variable token selected") | |
else: | |
make_const(current_token, current_function) |
This file contains hidden or 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 hidden or 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 / new tab | |
# To modify it for a new tab | |
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 hidden or 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) | |
settings.set_option(DisassemblyOption.WaitForIL, True) | |
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 | |
print(c_source(bv, current_function)) |
This file contains hidden or 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 hidden or 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 hidden or 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 = PySide6.QtGui.QGuiApplication.clipboard() | |
clip.setText('%x' % (dword)) |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 exports | |
# | |
# Open a new tab with a list of all exports, can be slow for extremely large files, modify to dump an external html for a perf improvement | |
import time | |
def genRow(sym, exportType): | |
return f"<tr><td><a href=\"binaryninja:?expr={hex(sym.address)}\">{hex(sym.address)}</a></td><td>{sym.short_name}</td><td>{exportType}</td></tr>\n" | |
begin = time.time() | |
# [ Offset, Symbol, Type] | |
html = "<h1>Exports</h1>\n\n" | |
html += "<table><tr><th>Offset</th><th>Name</th><th>Type</th></tr>\n" | |
html += f"<tr><td>{hex(bv.entry_point)}</td><td>{bv.entry_function.name}</td><td>Entry</td></tr>\n" | |
for sym in bv.get_symbols_of_type(SymbolType.FunctionSymbol): | |
if sym.binding == SymbolBinding.GlobalBinding: | |
html += genRow(sym, "Export Function") | |
for sym in bv.get_symbols_of_type(SymbolType.DataSymbol): | |
if sym.binding == SymbolBinding.GlobalBinding: | |
html += genRow(sym, "Export Data Var") | |
html += "</table>" | |
lines = len(html.split("\n")) | |
generated=time.time() | |
log_info(f"Lines: {lines}") | |
log_info(f"Time: {generated - begin}") | |
bv.show_html_report("Exports", html) | |
render = time.time() | |
log_info(f"Render: {render - generated }") |
This file contains hidden or 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 hidden or 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 PySide6 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_() | |
# Since Snippets now run on background threads, UI code needs to explicitly ensure that it is run on the main thread. | |
#execute_on_main_thread(qpixmap2) | |
#execute_on_main_thread(qpixmap) | |
#execute_on_main_thread(basic) | |
#execute_on_main_thread(qicon) | |
from binaryninjaui import (UIAction, UIActionHandler) | |
from binaryninja import log_info | |
from PySide6.QtWidgets import QDialog, QLabel, QVBoxLayout | |
def qd(context): | |
dialog = QDialog(parent=context.widget) | |
dialog.setWindowTitle("Hello Dialog") | |
label = QLabel("Hello text") | |
layout = QVBoxLayout() | |
layout.addWidget(label) | |
dialog.setLayout(layout) | |
dialog.show() | |
execute_on_main_thread_and_wait(lambda: qd(uicontext)) | |
# Note: outside of snippets you would use: | |
# UIAction.registerAction("Simple") | |
# UIActionHandler.globalActions().bindAction("Simple", UIAction(qd)) | |
# but snippets can provide the same UI context necessary to parent a QDialog |
This file contains hidden or 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 hidden or 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
# apply function types to structure parameters | |
# | |
# Lets you select a struct to automatically search type libraries for function signatures to apply type information | |
# Thanks @d0mnik! Original code: https://gist.github.com/d0mnik/1ee595605fc7c9a39364fdbfb660f268 | |
from binaryninja import * | |
api_call_protos = {} | |
type_to_parse = get_text_line_input("Enter structure name to parse:", "Struct Parser") | |
if type_to_parse is None: | |
exit() | |
struct_name = type_to_parse.decode() | |
if bv.get_type_by_name(struct_name) is None: | |
show_message_box(f"Struct Parser", "Struct with specified name not found!") | |
exit() | |
for typelib in bv.type_libraries: | |
for name, obj in typelib.named_objects.items(): | |
if not isinstance(obj, FunctionType): # filter for function calls | |
continue | |
api_call_protos[name.name[0]] = obj | |
mut: StructureBuilder = bv.get_type_by_name(struct_name).mutable_copy() | |
for member_idx, member in enumerate(mut.members): | |
for name, proto in api_call_protos.items(): | |
if name.lower() not in member.name.lower(): | |
continue | |
# replace the prototype | |
proto_pointer = PointerType.create(bv.arch, proto) | |
mut.replace(member_idx, proto_pointer, member.name) | |
break | |
bv.define_user_type(struct_name, mut) |
This file contains hidden or 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 hidden or 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 hidden or 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 UIContext | |
action = "About..." | |
execute_on_main_thread_and_wait(lambda: UIContext.activeContext().getCurrentActionHandler().executeAction(action)) |
This file contains hidden or 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 hidden or 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 hidden or 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
# unlifted opcodes | |
# | |
# Script to summarize frequency of unlifted opcodes for a given binary | |
# build list of all unimplemented opcodes | |
opcodes={} | |
for f in bv.functions: | |
for t in f.tags: # tuple of (arch, address, tag) | |
data = t[2].data | |
if "unimplemented" in data: | |
opcode = data.split('"')[1] | |
if opcode in opcodes.keys(): | |
opcodes[opcode].append(t[1]) | |
else: | |
opcodes[opcode] = [t[1]] | |
# print them out, sorted by number of occurrences | |
keys = list(opcodes.keys()) | |
for key in sorted(opcodes, key=lambda x: len(opcodes[x]), reverse=True): | |
if len(opcodes[key]) <= 5: | |
print(f"{len(opcodes[key])}: {key}: [{', '.join(hex(x) for x in opcodes[key])}]") | |
else: | |
print(f"{len(opcodes[key])}: {key}: [{', '.join(hex(x) for x in opcodes[key][0:5])}, ...]") |
This file contains hidden or 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