Source code for binaryninja.basedetection

# 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
from . import architecture


[docs] @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
[docs] 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)
[docs] def detect_base_address( self, arch: Optional[Union['architecture.Architecture', str]] = None, analysis: Optional[Literal["basic", "controlFlow", "full"]] = "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 Architecture 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 isinstance(arch, str): arch = architecture.Architecture[arch] if not arch and self._view_arch: arch = 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") archname = arch.name if arch else "" settings = core.BNBaseAddressDetectionSettings( archname.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
[docs] 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)
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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)