# Copyright (c) 2015-2023 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. # This is an example BinaryView plugin which demonstrates how to specify # and generate a load settings spec. Load settings can subsequently be presented # by the UI in the Open With Options dialog or headless when opening a # file with options. from binaryninja.log import log_error from binaryninja import _binaryninjacore as core from binaryninja.architecture import Architecture from binaryninja.binaryview import BinaryView from binaryninja.binaryview import BinaryViewType from binaryninja.enums import SegmentFlag from binaryninja import Settings import json import traceback use_default_loader_settings = True class MappedView(BinaryView): name = "Mapped (Python)" long_name = "Mapped (Python)" load_address = 0x100000 def __init__(self, data): BinaryView.__init__(self, parent_view=data, file_metadata=data.file) @staticmethod def is_valid_for_data(data): # Insert code that looks for a magic identifier and return True if this BinaryViewType can handle/parse the binary return True @classmethod def get_load_settings_for_data(cls, data): # This method is optional. If provided this is where the Load Settings for a BinaryViewType are specified. Binary Ninja provides # some default read-only load settings which are: # ["loader.architecture", "loader.platform", "loader.entryPointOffset", "loader.imageBase", "loader.segments", "loader.sections"] # The default load settings are provided for consistency and convenience. # The default load settings are always generated with a read-only indication which is respected by the UI. # The read-only indication is a property that consists of a JSON name/value pair ("readOnly" : true). load_settings = None if not use_default_loader_settings: # Create a new named Settings container for the load settings. load_settings = Settings("mapped_load_settings") else: # Optionally, perform light-weight parsing of the 'Raw' BinaryView to extract required information for load settings generation. # This allows finer control of the settings provided as well as their default values. # For example, the view.relocatable property could be used to control the read-only attribute of "loader.imageBase" registered_view = cls.registered_view_type assert registered_view is not None view = registered_view.parse(data) assert view is not None # Populate settings container with default load settings # Note: `get_default_load_settings_for_data` automatically tries to parse the input if the `data` BinaryViewType name does not match the # cls BinaryViewType name. In this case a parsed view is already being passed. load_settings = registered_view.get_default_load_settings_for_data(view) # Specify default load settings that can be overridden (from the UI). overrides = ["loader.architecture", "loader.platform", "loader.entryPointOffset", "loader.imageBase", "loader.segments", "loader.sections"] for override in overrides: if load_settings.contains(override): load_settings.update_property(override, json.dumps({'readOnly': False})) # Override the default setting values. load_settings.update_property("loader.imageBase", json.dumps({'default': 0})) load_settings.update_property("loader.entryPointOffset", json.dumps({'default': 0})) # Specify additional custom settings. load_settings.register_setting( "loader.my_custom_arch.customLoadSetting", '{"title" : "My Custom Load Setting",\ "type" : "boolean",\ "default" : false,\ "description" : "My custom load setting description."}' ) return load_settings def init(self): # The BinaryView init method is invoked as part of the BinaryView creation process. This method is called under several different conditions: # 1) When opening a file/bndb and no load options are provided. # 2) When opening a file/bndb and load options are provided. e.g. 'Open with Options' in the UI, or load # 3) When parsing a file to create an ephemeral BinaryView (self.parse_only == True) for the purpose of extracting header information. # Note: The get_load_settings_for_data classmethod is optional. If provided, it's used to generate load options for the BinaryViewType. # If not provided, then the default load options are automatically generated by the core, extracting information automatically from a parsed BinaryView. # Notice the call to get_default_load_settings_for_data above in get_load_settings_for_data. This will force parsing a file to create an ephemeral BinaryView. # It is also used as a convenience method to pre-populate a Settings object with relevant, generic loader settings that are tagged as 'readOnly'. It is the responsibility, # of the BinaryViewType to communicate and enforce which settings are mutable. if self.parse_only is True: # Perform light-weight parsing to extract required information for load settings generation. # A light-weight parsed view does not get finalized. print("Mapped (Python): init(): perform light-weight parsing") else: # Perform normal BinaryView initialization print("Mapped (Python): init(): perform normal BinaryView initialization") # Finish BinaryView initialization using the load settings, if they exist try: load_settings = self.get_load_settings(self.name) if load_settings is None: if self.parse_only is True: self.arch = Architecture['x86'] # type: ignore self.platform = Architecture['x86'].standalone_platform # type: ignore assert self.parent_view is not None self.add_auto_segment(0, len(self.parent_view), 0, len(self.parent_view), SegmentFlag.SegmentReadable) return True else: # Note: If there are no load settings and this is not a parse_only view, it's possible to call get_load_settings_for_data directly. # This allows us to generate default load options for the BinaryView. This step is not required but can be useful. load_settings = self.__class__.get_load_settings_for_data(self.parent_view) arch = load_settings.get_string("loader.architecture", self) self.arch = Architecture[arch] # type: ignore self.platform = Architecture[arch].standalone_platform # type: ignore self.load_address = load_settings.get_integer("loader.imageBase", self) self.add_auto_segment( self.load_address, len(self.parent_view), 0, len(self.parent_view), SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable ) if load_settings.contains("loader.entryPointOffset"): self.entry_point_offset = load_settings.get_integer("loader.entryPointOffset", self) self.add_entry_point(self.load_address + self.entry_point_offset) # Note: This MappedView (Python) BinaryView implementation is incomplete. It ignores platform, section, and segment settings. # It's preferred that values saved in the settings system be imageBase agnostic. return True except: log_error(traceback.format_exc()) return False def perform_get_entry_point(self): if hasattr(self, 'entry_point_offset'): return self.load_address + self.entry_point_offset else: return 0 def perform_is_executable(self): return True def perform_is_relocatable(self): return True def perform_get_address_size(self): return self.arch.address_size MappedView.register()