From 5d4fc5f1fb7a0c368d2a048a5d750564c970c9c9 Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Mon, 13 Oct 2025 19:35:01 -0400 Subject: Add derived strings and string recognizer API --- python/binaryview.py | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 2 deletions(-) (limited to 'python/binaryview.py') diff --git a/python/binaryview.py b/python/binaryview.py index 9e42d8fb..e38c334c 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -45,7 +45,7 @@ from . import decorators from .enums import ( AnalysisState, SymbolType, Endianness, ModificationStatus, StringType, SegmentFlag, SectionSemantics, FindFlag, TypeClass, BinaryViewEventType, FunctionGraphType, TagReferenceType, TagTypeType, RegisterValueType, DisassemblyOption, - RelocationType + RelocationType, DerivedStringLocationType ) from .exceptions import RelocationWriteException, ExternalLinkException @@ -82,6 +82,7 @@ from . import typecontainer from . import externallibrary from . import project from . import undo +from . import stringrecognizer PathType = Union[str, os.PathLike] @@ -216,6 +217,8 @@ class NotificationType(IntFlag): UndoEntryTaken = 1 << 50 RedoEntryTaken = 1 << 51 Rebased = 1 << 52 + DerivedStringFound = 1 << 53 + DerivedStringRemoved = 1 << 54 BinaryDataUpdates = DataWritten | DataInserted | DataRemoved FunctionLifetime = FunctionAdded | FunctionRemoved @@ -226,7 +229,7 @@ class NotificationType(IntFlag): TagUpdates = TagLifetime | TagUpdated SymbolLifetime = SymbolAdded | SymbolRemoved SymbolUpdates = SymbolLifetime | SymbolUpdated - StringUpdates = StringFound | StringRemoved + StringUpdates = StringFound | StringRemoved | DerivedStringFound | DerivedStringRemoved TypeLifetime = TypeDefined | TypeUndefined TypeUpdates = TypeLifetime | TypeReferenceChanged | TypeFieldReferenceChanged SegmentLifetime = SegmentAdded | SegmentRemoved @@ -390,6 +393,12 @@ class BinaryDataNotification: def string_removed(self, view: 'BinaryView', string_type: StringType, offset: int, length: int) -> None: pass + def derived_string_found(self, view: 'BinaryView', string: 'DerivedString') -> None: + pass + + def derived_string_removed(self, view: 'BinaryView', string: 'DerivedString') -> None: + pass + def type_defined(self, view: 'BinaryView', name: '_types.QualifiedName', type: '_types.Type') -> None: pass @@ -518,6 +527,136 @@ class StringReference: return self._view +class StringRef: + def __init__(self, handle): + self.handle = core.handle_of_type(handle, core.BNStringRef) + + def __del__(self): + if core is not None: + core.BNFreeStringRef(self.handle) + + def __bytes__(self): + # Do not call the wrapper BNGetStringRefContents here, it will crash as the generator + # does not understand that this API gives a direct reference to the string + value_ptr = core._BNGetStringRefContents(self.handle) + value_len = core.BNGetStringRefSize(self.handle) + buf = ctypes.create_string_buffer(value_len) + ctypes.memmove(buf, value_ptr, value_len) + return bytes(buf.raw) + + def __str__(self): + return core.pyNativeStr(bytes(self)) + + def __len__(self): + return core.BNGetStringRefSize(self.handle) + + def __repr__(self): + return repr(str(self)) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return bytes(self) == bytes(other) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __lt__(self, other) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return bytes(self) < bytes(self) + + def __gt__(self, other) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return bytes(self) > bytes(other) + + def __le__(self, other) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return bytes(self) <= bytes(other) + + def __ge__(self, other) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return bytes(self) >= bytes(other) + + def __hash__(self): + return hash(bytes(self)) + + +@dataclass(frozen=True) +class DerivedStringLocation: + location_type: 'DerivedStringLocationType' + address: int + length: int + + +@dataclass(frozen=True) +class DerivedString: + value: 'StringRef' + location: Optional[DerivedStringLocation] + custom_type: Optional[stringrecognizer.CustomStringType] + + def __init__( + self, value: Union['StringRef', str, bytes, bytearray, 'databuffer.DataBuffer'], + location: Optional[DerivedStringLocation], custom_type: Optional[stringrecognizer.CustomStringType] + ): + if isinstance(value, str): + value = value.encode("utf-8") + value = StringRef(core.BNCreateStringRefOfLength(value, len(value))) + elif isinstance(value, bytes): + value = StringRef(core.BNCreateStringRefOfLength(value, len(value))) + elif isinstance(value, bytearray): + value = bytes(value) + value = StringRef(core.BNCreateStringRefOfLength(value, len(value))) + elif isinstance(value, databuffer.DataBuffer): + value = bytes(value) + value = StringRef(core.BNCreateStringRefOfLength(value, len(value))) + elif not isinstance(value, StringRef): + value = str(value).encode("utf-8") + value = StringRef(core.BNCreateStringRefOfLength(value, len(value))) + + object.__setattr__(self, "value", value) + object.__setattr__(self, "location", location) + object.__setattr__(self, "custom_type", custom_type) + + def _to_core_struct(self, owned: bool) -> 'core.BNDerivedString': + result = core.BNDerivedString() + if owned: + result.value = core.BNDuplicateStringRef(self.value.handle) + else: + result.value = self.value.handle + result.locationValid = self.location is not None + if result.locationValid: + result.location.locationType = self.location.location_type + result.location.addr = self.location.address + result.location.len = self.location.length + if self.custom_type is None: + result.customType = None + else: + result.customType = self.custom_type.handle + return result + + @staticmethod + def _from_core_struct(obj: 'core.BNDerivedString', owned: bool) -> 'DerivedString': + if owned: + value = StringRef(obj.value) + else: + value = StringRef(core.BNDuplicateStringRef(obj.value)) + if obj.locationValid: + location = DerivedStringLocation(DerivedStringLocationType(obj.location.locationType), obj.location.addr, obj.location.len) + else: + location = None + if obj.customType: + custom_type = stringrecognizer.CustomStringType(obj.customType) + else: + custom_type = None + return DerivedString(value, location, custom_type) + + class AnalysisCompletionEvent: """ The ``AnalysisCompletionEvent`` object provides an asynchronous mechanism for receiving @@ -701,6 +840,8 @@ class BinaryDataNotificationCallbacks: self._cb.symbolUpdated = self._cb.symbolUpdated.__class__(self._symbol_updated) self._cb.stringFound = self._cb.stringFound.__class__(self._string_found) self._cb.stringRemoved = self._cb.stringRemoved.__class__(self._string_removed) + self._cb.derivedStringFound = self._cb.derivedStringFound.__class__(self._derived_string_found) + self._cb.derivedStringRemoved = self._cb.derivedStringRemoved.__class__(self._derived_string_removed) self._cb.typeDefined = self._cb.typeDefined.__class__(self._type_defined) self._cb.typeUndefined = self._cb.typeUndefined.__class__(self._type_undefined) self._cb.typeReferenceChanged = self._cb.typeReferenceChanged.__class__(self._type_ref_changed) @@ -774,6 +915,10 @@ class BinaryDataNotificationCallbacks: self._cb.stringFound = self._cb.stringFound.__class__(self._string_found) if notify.notifications & NotificationType.StringRemoved: self._cb.stringRemoved = self._cb.stringRemoved.__class__(self._string_removed) + if notify.notifications & NotificationType.DerivedStringFound: + self._cb.derivedStringFound = self._cb.derivedStringFound.__class__(self._derived_string_found) + if notify.notifications & NotificationType.DerivedStringRemoved: + self._cb.derivedStringRemoved = self._cb.derivedStringRemoved.__class__(self._derived_string_removed) if notify.notifications & NotificationType.TypeDefined: self._cb.typeDefined = self._cb.typeDefined.__class__(self._type_defined) if notify.notifications & NotificationType.TypeUndefined: @@ -1017,6 +1162,18 @@ class BinaryDataNotificationCallbacks: except: log_error_for_exception("Unhandled Python exception in BinaryDataNotificationCallbacks._string_removed") + def _derived_string_found(self, ctxt, view: core.BNBinaryView, string) -> None: + try: + self._notify.derived_string_found(self._view, DerivedString._from_core_struct(string[0], False)) + except: + log_error_for_exception("Unhandled Python exception in BinaryDataNotificationCallbacks._derived_string_found") + + def _derived_string_removed(self, ctxt, view: core.BNBinaryView, string) -> None: + try: + self._notify.derived_string_removed(self._view, DerivedString._from_core_struct(string[0], False)) + except: + log_error_for_exception("Unhandled Python exception in BinaryDataNotificationCallbacks._derived_string_removed") + def _type_defined(self, ctxt, view: core.BNBinaryView, name: str, type_obj: '_types.Type') -> None: try: qualified_name = _types.QualifiedName._from_core_struct(name[0]) @@ -3401,6 +3558,19 @@ class BinaryView: """List of strings (read-only)""" return self.get_strings() + @property + def derived_strings(self) -> List['DerivedString']: + count = ctypes.c_ulonglong(0) + strings = core.BNGetDerivedStrings(self.handle, count) + assert strings is not None, "core.BNGetDerivedStrings returned None" + try: + result = [] + for i in range(0, count.value): + result.append(DerivedString._from_core_struct(strings[i], False)) + return result + finally: + core.BNFreeDerivedStringList(strings, count.value) + @property def saved(self) -> bool: """boolean state of whether or not the file has been saved (read/write)""" @@ -5818,6 +5988,20 @@ class BinaryView: core.BNFreeTypeReferences(refs, count.value) return result + def get_derived_string_code_refs(self, str: 'DerivedString', max_items: Optional[int] = None) -> Generator['ReferenceSource', None, None]: + count = ctypes.c_ulonglong(0) + has_max_items = max_items is not None + max_items_value = max_items if has_max_items else 0 + core_str = str._to_core_struct(False) + refs = core.BNGetDerivedStringCodeReferences(self.handle, core_str, count, has_max_items, max_items_value) + assert refs is not None, "core.BNGetDerivedStringCodeReferences returned None" + + try: + for i in range(0, count.value): + yield ReferenceSource._from_core_struct(self, refs[i]) + finally: + core.BNFreeCodeReferences(refs, count.value) + def add_data_ref(self, from_addr: int, to_addr: int) -> None: """ ``add_data_ref`` adds an auto data cross-reference (xref) from the address ``from_addr`` to the address ``to_addr``. -- cgit v1.3.1