summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorRusty Wagner <rusty.wagner@gmail.com>2025-09-08 20:04:36 -0400
committerRusty Wagner <rusty.wagner@gmail.com>2025-10-21 13:52:39 -0400
commitcae26921cf2b2e564f66e58b77a3963fe36f6d2d (patch)
treebfa7ec8de23c1de758463a894583086c8d0b050e /python
parent11ad1fc28470b863326f7136cf9b5723a06e89d3 (diff)
Add constant renderer API
Diffstat (limited to 'python')
-rw-r--r--python/__init__.py1
-rw-r--r--python/constantrenderer.py180
-rw-r--r--python/examples/encoded_strings.py58
3 files changed, 239 insertions, 0 deletions
diff --git a/python/__init__.py b/python/__init__.py
index 77c64fce..acf8b293 100644
--- a/python/__init__.py
+++ b/python/__init__.py
@@ -82,6 +82,7 @@ from .fileaccessor import *
from .languagerepresentation import *
from .lineformatter import *
from .renderlayer import *
+from .constantrenderer 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/constantrenderer.py b/python/constantrenderer.py
new file mode 100644
index 00000000..abd98be7
--- /dev/null
+++ b/python/constantrenderer.py
@@ -0,0 +1,180 @@
+# Copyright (c) 2015-2025 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 traceback
+import ctypes
+from typing import Optional
+
+import binaryninja
+from . import _binaryninjacore as core
+from . import function
+from . import enums
+from .log import log_error_for_exception
+from . import types
+from . import highlevelil
+from . import languagerepresentation
+
+
+class _ConstantRendererMetaClass(type):
+ def __iter__(self):
+ binaryninja._init_plugins()
+ count = ctypes.c_ulonglong()
+ renderers = core.BNGetConstantRendererList(count)
+ assert renderers is not None, "core.BNGetConstantRendererList returned None"
+ try:
+ for i in range(0, count.value):
+ yield CoreConstantRenderer(handle=renderers[i])
+ finally:
+ core.BNFreeConstantRendererList(renderers)
+
+ def __getitem__(cls, value):
+ binaryninja._init_plugins()
+ renderer = core.BNGetConstantRendererByName(str(value))
+ if renderer is None:
+ raise KeyError("'%s' is not a valid renderer" % str(value))
+ return CoreConstantRenderer(handle=renderer)
+
+
+class ConstantRenderer(metaclass=_ConstantRendererMetaClass):
+ _registered_renderers = []
+ renderer_name = None
+
+ def __init__(self, handle=None):
+ if handle is not None:
+ self.handle = core.handle_of_type(handle, core.BNConstantRenderer)
+
+ def register(self):
+ if self.__class__.renderer_name is None:
+ raise ValueError("Renderer name is missing")
+ self._cb = core.BNCustomConstantRenderer()
+ self._cb.context = 0
+ if self.is_valid_for_type.__func__ != ConstantRenderer.is_valid_for_type:
+ self._cb.isValidForType = self._cb.isValidForType.__class__(self._is_valid_for_type)
+ if self.render_constant.__func__ != ConstantRenderer.render_constant:
+ self._cb.renderConstant = self._cb.renderConstant.__class__(self._render_constant)
+ if self.render_constant_pointer.__func__ != ConstantRenderer.render_constant_pointer:
+ self._cb.renderConstantPointer = self._cb.renderConstantPointer.__class__(self._render_constant_pointer)
+ self.handle = core.BNRegisterConstantRenderer(self.__class__.renderer_name, self._cb)
+ self.__class__._registered_renderers.append(self)
+
+ def _is_valid_for_type(self, ctxt, hlil, type):
+ try:
+ hlil = highlevelil.HighLevelILFunction(handle=core.BNNewHighLevelILFunctionReference(hlil))
+ type = types.Type.create(handle=core.BNNewTypeReference(type))
+ return self.is_valid_for_type(hlil, type)
+ except:
+ log_error_for_exception("Unhandled Python exception in ConstantRenderer._is_valid_for_type")
+ return False
+
+ def _render_constant(self, ctxt, hlil, expr, type, val, tokens, settings, precedence):
+ try:
+ hlil = highlevelil.HighLevelILFunction(handle=core.BNNewHighLevelILFunctionReference(hlil))
+ type = types.Type.create(handle=core.BNNewTypeReference(type))
+ tokens = languagerepresentation.HighLevelILTokenEmitter(core.BNNewHighLevelILTokenEmitterReference(tokens))
+ if settings:
+ settings = function.DisassemblySettings(core.BNNewDisassemblySettingsReference(settings))
+ instr = hlil.get_expr(highlevelil.ExpressionIndex(expr))
+ return self.render_constant(instr, type, val, tokens, settings, precedence)
+ except:
+ log_error_for_exception("Unhandled Python exception in ConstantRenderer._render_constant_pointer")
+ return False
+
+ def _render_constant_pointer(self, ctxt, hlil, expr, type, val, tokens, settings, symbol_display, precedence):
+ try:
+ hlil = highlevelil.HighLevelILFunction(handle=core.BNNewHighLevelILFunctionReference(hlil))
+ type = types.Type.create(handle=core.BNNewTypeReference(type))
+ tokens = languagerepresentation.HighLevelILTokenEmitter(core.BNNewHighLevelILTokenEmitterReference(tokens))
+ if settings:
+ settings = function.DisassemblySettings(core.BNNewDisassemblySettingsReference(settings))
+ symbol_display = enums.SymbolDisplayType(symbol_display)
+ instr = hlil.get_expr(highlevelil.ExpressionIndex(expr))
+ return self.render_constant_pointer(instr, type, val, tokens, settings, symbol_display, precedence)
+ except:
+ log_error_for_exception("Unhandled Python exception in ConstantRenderer._render_constant_pointer")
+ return False
+
+ @property
+ def name(self) -> str:
+ if hasattr(self, 'handle'):
+ return core.BNGetConstantRendererName(self.handle)
+ return self.__class__.renderer_name
+
+ def is_valid_for_type(self, func: 'highlevelil.HighLevelILFunction', type: 'types.Type') -> bool:
+ return True
+
+ def render_constant(
+ self, instr: 'highlevelil.HighLevelILInstruction', type: 'types.Type', val: int,
+ tokens: 'languagerepresentation.HighLevelILTokenEmitter',
+ settings: Optional['function.DisassemblySettings'], precedence: 'enums.OperatorPrecedence'
+ ) -> bool:
+ return False
+
+ def render_constant_pointer(
+ self, instr: 'highlevelil.HighLevelILInstruction', type: 'types.Type', val: int,
+ tokens: 'languagerepresentation.HighLevelILTokenEmitter',
+ settings: Optional['function.DisassemblySettings'], symbol_display: 'enums.SymbolDisplayType',
+ precedence: 'enums.OperatorPrecedence'
+ ) -> bool:
+ return False
+
+
+_renderer_cache = {}
+
+
+class CoreConstantRenderer(ConstantRenderer):
+ def __init__(self, handle: core.BNConstantRenderer):
+ super(CoreConstantRenderer, self).__init__(handle=handle)
+ if type(self) is CoreConstantRenderer:
+ global _renderer_cache
+ _renderer_cache[ctypes.addressof(handle.contents)] = self
+
+ @classmethod
+ def _from_cache(cls, handle) -> 'ConstantRenderer':
+ """
+ Look up a renderer type from a given BNConstantRenderer handle
+ :param handle: BNConstantRenderer pointer
+ :return: Renderer type instance responsible for this handle
+ """
+ global _renderer_cache
+ return _renderer_cache.get(ctypes.addressof(handle.contents)) or cls(handle)
+
+ def is_valid_for_type(self, func: 'highlevelil.HighLevelILFunction', type: 'types.Type') -> bool:
+ return core.BNIsConstantRendererValidForType(self.handle, func.handle, type.handle)
+
+ def render_constant(
+ self, instr: 'highlevelil.HighLevelILInstruction', type: 'types.Type', val: int,
+ tokens: 'languagerepresentation.HighLevelILTokenEmitter',
+ settings: Optional['function.DisassemblySettings'], precedence: 'enums.OperatorPrecedence'
+ ) -> bool:
+ if settings is not None:
+ settings = settings.handle
+ return core.BNConstantRendererRenderConstant(self.handle, instr.function.handle, instr.expr_index,
+ type.handle, val, tokens.handle, settings, precedence)
+
+ def render_constant_pointer(
+ self, instr: 'highlevelil.HighLevelILInstruction', type: 'types.Type', val: int,
+ tokens: 'languagerepresentation.HighLevelILTokenEmitter',
+ settings: Optional['function.DisassemblySettings'], symbol_display: 'enums.SymbolDisplayType',
+ precedence: 'enums.OperatorPrecedence'
+ ) -> bool:
+ if settings is not None:
+ settings = settings.handle
+ return core.BNConstantRendererRenderConstantPointer(self.handle, instr.function.handle, instr.expr_index,
+ type.handle, val, tokens.handle, settings, symbol_display, precedence)
diff --git a/python/examples/encoded_strings.py b/python/examples/encoded_strings.py
new file mode 100644
index 00000000..ce7d02db
--- /dev/null
+++ b/python/examples/encoded_strings.py
@@ -0,0 +1,58 @@
+from binaryninja import (ConstantRenderer, PointerType, InstructionTextToken, InstructionTextTokenType, DataBuffer)
+
+
+class EncodedStringConstantRenderer(ConstantRenderer):
+ renderer_name = "encoded_strings"
+ decoders = {
+ "xor_encoded": lambda encoded, key: encoded ^ key,
+ "sub_encoded": lambda encoded, key: (encoded - key) & 0xff,
+ "add_encoded": lambda encoded, key: (encoded + key) & 0xff
+ }
+
+ def is_valid_for_type(self, func, type):
+ if not isinstance(type, PointerType):
+ return False
+ for name in self.__class__.decoders.keys():
+ if name in type.target.attributes:
+ return True
+ return False
+
+ def render_constant_pointer(self, instr, type, val, tokens, settings, precedence):
+ if not isinstance(type, PointerType):
+ return False
+
+ values = None
+ decoder = None
+ for name in self.__class__.decoders.keys():
+ if name in type.target.attributes:
+ try:
+ values = bytes.fromhex(type.target.attributes[name])
+ decoder = self.__class__.decoders[name]
+ except:
+ return False
+ if values is None or decoder is None:
+ return False
+
+ encoded_null = "encoded_null" in type.target.attributes
+
+ result = b""
+ i = 0
+ while True:
+ byte = instr.function.view.read(val + i, 1)
+ if len(byte) != 1:
+ return False
+ if not encoded_null and byte[0] == 0:
+ break
+ byte = decoder(byte[0], values[i % len(values)])
+ if byte == 0:
+ break
+ result += bytes([byte])
+ i += 1
+
+ tokens.append(InstructionTextToken(InstructionTextTokenType.BraceToken, "\""))
+ tokens.append(InstructionTextToken(InstructionTextTokenType.StringToken, DataBuffer(result).escape()))
+ tokens.append(InstructionTextToken(InstructionTextTokenType.BraceToken, "\"_enc"))
+ return True
+
+
+EncodedStringConstantRenderer().register()