Last active
July 20, 2025 15:27
-
-
Save utkonos/0aa6e1d491b703489359ca40f1c1cb4d to your computer and use it in GitHub Desktop.
Binary Ninja plugin for copying opcode bytes to the clipboard formatted to YARA best practice
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
"""Binary Ninja plugin for copying opcode bytes to the clipboard formatted to YARA best practice.""" | |
import json | |
import re | |
from binaryninja.enums import DisassemblyOption, HighlightStandardColor, InstructionTextTokenType, LinearDisassemblyLineType | |
from binaryninja.function import DisassemblySettings | |
from binaryninja.interaction import get_text_line_input | |
from binaryninja.log import Logger | |
from binaryninja.plugin import PluginCommand | |
from binaryninja.settings import Settings | |
import PySide6 | |
s = Settings() | |
s.register_group('c', 'Copy Bytes') | |
setting = { | |
'description': 'YARA format bytes copied as uppercase.', | |
'title': 'Uppercase Bytes', | |
'default': False, | |
'type': 'boolean' | |
} | |
s.register_setting('c.upper', json.dumps(setting)) | |
log = Logger(0, 'CopyYARA') | |
def get_texts(bv, addr, end): | |
"""Get the opcode and disassembly texts in an address range.""" | |
settings = DisassemblySettings.default_linear_settings() | |
settings.set_option(DisassemblyOption.ExpandLongOpcode) | |
pos = bv.get_linear_disassembly_position_at(addr, settings) | |
opcode_bytes = list() | |
disasm_text = list() | |
while True: | |
lines = bv.get_next_linear_disassembly_lines(pos) | |
exit = False | |
for line in lines: | |
if line.contents.address < addr: | |
continue | |
if line.contents.address >= end: | |
exit = True | |
break | |
if line.type != LinearDisassemblyLineType.CodeDisassemblyLineType: | |
continue | |
if InstructionTextTokenType.OpcodeToken not in {t.type for t in line.contents.tokens}: | |
continue | |
for token in line.contents.tokens: | |
if token.type is InstructionTextTokenType.OpcodeToken: | |
if s.get_bool('c.upper'): | |
out = token.text.upper() | |
else: | |
out = token.text | |
opcode_bytes.append(out) | |
blacklist = [InstructionTextTokenType.TagToken] | |
line_text = ''.join([t.text for t in line.contents.tokens if t.type not in blacklist]) | |
no_auto_vars = re.sub(' {var_.+?}', '', line_text) | |
disasm_text.append(no_auto_vars) | |
if exit: | |
break | |
return opcode_bytes, disasm_text | |
def format_output(bv, addr, length, yara_string=False, named=False): | |
"""Copy properly formatted bytes to the clipboard.""" | |
end = addr + length | |
opcode_bytes, disasm_text = get_texts(bv, addr, end) | |
# Copy decoded string to clipboard. | |
clip = PySide6.QtGui.QGuiApplication.clipboard() | |
opcode_output = ' '.join(opcode_bytes) | |
if named: | |
var_name = get_text_line_input('Variable Name', 'Name YARA Variable').decode() | |
else: | |
var_name = 'op' | |
if yara_string: | |
template = ' ${} = {{ {} }}\n // {}' | |
indent = '\n' + ' '*12 + '// ' | |
disasm_output = indent.join(disasm_text) | |
output = template.format(var_name, opcode_output, disasm_output) | |
else: | |
output = opcode_output | |
clip.setText(output) | |
def format_opcode(bv, addr, length): | |
"""Output a YARA formatted opcode string.""" | |
format_output(bv, addr, length) | |
def format_yara_string(bv, addr, length): | |
"""Output a formatted YARA string.""" | |
format_output(bv, addr, length, yara_string=True) | |
def format_named_yara_string(bv, addr, length): | |
"""Output a formatted YARA string.""" | |
format_output(bv, addr, length, yara_string=True, named=True) | |
def set_yara_target(bv, start, length): | |
"""Set tag and highlighting on a YARA rule target.""" | |
tag_name = 'YARA Target' | |
if tag_name not in bv.tag_types: | |
bv.create_tag_type(tag_name, b'\xf0\x9f\x8e\xaf') | |
log.log_info(f'Tag created: {tag_name}') | |
f = next(iter(bv.get_functions_containing(start))) | |
clip = PySide6.QtGui.QGuiApplication.clipboard() | |
rule_name = clip.text() | |
f.add_tag(tag_name, rule_name, start) | |
cur = bv.get_linear_disassembly_position_at(start) | |
addr = 0 | |
end = start + length | |
while addr <= end: | |
for line in cur.lines: | |
addr = line.contents.address | |
if addr < start or addr >= end: | |
continue | |
f.set_user_instr_highlight(addr, HighlightStandardColor.RedHighlightColor) | |
cur.next() | |
description = 'Copy YARA format opcodes to clipboard for selected instructions.' | |
PluginCommand.register_for_range('Copy YARA\\\U000E0102Opcodes', description, format_opcode) | |
description = 'Copy fully formatted YARA string to clipboard for selected instructions.' | |
PluginCommand.register_for_range('Copy YARA\\\U000E0100String', description, format_yara_string) | |
description = 'Copy fully formatted named YARA string to clipboard for selected instructions.' | |
PluginCommand.register_for_range('Copy YARA\\\U000E0101Named String', description, format_named_yara_string) | |
description = 'Paste the YARA rule name as a YARA target tag and highlight the selection.' | |
PluginCommand.register_for_range('Copy YARA\\\U000E0103Set Target', description, set_yara_target) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment