# coding=utf-8
# 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 List, Optional, Dict, Callable
import binaryninja
from . import _binaryninjacore as core
from . import databuffer
from . import filemetadata
[docs]
class KeyValueStore:
"""
``class KeyValueStore`` maintains access to the raw data stored in Snapshots and various
other Database-related structures.
"""
def __init__(self, buffer: Optional[databuffer.DataBuffer] = None, handle=None):
if handle is not None:
self.handle = core.handle_of_type(handle, core.BNKeyValueStore)
else:
if buffer is None:
_handle = core.BNCreateKeyValueStore()
else:
_handle = core.BNCreateKeyValueStoreFromDataBuffer(buffer.handle)
assert _handle is not None
self.handle = _handle
def __del__(self):
core.BNFreeKeyValueStore(self.handle)
def __getitem__(self, item: str) -> databuffer.DataBuffer:
return self.get_value(item)
def __setitem__(self, key: str, value: databuffer.DataBuffer):
return self.set_value(key, value)
@property
def keys(self):
"""Get a list of all keys stored in the kvs (read-only)"""
count = ctypes.c_ulonglong(0)
value = core.BNGetKeyValueStoreKeys(self.handle, count)
assert value is not None
result = []
try:
for i in range(0, count.value):
result.append(value[i])
return result
finally:
core.BNFreeStringList(value, count)
[docs]
def get_value(self, key: str) -> databuffer.DataBuffer:
"""Get the value for a single key"""
handle = core.BNGetKeyValueStoreBuffer(self.handle, key)
assert handle is not None
return databuffer.DataBuffer(handle=handle)
[docs]
def set_value(self, key: str, value: databuffer.DataBuffer):
"""Set the value for a single key"""
core.BNSetKeyValueStoreBuffer(self.handle, key, value.handle)
@property
def serialized_data(self) -> databuffer.DataBuffer:
"""Get the stored representation of the kvs (read-only)"""
handle = core.BNGetKeyValueStoreSerializedData(self.handle)
assert handle is not None
return databuffer.DataBuffer(handle=handle)
[docs]
def begin_namespace(self, name: str):
"""Begin storing new keys into a namespace"""
core.BNBeginKeyValueStoreNamespace(self.handle, name)
[docs]
def end_namespace(self):
"""End storing new keys into a namespace"""
core.BNEndKeyValueStoreNamespace(self.handle)
@property
def empty(self) -> bool:
"""If the kvs is empty (read-only)"""
return core.BNIsKeyValueStoreEmpty(self.handle)
@property
def value_size(self) -> int:
"""Number of values in the kvs (read-only)"""
return core.BNGetKeyValueStoreValueSize(self.handle)
@property
def data_size(self) -> int:
"""Length of serialized data (read-only)"""
return core.BNGetKeyValueStoreDataSize(self.handle)
@property
def value_storage_size(self) -> int:
"""Size of all data in storage (read-only)"""
return core.BNGetKeyValueStoreValueStorageSize(self.handle)
@property
def namespace_size(self) -> int:
"""Number of namespaces pushed with begin_namespace (read-only)"""
return core.BNGetKeyValueStoreNamespaceSize(self.handle)
[docs]
class Snapshot:
"""
``class Snapshot`` is a model of an individual database snapshot, created on save.
"""
def __init__(self, handle):
self.handle = core.handle_of_type(handle, core.BNSnapshot)
def __del__(self):
core.BNFreeSnapshot(self.handle)
@property
def database(self) -> 'Database':
"""Get the owning database (read-only)"""
return Database(handle=core.BNGetSnapshotDatabase(self.handle))
@property
def id(self) -> int:
"""Get the numerical id (read-only)"""
return core.BNGetSnapshotId(self.handle)
@property
def name(self) -> str:
"""Get the displayed snapshot name"""
return core.BNGetSnapshotName(self.handle)
@name.setter
def name(self, value: str) -> None:
"""Set the displayed snapshot name"""
core.BNSetSnapshotName(self.handle, value)
@property
def is_auto_save(self) -> bool:
"""If the snapshot was the result of an auto-save (read-only)"""
return core.BNIsSnapshotAutoSave(self.handle)
@property
def has_contents(self) -> bool:
"""If the snapshot has contents, and has not been trimmed (read-only)"""
return core.BNSnapshotHasContents(self.handle)
@property
def has_undo(self) -> bool:
"""If the snapshot has undo data (read-only)"""
return core.BNSnapshotHasUndo(self.handle)
@property
def first_parent(self) -> Optional['Snapshot']:
"""Get the first parent of the snapshot, or None if it has no parents (read-only)"""
handle = core.BNGetSnapshotFirstParent(self.handle)
if handle is None:
return None
return Snapshot(handle=handle)
@property
def parents(self) -> List['Snapshot']:
"""Get a list of all parent snapshots of the snapshot (read-only)"""
count = ctypes.c_ulonglong(0)
parents = core.BNGetSnapshotParents(self.handle, count)
result = []
try:
for i in range(0, count.value):
handle = core.BNNewSnapshotReference(parents[i])
result.append(Snapshot(handle=handle))
return result
finally:
core.BNFreeSnapshotList(parents, count)
@property
def children(self) -> List['Snapshot']:
"""Get a list of all child snapshots of the snapshot (read-only)"""
count = ctypes.c_ulonglong(0)
children = core.BNGetSnapshotChildren(self.handle, count)
result = []
try:
for i in range(0, count.value):
handle = core.BNNewSnapshotReference(children[i])
result.append(Snapshot(handle=handle))
return result
finally:
core.BNFreeSnapshotList(children, count)
@property
def file_contents(self) -> databuffer.DataBuffer:
"""Get a buffer of the raw data at the time of the snapshot (read-only)"""
assert self.has_contents
handle = core.BNGetSnapshotFileContents(self.handle)
assert handle is not None
return databuffer.DataBuffer(handle=handle)
@property
def file_contents_hash(self) -> databuffer.DataBuffer:
"""Get a hash of the data at the time of the snapshot (read-only)"""
assert self.has_contents
handle = core.BNGetSnapshotFileContentsHash(self.handle)
assert handle is not None
return databuffer.DataBuffer(handle=handle)
@property
def undo_entries(self):
"""Get a list of undo entries at the time of the snapshot (read-only)"""
assert self.has_undo
raise NotImplementedError("GetUndoEntries is not implemented in python")
@property
def data(self) -> KeyValueStore:
"""Get the backing kvs data with snapshot fields (read-only)"""
assert self.has_contents
handle = core.BNReadSnapshotData(self.handle)
assert handle is not None
return KeyValueStore(handle=handle)
[docs]
def has_ancestor(self, other: 'Snapshot') -> bool:
"""Determine if this snapshot has another as an ancestor"""
return core.BNSnapshotHasAncestor(self.handle, other.handle)
[docs]
class Database:
"""
``class Database`` provides lower level access to raw snapshot data used to construct analysis data
"""
def __init__(self, handle):
self.handle = core.handle_of_type(handle, core.BNDatabase)
def __del__(self):
core.BNFreeDatabase(self.handle)
def __getitem__(self, item: int) -> Optional[Snapshot]:
return self.get_snapshot(item)
[docs]
def get_snapshot(self, id: int) -> Optional[Snapshot]:
"""Get a snapshot by its id, or None if no snapshot with that id exists"""
snap = core.BNGetDatabaseSnapshot(self.handle, id)
if snap is None:
return None
return Snapshot(handle=snap)
@property
def snapshots(self) -> List[Snapshot]:
"""Get a list of all snapshots in the database (read-only)"""
count = ctypes.c_ulonglong(0)
snapshots = core.BNGetDatabaseSnapshots(self.handle, count)
assert snapshots is not None
result = []
try:
for i in range(0, count.value):
handle = core.BNNewSnapshotReference(snapshots[i])
result.append(Snapshot(handle=handle))
return result
finally:
core.BNFreeSnapshotList(snapshots, count)
@property
def current_snapshot(self) -> Optional[Snapshot]:
"""Get the current snapshot"""
snap = core.BNGetDatabaseCurrentSnapshot(self.handle)
if snap is None:
return None
return Snapshot(handle=snap)
@current_snapshot.setter
def current_snapshot(self, value: Snapshot):
core.BNSetDatabaseCurrentSnapshot(self.handle, value.id)
[docs]
def trim_snapshot(self, id: int):
"""
Trim a snapshot's contents in the database by id, but leave the parent/child
hierarchy intact. Future references to this snapshot will return False for has_contents
"""
if not core.BNTrimDatabaseSnapshot(self.handle, id):
raise RuntimeError("BNTrimDatabaseSnapshot returned False")
[docs]
def remove_snapshot(self, id: int):
"""
Remove a snapshot in the database by id, deleting its contents and references.
Attempting to remove a snapshot with children will raise an exception.
"""
if not core.BNRemoveDatabaseSnapshot(self.handle, id):
raise RuntimeError("BNRemoveDatabaseSnapshot returned False")
@property
def global_keys(self) -> List[str]:
"""Get a list of keys for all globals in the database (read-only)"""
count = ctypes.c_ulonglong(0)
value = core.BNGetDatabaseGlobalKeys(self.handle, count)
assert value is not None
result = []
try:
for i in range(0, count.value):
result.append(core.pyNativeStr(value[i]))
return result
finally:
core.BNFreeStringList(value, count)
@property
def globals(self) -> Dict[str, str]:
"""Get a dictionary of all globals (read-only)"""
count = ctypes.c_ulonglong(0)
value = core.BNGetDatabaseGlobalKeys(self.handle, count)
assert value is not None
result = {}
try:
for i in range(0, count.value):
key = core.pyNativeStr(value[i])
result[key] = self.read_global(key)
return result
finally:
core.BNFreeStringList(value, count)
[docs]
def read_global(self, key: str) -> str:
"""Get a specific global by key"""
value = core.BNReadDatabaseGlobal(self.handle, key)
assert value is not None
return value
[docs]
def write_global(self, key: str, value: str):
"""Write a global into the database"""
core.BNWriteDatabaseGlobal(self.handle, key, value)
[docs]
def read_global_data(self, key: str) -> databuffer.DataBuffer:
"""Get a specific global by key, as a binary buffer"""
handle = core.BNReadDatabaseGlobalData(self.handle, key)
assert handle is not None
return databuffer.DataBuffer(handle=handle)
[docs]
def write_global_data(self, key: str, value: databuffer.DataBuffer):
"""Write a binary buffer into a global in the database"""
core.BNWriteDatabaseGlobalData(self.handle, key, value.handle)
@property
def file(self) -> 'filemetadata.FileMetadata':
"""Get the owning FileMetadata (read-only)"""
handle = core.BNGetDatabaseFile(self.handle)
assert handle is not None
return filemetadata.FileMetadata(handle=handle)
@property
def analysis_cache(self) -> KeyValueStore:
"""Get the backing analysis cache kvs (read-only)"""
handle = core.BNReadDatabaseAnalysisCache(self.handle)
assert handle is not None
return KeyValueStore(handle=handle)