summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/__init__.py1
-rw-r--r--python/basicblock.py105
-rw-r--r--python/binaryview.py17
-rw-r--r--python/examples/args_render_layer.py213
-rw-r--r--python/examples/follow_reg_render_layer.py209
-rw-r--r--python/flowgraph.py97
-rw-r--r--python/function.py82
-rw-r--r--python/lineardisassembly.py105
-rw-r--r--python/renderlayer.py450
9 files changed, 1219 insertions, 60 deletions
diff --git a/python/__init__.py b/python/__init__.py
index 1cdc541e..db4e8e69 100644
--- a/python/__init__.py
+++ b/python/__init__.py
@@ -81,6 +81,7 @@ from .undo import *
from .fileaccessor import *
from .languagerepresentation import *
from .lineformatter import *
+from .renderlayer import *
# We import each of these by name to prevent conflicts between
# log.py and the function 'log' which we don't import below
from .log import (
diff --git a/python/basicblock.py b/python/basicblock.py
index 6c03fe32..a8dc85a8 100644
--- a/python/basicblock.py
+++ b/python/basicblock.py
@@ -23,6 +23,7 @@ from dataclasses import dataclass
from typing import Generator, Optional, List, Tuple
# Binary Ninja components
+import binaryninja
from . import _binaryninjacore as core
from .enums import BranchType, HighlightStandardColor
from . import binaryview
@@ -88,6 +89,33 @@ class BasicBlock:
if core is not None:
core.BNFreeBasicBlock(self.handle)
+ @classmethod
+ def _from_core_block(cls, block: core.BNBasicBlockHandle) -> Optional['BasicBlock']:
+ """From a BNBasicBlockHandle, get a BasicBlock or one of the IL subclasses (takes ref)"""
+ func_handle = core.BNGetBasicBlockFunction(block)
+ if not func_handle:
+ core.BNFreeBasicBlock(block)
+ return None
+
+ view = binaryview.BinaryView(handle=core.BNGetFunctionData(func_handle))
+ func = _function.Function(view, func_handle)
+
+ if core.BNIsLowLevelILBasicBlock(block):
+ return binaryninja.lowlevelil.LowLevelILBasicBlock(
+ block, binaryninja.lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func),
+ view
+ )
+ elif core.BNIsMediumLevelILBasicBlock(block):
+ mlil_func = binaryninja.mediumlevelil.MediumLevelILFunction(
+ func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func
+ )
+ return binaryninja.mediumlevelil.MediumLevelILBasicBlock(block, mlil_func, view)
+ elif core.BNIsHighLevelILBasicBlock(block):
+ hlil_func = binaryninja.highlevelil.HighLevelILFunction(func.arch, core.BNGetBasicBlockHighLevelILFunction(block), func)
+ return binaryninja.highlevelil.HighLevelILBasicBlock(block, hlil_func, view)
+ else:
+ return BasicBlock(block, view)
+
def __repr__(self):
arch = self.arch
if arch:
@@ -211,6 +239,78 @@ class BasicBlock:
return self._func
@property
+ def il_function(self) -> Optional['_function.ILFunctionType']:
+ """IL Function of which this block is a part, if the block is part of an IL Function."""
+ func = self.function
+ if func is None:
+ return None
+ il_type = self.function_graph_type
+ if il_type == _function.FunctionGraphType.NormalFunctionGraph:
+ return None
+ elif il_type == _function.FunctionGraphType.LowLevelILFunctionGraph:
+ return func.low_level_il
+ elif il_type == _function.FunctionGraphType.LiftedILFunctionGraph:
+ return func.lifted_il
+ elif il_type == _function.FunctionGraphType.LowLevelILSSAFormFunctionGraph:
+ return func.low_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.MediumLevelILFunctionGraph:
+ return func.medium_level_il
+ elif il_type == _function.FunctionGraphType.MediumLevelILSSAFormFunctionGraph:
+ return func.medium_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILFunctionGraph:
+ return func.mapped_medium_level_il
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILSSAFormFunctionGraph:
+ return func.mapped_medium_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelILFunctionGraph:
+ return func.high_level_il
+ elif il_type == _function.FunctionGraphType.HighLevelILSSAFormFunctionGraph:
+ return func.high_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelLanguageRepresentationFunctionGraph:
+ return func.high_level_il
+ else:
+ return None
+
+ @property
+ def il_function_if_available(self) -> Optional['_function.ILFunctionType']:
+ """IL Function of which this block is a part, if the block is part of an IL Function, and if the function has generated IL already."""
+ func = self.function
+ if func is None:
+ return None
+ il_type = self.function_graph_type
+ if il_type == _function.FunctionGraphType.NormalFunctionGraph:
+ return None
+ elif il_type == _function.FunctionGraphType.LowLevelILFunctionGraph:
+ return func.llil_if_available
+ elif il_type == _function.FunctionGraphType.LiftedILFunctionGraph:
+ return func.lifted_il_if_available
+ elif il_type == _function.FunctionGraphType.LowLevelILSSAFormFunctionGraph:
+ if func.llil_if_available is None:
+ return None
+ return func.llil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.MediumLevelILFunctionGraph:
+ return func.mlil_if_available
+ elif il_type == _function.FunctionGraphType.MediumLevelILSSAFormFunctionGraph:
+ if func.mlil_if_available is None:
+ return None
+ return func.mlil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILFunctionGraph:
+ return func.mmlil_if_available
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILSSAFormFunctionGraph:
+ if func.mmlil_if_available is None:
+ return None
+ return func.mmlil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelILFunctionGraph:
+ return func.hlil_if_available
+ elif il_type == _function.FunctionGraphType.HighLevelILSSAFormFunctionGraph:
+ if func.hlil_if_available is None:
+ return None
+ return func.hlil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelLanguageRepresentationFunctionGraph:
+ return func.hlil_if_available
+ else:
+ return None
+
+ @property
def view(self) -> Optional['binaryview.BinaryView']:
"""BinaryView that contains the basic block (read-only)"""
if self._view is not None:
@@ -426,6 +526,11 @@ class BasicBlock:
self.set_user_highlight(value)
@property
+ def function_graph_type(self) -> '_function.FunctionGraphType':
+ """Type of function graph from which this block represents instructions"""
+ return _function.FunctionGraphType(core.BNGetBasicBlockFunctionGraphType(self.handle))
+
+ @property
def is_il(self) -> bool:
"""Whether the basic block contains IL"""
return core.BNIsILBasicBlock(self.handle)
diff --git a/python/binaryview.py b/python/binaryview.py
index 73ddfd39..c1a57765 100644
--- a/python/binaryview.py
+++ b/python/binaryview.py
@@ -9011,20 +9011,9 @@ to a the type "tagRECT" found in the typelibrary "winX64common"
def _LinearDisassemblyLine_convertor(
self, lines: core.BNLinearDisassemblyLineHandle
) -> 'lineardisassembly.LinearDisassemblyLine':
- func = None
- block = None
- line = lines[0]
- if line.function:
- func = _function.Function(self, core.BNNewFunctionReference(line.function))
- if line.block:
- block_handle = core.BNNewBasicBlockReference(line.block)
- assert block_handle is not None, "core.BNNewBasicBlockReference returned None"
- block = basicblock.BasicBlock(block_handle, self)
- color = highlight.HighlightColor._from_core_struct(line.contents.highlight)
- addr = line.contents.addr
- tokens = _function.InstructionTextToken._from_core_struct(line.contents.tokens, line.contents.count)
- contents = _function.DisassemblyTextLine(tokens, addr, color=color)
- return lineardisassembly.LinearDisassemblyLine(line.type, func, block, contents)
+ line = lineardisassembly.LinearDisassemblyLine._from_core_struct(lines[0])
+ core.BNFreeLinearDisassemblyLines(lines, 1)
+ return line
def find_all_text(
self, start: int, end: int, text: str, settings: Optional[_function.DisassemblySettings] = None,
diff --git a/python/examples/args_render_layer.py b/python/examples/args_render_layer.py
new file mode 100644
index 00000000..bac0889e
--- /dev/null
+++ b/python/examples/args_render_layer.py
@@ -0,0 +1,213 @@
+import functools
+from typing import List, Mapping, Tuple, Iterator
+
+from binaryninja import DisassemblyTextLine, LowLevelILInstruction, LowLevelILOperation, \
+ TypeClass, DisassemblyTextRenderer, MediumLevelILFunction, \
+ MediumLevelILCallSsa, MediumLevelILVarSsa, MediumLevelILConstBase, \
+ MediumLevelILInstruction, MediumLevelILTailcallSsa, MediumLevelILOperation, \
+ MediumLevelILVarPhi, log_debug, RenderLayer, BasicBlock, InstructionTextTokenType, \
+ RenderLayerDefaultEnableState
+
+"""
+Render Layer that shows you where the arguments to calls are set, for Disasm/LLIL.
+- Adds "Argument '<arg>' for call at <callsite>" comments to lines that set up call args
+- Adds "Call at <callsite>" comments to the call sites
+
+=========================================================================================
+
+But how do you determine the argument to a call?
+What seems like it has worked:
+- You can't determine that an instruction is a parameter, you have to go from the call to its parameters
+- Since trying to look up the call for an instruction is impossible, instead go through every call at once for a function (and memoize it)
+- LLIL is useless for looking this up, since it has no types and the call parameters often include a list of every register
+- Using MLIL, we can find all of the parameters as MLIL instructions, but we need to map them to LLIL so we can use them in the LLIL/Disasm display
+- How do we map them? Turns out that's rather inconvenient:
+ - Register arguments are generally pretty easy because they are just MLIL vars
+ - Stack arguments somehow also work out generally, the .llil on the MLIL points to the push()
+ - Constants are a mess and I just use the MLIL's address (this is often incorrect)
+ - Flags are completely unhandled for now
+ - Phis are handled by just looking up every var they use... probably not proper but sort of works
+- This fails in a couple of scenarios though, notably __builtin_xxxxxx() functions
+ - Which instruction specifies the length of a group of `mov qword [rbx+8], rax {0}` calls? I think it just picks one?
+ - The `rep` instructions could actually have these params resolved (they use real registers) but in practice this doesn't work
+ - Thunks are unhandled
+"""
+
+
+@functools.lru_cache(maxsize=64)
+def get_param_sites(mlil: MediumLevelILFunction) -> Mapping[LowLevelILInstruction, List[Tuple[MediumLevelILInstruction, int]]]:
+ """
+ For a given function, find all LLIL instructions that are parameters to a call,
+ and return a mapping for each instruction with all the calls that it maps to,
+ their corresponding MLIL call instruction, and which numbered parameter they are
+ in the call.
+
+ :param mlil: MLIL function to search
+ :return: Map of param sites as described above
+ """
+ call_sites = {}
+ mlil = mlil.ssa_form
+
+ # As a function to handle call and tailcall identically
+ def collect_call_params(call_site, dest, params):
+ def_sites = []
+ for i, param in enumerate(params):
+ llil = param.llil
+ if llil is not None:
+ def_sites.append((param, llil))
+ continue
+
+ match param:
+ case MediumLevelILVarSsa(src=var_src):
+ def_site = mlil.get_ssa_var_definition(var_src)
+ if def_site is not None and def_site.llil is not None:
+ def_sites.append((i, def_site.llil))
+ continue
+ # Handle phis by just looking up the def sites of all their sources
+ match def_site:
+ case MediumLevelILVarPhi(src=phis):
+ for phi in phis:
+ phi_def = mlil.get_ssa_var_definition(phi)
+ if phi_def is not None and phi_def.llil is not None:
+ def_sites.append((i, phi_def.llil))
+ case MediumLevelILConstBase():
+ # This is wrong, but it works (sometimes)
+ # Oh god, have I just quoted php.net
+ def_site_idx = mlil.llil.get_instruction_start(param.address)
+ if def_site_idx is not None:
+ def_sites.append((i, mlil.llil[def_site_idx].ssa_form))
+ continue
+
+ if len(def_sites) == 0:
+ log_debug(f"Could not find def site for param {i} in call at {call_site.address:#x}")
+
+ call_sites[call_site] = def_sites
+
+ for instr in mlil.instructions:
+ match instr:
+ case MediumLevelILCallSsa(dest=dest, params=params) as call_site:
+ collect_call_params(call_site, dest, params)
+ case MediumLevelILTailcallSsa(dest=dest, params=params) as call_site:
+ collect_call_params(call_site, dest, params)
+
+ # Inverse args
+ all_def_sites = {}
+ for call_site, params in call_sites.items():
+ for (param_idx, llil) in params:
+ if llil not in all_def_sites:
+ all_def_sites[llil] = []
+ else:
+ print(f"got two at {llil.instr_index} @ {llil.address:#x} -> {call_site.address:#x}")
+ all_def_sites[llil].append((call_site, param_idx))
+
+ return all_def_sites
+
+
+def get_llil_arg(llil: LowLevelILInstruction) -> Iterator[Tuple[str, MediumLevelILInstruction]]:
+ args = get_param_sites(llil.function.mlil)
+
+ if llil.ssa_form in args:
+ for call_site, param_idx in args[llil.ssa_form]:
+ target_type = call_site.function.get_expr_type(call_site.dest.expr_index)
+
+ # Try getting the param name from the call's type
+ if target_type is not None:
+ if target_type.type_class == TypeClass.PointerTypeClass:
+ target_type = target_type.target
+ if target_type.type_class == TypeClass.FunctionTypeClass:
+ target_params = target_type.parameters
+ if param_idx < len(target_params):
+ param_name = target_params[param_idx].name
+ if param_name == '':
+ param_name = f"arg{param_idx+1}"
+ yield param_name, call_site
+ continue
+
+ # Some calls have extra params that aren't reflected in their type
+ yield f"arg{param_idx+1}", call_site
+ return
+
+
+def apply_to_lines(lines, get_instr, renderer):
+ # So we don't process lines twice since we're iterating over a list as we modify it
+ skip_lines = []
+
+ # Tailcalls that don't return incorrectly mark the { Does not return } line as a call
+ ignore_calls = set()
+
+ for i, line in enumerate(lines):
+ if len(line.tokens) == 0:
+ continue
+ if i in skip_lines:
+ continue
+
+ llil_instr = get_instr(line)
+ if llil_instr is not None:
+ new_lines = []
+ for (arg, call) in get_llil_arg(llil_instr):
+ if call.operation == MediumLevelILOperation.MLIL_TAILCALL_SSA:
+ comment = f"Argument '{arg}' for tailcall at {call.address:#x}"
+ else:
+ comment = f"Argument '{arg}' for call at {call.address:#x}"
+ renderer.wrap_comment(new_lines, line, comment, False, " ", "")
+ for j, token in enumerate(line.tokens):
+ if token.type == InstructionTextTokenType.AddressSeparatorToken:
+ line.tokens = line.tokens[:j]
+ break
+
+ # Annotate calls too so we can see them easily next to their args
+ if llil_instr.address == line.address and llil_instr.address not in ignore_calls:
+ if llil_instr.operation in [
+ LowLevelILOperation.LLIL_CALL,
+ LowLevelILOperation.LLIL_CALL_SSA,
+ LowLevelILOperation.LLIL_TAILCALL,
+ LowLevelILOperation.LLIL_TAILCALL_SSA
+ ]:
+ ignore_calls.add(llil_instr.address)
+ if llil_instr.operation in [
+ LowLevelILOperation.LLIL_TAILCALL,
+ LowLevelILOperation.LLIL_TAILCALL_SSA
+ ]:
+ comment = f"Tailcall at {llil_instr.address:#x}"
+ else:
+ comment = f"Call at {llil_instr.address:#x}"
+ # Creating comments is a bit unwieldy at the moment
+ renderer.wrap_comment(new_lines, line, comment, False, " ", "")
+ for j, token in enumerate(line.tokens):
+ if token.type == InstructionTextTokenType.AddressSeparatorToken:
+ line.tokens = line.tokens[:j]
+ break
+
+ # If any of our lines changed, swap out the existing lines with the new ones
+ if len(new_lines) > 0:
+ lines.pop(i)
+ for j, new_line in enumerate(new_lines):
+ lines.insert(i + j, new_line)
+ skip_lines.append(i + j)
+ return lines
+
+
+class ArgumentsRenderLayer(RenderLayer):
+ name = "Annotate Call Parameters"
+ default_enable_state = RenderLayerDefaultEnableState.EnabledByDefaultRenderLayerDefaultEnableState
+
+ def apply_to_disassembly_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ renderer = DisassemblyTextRenderer(block.function)
+ return apply_to_lines(lines, lambda line: block.function.get_llil_at(line.address), renderer)
+
+ def apply_to_low_level_il_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ renderer = DisassemblyTextRenderer(block.function)
+ return apply_to_lines(lines, lambda line: line.il_instruction, renderer)
+
+
+ArgumentsRenderLayer.register()
diff --git a/python/examples/follow_reg_render_layer.py b/python/examples/follow_reg_render_layer.py
new file mode 100644
index 00000000..36e66576
--- /dev/null
+++ b/python/examples/follow_reg_render_layer.py
@@ -0,0 +1,209 @@
+from typing import List
+
+from PySide6.QtCore import QSettings
+from binaryninja import DisassemblyTextLine, LowLevelILOperation, DisassemblyTextRenderer, \
+ MediumLevelILOperation, \
+ RenderLayer, BasicBlock, InstructionTextTokenType, RenderLayerDefaultEnableState, \
+ PluginCommand, BinaryView, interaction, InstructionTextToken, RegisterValueType
+
+
+"""
+Render layer that adds annotations to follow the value of a register, for Disasm/LLIL.
+- Adds "<reg> after: <value>" comments to every line that modifies your register
+- Adds "<reg> at block entry: <value>" comments to every block
+- Adds "<reg> at block exit: <value>" comments to every block
+You can switch registers with the plugin command "Set Followed Register"
+"""
+
+
+class FollowRegRenderLayer(RenderLayer):
+ name = "Follow Register"
+ default_enable_state = RenderLayerDefaultEnableState.EnabledByDefaultRenderLayerDefaultEnableState
+
+ def __init__(self, handle=None):
+ super().__init__(handle)
+ self.followed_reg = QSettings().value("layer/followed_reg", None)
+
+ def apply_to_lines(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine'],
+ ):
+ if self.followed_reg is None:
+ return lines
+ if self.followed_reg not in block.arch.regs:
+ return lines
+
+ func = block.function
+ arch = block.arch
+
+ # Give our tokens a unique value so they don't highlight all the other comments
+ token_value = arch.get_reg_index(self.followed_reg) ^ 0x12345678
+
+ # ===============================================================================
+ # Render the "<reg> after" comments
+
+ for i, line in enumerate(lines):
+ # Ignore non-disassembly lines
+ if not any(token.type == InstructionTextTokenType.AddressSeparatorToken for token in line.tokens):
+ continue
+
+ # If the register has changed, we should render the update
+ written = False
+ for w in func.get_regs_written_by(line.address, arch):
+ # In case this instruction only modifies a sub register of our target
+ # Or in the case that we wanted to follow a sub register
+ if arch.regs[w].full_width_reg == arch.regs[self.followed_reg].full_width_reg:
+ written = True
+ break
+
+ if written:
+ # Add comment tokens to existing line
+ line.tokens.extend([
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' // ', token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' after: ', token_value),
+ ])
+
+ after = func.get_reg_value_after(line.address, self.followed_reg, arch)
+ if after.type == RegisterValueType.UndeterminedValue:
+ if line.il_instruction is not None:
+ instr = line.il_instruction
+ else:
+ instr_start = func.low_level_il.get_instruction_start(line.address, arch)
+ instr = func.low_level_il[instr_start]
+ if instr is not None:
+ after_possible = instr.get_possible_reg_values_after(self.followed_reg)
+ line.tokens.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(after_possible), token_value)
+ )
+ else:
+ line.tokens.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(after), token_value)
+ )
+ else:
+ line.tokens.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(after), token_value)
+ )
+
+ block_start = block.start
+ block_end = block.end
+ if block.is_low_level_il:
+ block_start = func.llil[block.start].address
+ block_end = func.llil[block.end - 1].address
+
+ # ===============================================================================
+ # Render the before-block "<reg> at block entry" comment
+
+ start_before = func.get_reg_value_at(block_start, self.followed_reg, arch)
+ line = [
+ InstructionTextToken(InstructionTextTokenType.CommentToken, '// ', token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' at block entry: ', token_value),
+ ]
+
+ # Sometimes the first line is blank and we want to insert after it
+ first_line = 0
+ if len(lines[0].tokens) == 0:
+ first_line = 1
+ if start_before.type == RegisterValueType.UndeterminedValue:
+ if lines[first_line].il_instruction is not None:
+ instr = lines[first_line].il_instruction
+ else:
+ instr_start = func.low_level_il.get_instruction_start(block_start, arch)
+ instr = func.low_level_il[instr_start]
+ if instr is not None:
+ start_before_possible = instr.get_possible_reg_values(self.followed_reg)
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before_possible), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before), token_value)
+ )
+
+ lines.insert(first_line, DisassemblyTextLine(line, block_start))
+
+ # ===============================================================================
+ # Render the after-block "<reg> at block exit" comment
+
+ end_after = func.get_reg_value_after(block_end, self.followed_reg, arch)
+ line = [
+ InstructionTextToken(InstructionTextTokenType.CommentToken, '// ', token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' at block exit: ', token_value),
+ ]
+
+ # Sometimes the last line is blank and we want to insert before it
+ if len(lines) > 1 and len(lines[-1].tokens) == 0:
+ last_line = len(lines) - 1
+ else:
+ last_line = len(lines)
+ if end_after.type == RegisterValueType.UndeterminedValue:
+ if lines[last_line - 1].il_instruction is not None:
+ instr = lines[last_line - 1].il_instruction
+ else:
+ instr_start = func.low_level_il.get_instruction_start(block_end, arch)
+ instr = func.low_level_il[instr_start]
+ if instr is not None:
+ end_after_possible = instr.get_possible_reg_values_after(self.followed_reg)
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after_possible), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after), token_value)
+ )
+
+ lines.insert(last_line, DisassemblyTextLine(line, block_end))
+
+ return lines
+
+ def apply_to_disassembly_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ return self.apply_to_lines(block, lines)
+
+ def apply_to_low_level_il_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ return self.apply_to_lines(block, lines)
+
+
+def set_follow_reg(bv: BinaryView):
+ if bv.platform is not None:
+ regs = list(bv.platform.arch.regs.keys())
+ idx = interaction.get_large_choice_input("Choose", "Choose Followed Register", regs)
+
+ # Save choice both in QSettings and on the RenderLayer object instance
+ layer = RenderLayer[FollowRegRenderLayer.name]
+ if idx is None:
+ layer.followed_reg = None
+ QSettings().remove("layer/followed_reg")
+ else:
+ layer.followed_reg = regs[idx]
+ QSettings().setValue("layer/followed_reg", regs[idx])
+
+ # Trigger view refresh (gross)
+ for func in bv.get_functions_containing(bv.offset):
+ func.reanalyze()
+
+
+FollowRegRenderLayer.register()
+
+# Using a command for this is kinda janky but currently the only option
+PluginCommand.register("Set Followed Register", "Choose which register to follow for the Follow Register Render Layer", set_follow_reg)
diff --git a/python/flowgraph.py b/python/flowgraph.py
index e661fff8..1f5040c2 100644
--- a/python/flowgraph.py
+++ b/python/flowgraph.py
@@ -156,30 +156,7 @@ class FlowGraphNode:
block = core.BNGetFlowGraphBasicBlock(self.handle)
if not block:
return None
- func_handle = core.BNGetBasicBlockFunction(block)
- if not func_handle:
- core.BNFreeBasicBlock(block)
- return None
-
- view = binaryview.BinaryView(handle=core.BNGetFunctionData(func_handle))
- func = function.Function(view, func_handle)
-
- if core.BNIsLowLevelILBasicBlock(block):
- block = lowlevelil.LowLevelILBasicBlock(
- block, lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func),
- view
- )
- elif core.BNIsMediumLevelILBasicBlock(block):
- mlil_func = mediumlevelil.MediumLevelILFunction(
- func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func
- )
- block = mediumlevelil.MediumLevelILBasicBlock(block, mlil_func, view)
- elif core.BNIsHighLevelILBasicBlock(block):
- hlil_func = highlevelil.HighLevelILFunction(func.arch, core.BNGetBasicBlockHighLevelILFunction(block), func)
- block = highlevelil.HighLevelILBasicBlock(block, hlil_func, view)
- else:
- block = basicblock.BasicBlock(block, view)
- return block
+ return basicblock.BasicBlock._from_core_block(block)
@basic_block.setter
def basic_block(self, block):
@@ -613,6 +590,11 @@ class FlowGraph:
return result
@property
+ def node_count(self) -> int:
+ """Number of nodes in graph (read-only)"""
+ return core.BNGetFlowGraphNodeCount(self.handle)
+
+ @property
def has_nodes(self):
"""Whether the flow graph has at least one node (read-only)"""
return core.BNFlowGraphHasNodes(self.handle)
@@ -835,12 +817,35 @@ class FlowGraph:
"""
``append`` adds a node to a flow graph.
+ .. note:: After the graph has completed layout, this function has no effect.
+
:param FlowGraphNode node: Node to add
:return: Index of node
:rtype: int
"""
return core.BNAddFlowGraphNode(self.handle, node.handle)
+ def replace(self, index, node):
+ """
+ ``replace`` replaces an existing node in the graph with a new node.
+ Any existing edges referencing the old node will be updated to point to
+ the new node.
+
+ .. note:: After the graph has completed layout, this function has no effect.
+
+ :param index: Index of the node to replace
+ :param node: New node with which to replace the old node
+ """
+ core.BNReplaceFlowGraphNode(self.handle, index, node.handle)
+
+ def clear(self):
+ """
+ ``clear`` clears all the nodes in the graph
+
+ .. note:: After the graph has completed layout, this function has no effect.
+ """
+ core.BNClearFlowGraphNodes(self.handle)
+
def show(self, title):
"""
``show`` displays the graph in a new tab in the UI.
@@ -869,6 +874,50 @@ class FlowGraph:
def is_option_set(self, option):
return core.BNIsFlowGraphOptionSet(self.handle, option)
+ @property
+ def render_layers(self) -> List['binaryninja.RenderLayer']:
+ """
+ Get the list of Render Layers which will be applied to this Flow Graph,
+ after it calls populate_nodes.
+ :return: List of Render Layers
+ """
+ count = ctypes.c_size_t(0)
+ layers = core.BNGetFlowGraphRenderLayers(self.handle, count)
+ assert layers is not None, "core.BNGetFlowGraphRenderLayers returned None"
+
+ try:
+ result = []
+ for i in range(0, count.value):
+ result.append(binaryninja.RenderLayer(handle=layers[i]))
+
+ return result
+ finally:
+ core.BNFreeRenderLayerList(layers)
+
+ @render_layers.setter
+ def render_layers(self, render_layers: List['binaryninja.RenderLayer']):
+ for layer in self.render_layers:
+ self.remove_render_layer(layer)
+ for layer in render_layers:
+ self.add_render_layer(layer)
+
+ def add_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Add a Render Layer to be applied to this Flow Graph. Note that layers will
+ be applied in the order in which they are added.
+
+ :param layer: Render Layer to add
+ """
+ core.BNAddFlowGraphRenderLayer(self.handle, layer.handle)
+
+ def remove_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Remove a Render Layer from being applied to this Flow Graph
+
+ :param layer: Render Layer to remove
+ """
+ core.BNRemoveFlowGraphRenderLayer(self.handle, layer.handle)
+
class CoreFlowGraph(FlowGraph):
def __init__(self, handle):
diff --git a/python/function.py b/python/function.py
index 80f0a37c..14cc5e43 100644
--- a/python/function.py
+++ b/python/function.py
@@ -3335,15 +3335,29 @@ class AdvancedFunctionAnalysisDataRequestor:
@dataclass
+class DisassemblyTextLineTypeInfo:
+ parent_type: Optional['types.Type']
+ field_index: int
+ offset: int
+
+
+@dataclass
class DisassemblyTextLine:
tokens: List['InstructionTextToken']
highlight: '_highlight.HighlightColor'
address: Optional[int]
il_instruction: Optional[ILInstructionType]
+ tags: List['binaryview.Tag']
+ type_info: Optional[DisassemblyTextLineTypeInfo]
def __init__(
- self, tokens: List['InstructionTextToken'], address: Optional[int] = None, il_instr: Optional[ILInstructionType] = None,
- color: Optional[Union['_highlight.HighlightColor', HighlightStandardColor]] = None
+ self,
+ tokens: List['InstructionTextToken'],
+ address: Optional[int] = None,
+ il_instr: Optional[ILInstructionType] = None,
+ color: Optional[Union['_highlight.HighlightColor', HighlightStandardColor]] = None,
+ tags: Optional[List['binaryview.Tag']] = None,
+ type_info: Optional[DisassemblyTextLineTypeInfo] = None,
):
self.address = address
self.tokens = tokens
@@ -3358,6 +3372,10 @@ class DisassemblyTextLine:
self.highlight = _highlight.HighlightColor(color)
else:
self.highlight = color
+ if tags is None:
+ tags = []
+ self.tags = tags
+ self.type_info = type_info
def __str__(self):
return "".join(map(str, self.tokens))
@@ -3413,6 +3431,64 @@ class DisassemblyTextLine:
self._find_address_and_indentation_tokens(collect_tokens)
return result
+ @classmethod
+ def _from_core_struct(cls, struct: core.BNDisassemblyTextLine, il_func: Optional['ILFunctionType'] = None):
+ il_instr = None
+ if il_func is not None and struct.instrIndex < len(il_func):
+ try:
+ il_instr = il_func[struct.instrIndex]
+ except:
+ il_instr = None
+ tokens = InstructionTextToken._from_core_struct(struct.tokens, struct.count)
+
+ tags = []
+ for i in range(struct.tagCount):
+ tags.append(binaryview.Tag(handle=core.BNNewTagReference(struct.tags[i])))
+
+ type_info = None
+ if struct.typeInfo.hasTypeInfo:
+ parent_type = None
+ if struct.typeInfo.parentType:
+ parent_type = types.Type.create(core.BNNewTypeReference(struct.typeInfo.parentType))
+ type_info = DisassemblyTextLineTypeInfo(
+ parent_type=parent_type,
+ field_index=struct.typeInfo.fieldIndex,
+ offset=struct.typeInfo.offset
+ )
+
+ return DisassemblyTextLine(
+ tokens,
+ struct.addr,
+ il_instr,
+ _highlight.HighlightColor._from_core_struct(struct.highlight),
+ tags,
+ type_info
+ )
+
+ def _to_core_struct(self) -> core.BNDisassemblyTextLine:
+ result = core.BNDisassemblyTextLine()
+ result.addr = self.address
+ if self.il_instruction is not None:
+ result.instrIndex = self.il_instruction.instr_index
+ else:
+ result.instrIndex = 0xffffffffffffffff
+ result.tokens = InstructionTextToken._get_core_struct(self.tokens)
+ result.count = len(self.tokens)
+ result.highlight = self.highlight._to_core_struct()
+ result.tagCount = len(self.tags)
+ result.tags = (ctypes.POINTER(core.BNTag) * len(self.tags))()
+ for i, tag in enumerate(self.tags):
+ result.tags[i] = tag.handle
+ if self.type_info is None:
+ result.typeInfo.hasTypeInfo = False
+ else:
+ result.typeInfo.hasTypeInfo = True
+ if self.type_info.parent_type is not None:
+ result.typeInfo.parentType = self.type_info.parent_type.handle
+ result.typeInfo.fieldIndex = self.type_info.field_index
+ result.typeInfo.offset = self.type_info.offset
+ return result
+
class DisassemblyTextRenderer:
def __init__(
@@ -3463,7 +3539,7 @@ class DisassemblyTextRenderer:
def basic_block(self) -> Optional['basicblock.BasicBlock']:
result = core.BNGetDisassemblyTextRendererBasicBlock(self.handle)
if result:
- return basicblock.BasicBlock(handle=result)
+ return basicblock.BasicBlock._from_core_block(handle=result)
return None
@basic_block.setter
diff --git a/python/lineardisassembly.py b/python/lineardisassembly.py
index 40a0d6c5..8c4b0eb5 100644
--- a/python/lineardisassembly.py
+++ b/python/lineardisassembly.py
@@ -27,7 +27,7 @@ from . import highlight
from . import function as _function
from . import basicblock
from . import binaryview
-from .enums import LinearViewObjectIdentifierType
+from .enums import (LinearDisassemblyLineType, LinearViewObjectIdentifierType)
class LinearDisassemblyLine:
@@ -43,6 +43,41 @@ class LinearDisassemblyLine:
def __str__(self):
return str(self.contents)
+ @classmethod
+ def _from_core_struct(cls, struct: core.BNLinearDisassemblyLine, obj: Optional['LinearViewObject'] = None) -> 'LinearDisassemblyLine':
+ function = None
+ if struct.function:
+ function = _function.Function(handle=core.BNNewFunctionReference(struct.function))
+ block = None
+ if struct.block:
+ block = basicblock.BasicBlock._from_core_block(core.BNNewBasicBlockReference(struct.block))
+ il_func = None
+ if block is not None:
+ il_func = block.il_function
+ if obj is not None and function is not None:
+ # Hack: HLIL bodies don't have basic blocks
+ if obj.identifier.name == "HLIL Function Body":
+ il_func = function.hlil
+ elif obj.identifier.name == "HLIL SSA Function Body":
+ il_func = function.hlil.ssa_form
+ elif obj.identifier.name == "Language Representation Function Body":
+ il_func = function.hlil
+
+ contents = _function.DisassemblyTextLine._from_core_struct(struct.contents, il_func)
+ return LinearDisassemblyLine(LinearDisassemblyLineType(struct.type), function, block, contents)
+
+ def _to_core_struct(self) -> core.BNLinearDisassemblyLine:
+ result = core.BNLinearDisassemblyLine()
+ result.type = self.type
+ result.function = None
+ if self.function is not None:
+ result.function = self.function.handle
+ result.block = None
+ if self.block is not None:
+ result.block = self.block.handle
+ result.contents = _function.DisassemblyTextLine._to_core_struct(self.contents)
+ return result
+
class LinearViewObjectIdentifier:
def __init__(self, name, start=None, end=None):
@@ -247,7 +282,7 @@ class LinearViewObject:
count = ctypes.c_ulonglong(0)
return LinearViewCursor._make_lines(
- core.BNGetLinearViewObjectLines(self.handle, prev_obj, next_obj, count), count.value
+ core.BNGetLinearViewObjectLines(self.handle, prev_obj, next_obj, count), count.value, self
)
def ordering_index_for_child(self, child):
@@ -602,34 +637,21 @@ class LinearViewCursor:
return core.BNLinearViewCursorNext(self.handle)
@staticmethod
- def _make_lines(lines, count: int) -> List['LinearDisassemblyLine']:
+ def _make_lines(lines, count: int, object) -> List['LinearDisassemblyLine']:
assert lines is not None, "core returned None for LinearDisassembly lines"
try:
result = []
for i in range(0, count):
- func = None
- block = None
- if lines[i].function:
- func = _function.Function(None, core.BNNewFunctionReference(lines[i].function))
- if lines[i].block:
- core_block = core.BNNewBasicBlockReference(lines[i].block)
- assert core_block is not None, "core.BNNewBasicBlockReference returned None"
- block = basicblock.BasicBlock(core_block, None)
- color = highlight.HighlightColor._from_core_struct(lines[i].contents.highlight)
- addr = lines[i].contents.addr
- tokens = _function.InstructionTextToken._from_core_struct(
- lines[i].contents.tokens, lines[i].contents.count
- )
- contents = _function.DisassemblyTextLine(tokens, addr, color=color)
- result.append(LinearDisassemblyLine(lines[i].type, func, block, contents))
+ result.append(LinearDisassemblyLine._from_core_struct(lines[i], obj=object))
return result
finally:
core.BNFreeLinearDisassemblyLines(lines, count)
@property
def lines(self):
+ current = self.current_object
count = ctypes.c_ulonglong(0)
- return LinearViewCursor._make_lines(core.BNGetLinearViewCursorLines(self.handle, count), count.value)
+ return LinearViewCursor._make_lines(core.BNGetLinearViewCursorLines(self.handle, count), count.value, current)
def duplicate(self):
return LinearViewCursor(None, handle=core.BNDuplicateLinearViewCursor(self.handle))
@@ -637,3 +659,48 @@ class LinearViewCursor:
@staticmethod
def compare(a, b):
return core.BNCompareLinearViewCursors(a.handle, b.handle)
+
+ @property
+ def render_layers(self) -> List['binaryninja.RenderLayer']:
+ """
+ Get the list of Render Layers which will be applied to this cursor, at the
+ end of calls to lines().
+
+ :return: List of Render Layers
+ """
+ count = ctypes.c_size_t(0)
+ layers = core.BNGetLinearViewCursorRenderLayers(self.handle, count)
+ assert layers is not None, "core.BNGetLinearViewCursorRenderLayers returned None"
+
+ try:
+ result = []
+ for i in range(0, count.value):
+ result.append(binaryninja.RenderLayer(handle=layers[i]))
+
+ return result
+ finally:
+ core.BNFreeRenderLayerList(layers)
+
+ @render_layers.setter
+ def render_layers(self, render_layers: List['binaryninja.RenderLayer']):
+ for layer in self.render_layers:
+ self.remove_render_layer(layer)
+ for layer in render_layers:
+ self.add_render_layer(layer)
+
+ def add_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Add a Render Layer to be applied to this cursor. Note that layers will
+ be applied in the order in which they are added.
+
+ :param layer: Render Layer to add
+ """
+ core.BNAddLinearViewCursorRenderLayer(self.handle, layer.handle)
+
+ def remove_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Remove a Render Layer from being applied to this cursor
+
+ :param layer: Render Layer to remove
+ """
+ core.BNRemoveLinearViewCursorRenderLayer(self.handle, layer.handle)
diff --git a/python/renderlayer.py b/python/renderlayer.py
new file mode 100644
index 00000000..6f5984e0
--- /dev/null
+++ b/python/renderlayer.py
@@ -0,0 +1,450 @@
+# Copyright (c) 2015-2024 Vector 35 Inc
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import ctypes
+import traceback
+
+# Binary Ninja components
+import binaryninja
+from . import _binaryninjacore as core, LinearDisassemblyLine
+from .enums import LinearDisassemblyLineType, RenderLayerDefaultEnableState
+from . import binaryview
+from . import types
+from .log import log_error
+from typing import Iterable, List, Optional, Union, Tuple
+
+
+class _RenderLayerMetaclass(type):
+ def __iter__(self):
+ binaryninja._init_plugins()
+ count = ctypes.c_ulonglong()
+ instances = core.BNGetRenderLayerList(count)
+ try:
+ for i in range(0, count.value):
+ yield self._handle_to_instance(instances[i])
+ finally:
+ core.BNFreeRenderLayerList(instances)
+
+ def __getitem__(self, value):
+ binaryninja._init_plugins()
+ handle = core.BNGetRenderLayerByName(str(value))
+ if handle is None:
+ raise KeyError(f"'{value}' is not a valid RenderLayer")
+ return self._handle_to_instance(handle)
+
+ def _handle_to_instance(self, handle):
+ handle_ptr = ctypes.cast(handle, ctypes.c_void_p)
+ if handle_ptr.value in RenderLayer._registered_instances:
+ return RenderLayer._registered_instances[handle_ptr.value]
+ return CoreRenderLayer(handle)
+
+
+class RenderLayer(metaclass=_RenderLayerMetaclass):
+ """
+ RenderLayer is a plugin class that allows you to customize the presentation of
+ Linear and Graph view output, adding, changing, or removing lines before they are
+ presented in the UI.
+ """
+
+ name = None
+ default_enable_state = RenderLayerDefaultEnableState.DisabledByDefaultRenderLayerDefaultEnableState
+ _registered_instances = {}
+ _pending_lines = {}
+
+ def __init__(self, handle=None):
+ if handle is not None:
+ self.handle = core.handle_of_type(handle, core.BNRenderLayer)
+ self.__dict__["name"] = core.BNGetRenderLayerName(handle)
+ self.__dict__["default_enable_state"] = core.BNGetRenderLayerDefaultEnableState(handle)
+ else:
+ self.handle = None
+
+ @classmethod
+ def register(cls):
+ """
+ Register a custom Render Layer.
+ """
+ layer = cls()
+
+ assert layer.__class__.name is not None
+ assert layer.handle is None
+
+ layer._cb = core.BNRenderLayerCallbacks()
+ layer._cb.context = 0
+ layer._cb.applyToFlowGraph = layer._cb.applyToFlowGraph.__class__(layer._apply_to_flow_graph)
+ layer._cb.applyToLinearViewObject = layer._cb.applyToLinearViewObject.__class__(layer._apply_to_linear_view_object)
+ layer._cb.freeLines = layer._cb.freeLines.__class__(layer._free_lines)
+ layer.handle = core.BNRegisterRenderLayer(layer.__class__.name, layer._cb, layer.default_enable_state)
+ handle_ptr = ctypes.cast(layer.handle, ctypes.c_void_p)
+ cls._registered_instances[handle_ptr.value] = layer
+
+ def __eq__(self, other):
+ if not isinstance(other, RenderLayer):
+ return False
+ return self.name == other.name
+
+ def __str__(self):
+ return f'<RenderLayer: {self.name}>'
+
+ def __repr__(self):
+ return f'<RenderLayer: {self.name}>'
+
+ def _apply_to_flow_graph(self, ctxt, graph):
+ try:
+ self.apply_to_flow_graph(binaryninja.FlowGraph(handle=core.BNNewFlowGraphReference(graph)))
+ except:
+ log_error(traceback.format_exc())
+
+ def _apply_to_linear_view_object(self, ctxt, obj, prev, next, in_lines, in_line_count, out_lines, out_line_count):
+ try:
+ obj_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(obj))
+ prev_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(prev)) if prev else None
+ next_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(next)) if next else None
+
+ lines = []
+ for i in range(in_line_count):
+ lines.append(LinearDisassemblyLine._from_core_struct(in_lines[i], obj=obj_obj))
+
+ lines = self.apply_to_linear_view_object(obj_obj, prev_obj, next_obj, lines)
+
+ out_line_count[0] = len(lines)
+ out_lines_buf = (core.BNLinearDisassemblyLine * len(lines))()
+ for i, r in enumerate(lines):
+ out_lines_buf[i] = r._to_core_struct()
+ out_lines_ptr = ctypes.cast(out_lines_buf, ctypes.c_void_p)
+ out_lines[0] = out_lines_buf
+ self._pending_lines[out_lines_ptr.value] = (out_lines_ptr.value, out_lines_buf)
+ except:
+ log_error(traceback.format_exc())
+ out_lines[0] = None
+ out_line_count[0] = 0
+
+ def _free_lines(self, ctxt, lines, count):
+ try:
+ buf = ctypes.cast(lines, ctypes.c_void_p)
+ if buf.value is not None:
+ if buf.value not in self._pending_lines:
+ raise ValueError("freeing lines list that wasn't allocated")
+ del self._pending_lines[buf.value]
+ except:
+ log_error(traceback.format_exc())
+
+ def apply_to_disassembly_block(
+ self,
+ block: 'binaryninja.BasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of Disassembly lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle Disassembly lines, and not any ILs.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_low_level_il_block(
+ self,
+ block: 'binaryninja.LowLevelILBasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of Low Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle Lifted IL/LLIL/LLIL(SSA) lines. \
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_medium_level_il_block(
+ self,
+ block: 'binaryninja.MediumLevelILBasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of Medium Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle MLIL/MLIL(SSA)/Mapped MLIL/Mapped MLIL(SSA) lines. \
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_high_level_il_block(
+ self,
+ block: 'binaryninja.HighLevelILBasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of High Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle HLIL/HLIL(SSA)/Language Representation lines. \
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ .. warning:: This function will NOT apply to High Level IL bodies as displayed \
+ in Linear View! Those are handled by `apply_to_high_level_il_body` instead as they \
+ do not have a Basic Block associated with them.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_high_level_il_body(
+ self,
+ function: 'binaryninja.Function',
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ):
+ """
+ Apply this Render Layer to the entire body of a High Level IL function.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the function.
+
+ .. warning:: This function only applies to Linear View, and not to Graph View! \
+ If you want to handle Graph View too, you will need to use `apply_to_high_level_il_block` \
+ and handle the lines one block at a time.
+
+ :param function: Function containing those lines
+ :param lines: Original lines of text for the function
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_misc_linear_lines(
+ self,
+ obj: 'binaryninja.LinearViewObject',
+ prev: Optional['binaryninja.LinearViewObject'],
+ next: Optional['binaryninja.LinearViewObject'],
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ):
+ """
+ Apply to lines generated by Linear View that are not part of a function.
+ It is up to your implementation to figure out which type of Linear View Object
+ lines these are, and what to do with them.
+
+ :param obj: Linear View Object being rendered
+ :param prev: Linear View Object located directly above this one
+ :param next: Linear View Object located directly below this one
+ :param lines: Original lines rendered by `obj`
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_block(
+ self,
+ block: 'binaryninja.BasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine'],
+ ):
+ """
+ Apply to lines generated by a Basic Block, of any type. If not overridden, this
+ function will call the appropriate apply_to_X_level_il_block function.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ if not block.is_il:
+ return self.apply_to_disassembly_block(block, lines)
+ elif block.is_low_level_il:
+ return self.apply_to_low_level_il_block(block, lines)
+ elif block.is_medium_level_il:
+ return self.apply_to_medium_level_il_block(block, lines)
+ elif block.is_high_level_il:
+ return self.apply_to_high_level_il_block(block, lines)
+ else:
+ # ???
+ return lines
+
+ def apply_to_flow_graph(self, graph: 'binaryninja.FlowGraph') -> None:
+ """
+ Apply this Render Layer to a Flow Graph, potentially modifying its nodes,
+ their edges, their lines, and their lines' content.
+
+ .. note:: If you override this function, you will need to call the ``super()`` \
+ implementation if you want to use the higher level ``apply_to_X_level_il_block`` \
+ functionality.
+
+ :param graph: Graph to modify
+ """
+ pass
+ for i, node in enumerate(graph.nodes):
+ lines = node.lines
+ if node.basic_block is not None and isinstance(node.basic_block, binaryninja.BasicBlock):
+ lines = self.apply_to_block(node.basic_block, lines)
+ node.lines = lines
+
+ def apply_to_linear_view_object(
+ self,
+ obj: 'binaryninja.LinearViewObject',
+ prev: Optional['binaryninja.LinearViewObject'],
+ next: Optional['binaryninja.LinearViewObject'],
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ) -> List['binaryninja.LinearDisassemblyLine']:
+ """
+ Apply this Render Layer to the lines produced by a LinearViewObject for rendering
+ in Linear View, potentially modifying the lines and their contents.
+
+ .. note:: If you override this function, you will need to call the ``super()`` \
+ implementation if you want to use the higher level ``apply_to_X_level_il_block`` \
+ functionality.
+
+ :param obj: Linear View Object being rendered
+ :param prev: Linear View Object located directly above this one
+ :param next: Linear View Object located directly below this one
+ :param lines: Original lines rendered by the Linear View Object
+ :return: Modified list of lines to display in Linear View
+ """
+ # Hack: HLIL bodies don't have basic blocks
+ if len(lines) > 0 and obj.identifier.name in [
+ "HLIL Function Body",
+ "HLIL SSA Function Body",
+ "Language Representation Function Body"
+ ]:
+ return self.apply_to_high_level_il_body(lines[0].function, lines)
+
+ block_lines = []
+ final_lines = []
+ last_block = None
+
+ def finish_block():
+ nonlocal block_lines
+ nonlocal final_lines
+ if len(block_lines) > 0:
+ if last_block is not None:
+ # Convert linear lines to disassembly lines for the apply()
+ # and then convert back for linear view
+ new_block_lines = []
+ disasm_lines = []
+ misc_lines = []
+
+ def process_disasm():
+ nonlocal disasm_lines
+
+ if len(disasm_lines) > 0:
+ disasm_lines = self.apply_to_block(last_block, disasm_lines)
+ func = block_lines[0].function
+ block = block_lines[0].block
+ for block_line in disasm_lines:
+ new_block_lines.append(
+ LinearDisassemblyLine(
+ LinearDisassemblyLineType.CodeDisassemblyLineType,
+ func,
+ block,
+ block_line
+ )
+ )
+ disasm_lines = []
+
+ def process_misc():
+ nonlocal misc_lines
+ nonlocal new_block_lines
+
+ if len(misc_lines) > 0:
+ misc_lines = self.apply_to_misc_linear_lines(obj, prev, next, misc_lines)
+ new_block_lines += misc_lines
+ misc_lines = []
+
+ for block_line in block_lines:
+ # Lines in the block get sent to process_disasm, anything else goes
+ # to process_misc so we preserve line information
+ if block_line.type == LinearDisassemblyLineType.CodeDisassemblyLineType:
+ process_misc()
+ disasm_lines.append(block_line.contents)
+ else:
+ process_disasm()
+ misc_lines.append(block_line)
+
+ # At the end, zero or one of these has lines in it
+ process_misc()
+ process_disasm()
+ block_lines = new_block_lines
+ else:
+ block_lines = self.apply_to_misc_linear_lines(obj, prev, next, block_lines)
+ final_lines += block_lines
+ block_lines = []
+
+ for line in lines:
+ # Assume we've finished a block when the line's block changes
+ if line.block != last_block:
+ finish_block()
+ block_lines.append(line)
+ last_block = line.block
+
+ # And we've finished a block when we're done with every line
+ finish_block()
+ return final_lines
+
+
+class CoreRenderLayer(RenderLayer):
+
+ def apply_to_flow_graph(self, graph: 'binaryninja.FlowGraph') -> None:
+ core.BNApplyRenderLayerToFlowGraph(self.handle, graph.handle)
+
+ def apply_to_linear_view_object(
+ self,
+ obj: 'binaryninja.LinearViewObject',
+ prev: Optional['binaryninja.LinearViewObject'],
+ next: Optional['binaryninja.LinearViewObject'],
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ) -> List['binaryninja.LinearDisassemblyLine']:
+
+ in_lines_buf = (core.BNLinearDisassemblyLine * len(lines))()
+ for i, r in enumerate(lines):
+ in_lines_buf[i] = r._to_core_struct()
+
+ out_lines = ctypes.POINTER(core.BNLinearDisassemblyLine)()
+ out_line_count = ctypes.c_size_t(0)
+
+ core.BNApplyRenderLayerToLinearViewObject(
+ self.handle,
+ obj.handle,
+ prev.handle if prev is not None else None,
+ next.handle if next is not None else None,
+ in_lines_buf,
+ len(lines),
+ out_lines,
+ out_line_count
+ )
+
+ result = []
+ for i in range(out_line_count.value):
+ result.append(binaryninja.LinearDisassemblyLine._from_core_struct(out_lines[i], obj=obj))
+
+ core.BNFreeLinearDisassemblyLines(out_lines, out_line_count.value)
+
+ return result