diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/basedetection.py | 321 | ||||
| -rw-r--r-- | python/binaryview.py | 114 | ||||
| -rw-r--r-- | python/debuginfo.py | 1 | ||||
| -rw-r--r-- | python/enterprise.py | 7 | ||||
| -rw-r--r-- | python/examples/bin_info.py | 38 | ||||
| -rw-r--r-- | python/examples/raw_binary_base_detection.py | 101 | ||||
| -rw-r--r-- | python/filemetadata.py | 9 | ||||
| -rw-r--r-- | python/function.py | 43 | ||||
| -rw-r--r-- | python/highlevelil.py | 22 | ||||
| -rw-r--r-- | python/interaction.py | 19 | ||||
| -rw-r--r-- | python/lowlevelil.py | 10 | ||||
| -rw-r--r-- | python/mainthread.py | 35 | ||||
| -rw-r--r-- | python/mediumlevelil.py | 10 | ||||
| -rw-r--r-- | python/plugin.py | 21 | ||||
| -rw-r--r-- | python/typearchive.py | 54 | ||||
| -rw-r--r-- | python/typecontainer.py | 19 | ||||
| -rw-r--r-- | python/websocketprovider.py | 11 |
18 files changed, 792 insertions, 44 deletions
diff --git a/python/__init__.py b/python/__init__.py index 498475f4..b7702569 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -74,6 +74,7 @@ from .typearchive import * from .typecontainer import * from .exceptions import * from .project import * +from .basedetection 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/basedetection.py b/python/basedetection.py new file mode 100644 index 00000000..655f017b --- /dev/null +++ b/python/basedetection.py @@ -0,0 +1,321 @@ +# 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 os +import ctypes +from typing import Optional, Union, Literal +from dataclasses import dataclass +from .enums import BaseAddressDetectionPOIType, BaseAddressDetectionConfidence, BaseAddressDetectionPOISetting +from .binaryview import BinaryView +from . import _binaryninjacore as core + + +@dataclass +class BaseAddressDetectionReason: + """``class BaseAddressDetectionReason`` is a class that stores information used to understand why a base address + is a candidate. It consists of a pointer, the offset of the point-of-interest that the pointer aligns with, and the + type of point-of-interest (string, function, or data variable)""" + + pointer: int + offset: int + type: BaseAddressDetectionPOIType + + +class BaseAddressDetection: + """ + ``class BaseAddressDetection`` is a class that is used to detect candidate base addresses for position-dependent + raw binaries + + :Example: + + >>> from binaryninja import * + >>> bad = BaseAddressDetection("firmware.bin") + >>> bad.detect_base_address() + True + >>> hex(bad.preferred_base_address) + '0x4000000' + """ + + def __init__(self, view: Union[str, os.PathLike, BinaryView]) -> None: + if isinstance(view, str) or isinstance(view, os.PathLike): + view = BinaryView.load(str(view), update_analysis=False) + + _handle = core.BNCreateBaseAddressDetection(view.handle) + assert _handle is not None, "core.BNCreateBaseAddressDetection returned None" + self._handle = _handle + self._view_arch = view.arch + + self._scores = list() + self._confidence = 0 + self._last_tested_base_address = None + + def __del__(self): + if core is not None: + core.BNFreeBaseAddressDetection(self._handle) + + @property + def scores(self) -> list[tuple[int, int]]: + """ + ``scores`` returns a list of candidate base addresses and their scores + + .. note:: The score is set to the number of times a pointer pointed to a point-of-interest at that base address + + :Example: + + >>> from binaryninja import * + >>> bad = BaseAddressDetection("firmware.bin") + >>> bad.detect_base_address() + True + >>> for addr, score in bad.scores: + ... print(f"0x{addr:x}: {score}") + ... + 0x4000000: 7 + 0x400dc00: 1 + 0x400d800: 1 + 0x400cc00: 1 + 0x400c400: 1 + 0x400bc00: 1 + 0x400b800: 1 + 0x3fffc00: 1 + + :return: list of tuples containing each base address and score + :rtype: list[tuple[int, int]] + """ + + return self._scores + + @property + def confidence(self) -> BaseAddressDetectionConfidence: + """ + ``confidence`` returns an enum that indicates confidence the preferred candidate base address is correct + + :return: confidence of the base address detection results + :rtype: BaseAddressDetectionConfidence + """ + + return self._confidence + + @property + def last_tested_base_address(self) -> int: + """ + ``last_tested_base_address`` returns the last candidate base address that was tested + + .. note:: This is useful for situations where the user aborts the analysis and wants to restart from the last \ + tested base address by setting the ``low_boundary`` parameter in :py:func:`BaseAddressDetection.detect_base_address` + + :return: last candidate base address tested + :rtype: int + """ + + return self._last_tested_base_address + + @property + def preferred_base_address(self) -> Optional[int]: + """ + ``preferred_base_address`` returns the candidate base address which contains the most amount of pointers that + align with discovered points-of-interest in the binary + + .. note:: :py:attr:`BaseAddressDetection.confidence` reports a confidence level that the preferred base is correct + + .. note:: :py:attr:`BaseAddressDetection.scores` returns a list of the top 10 candidate base addresses and their \ + scores and can be used to discover other potential candidates + + :return: preferred candidate base address + :rtype: int + """ + + if not self._scores: + return None + + return self._scores[0][0] + + @property + def aborted(self) -> bool: + """ + ``aborted`` indicates whether or not base address detection analysis was aborted early + + :return: True if the analysis was aborted, False otherwise + :rtype: bool + """ + + return core.BNIsBaseAddressDetectionAborted(self._handle) + + def detect_base_address( + self, + arch: Optional[str] = "", + analysis: Optional[str] = Literal["basic", "controlFlow", "full"], + min_strlen: Optional[int] = 10, + alignment: Optional[int] = 1024, + low_boundary: Optional[int] = 0, + high_boundary: Optional[int] = 0xFFFFFFFFFFFFFFFF, + poi_analysis: Optional[BaseAddressDetectionPOISetting] = BaseAddressDetectionPOISetting.POIAnalysisAll, + max_pointers: Optional[int] = 128, + ) -> bool: + """ + ``detect_base_address`` runs initial analysis and attempts to identify candidate base addresses + + .. note:: This operation can take a long time to complete depending on the size and complexity of the binary \ + and the settings used + + :param str arch: CPU architecture of the binary (defaults to using auto-detection) + :param str analysis: analysis mode (``basic``, ``controlFlow``, or ``full``) + :param int min_strlen: minimum length of a string to be considered a point-of-interest + :param int alignment: byte boundary to align the base address to while brute-forcing + :param int low_boundary: lower boundary of the base address range to test + :param int high_boundary: upper boundary of the base address range to test + :param BaseAddressDetectionPOISetting poi_analysis: specifies types of points-of-interest to use for analysis + :param int max_pointers: maximum number of candidate pointers to collect per pointer cluster + :return: True if initial analysis completed with results, False otherwise + :rtype: bool + """ + + if not arch and self._view_arch: + arch = str(self._view_arch) + + if analysis not in ["basic", "controlFlow", "full"]: + raise ValueError("invalid analysis setting") + + if alignment <= 0: + raise ValueError("alignment must be greater than 0") + + if max_pointers < 2: + raise ValueError("max pointers must be at least 2") + + if high_boundary < low_boundary: + raise ValueError("upper boundary must be greater than lower boundary") + + settings = core.BNBaseAddressDetectionSettings( + arch.encode(), + analysis.encode(), + min_strlen, + alignment, + low_boundary, + high_boundary, + poi_analysis, + max_pointers, + ) + + if not core.BNDetectBaseAddress(self._handle, settings): + return False + + max_candidates = 10 + scores = (core.BNBaseAddressDetectionScore * max_candidates)() + confidence = core.BaseAddressDetectionConfidenceEnum() + last_base = ctypes.c_ulonglong() + num_candidates = core.BNGetBaseAddressDetectionScores( + self._handle, scores, max_candidates, ctypes.byref(confidence), ctypes.byref(last_base) + ) + + if num_candidates == 0: + return False + + self._scores.clear() + for i in range(num_candidates): + self._scores.append((scores[i].BaseAddress, scores[i].Score)) + + self._confidence = confidence.value + self._last_tested_base_address = last_base.value + return True + + def abort(self) -> None: + """ + ``abort`` aborts base address detection analysis + + .. note:: ``abort`` does not stop base address detection until after initial analysis has completed and it is \ + in the base address enumeration phase + + :rtype: None + """ + + core.BNAbortBaseAddressDetection(self._handle) + + def get_reasons(self, base_address: int) -> list[BaseAddressDetectionReason]: + """ + ``get_reasons`` returns a list of reasons that can be used to determine why a base address is a candidate + + :param int base_address: base address to get reasons for + :return: list of reasons for the specified base address + :rtype: list[BaseAddressDetectionReason] + """ + + count = ctypes.c_size_t() + reasons = core.BNGetBaseAddressDetectionReasons(self._handle, base_address, ctypes.byref(count)) + if count.value == 0: + return [] + + try: + result = list() + for i in range(count.value): + result.append(BaseAddressDetectionReason(reasons[i].Pointer, reasons[i].POIOffset, reasons[i].POIType)) + return result + finally: + core.BNFreeBaseAddressDetectionReasons(reasons) + + def _get_data_hits_by_type(self, base_address: int, poi_type: int) -> int: + reasons = self.get_reasons(base_address) + if not reasons: + return 0 + + hits = 0 + for reason in reasons: + if reason.type == poi_type: + hits += 1 + + return hits + + def get_string_hits(self, base_address: int) -> int: + """ + ``get_string_hits`` returns the number of times a pointer pointed to a string at the specified + base address + + .. note:: Data variables are only used as points-of-interest if analysis doesn't discover enough strings and \ + functions + + :param int base_address: base address to get string hits for + :return: number of string hits for the specified base address + :rtype: int + """ + + return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIString) + + def get_function_hits(self, base_address: int) -> int: + """ + ``get_function_hits`` returns the number of times a pointer pointed to a function at the + specified base address + + :param int base_address: base address to get function hits for + :return: number of function hits for the specified base address + :rtype: int + """ + + return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIFunction) + + def get_data_hits(self, base_address: int) -> int: + """ + ``get_data_hits`` returns the number of times a pointer pointed to a data variable at the + specified base address + + :param int base_address: base address to get data hits for + :return: number of data hits for the specified base address + :rtype: int + """ + + return self._get_data_hits_by_type(base_address, BaseAddressDetectionPOIType.POIDataVariable) diff --git a/python/binaryview.py b/python/binaryview.py index a281a460..43eda87f 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -30,7 +30,7 @@ import inspect import os import uuid from typing import Callable, Generator, Optional, Union, Tuple, List, Mapping, Any, \ - Iterator, Iterable, KeysView, ItemsView, ValuesView, Dict + Iterator, Iterable, KeysView, ItemsView, ValuesView, Dict, overload from dataclasses import dataclass from enum import IntFlag @@ -1391,6 +1391,42 @@ class Segment: def __len__(self): return self.length + @classmethod + def serialize(cls, image_base: int, start: int, length: int, data_offset: int=0, data_length: int=0, flags: 'SegmentFlag'=SegmentFlag.SegmentReadable, auto_defined=True, segments: str="[]"): + """ + Serialize segment parameters into a JSON string. This is useful for generating a properly formatted segment description as options when using `load`. + :param int image_base: The base address of the image. + :param int start: The start address of the segment. + :param int length: The length of the segment. + :param int data_offset: The offset of the data within the segment. + :param int data_length: The length of the data within the segment. + :param SegmentFlag flags: The flags of the segment. + :param bool auto_defined: Whether the segment is auto-defined. + :param str segments: An optional, existing array of segments to append to. + :return: A JSON string representing the segment. + :rtype: str + + Example usage: + ``` + >>> base = 0x400000 + >>> rom_base = 0xffff0000 + >>> segments = Segment.serialize(image_base=base, start=base, length=0x1000, data_offset=0, data_length=0x1000, flags=SegmentFlag.SegmentReadable|SegmentFlag.SegmentExecutable) + >>> segments = Segment.serialize(image_base=base, start=rom_base, length=0x1000, flags=SegmentFlag.SegmentReadable, segments=segments) + >>> view = load(bytes.fromhex('5054ebfe'), options={'loader.imageBase': base, 'loader.architecture': 'x86', 'loader.segments': segments}) + ``` + """ + segments_list = json.loads(segments) + segment_info = { + "auto_defined": auto_defined, + "data_length": data_length, + "data_offset": data_offset, + "flags": flags, + "length": length, + "start": start - image_base + } + segments_list.append(segment_info) + return json.dumps(segments_list) + @property def length(self): return int(core.BNSegmentGetLength(self.handle)) @@ -1516,6 +1552,43 @@ class Section: def __contains__(self, i: int): return i >= self.start and i < self.end + @classmethod + def serialize(cls, image_base: int, name: str, start: int, length: int, semantics: SectionSemantics=SectionSemantics.DefaultSectionSemantics, type: str="", align: int=1, entry_size: int=0, link: str="", info_section: str="", info_data: int=0, auto_defined: bool=True, sections: str="[]"): + """ + Serialize section parameters into a JSON string. This is useful for generating a properly formatted section description as options when using `load`. + :param int image_base: The base address of the image. + :param str name: The name of the section. + :param int start: The start address of the section. + :param int length: The length of the section. + :param SectionSemantics semantics: The semantics of the section. + :param str type: The type of the section. + :param int align: The alignment of the section. + :param int entry_size: The entry size of the section. + :param str link: The linked section of the section. + :param str info_section: The info section of the section. + :param int info_data: The info data of the section. + :param bool auto_defined: Whether the section is auto-defined. + :param str sections: An optional, existing array of sections to append to. + :return: A JSON string representing the section. + :rtype: str + """ + sections_list = json.loads(sections) + section_info = { + "align": align, + "auto_defined": auto_defined, + "entry_size": entry_size, + "info_data": info_data, + "info_section": info_section, + "length": length, + "link": link, + "name": name, + "semantics": semantics, + "start": start - image_base, + "type": type + } + sections_list.append(section_info) + return json.dumps(sections_list) + @property def name(self) -> str: return core.BNSectionGetName(self.handle) @@ -1912,6 +1985,12 @@ class FunctionList: self._n += 1 return _function.Function(self._view, func) + @overload + def __getitem__(self, i: int) -> '_function.Function': ... + + @overload + def __getitem__(self, i: slice) -> List['_function.Function']: ... + def __getitem__(self, i: Union[int, slice]) -> Union['_function.Function', List['_function.Function']]: if isinstance(i, int): if i < 0: @@ -7810,6 +7889,7 @@ class BinaryView: Attach a given type archive to the analysis and try to connect to it. If attaching was successful, names from that archive will become available to pull, but no types will actually be associated by calling this. + :param archive: New archive """ attached = self.attach_type_archive_by_id(archive.id, archive.path) @@ -7854,6 +7934,7 @@ class BinaryView: def detach_type_archive(self, archive: 'typearchive.TypeArchive'): """ Detach from a type archive, breaking all associations to types within the archive + :param archive: Type archive to detach """ self.detach_type_archive_by_id(archive.id) @@ -7861,6 +7942,7 @@ class BinaryView: def detach_type_archive_by_id(self, id: str): """ Detach from a type archive, breaking all associations to types within the archive + :param id: Id of archive to detach """ if not core.BNBinaryViewDetachTypeArchive(self.handle, id): @@ -7869,6 +7951,7 @@ class BinaryView: def get_type_archive(self, id: str) -> Optional['typearchive.TypeArchive']: """ Look up a connected archive by its id + :param id: Id of archive :return: Archive, if one exists with that id. Otherwise None """ @@ -7880,6 +7963,7 @@ class BinaryView: def get_type_archive_path(self, id: str) -> Optional[str]: """ Look up the path for an attached (but not necessarily connected) type archive by its id + :param id: Id of archive :return: Archive path, if it is attached. Otherwise None. """ @@ -7892,6 +7976,7 @@ class BinaryView: def type_archive_type_names(self) -> Mapping['_types.QualifiedName', List[Tuple['typearchive.TypeArchive', str]]]: """ Get a list of all available type names in all connected archives, and their archive/type id pair + :return: name <-> [(archive, archive type id)] for all type names """ result = {} @@ -7908,6 +7993,7 @@ class BinaryView: def get_type_archives_for_type_name(self, name: '_types.QualifiedNameType') -> List[Tuple['typearchive.TypeArchive', str]]: """ Get a list of all connected type archives that have a given type name + :return: (archive, archive type id) for all archives """ name = _types.QualifiedName(name) @@ -7932,6 +8018,7 @@ class BinaryView: def associated_type_archive_types(self) -> Mapping['_types.QualifiedName', Tuple[Optional['typearchive.TypeArchive'], str]]: """ Get a list of all types in the analysis that are associated with attached type archives + :return: Map of all analysis types to their corresponding archive / id. If a type is associated with a disconnected type archive, the archive will be None. """ result = {} @@ -7952,6 +8039,7 @@ class BinaryView: def associated_type_archive_type_ids(self) -> Mapping[str, Tuple[str, str]]: """ Get a list of all types in the analysis that are associated with type archives + :return: Map of all analysis types to their corresponding archive / id """ @@ -7976,6 +8064,7 @@ class BinaryView: def get_associated_types_from_archive(self, archive: 'typearchive.TypeArchive') -> Mapping['_types.QualifiedName', str]: """ Get a list of all types in the analysis that are associated with a specific type archive + :return: Map of all analysis types to their corresponding archive id """ result = {} @@ -8011,6 +8100,7 @@ class BinaryView: def get_associated_type_archive_type_target(self, name: '_types.QualifiedNameType') -> Optional[Tuple[Optional['typearchive.TypeArchive'], str]]: """ Determine the target archive / type id of a given analysis type + :param name: Analysis type :return: (archive, archive type id) if the type is associated. None otherwise. """ @@ -8027,6 +8117,7 @@ class BinaryView: def get_associated_type_archive_type_target_by_id(self, type_id: str) -> Optional[Tuple[str, str]]: """ Determine the target archive / type id of a given analysis type + :param type_id: Analysis type id :return: (archive id, archive type id) if the type is associated. None otherwise. """ @@ -8042,6 +8133,7 @@ class BinaryView: def get_associated_type_archive_type_source(self, archive: 'typearchive.TypeArchive', archive_type: '_types.QualifiedNameType') -> Optional['_types.QualifiedName']: """ Determine the local source type name for a given archive type + :param archive: Target type archive :param archive_type: Name of target archive type :return: Name of source analysis type, if this type is associated. None otherwise. @@ -8057,6 +8149,7 @@ class BinaryView: def get_associated_type_archive_type_source_by_id(self, archive_id: str, archive_type_id: str) -> Optional[str]: """ Determine the local source type id for a given archive type + :param archive_id: Id of target type archive :param archive_type_id: Id of target archive type :return: Id of source analysis type, if this type is associated. None otherwise. @@ -8071,6 +8164,7 @@ class BinaryView: def disassociate_type_archive_type(self, type: '_types.QualifiedNameType') -> bool: """ Disassociate an associated type, so that it will no longer receive updates from its connected type archive + :param type: Name of type in analysis :return: True if successful """ @@ -8082,6 +8176,7 @@ class BinaryView: def disassociate_type_archive_type_by_id(self, type_id: str) -> bool: """ Disassociate an associated type id, so that it will no longer receive updates from its connected type archive + :param type_id: Id of type in analysis :return: True if successful """ @@ -8091,6 +8186,7 @@ class BinaryView: -> Optional[Mapping['_types.QualifiedName', Tuple['_types.QualifiedName', '_types.Type']]]: """ Pull types from a type archive, updating them and any dependencies + :param archive: Target type archive :param names: Names of desired types in type archive :return: { name: (name, type) } Mapping from archive name to (analysis name, definition), None on error @@ -8115,6 +8211,7 @@ class BinaryView: -> Optional[Mapping[str, str]]: """ Pull types from a type archive by id, updating them and any dependencies + :param archive_id: Target type archive id :param archive_type_ids: Ids of desired types in type archive :return: { id: id } Mapping from archive type id to analysis type id, None on error @@ -8142,6 +8239,7 @@ class BinaryView: -> Optional[Mapping['_types.QualifiedName', Tuple['_types.QualifiedName', '_types.Type']]]: """ Push a collection of types, and all their dependencies, into a type archive + :param archive: Target type archive :param names: Names of types in analysis :return: { name: (name, type) } Mapping from analysis name to (archive name, definition), None on error @@ -8166,6 +8264,7 @@ class BinaryView: -> Optional[Mapping[str, str]]: """ Push a collection of types, and all their dependencies, into a type archive + :param archive_id: Id of target type archive :param type_ids: Ids of types in analysis :return: True if successful @@ -8601,6 +8700,15 @@ class BinaryView: :return: A generator object that yields the offset and matched DataBuffer for each match found. :rtype: QueueGenerator + :Example: + >>> from binaryninja import load + >>> bv = load('/bin/ls') + >>> print(bv) + <BinaryView: '/bin/ls', start 0x100000000, len 0x182f8> + >>> bytes(list(bv.search("50 ?4"))[0][1]).hex() + '5004' + >>> bytes(list(bv.search("[\\x20-\\x25][\\x60-\\x67]"))[0][1]).hex() + '2062' """ if start is None: start = self.start @@ -8725,7 +8833,7 @@ class BinaryView: ``show_graph_report`` displays a :py:class:`FlowGraph` object `graph` in a new tab with ``title``. :param title: Title of the graph - :type title: Plain text string title + :type title: Text string title of the tab :param graph: The graph you wish to display :type graph: :py:class:`FlowGraph` object """ @@ -8736,7 +8844,7 @@ class BinaryView: ``get_address_input`` Gets a virtual address via a prompt displayed to the user :param prompt: Prompt for the dialog - :param title: Display title, if displayed via the UI + :param title: Window title, if used in the UI :param current_address: Optional current address, for relative inputs :return: The value entered by the user, if one was entered """ diff --git a/python/debuginfo.py b/python/debuginfo.py index b1d25503..806e61a8 100644 --- a/python/debuginfo.py +++ b/python/debuginfo.py @@ -314,6 +314,7 @@ class DebugInfo(object): """ Type Container for all types in the DebugInfo that resulted from the parse of the given parser. + :param parser_name: Name of parser :return: Type Container for types from that parser """ diff --git a/python/enterprise.py b/python/enterprise.py index 636b9023..e645ed5f 100644 --- a/python/enterprise.py +++ b/python/enterprise.py @@ -320,6 +320,11 @@ class LicenseCheckout: """ Helper class for scripts to make use of a license checkout in a scope. + :param duration: Duration between refreshes + :param _cache: Deprecated but left in for compatibility + :param release: If the license should be released at the end of scope. If `False`, you + can either manually release it later or it will expire after `duration`. + :Example: >>> enterprise.connect() >>> enterprise.authenticate_with_credentials("username", "password") @@ -335,7 +340,7 @@ class LicenseCheckout: :param duration: Duration between refreshes :param _cache: Deprecated but left in for compatibility - :param release: If the license should be released at the end of scope. If False, you + :param release: If the license should be released at the end of scope. If `False`, you can either manually release it later or it will expire after `duration`. """ self.desired_duration = duration diff --git a/python/examples/bin_info.py b/python/examples/bin_info.py index bcf5adcd..b2fd194e 100644 --- a/python/examples/bin_info.py +++ b/python/examples/bin_info.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (c) 2015-2024 Vector 35 Inc # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -19,17 +19,19 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -import sys import os +import sys +from glob import glob -from binaryninja.log import log_warn, log_to_stdout -import binaryninja.interaction as interaction -from binaryninja.plugin import PluginCommand -from binaryninja import load +from binaryninja import LogLevel, PluginCommand, interaction, load, log, log_to_stdout, log_warn -def get_bininfo(bv): +def get_bininfo(bv, filename=None): if bv is None: + if not (os.path.isfile(filename) and os.access(filename, os.R_OK)): + return("Cannot read {}\n".format(filename)) + bv = load(filename, options={'analysis.mode': 'basic', 'analysis.linearSweep.autorun' : False}) + else: filename = "" if len(sys.argv) > 1: filename = sys.argv[1] @@ -40,7 +42,7 @@ def get_bininfo(bv): sys.exit(1) bv = load(filename) - log_to_stdout(True) + log_to_stdout(LogLevel.InfoLog) contents = "## %s ##\n" % os.path.basename(bv.file.filename) contents += "- START: 0x%x\n\n" % bv.start @@ -62,6 +64,13 @@ def get_bininfo(bv): length = bv.strings[i].length string = bv.strings[i].value contents += "| 0x%x |%d | %s |\n" % (start, length, string) + + # Note that we need to close BV file handles that we opened to prevent a + # memory leak due to a circular reference between BinaryViews and the + # FileMetadata that backs them + + if filename != "": + bv.file.close() return contents @@ -70,6 +79,15 @@ def display_bininfo(bv): if __name__ == "__main__": - print(get_bininfo(None)) + if len(sys.argv) == 1: + filename = interaction.get_open_filename_input("Filename:") + if filename is None: + log.log_warn("No file specified") + else: + print(get_bininfo(None, filename=filename)) + else: + for pattern in sys.argv[1:]: + for filename in glob(pattern): + print(get_bininfo(None, filename=filename)) else: - PluginCommand.register("Binary Info", "Display basic info about the binary", display_bininfo) + PluginCommand.register("Binary Info", "Display basic info about the binary using minimal analysis modes", display_bininfo) diff --git a/python/examples/raw_binary_base_detection.py b/python/examples/raw_binary_base_detection.py new file mode 100644 index 00000000..76268fbe --- /dev/null +++ b/python/examples/raw_binary_base_detection.py @@ -0,0 +1,101 @@ +# 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. + +"""Headless script for demonstrating Binary Ninja automated base address detection for +raw position-dependent firmware binaries +""" + +import argparse +import json +from os import walk, path +from binaryninja import BaseAddressDetection, log_to_stderr, LogLevel, log_info, log_error + + +def _get_directory_listing(_path: str) -> list[str]: + if path.isfile(_path): + return [_path] + + if not path.isdir(_path): + raise FileNotFoundError(f"Path '{_path}' is not a file or directory") + + files = [] + for dirpath, _, filenames in walk(_path): + for filename in filenames: + files.append(path.join(dirpath, filename)) + return files + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="detect base address of position-dependent raw firmware binaries") + parser.add_argument("path", help="path to the position-dependent raw firmware binary or directory") + parser.add_argument("--debug", action="store_true", help="enable debug logging") + parser.add_argument("--reasons", action="store_true", help="show reasons for base address selection") + parser.add_argument("--analysis", type=str, help="analysis level", default="basic") + parser.add_argument("--arch", type=str, default="", help="architecture of the binary") + return parser.parse_args() + + +def _setup_logger(debug: bool) -> None: + if debug: + log_to_stderr(LogLevel.DebugLog) + else: + log_to_stderr(LogLevel.InfoLog) + + +def main() -> None: + """Run the program""" + args = _parse_args() + _setup_logger(args.debug) + + files = _get_directory_listing(args.path) + for _file in files: + log_info(f"Running base address detection analysis on '{_file}'...") + bad = BaseAddressDetection(_file) + if not bad.detect_base_address(analysis=args.analysis, arch=args.arch): + log_error("Base address detection analysis failed") + continue + + json_dict = dict() + json_dict["filename"] = path.basename(_file) + json_dict["preferred_candidate"] = dict() + json_dict["preferred_candidate"]["address"] = f"0x{bad.preferred_base_address:x}" + json_dict["preferred_candidate"]["confidence"] = bad.confidence + json_dict["aborted"] = bad.aborted + json_dict["last_tested"] = f"0x{bad.last_tested_base_address:x}" + json_dict["candidates"] = dict() + for baseaddr, score in bad.scores: + json_dict["candidates"][f"0x{baseaddr:x}"] = dict() + json_dict["candidates"][f"0x{baseaddr:x}"]["score"] = score + json_dict["candidates"][f"0x{baseaddr:x}"]["function hits"] = bad.get_function_hits(baseaddr) + json_dict["candidates"][f"0x{baseaddr:x}"]["string hits"] = bad.get_string_hits(baseaddr) + json_dict["candidates"][f"0x{baseaddr:x}"]["data hits"] = bad.get_data_hits(baseaddr) + if args.reasons: + json_dict["candidates"][f"0x{baseaddr:x}"]["reasons"] = dict() + for reason in bad.get_reasons(baseaddr): + json_dict["candidates"][f"0x{baseaddr:x}"]["reasons"][f"0x{reason.pointer:x}"] = { + "poi_offset": f"0x{reason.offset:x}", + "poi_type": reason.type, + } + + print(json.dumps(json_dict, indent=4)) + + +if __name__ == "__main__": + main() diff --git a/python/filemetadata.py b/python/filemetadata.py index a5dda467..e0222fd2 100644 --- a/python/filemetadata.py +++ b/python/filemetadata.py @@ -120,17 +120,14 @@ class FileMetadata: """ ``class FileMetadata`` represents the file being analyzed by Binary Ninja. It is responsible for opening, closing, creating the database (.bndb) files, and is used to keep track of undoable actions. + + :param str filename: The string path to the file to be opened. Defaults to None. + :param handle: A handle to the underlying C FileMetadata object. Defaults to None. (Internal use only.) """ _associated_data = {} def __init__(self, filename: Optional[str] = None, handle: Optional[core.BNFileMetadataHandle] = None): - """ - Instantiates a new FileMetadata class. - - :param str filename: The string path to the file to be opened. Defaults to None. - :param handle: A handle to the underlying C FileMetadata object. Defaults to None. - """ if handle is not None: _type = core.BNFileMetadataHandle _handle = ctypes.cast(handle, _type) diff --git a/python/function.py b/python/function.py index a3d53feb..e08bb6c3 100644 --- a/python/function.py +++ b/python/function.py @@ -21,7 +21,7 @@ import ctypes import inspect -from typing import Generator, Optional, List, Tuple, Union, Mapping, Any, Dict +from typing import Generator, Optional, List, Tuple, Union, Mapping, Any, Dict, overload from dataclasses import dataclass # Binary Ninja components @@ -211,6 +211,12 @@ class BasicBlockList: self._n += 1 return self._function._instantiate_block(block) + @overload + def __getitem__(self, i: int) -> 'basicblock.BasicBlock': ... + + @overload + def __getitem__(self, i: slice) -> List['basicblock.BasicBlock']: ... + def __getitem__(self, i: Union[int, slice]) -> Union['basicblock.BasicBlock', List['basicblock.BasicBlock']]: if isinstance(i, int): if i < 0: @@ -237,6 +243,12 @@ class LowLevelILBasicBlockList(BasicBlockList): def __repr__(self): return f"<LowLevelILBasicBlockList {len(self)} BasicBlocks: {list(self)}>" + @overload + def __getitem__(self, i: int) -> 'lowlevelil.LowLevelILBasicBlock': ... + + @overload + def __getitem__(self, i: slice) -> List['lowlevelil.LowLevelILBasicBlock']: ... + def __getitem__( self, i: Union[int, slice] ) -> Union['lowlevelil.LowLevelILBasicBlock', List['lowlevelil.LowLevelILBasicBlock']]: @@ -250,6 +262,12 @@ class MediumLevelILBasicBlockList(BasicBlockList): def __repr__(self): return f"<MediumLevelILBasicBlockList {len(self)} BasicBlocks: {list(self)}>" + @overload + def __getitem__(self, i: int) -> 'mediumlevelil.MediumLevelILBasicBlock': ... + + @overload + def __getitem__(self, i: slice) -> List['mediumlevelil.MediumLevelILBasicBlock']: ... + def __getitem__( self, i: Union[int, slice] ) -> Union['mediumlevelil.MediumLevelILBasicBlock', List['mediumlevelil.MediumLevelILBasicBlock']]: @@ -263,6 +281,12 @@ class HighLevelILBasicBlockList(BasicBlockList): def __repr__(self): return f"<HighLevelILBasicBlockList {len(self)} BasicBlocks: {list(self)}>" + @overload + def __getitem__(self, i: int) -> 'highlevelil.HighLevelILBasicBlock': ... + + @overload + def __getitem__(self, i: slice) -> List['highlevelil.HighLevelILBasicBlock']: ... + def __getitem__( self, i: Union[int, slice] ) -> Union['highlevelil.HighLevelILBasicBlock', List['highlevelil.HighLevelILBasicBlock']]: @@ -304,10 +328,15 @@ class TagList: self._n += 1 return arch, address, binaryview.Tag(core_tag) + @overload + def __getitem__(self, i: int) -> Tuple['architecture.Architecture', int, 'binaryview.Tag']: ... + + @overload + def __getitem__(self, i: slice) -> List[Tuple['architecture.Architecture', int, 'binaryview.Tag']]: ... + def __getitem__( self, i: Union[int, slice] - ) -> Union[Tuple['architecture.Architecture', int, 'binaryview.Tag'], List[Tuple['architecture.Architecture', int, - 'binaryview.Tag']]]: + ) -> Union[Tuple['architecture.Architecture', int, 'binaryview.Tag'], List[Tuple['architecture.Architecture', int, 'binaryview.Tag']]]: if isinstance(i, int): if i < 0: i = len(self) + i @@ -400,7 +429,13 @@ class Function: def __hash__(self): return hash((self.start, self.arch, self.platform)) - def __getitem__(self, i) -> Union['basicblock.BasicBlock', List['basicblock.BasicBlock']]: + @overload + def __getitem__(self, i: int) -> 'basicblock.BasicBlock': ... + + @overload + def __getitem__(self, i: slice) -> List['basicblock.BasicBlock']: ... + + def __getitem__(self, i: Union[int, slice]) -> Union['basicblock.BasicBlock', List['basicblock.BasicBlock']]: return self.basic_blocks[i] def __iter__(self) -> Generator['basicblock.BasicBlock', None, None]: diff --git a/python/highlevelil.py b/python/highlevelil.py index 2b4d9153..afdc2b2e 100644 --- a/python/highlevelil.py +++ b/python/highlevelil.py @@ -20,7 +20,7 @@ import ctypes import struct -from typing import Optional, Generator, List, Union, NewType, Tuple, ClassVar, Mapping, Set, Callable, Any, Iterator +from typing import Optional, Generator, List, Union, NewType, Tuple, ClassVar, Mapping, Set, Callable, Any, Iterator, overload from dataclasses import dataclass from enum import Enum @@ -798,10 +798,11 @@ class HighLevelILInstruction(BaseILInstruction): :Example: >>> def get_constant_less_than_value(inst: HighLevelILInstruction, value: int) -> int: - >>> if isinstance(inst, Constant) and inst.constant < value: - >>> return inst.constant + ... if isinstance(inst, Constant) and inst.constant < value: + ... return inst.constant >>> - >>> list(inst.traverse(get_constant_less_than_value, 10)) + >>> for result in inst.traverse(get_constant_less_than_value, 10): + ... print(f"Found a constant {result} < 10 in {repr(inst)}") """ if (result := cb(self, *args, **kwargs)) is not None: yield result @@ -2556,7 +2557,7 @@ class HighLevelILFunction: def traverse(self, cb: Callable[['HighLevelILInstruction', Any], Any], *args: Any, **kwargs: Any) -> Iterator[Any]: """ - ``traverse`` iterates through all the instructions in the HighLevelILInstruction and calls the callback function for + ``traverse`` iterates through all the instructions in the HighLevelILFunction and calls the callback function for each instruction and sub-instruction. See the `Developer Docs <https://docs.binary.ninja/dev/concepts.html#walking-ils>`_ for more examples. :param Callable[[HighLevelILInstruction, Any], Any] cb: The callback function to call for each node in the HighLevelILInstruction @@ -2572,7 +2573,8 @@ class HighLevelILFunction: ... case Localcall(dest=Constant(constant=c), params=[_, _, p]) if c == target and not isinstance(p, Constant): ... return i >>> target_address = bv.get_symbol_by_raw_name('_memcpy').address - >>> list(current_il_function.traverse(find_non_constant_memcpy, target_address)) + >>> for result in current_il_function.traverse(find_non_constant_memcpy, target_address): + ... print(f"Found suspicious memcpy: {repr(i)}") """ root = self.root if root is None: @@ -3089,7 +3091,13 @@ class HighLevelILBasicBlock(basicblock.BasicBlock): for idx in range(self.start, self.end): yield self.il_function[idx] - def __getitem__(self, idx) -> Union[List[HighLevelILInstruction], HighLevelILInstruction]: + @overload + def __getitem__(self, idx: int) -> 'HighLevelILInstruction': ... + + @overload + def __getitem__(self, idx: slice) -> List['HighLevelILInstruction']: ... + + def __getitem__(self, idx: Union[int, slice]) -> Union[List[HighLevelILInstruction], HighLevelILInstruction]: size = self.end - self.start if isinstance(idx, slice): return [self[index] for index in range(*idx.indices(size))] # type: ignore diff --git a/python/interaction.py b/python/interaction.py index c767af7c..c3bfb3a2 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -1062,8 +1062,8 @@ def show_plain_text_report(title, contents): .. note:: This API functions differently on the command-line vs the UI. In the UI, a pop-up is used. On the command-line, \ a simple text prompt is used. - :param str title: title to display in the UI pop-up - :param str contents: plaintext contents to display + :param str title: Title to display in the tab + :param str contents: Plaintext contents to display :rtype: None :Example: >>> show_plain_text_report("title", "contents") @@ -1081,6 +1081,7 @@ def show_markdown_report(title, contents, plaintext=""): .. note:: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line \ a simple text prompt is used. + :param str title: title to display in the tab :param str contents: markdown contents to display :param str plaintext: Plain text version to display (used on the command-line) :rtype: None @@ -1097,6 +1098,7 @@ def show_html_report(title, contents, plaintext=""): applications. This API doesn't support hyperlinking into the BinaryView, use the :py:meth:`BinaryView.show_html_report` \ API if hyperlinking is needed. + :param str title: Title to display in the tab :param str contents: HTML contents to display :param str plaintext: Plain text version to display (used on the command-line) :rtype: None @@ -1115,6 +1117,7 @@ def show_graph_report(title, graph): .. note:: This API function will have no effect outside the UI. + :param str title: Title to display in the tab :param FlowGraph graph: Flow graph to display :rtype: None """ @@ -1144,9 +1147,9 @@ def get_text_line_input(prompt, title): .. note:: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line \ a simple text prompt is used. - :param str prompt: String to prompt with. - :param str title: Title of the window when executed in the UI. - :rtype: str containing the input without trailing newline character. + :param str prompt: String to prompt with + :param str title: Title of the window when executed in the UI + :rtype: str containing the input without trailing newline character :Example: >>> get_text_line_input("PROMPT>", "getinfo") PROMPT> Input! @@ -1167,9 +1170,9 @@ def get_int_input(prompt, title): .. note:: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line \ a simple text prompt is used. - :param str prompt: String to prompt with. - :param str title: Title of the window when executed in the UI. - :rtype: integer value input by the user. + :param str prompt: String to prompt with + :param str title: Title of the window when executed in the UI + :rtype: integer value input by the user :Example: >>> get_int_input("PROMPT>", "getinfo") PROMPT> 10 diff --git a/python/lowlevelil.py b/python/lowlevelil.py index 02586113..e3c3ccab 100644 --- a/python/lowlevelil.py +++ b/python/lowlevelil.py @@ -20,7 +20,7 @@ import ctypes import struct -from typing import Generator, List, Optional, Dict, Union, Tuple, NewType, ClassVar, Set, Callable, Any, Iterator +from typing import Generator, List, Optional, Dict, Union, Tuple, NewType, ClassVar, Set, Callable, Any, Iterator, overload from dataclasses import dataclass # Binary Ninja components @@ -5503,7 +5503,13 @@ class LowLevelILBasicBlock(basicblock.BasicBlock): for idx in range(self.start, self.end): yield self._il_function[idx] - def __getitem__(self, idx): + @overload + def __getitem__(self, idx: int) -> 'LowLevelILInstruction': ... + + @overload + def __getitem__(self, idx: slice) -> List['LowLevelILInstruction']: ... + + def __getitem__(self, idx: Union[int, slice]) -> Union['LowLevelILInstruction', List['LowLevelILInstruction']]: size = self.end - self.start if isinstance(idx, slice): return [self[index] for index in range(*idx.indices(size))] diff --git a/python/mainthread.py b/python/mainthread.py index 14698b6a..9064a666 100644 --- a/python/mainthread.py +++ b/python/mainthread.py @@ -18,6 +18,41 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. +""" +.. py:module:: mainthread + +This module provides two ways to execute "jobs": + +1. On the Binary Ninja main thread (the UI event thread when running in the GUI application): + * :py:func:`.execute_on_main_thread` + * :py:func:`.execute_on_main_thread_and_wait` +2. On a worker thread + +Any manipulation of the GUI should be performed on the main thread, but any +non-GUI work is generally better to be performed using a worker. This is +especially true for any longer-running work, as the user interface will +be unable to update itself while a job is executing on the main thread. + +There are three worker queues, in order of decreasing priority: + + 1. The Interactive Queue (:py:func:`.worker_interactive_enqueue`) + 2. The Priority Queue (:py:func:`.worker_priority_enqueue`) + 3. The Worker Queue (:py:func:`.worker_enqueue`) + +All of these queues are serviced by the same pool of worker threads. The +difference between the queues is basically one of priority: one queue must +be empty of jobs before a worker thread will execute a job from a lower +priority queue. + +The default maximum number of concurrent worker threads is controlled by the +`analysis.limits.workerThreadCount` setting but can be adjusted at runtime via +:py:func:`.set_worker_thread_count`. + +The worker threads are native threads, managed by the Binary Ninja core. If +more control over the thread is required, consider using the +:py:class:`~binaryninja.plugin.BackgroundTaskThread` class. +""" + # Binary Ninja components from . import _binaryninjacore as core from . import scriptingprovider diff --git a/python/mediumlevelil.py b/python/mediumlevelil.py index e857920f..291da4b9 100644 --- a/python/mediumlevelil.py +++ b/python/mediumlevelil.py @@ -21,7 +21,7 @@ import ctypes import struct from typing import (Optional, List, Union, Mapping, - Generator, NewType, Tuple, ClassVar, Dict, Set, Callable, Any, Iterator) + Generator, NewType, Tuple, ClassVar, Dict, Set, Callable, Any, Iterator, overload) from dataclasses import dataclass from . import deprecation @@ -3957,7 +3957,13 @@ class MediumLevelILBasicBlock(basicblock.BasicBlock): for idx in range(self.start, self.end): yield self._il_function[idx] - def __getitem__(self, idx) -> Union[List['MediumLevelILInstruction'], 'MediumLevelILInstruction']: + @overload + def __getitem__(self, idx: int) -> 'MediumLevelILInstruction': ... + + @overload + def __getitem__(self, idx: slice) -> List['MediumLevelILInstruction']: ... + + def __getitem__(self, idx: Union[int, slice]) -> Union[List['MediumLevelILInstruction'], 'MediumLevelILInstruction']: size = self.end - self.start if isinstance(idx, slice): return [self[index] for index in range(*idx.indices(size))] # type: ignore diff --git a/python/plugin.py b/python/plugin.py index e68e30d5..5a92215b 100644 --- a/python/plugin.py +++ b/python/plugin.py @@ -970,6 +970,18 @@ class _BackgroundTaskMetaclass(type): class BackgroundTask(metaclass=_BackgroundTaskMetaclass): + """ + The ``BackgroundTask`` class provides a mechanism for reporting progress of + an optionally cancelable task to the user via the status bar in the UI. + If ``can_cancel`` is is `True`, then the task can be cancelled either + programmatically (via :py:meth:`.cancel`) or by the user via the UI. + + Note this class does not provide a means to execute a task, which is + available via the :py:class:`.BackgroundTaskThread` class. + + :param initial_progress_text: text description of the task to display in the status bar in the UI, defaults to `""` + :param can_cancel: whether to enable cancelation of the task, defaults to `False` + """ def __init__(self, initial_progress_text="", can_cancel=False, handle=None): if handle is None: self.handle = core.BNBeginBackgroundTask(initial_progress_text, can_cancel) @@ -1022,6 +1034,15 @@ class BackgroundTask(metaclass=_BackgroundTaskMetaclass): class BackgroundTaskThread(BackgroundTask): + """ + The ``BackgroundTaskThread`` class provides an all-in-one solution for executing a :py:class:`.BackgroundTask` + in a thread. + + See the :py:class:`.BackgroundTask` for additional information. + + :param initial_progress_text: text description of the task to display in the status bar in the UI, defaults to `""` + :param can_cancel: whether to enable cancelation of the task, defaults to `False` + """ def __init__(self, initial_progress_text: str = "", can_cancel: bool = False): class _Thread(threading.Thread): def __init__(self, task: 'BackgroundTaskThread'): diff --git a/python/typearchive.py b/python/typearchive.py index 26b15b5f..7b264056 100644 --- a/python/typearchive.py +++ b/python/typearchive.py @@ -39,13 +39,20 @@ class TypeArchive: Type Archives are a collection of types which can be shared between different analysis sessions and are backed by a database file on disk. Their types can be modified, and a history of previous versions of types is stored in snapshots in the archive. + + + Internal-use constructor. API users will want to use :py:meth:`.TypeArchive.open` + or :py:meth:`.TypeArchive.create` instead to get an instance of a TypeArchive. + + :param handle: Handle pointer (Internal use only.) """ def __init__(self, handle: core.BNTypeArchiveHandle): """ Internal-use constructor. API users will want to use `:py:func:TypeArchive.open` or `:py:func:TypeArchive.create` instead to get an instance of a TypeArchive. - :param handle: + + :param handle: Handle pointer (Internal use only.) """ binaryninja._init_plugins() self.handle: core.BNTypeArchiveHandle = core.handle_of_type(handle, core.BNTypeArchive) @@ -70,6 +77,7 @@ class TypeArchive: def open(path: str) -> Optional['TypeArchive']: """ Open the Type Archive at the given path, if it exists. + :param path: Path to Type Archive file :return: Type Archive, or None if it could not be loaded. """ @@ -82,6 +90,7 @@ class TypeArchive: def create(path: str, platform: 'platform.Platform') -> Optional['TypeArchive']: """ Create a Type Archive at the given path. + :param path: Path to Type Archive file :param platform: Relevant platform for types in the archive :return: Type Archive, or None if it could not be created. @@ -95,6 +104,7 @@ class TypeArchive: def lookup_by_id(id: str) -> Optional['TypeArchive']: """ Get a reference to the Type Archive with the known id, if one exists. + :param id: Type Archive id :return: Type archive, or None if it could not be found. """ @@ -107,6 +117,7 @@ class TypeArchive: def path(self) -> Optional[str]: """ Get the path to the Type Archive's file + :return: File path """ return core.BNGetTypeArchivePath(self.handle) @@ -115,6 +126,7 @@ class TypeArchive: def id(self) -> Optional[str]: """ Get the guid for a Type Archive + :return: Guid string """ return core.BNGetTypeArchiveId(self.handle) @@ -123,6 +135,7 @@ class TypeArchive: def platform(self) -> 'platform.Platform': """ Get the associated Platform for a Type Archive + :return: Platform object """ handle = core.BNGetTypeArchivePlatform(self.handle) @@ -133,6 +146,7 @@ class TypeArchive: def current_snapshot_id(self) -> str: """ Get the id of the current snapshot in the type archive + :return: Snapshot id """ result = core.BNGetTypeArchiveCurrentSnapshotId(self.handle) @@ -144,6 +158,7 @@ class TypeArchive: def current_snapshot_id(self, value: str): """ Revert the type archive's current snapshot to the given snapshot + :param value: Snapshot id """ core.BNSetTypeArchiveCurrentSnapshot(self.handle, value) @@ -152,6 +167,7 @@ class TypeArchive: def all_snapshot_ids(self) -> List[str]: """ Get a list of every snapshot's id + :return: All ids (including the empty first snapshot) """ count = ctypes.c_ulonglong(0) @@ -169,6 +185,7 @@ class TypeArchive: def get_snapshot_parent_ids(self, snapshot: str) -> Optional[List[str]]: """ Get the ids of the parents to the given snapshot + :param snapshot: Child snapshot id :return: Parent snapshot ids, or empty list if the snapshot is a root """ @@ -187,6 +204,7 @@ class TypeArchive: def get_snapshot_child_ids(self, snapshot: str) -> Optional[List[str]]: """ Get the ids of the children to the given snapshot + :param snapshot: Parent snapshot id :return: Child snapshot ids, or empty list if the snapshot is a leaf """ @@ -207,6 +225,7 @@ class TypeArchive: Add named types to the type archive. Type must have all dependant named types added prior to being added, or this function will fail. If the type already exists, it will be overwritten. + :param name: Name of new type :param type: Definition of new type """ @@ -217,6 +236,7 @@ class TypeArchive: Add named types to the type archive. Types must have all dependant named types prior to being added, or included in the list, or this function will fail. Types already existing with any added names will be overwritten. + :param new_types: Names and definitions of new types """ api_types = (core.BNQualifiedNameAndType * len(new_types))() @@ -237,6 +257,7 @@ class TypeArchive: def rename_type(self, old_name: '_types.QualifiedNameType', new_name: '_types.QualifiedNameType') -> None: """ Change the name of an existing type in the type archive. + :param old_name: Old type name in archive :param new_name: New type name """ @@ -246,6 +267,7 @@ class TypeArchive: def rename_type_by_id(self, id: str, new_name: '_types.QualifiedNameType') -> None: """ Change the name of an existing type in the type archive. + :param id: Old id of type in archive :param new_name: New type name """ @@ -257,6 +279,7 @@ class TypeArchive: def delete_type(self, name: '_types.QualifiedNameType') -> None: """ Delete an existing type in the type archive. + :param name: Type name """ id = self.get_type_id(name) @@ -267,6 +290,7 @@ class TypeArchive: def delete_type_by_id(self, id: str) -> None: """ Delete an existing type in the type archive. + :param id: Type id """ if not core.BNDeleteTypeArchiveType(self.handle, id): @@ -275,6 +299,7 @@ class TypeArchive: def get_type_by_name(self, name: '_types.QualifiedNameType', snapshot: Optional[str] = None) -> Optional[_types.Type]: """ Retrieve a stored type in the archive + :param name: Type name :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: Type, if it exists. Otherwise None @@ -291,6 +316,7 @@ class TypeArchive: def get_type_by_id(self, id: str, snapshot: Optional[str] = None) -> Optional[_types.Type]: """ Retrieve a stored type in the archive by id + :param id: Type id :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: Type, if it exists. Otherwise None @@ -306,6 +332,7 @@ class TypeArchive: def get_type_name_by_id(self, id: str, snapshot: Optional[str] = None) -> Optional['_types.QualifiedName']: """ Retrieve a type's name by its id + :param id: Type id :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: Type name, if it exists. Otherwise None @@ -324,6 +351,7 @@ class TypeArchive: def get_type_id(self, name: '_types.QualifiedNameType', snapshot: Optional[str] = None) -> Optional[str]: """ Retrieve a type's id by its name + :param name: Type name :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: Type id, if it exists. Otherwise None @@ -343,6 +371,7 @@ class TypeArchive: def types(self) -> Dict[_types.QualifiedName, _types.Type]: """ Retrieve all stored types in the archive at the current snapshot + :return: Map of all types, by name """ return self.get_types() @@ -351,6 +380,7 @@ class TypeArchive: def types_and_ids(self) -> Dict[str, Tuple[_types.QualifiedName, _types.Type]]: """ Retrieve all stored types in the archive at the current snapshot + :return: Map of type id to type name and definition """ return self.get_types_and_ids() @@ -358,6 +388,7 @@ class TypeArchive: def get_types(self, snapshot: Optional[str] = None) -> Dict[_types.QualifiedName, _types.Type]: """ Retrieve all stored types in the archive at a snapshot + :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: Map of all types, by name """ @@ -369,6 +400,7 @@ class TypeArchive: def get_types_and_ids(self, snapshot: Optional[str] = None) -> Dict[str, Tuple[_types.QualifiedName, _types.Type]]: """ Retrieve all stored types in the archive at a snapshot + :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: Map of type id to type name and definition """ @@ -391,6 +423,7 @@ class TypeArchive: def type_ids(self) -> List[str]: """ Get a list of all types' ids in the archive at the current snapshot + :return: All type ids """ return self.get_type_ids() @@ -398,6 +431,7 @@ class TypeArchive: def get_type_ids(self, snapshot: Optional[str] = None) -> List[str]: """ Get a list of all types' ids in the archive at a snapshot + :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: All type ids """ @@ -425,6 +459,7 @@ class TypeArchive: def get_type_names(self, snapshot: Optional[str] = None) -> List['_types.QualifiedName']: """ Get a list of all types' names in the archive at a snapshot + :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: All type names """ @@ -445,6 +480,7 @@ class TypeArchive: def type_names_and_ids(self) -> Dict[str, '_types.QualifiedName']: """ Get a list of all types' names and ids in the archive at the current snapshot + :return: Mapping of all type ids to names """ return self.get_type_names_and_ids() @@ -452,6 +488,7 @@ class TypeArchive: def get_type_names_and_ids(self, snapshot: Optional[str] = None) -> Dict[str, '_types.QualifiedName']: """ Get a list of all types' names and ids in the archive at a current snapshot + :param snapshot: Snapshot id to search for types, or None to search the latest snapshot :return: Mapping of all type ids to names """ @@ -475,6 +512,7 @@ class TypeArchive: def get_outgoing_direct_references(self, id: str, snapshot: Optional[str] = None) -> List[str]: """ Get all types a given type references directly + :param id: Source type id :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot :return: Target type ids @@ -497,6 +535,7 @@ class TypeArchive: def get_outgoing_recursive_references(self, id: str, snapshot: Optional[str] = None) -> List[str]: """ Get all types a given type references, and any types that the referenced types reference + :param id: Source type id :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot :return: Target type ids @@ -519,6 +558,7 @@ class TypeArchive: def get_incoming_direct_references(self, id: str, snapshot: Optional[str] = None) -> List[str]: """ Get all types that reference a given type + :param id: Target type id :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot :return: Source type ids @@ -541,6 +581,7 @@ class TypeArchive: def get_incoming_recursive_references(self, id: str, snapshot: Optional[str] = None) -> List[str]: """ Get all types that reference a given type, and all types that reference them, recursively + :param id: Target type id :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot :return: Source type ids @@ -563,6 +604,7 @@ class TypeArchive: def query_metadata(self, key: str) -> Optional['metadata.MetadataValueType']: """ Look up a metadata entry in the archive + :param string key: key to query :rtype: Metadata associated with the key, if it exists. Otherwise, None :Example: @@ -580,6 +622,7 @@ class TypeArchive: def store_metadata(self, key: str, md: 'metadata.MetadataValueType') -> None: """ Store a key/value pair in the archive's metadata storage + :param string key: key value to associate the Metadata object with :param Varies md: object to store. :Example: @@ -597,6 +640,7 @@ class TypeArchive: def remove_metadata(self, key: str) -> None: """ Delete a given metadata entry in the archive + :param string key: key associated with metadata :Example: @@ -609,6 +653,7 @@ class TypeArchive: def serialize_snapshot(self, snapshot: str) -> 'databuffer.DataBuffer': """ Turn a given snapshot into a data stream + :param snapshot: Snapshot id :return: Buffer containing serialized snapshot data """ @@ -620,6 +665,7 @@ class TypeArchive: def deserialize_snapshot(self, data: 'databuffer.DataBufferInputType') -> str: """ Take a serialized snapshot data stream and create a new snapshot from it + :param data: Snapshot data :return: String of created snapshot id """ @@ -632,6 +678,7 @@ class TypeArchive: def register_notification(self, notify: 'TypeArchiveNotification') -> None: """ Register a notification listener + :param notify: Object to receive notifications """ cb = TypeArchiveNotificationCallbacks(self, notify) @@ -641,6 +688,7 @@ class TypeArchive: def unregister_notification(self, notify: 'TypeArchiveNotification') -> None: """ Unregister a notification listener + :param notify: Object to no longer receive notifications """ if notify in self._notifications: @@ -660,6 +708,7 @@ class TypeArchiveNotification: def type_added(self, archive: 'TypeArchive', id: str, definition: '_types.Type') -> None: """ Called when a type is added to the archive + :param archive: Source Type archive :param id: Id of type added :param definition: Definition of type @@ -669,6 +718,7 @@ class TypeArchiveNotification: def type_updated(self, archive: 'TypeArchive', id: str, old_definition: '_types.Type', new_definition: '_types.Type') -> None: """ Called when a type in the archive is updated to a new definition + :param archive: Source Type archive :param id: Id of type :param old_definition: Previous definition @@ -679,6 +729,7 @@ class TypeArchiveNotification: def type_renamed(self, archive: 'TypeArchive', id: str, old_name: '_types.QualifiedName', new_name: '_types.QualifiedName') -> None: """ Called when a type in the archive is renamed + :param archive: Source Type archive :param id: Type id :param old_name: Previous name @@ -689,6 +740,7 @@ class TypeArchiveNotification: def type_deleted(self, archive: 'TypeArchive', id: str, definition: '_types.Type') -> None: """ Called when a type in the archive is deleted from the archive + :param archive: Source Type archive :param id: Id of type deleted :param definition: Definition of type deleted diff --git a/python/typecontainer.py b/python/typecontainer.py index cc1b3288..0b3221b8 100644 --- a/python/typecontainer.py +++ b/python/typecontainer.py @@ -37,10 +37,23 @@ class TypeContainer: """ A ``TypeContainer`` is a generic interface to access various Binary Ninja models that contain types. Types are stored with both a unique id and a unique name. + + The ``TypeContainer`` class should not generally be instantiated directly. Instances + can be retrieved from the following properties and methods in the API: + + * :py:meth:`.BinaryView.type_container` + * :py:meth:`.BinaryView.auto_type_container` + * :py:meth:`.BinaryView.user_type_container` + * :py:meth:`.Platform.type_container` + * :py:meth:`.TypeLibrary.type_container` + * :py:meth:`.DebugInfo.get_type_container` + + :param handle: Handle pointer (Internal use only.) """ def __init__(self, handle: core.BNTypeContainerHandle): """ Construct a Type Container, internal use only + :param handle: Handle pointer """ binaryninja._init_plugins() @@ -147,6 +160,7 @@ class TypeContainer: """ Rename a type in the Type Container. All references to this type will be updated (by id) to use the new name. + :param type_id: Id of type to update :param new_name: New name for the type :return: True if successful @@ -157,6 +171,7 @@ class TypeContainer: """ Delete a type in the Type Container. Behavior of references to this type is not specified and you may end up with broken references if any still exist. + :param type_id: Id of type to delete :return: True if successful """ @@ -166,6 +181,7 @@ class TypeContainer: """ Get the unique id of the type in the Type Container with the given name. If no type with that name exists, returns None. + :param type_name: Name of type :return: Type id, if exists, else, None """ @@ -178,6 +194,7 @@ class TypeContainer: """ Get the unique name of the type in the Type Container with the given id. If no type with that id exists, returns None. + :param type_id: Id of type :return: Type name, if exists, else, None """ @@ -192,6 +209,7 @@ class TypeContainer: """ Get the definition of the type in the Type Container with the given id. If no type with that id exists, returns None. + :param type_id: Id of type :return: Type object, if exists, else, None """ @@ -232,6 +250,7 @@ class TypeContainer: """ Get the definition of the type in the Type Container with the given name. If no type with that name exists, returns None. + :param type_name: Name of type :return: Type object, if exists, else, None """ diff --git a/python/websocketprovider.py b/python/websocketprovider.py index 93e88eb8..420f43c6 100644 --- a/python/websocketprovider.py +++ b/python/websocketprovider.py @@ -46,6 +46,9 @@ def to_bytes(field): class WebsocketClient(object): + """ + This class implements a websocket client. See :py:func:`~WebsocketClient.connect` for more details. + """ _registered_clients = [] def __init__(self, provider, handle=None): @@ -157,12 +160,20 @@ class WebsocketClient(object): :param function(bytes) -> bool on_data: function to call when data is read from the websocket :return: if the connection has started, but not necessarily if it succeeded :rtype: bool + + :Example: + >>> provider = list(WebsocketProvider)[0] + >>> client = provider.create_instance() + >>> client.connect("ws://localhost:8080", {}) + True """ if self._connected: raise RuntimeError("Cannot use connect() twice on the same WebsocketClient") self._connected = True + if headers is None: + headers = {} header_keys = (ctypes.c_char_p * len(headers))() header_values = (ctypes.c_char_p * len(headers))() for (i, item) in enumerate(headers.items()): |
