diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/constantrenderer.py | 180 | ||||
| -rw-r--r-- | python/examples/encoded_strings.py | 58 |
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() |
