Source code for binaryninja.metadata

# 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
from typing import Union, Optional, List, Tuple, Any

# Binary Ninja components
from . import _binaryninjacore as core
from .enums import MetadataType

MetadataValueType = Union['Metadata', int, bool, str, bytes, float, List['MetadataValueType'], Tuple['MetadataValueType'], dict]


[docs] class Metadata: def __init__( self, value: Optional[MetadataValueType] = None, signed: Optional[bool] = None, raw: Optional[bool] = None, handle: Optional[core.BNMetadata] = None ): """ The 'raw' parameter is no longer needed it was a work around for a python 2 limitation. To pass raw data into this api simply use a `bytes` object """ if handle is not None: self.handle = handle elif isinstance(value, bool): self.handle = core.BNCreateMetadataBooleanData(value) elif isinstance(value, int): if signed: self.handle = core.BNCreateMetadataSignedIntegerData(value) else: self.handle = core.BNCreateMetadataUnsignedIntegerData(value) elif isinstance(value, str): self.handle = core.BNCreateMetadataStringData(value) elif isinstance(value, bytes): buffer = (ctypes.c_ubyte * len(value)).from_buffer_copy(value) self.handle = core.BNCreateMetadataRawData(buffer, len(value)) elif isinstance(value, float): self.handle = core.BNCreateMetadataDoubleData(value) elif isinstance(value, (list, tuple)): self.handle = core.BNCreateMetadataOfType(MetadataType.ArrayDataType) for elm in value: md = Metadata(elm, signed, raw) core.BNMetadataArrayAppend(self.handle, md.handle) elif isinstance(value, dict): self.handle = core.BNCreateMetadataOfType(MetadataType.KeyValueDataType) for elm in value: md = Metadata(value[elm], signed, raw) core.BNMetadataSetValueForKey(self.handle, str(elm), md.handle) else: raise ValueError(f"{type(value)} doesn't contain type of: int, bool, str, float, list, dict") def __len__(self): if self.is_array or self.is_dict or self.is_string or self.is_bytes: return core.BNMetadataSize(self.handle) raise TypeError("Metadata object doesn't support len()") def __eq__(self, other): if isinstance(other, int) and self.is_integer: return int(self) == other elif isinstance(other, str) and self.is_string: return str(self) == other elif isinstance(other, bytes) and self.is_bytes: return bytes(self) == other elif isinstance(other, float) and self.is_float: return float(self) == other elif isinstance(other, bool) and self.is_boolean: return bool(self) == other elif self.is_array and ((isinstance(other, Metadata) and other.is_array) or isinstance(other, list)): if len(self) != len(other): return False for a, b in zip(self, other): if a != b: return False return True elif self.is_dict and ((isinstance(other, Metadata) and other.is_dict) or isinstance(other, dict)): if len(self) != len(other): return False for a, b in zip(self, other): if a != b or self[a] != other[b]: return False return True elif isinstance(other, Metadata) and self.is_integer and other.is_integer: return int(self) == int(other) elif isinstance(other, Metadata) and self.is_string and other.is_string: return str(self) == str(other) elif isinstance(other, Metadata) and self.is_bytes and other.is_bytes: return bytes(self) == bytes(other) elif isinstance(other, Metadata) and self.is_float and other.is_float: return float(self) == float(other) elif isinstance(other, Metadata) and self.is_boolean and other.is_boolean: return bool(self) == bool(other) return NotImplemented def __ne__(self, other): return not self.__eq__(other) def __iter__(self): if self.is_array: for i in range(core.BNMetadataSize(self.handle)): yield Metadata(handle=core.BNMetadataGetForIndex(self.handle, i)).value elif self.is_dict: result = core.BNMetadataGetValueStore(self.handle) assert result is not None, "core.BNMetadataGetValueStore returned None" try: for i in range(result.contents.size): if isinstance(result.contents.keys[i], bytes): yield result.contents.keys[i].decode("utf-8") else: yield result.contents.keys[i] finally: core.BNFreeMetadataValueStore(result) else: raise TypeError("Metadata object doesn't support iteration") def __getitem__(self, value): if self.is_array: if not isinstance(value, int): raise ValueError("Metadata object only supports integers for indexing") if value >= len(self): raise IndexError("Index value out of range") return Metadata(handle=core.BNMetadataGetForIndex(self.handle, value)).value if self.is_dict: if not isinstance(value, str): raise ValueError("Metadata object only supports strings for indexing") handle = core.BNMetadataGetForKey(self.handle, value) if handle is None: raise KeyError(value) return Metadata(handle=handle).value raise NotImplementedError("Metadata object doesn't support indexing") def __str__(self): if self.is_string: return str(core.BNMetadataGetString(self.handle)) raise ValueError("Metadata object not a string") def __bool__(self): if self.is_boolean: return bool(core.BNMetadataGetBoolean(self.handle)) raise ValueError("Metadata object not a boolean") def __bytes__(self): try: length = ctypes.c_ulonglong() length.value = 0 native_list = core.BNMetadataGetRaw(self.handle, ctypes.byref(length)) assert native_list is not None, "core.BNMetadataGetRaw returned None" return ctypes.string_at(native_list, length.value) finally: core.BNFreeMetadataRaw(native_list) def __int__(self): if self.is_signed_integer: return core.BNMetadataGetSignedInteger(self.handle) if self.is_unsigned_integer: return core.BNMetadataGetUnsignedInteger(self.handle) raise ValueError("Metadata object not of integer type") def __float__(self): if not self.is_float: raise ValueError("Metadata object is not float type") return core.BNMetadataGetDouble(self.handle) def __nonzero__(self): if not self.is_boolean: raise ValueError("Metadata object is not boolean type") return core.BNMetadataGetBoolean(self.handle)
[docs] def get(self, key:str, default:Any=None) -> Any: try: return self[key] except (KeyError, IndexError, ValueError, NotImplementedError): return default
@property def value(self): if self.is_integer: return int(self) elif self.is_string: return str(self) elif self.is_bytes: return bytes(self) elif self.is_float: return float(self) elif self.is_boolean: return bool(self) elif self.is_array: return list(self) elif self.is_dict: return self.get_dict() raise TypeError()
[docs] def get_dict(self): if not self.is_dict: raise TypeError() result = {} for key in self: result[key] = self[key] return result
[docs] def get_json_string(self): return str(core.BNMetadataGetJsonString(self.handle))
@property def type(self): return MetadataType(core.BNMetadataGetType(self.handle)) @property def is_integer(self): return self.is_signed_integer or self.is_unsigned_integer @property def is_signed_integer(self): return core.BNMetadataIsSignedInteger(self.handle) @property def is_unsigned_integer(self): return core.BNMetadataIsUnsignedInteger(self.handle) @property def is_float(self): return core.BNMetadataIsDouble(self.handle) @property def is_boolean(self): return core.BNMetadataIsBoolean(self.handle) @property def is_string(self): return core.BNMetadataIsString(self.handle) @property def is_raw(self): """deprecated in favor of `is_bytes`""" return core.BNMetadataIsRaw(self.handle) @property def is_bytes(self): return core.BNMetadataIsRaw(self.handle) @property def is_array(self): return core.BNMetadataIsArray(self.handle) @property def is_dict(self): return core.BNMetadataIsKeyValueStore(self.handle)
[docs] def remove(self, key_or_index): if isinstance(key_or_index, str) and self.is_dict: core.BNMetadataRemoveKey(self.handle, key_or_index) elif isinstance(key_or_index, int) and self.is_array: core.BNMetadataRemoveIndex(self.handle, key_or_index) else: raise TypeError("remove only valid for dict and array objects")