|
| 1 | +# Copyright (c) 2015-2026 Vector 35 Inc |
| 2 | +# |
| 3 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 4 | +# of this software and associated documentation files (the "Software"), to |
| 5 | +# deal in the Software without restriction, including without limitation the |
| 6 | +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| 7 | +# sell copies of the Software, and to permit persons to whom the Software is |
| 8 | +# furnished to do so, subject to the following conditions: |
| 9 | +# |
| 10 | +# The above copyright notice and this permission notice shall be included in |
| 11 | +# all copies or substantial portions of the Software. |
| 12 | +# |
| 13 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 14 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 15 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 16 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 17 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 18 | +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 19 | +# IN THE SOFTWARE. |
| 20 | + |
| 21 | +import ctypes |
| 22 | +from typing import Any, List, Optional |
| 23 | + |
| 24 | +import binaryninja |
| 25 | +import binaryninja._binaryninjacore as core |
| 26 | +from . import platform as _platform |
| 27 | +from . import types as _types |
| 28 | +from .log import log_error_for_exception |
| 29 | + |
| 30 | + |
| 31 | +class _FormatStringResolutionProviderMetaclass(type): |
| 32 | + def __iter__(self): |
| 33 | + binaryninja._init_plugins() |
| 34 | + count = ctypes.c_ulonglong() |
| 35 | + providers = core.BNGetFormatStringResolutionProviderList(count) |
| 36 | + try: |
| 37 | + for i in range(count.value): |
| 38 | + yield FormatStringResolutionProvider(providers[i]) |
| 39 | + finally: |
| 40 | + core.BNFreeFormatStringResolutionProviderList(providers) |
| 41 | + |
| 42 | + def __getitem__(self, value): |
| 43 | + binaryninja._init_plugins() |
| 44 | + provider = core.BNGetFormatStringResolutionProviderByName(str(value)) |
| 45 | + if provider is None: |
| 46 | + raise KeyError(f"'{value}' is not a valid format string resolution provider") |
| 47 | + return FormatStringResolutionProvider(provider) |
| 48 | + |
| 49 | + def __contains__(cls: '_FormatStringResolutionProviderMetaclass', name: object) -> bool: |
| 50 | + if not isinstance(name, str): |
| 51 | + return False |
| 52 | + try: |
| 53 | + cls[name] |
| 54 | + return True |
| 55 | + except KeyError: |
| 56 | + return False |
| 57 | + |
| 58 | + def get( |
| 59 | + cls: '_FormatStringResolutionProviderMetaclass', name: str, default: Any = None |
| 60 | + ) -> Optional['FormatStringResolutionProvider']: |
| 61 | + try: |
| 62 | + return cls[name] |
| 63 | + except KeyError: |
| 64 | + if default is not None: |
| 65 | + return default |
| 66 | + return None |
| 67 | + |
| 68 | + |
| 69 | +class FormatStringResolutionProvider(metaclass=_FormatStringResolutionProviderMetaclass): |
| 70 | + """ |
| 71 | + ``FormatStringResolutionProvider`` resolves the variadic argument types described by a format string. |
| 72 | +
|
| 73 | + To implement a provider, subclass this class, set :py:attr:`name`, implement |
| 74 | + :py:meth:`perform_is_valid`, and call :py:meth:`register`. The confidence attached to each returned |
| 75 | + :py:class:`Type` is preserved when the result crosses the core API boundary. |
| 76 | + """ |
| 77 | + name = None |
| 78 | + _registered_providers = [] |
| 79 | + |
| 80 | + def __init__(self, handle=None): |
| 81 | + self._pending_type_lists = {} |
| 82 | + if handle is not None: |
| 83 | + self.handle = core.handle_of_type(handle, core.BNFormatStringResolutionProvider) |
| 84 | + self.__dict__["name"] = core.BNGetFormatStringResolutionProviderName(handle) |
| 85 | + |
| 86 | + def __repr__(self): |
| 87 | + return f"<FormatStringResolutionProvider: {self.name}>" |
| 88 | + |
| 89 | + def register(self): |
| 90 | + """Register this provider with the Binary Ninja core.""" |
| 91 | + if self.__class__.name is None: |
| 92 | + raise ValueError("name is missing") |
| 93 | + if hasattr(self, "handle"): |
| 94 | + raise ValueError("provider is already registered") |
| 95 | + |
| 96 | + self._cb = core.BNFormatStringResolutionProviderCallbacks() |
| 97 | + self._cb.context = 0 |
| 98 | + self._cb.isValid = self._cb.isValid.__class__(self._is_valid) |
| 99 | + self._cb.freeTypeList = self._cb.freeTypeList.__class__(self._free_type_list) |
| 100 | + self.handle = core.BNRegisterFormatStringResolutionProvider(self.__class__.name, self._cb) |
| 101 | + assert self.handle is not None, "core.BNRegisterFormatStringResolutionProvider returned None" |
| 102 | + self.__class__._registered_providers.append(self) |
| 103 | + |
| 104 | + def _is_valid(self, ctxt, format_string, platform, types, count) -> bool: |
| 105 | + types[0] = None |
| 106 | + count[0] = 0 |
| 107 | + try: |
| 108 | + platform_obj = None |
| 109 | + if platform: |
| 110 | + platform_obj = _platform.CorePlatform._from_cache( |
| 111 | + core.BNNewPlatformReference(platform)) |
| 112 | + |
| 113 | + result = self.perform_is_valid(core.pyNativeStr(format_string), platform_obj) |
| 114 | + if result is None: |
| 115 | + return False |
| 116 | + |
| 117 | + resolved_types = list(result) |
| 118 | + for resolved_type in resolved_types: |
| 119 | + if not isinstance(resolved_type, _types.Type): |
| 120 | + raise TypeError("perform_is_valid must return Type objects") |
| 121 | + |
| 122 | + count[0] = len(resolved_types) |
| 123 | + if not resolved_types: |
| 124 | + return True |
| 125 | + |
| 126 | + output_buf = (core.BNTypeWithConfidence * len(resolved_types))() |
| 127 | + created_count = 0 |
| 128 | + try: |
| 129 | + for i, resolved_type in enumerate(resolved_types): |
| 130 | + output_buf[i].type = core.BNNewTypeReference(resolved_type.handle) |
| 131 | + output_buf[i].confidence = resolved_type.confidence |
| 132 | + created_count += 1 |
| 133 | + except Exception: |
| 134 | + for i in range(created_count): |
| 135 | + core.BNFreeType(output_buf[i].type) |
| 136 | + raise |
| 137 | + |
| 138 | + output_ptr = ctypes.cast(output_buf, ctypes.POINTER(core.BNTypeWithConfidence)) |
| 139 | + key = ctypes.cast(output_ptr, ctypes.c_void_p).value |
| 140 | + self._pending_type_lists[key] = (output_ptr, output_buf, len(resolved_types)) |
| 141 | + types[0] = output_ptr |
| 142 | + return True |
| 143 | + except Exception: |
| 144 | + types[0] = None |
| 145 | + count[0] = 0 |
| 146 | + log_error_for_exception("Unhandled Python exception in FormatStringResolutionProvider._is_valid") |
| 147 | + return False |
| 148 | + |
| 149 | + def _free_type_list(self, ctxt, type_list, count): |
| 150 | + try: |
| 151 | + key = ctypes.cast(type_list, ctypes.c_void_p).value |
| 152 | + if key not in self._pending_type_lists: |
| 153 | + raise ValueError("freeing type list that wasn't allocated") |
| 154 | + _, output_buf, output_count = self._pending_type_lists.pop(key) |
| 155 | + for i in range(output_count): |
| 156 | + core.BNFreeType(output_buf[i].type) |
| 157 | + except Exception: |
| 158 | + log_error_for_exception("Unhandled Python exception in FormatStringResolutionProvider._free_type_list") |
| 159 | + |
| 160 | + def perform_is_valid( |
| 161 | + self, format_string: str, platform: Optional['_platform.Platform'] |
| 162 | + ) -> Optional[List['_types.Type']]: |
| 163 | + """ |
| 164 | + Resolve the argument types described by ``format_string`` for ``platform``. |
| 165 | +
|
| 166 | + Return ``None`` when the format is invalid, an empty list for a valid format with no arguments, |
| 167 | + or a list of :py:class:`Type` objects for a valid format. Override this method in custom providers. |
| 168 | + """ |
| 169 | + raise NotImplementedError("Not implemented") |
| 170 | + |
| 171 | + def is_valid( |
| 172 | + self, format_string: str, platform: Optional['_platform.Platform'] |
| 173 | + ) -> Optional[List['_types.Type']]: |
| 174 | + """ |
| 175 | + Resolve the argument types described by ``format_string`` for ``platform``. |
| 176 | +
|
| 177 | + The returned type objects carry the confidence supplied by the provider. ``None`` denotes an invalid |
| 178 | + format; an empty list denotes a valid format that consumes no arguments. |
| 179 | + """ |
| 180 | + if not isinstance(format_string, str): |
| 181 | + raise TypeError("format_string must be a string") |
| 182 | + if platform is not None and not isinstance(platform, _platform.Platform): |
| 183 | + raise TypeError("platform must be a Platform or None") |
| 184 | + if not hasattr(self, "handle"): |
| 185 | + raise ValueError("provider is not registered") |
| 186 | + |
| 187 | + type_list = ctypes.POINTER(core.BNTypeWithConfidence)() |
| 188 | + count = ctypes.c_ulonglong() |
| 189 | + valid = core.BNFormatStringResolutionProviderIsValid( |
| 190 | + self.handle, format_string, platform.handle if platform is not None else None, type_list, count) |
| 191 | + if not valid: |
| 192 | + if type_list: |
| 193 | + core.BNFreeTypeWithConfidenceList(type_list, count.value) |
| 194 | + return None |
| 195 | + |
| 196 | + try: |
| 197 | + return [ |
| 198 | + _types.Type.create( |
| 199 | + core.BNNewTypeReference(type_list[i].type), platform=platform, |
| 200 | + confidence=type_list[i].confidence) |
| 201 | + for i in range(count.value) |
| 202 | + ] |
| 203 | + finally: |
| 204 | + core.BNFreeTypeWithConfidenceList(type_list, count.value) |
0 commit comments