Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
my current collection of snippets
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.
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
# 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!"
)
# 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))
# 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))
# 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()
# 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))
# 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)
# 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)
# 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()
# 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")
# 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)
# 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
# create common tags for function attributes like leaf, loop, stub, etc
# See below for the tags variable for the various tag types created/added
large = 40
complex = 40
def init_tags():
for tagType in tags:
if tagType['name'] not in bv.tag_types.keys():
bv.create_tag_type(tagType['name'], tagType['emoji'])
def cc(fn):
nodes = len(fn.basic_blocks)
edges = sum(len(x.outgoing_edges) for x in fn.basic_blocks)
connected = 1 #always 1 for binary control flow graphs, kinda the whole point
return edges - nodes + 2 * connected
def iscomplex(fn):
return cc(fn) > complex
def isleaf(fn):
return len(fn.callees) == 0
def islarge(fn):
return len(fn.basic_blocks) >= large
def isstub(fn):
"""Returns true if a function is likely only a stub"""
if len(fn.basic_blocks) > 1 or len(fn.llil.basic_blocks) > 1:
return False
if fn.llil.basic_blocks[0].has_undetermined_outgoing_edges or len(fn.callees) == 1:
return True
return False
def hasloop(fn):
"""Returns true if a function has a 'strange loop' (ignore this, inside joke)"""
for bb in fn.basic_blocks:
if bb in bb.dominance_frontier:
return True
return False
tags = [ \
{'emoji': '🍃', 'name': 'Leaf Function', 'description': 'Leaf function (does not call anything else)', 'fn': isleaf},
{'emoji': '🔄', 'name': 'Loop Function', 'description': 'Function contains a loop', 'fn': hasloop},
{'emoji': '🥾', 'name': 'Stub Function', 'description': 'Function is likely a stub (only contains one basic block and one call or indirect jump)', 'fn': isstub},
{'emoji': '🐘', 'name': 'Large Function', 'description': 'Function is "large" (IE, it has more than the blocks defined above)', 'fn': islarge},
{'emoji': '🤯', 'name': 'Complex Function', 'description': 'Function is "complex" (IE, it has a cyclomatic complexity greater than a defined constant)', 'fn': iscomplex},
]
init_tags()
for fn in bv.functions:
for tagType in tags:
if tagType['fn'](fn):
fn.create_user_function_tag(bv.tag_types[tagType['name']], '', unique=True)
# 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()
# debug HLIL decompilation
#
# trigger a debug report to show all intermediate stages of analysis creating HLIL
current_function.request_debug_report("high_level_il")
# 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 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))))
# 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)
# 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')
# 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()
# navigate to a file offset
#
# Takes a file offset, converts it to a virtual address and navigates there. Note that the relative checkbox wont work but we use this dialog to get calculation feature
while True:
offset=get_address_input("File offset: ", "Offset")
if not offset:
break
if offset_to_vaddr(offset):
vaddr = bv.get_address_for_data_offset(offset)
log_info("Navigating to file offset %x" % vaddr)
bv.navigate(bv.view, vaddr)
break
# 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))
# 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
# 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))
# 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
# 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)
# 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()
# 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)
annotation += chr(instruction.tokens[-1].value)
log_info("Adding comment for string: %s" % annotation)
current_function.set_comment_at(current_selection[0], annotation)
# 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.')
# 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}")
# 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))
# 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))
# checks all LLIL operations to find any unlimiplemented lifting
#
# Especially useful when building a new architecture and wanting to see what disassembly you're running into with real binaries to prioritize
from binaryninja.lowlevelil import LowLevelILInstruction
def visit(unimp, expr):
for field in LowLevelILInstruction.ILOperations[expr.operation]:
if field[1] == "expr":
visit(unimp, getattr(expr, field[0]))
if expr.operation in [LowLevelILOperation.LLIL_UNIMPL, LowLevelILOperation.LLIL_UNIMPL_MEM]:
if hasattr(expr, "expr_index"):
index = expr.expr_index
else:
index = 0
dis = bv.get_disassembly(expr.address)
mnemonic = dis.split(" ")[0]
if mnemonic in unimp.keys():
unimp[mnemonic].append([expr.address, index])
else:
unimp[mnemonic] = [[expr.address, index]]
unimp = {}
for llili in bv.llil_instructions:
visit(unimp, llili)
log_info(f"Found {len(unimp)} total unimplemented mnemonics")
for k, v in sorted(unimp.items(), key=len):
log_info(f"Unimplemented mnemonic: {k}:")
for x in v:
log_info(f" {hex(x[0])} / {x[1]}")
# 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
def download(url):
provider = DownloadProvider.list[0].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:
f.write(zip.read(item))
log_info("Extracting %s" % item.filename)
update_snippets()
@trib0r3

This comment has been minimized.

Copy link

@trib0r3 trib0r3 commented Jun 24, 2020

Hello @psifertex, I modified a bit the auto_strings.py, now it can also rename (or create) global variable based on the string value, it is not touching already defined symbols. Additionally it is no longer noisy at the logger level, but instead it shows the message box with short info about performed changes:

# Auto-rename string variables based on the string value
#

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!"
)
@psifertex

This comment has been minimized.

Copy link
Owner Author

@psifertex psifertex commented Jun 24, 2020

Hmm, I personally prefer log_info even if it's just a summary at the end for ones I plan to use, but I like the use of the interaction API as an example since I don't have too many here so I'll add that. Thanks!

@shinmai

This comment has been minimized.

Copy link

@shinmai shinmai commented Oct 7, 2020

Neither create_executable sections.py nor create_symbol seem to work for me. No errors in the Log window, but nothing actually happens.

@psifertex

This comment has been minimized.

Copy link
Owner Author

@psifertex psifertex commented Oct 7, 2020

What does bv.segments show by itself before running the snippet? Also, what type of file are you analyzing?

@galenbwill

This comment has been minimized.

Copy link

@galenbwill galenbwill commented Jul 12, 2021

Is there a reason unimplemented doesn't have the .py extension?

This prevents the snippet downloaded by update_snippets from being accessible in the snippets editor.

@psifertex

This comment has been minimized.

Copy link
Owner Author

@psifertex psifertex commented Jul 13, 2021

Good catch, fixed.

@galenbwill

This comment has been minimized.

Copy link

@galenbwill galenbwill commented Jul 13, 2021

Fixes for toggle_arm_thumb.py:

# 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}")
@galenbwill

This comment has been minimized.

Copy link

@galenbwill galenbwill commented Oct 14, 2021

looks like the DownloadProvider API has changed, breaking update_snippets:

  File "<string>", line 51, in <module>
  File "<string>", line 31, in update_snippets
  File "<string>", line 16, in download
AttributeError: type object 'DownloadProvider' has no attribute 'list'
@marpie

This comment has been minimized.

Copy link

@marpie marpie commented Nov 30, 2021

To fix the update_snippets.py just change the DownloadProvider to an iterator and get the first element:

diff --git a/snippets/default/update_snippets.py b/snippets/default/update_snippets.py
index 7d7b945..d4d579d 100644
--- a/snippets/default/update_snippets.py
+++ b/snippets/default/update_snippets.py
@@ -13,7 +13,7 @@ path = b'/psifertex/6fbc7532f536775194edd26290892ef7' # Feel free to adapt to yo
 subfolder = 'default'                                  # Change to save the snippets to a different sub-folder
 
 def download(url):
-       provider = DownloadProvider.list[0].create_instance()
+       provider = next(iter(DownloadProvider)).create_instance()
        code, data = provider.get_response(url)
        if code == 0:
                return data

@galenbwill

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment