mirror of
https://github.com/gaasedelen/lighthouse
synced 2026-06-08 14:19:10 +00:00
424 lines
13 KiB
Python
424 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
import sys
|
|
import logging
|
|
import functools
|
|
import threading
|
|
import collections
|
|
|
|
from .api import DisassemblerCoreAPI, DisassemblerContextAPI
|
|
from ..qt import *
|
|
from ..misc import is_mainthread, not_mainthread
|
|
|
|
import binaryninja
|
|
from binaryninja import PythonScriptingInstance, binaryview
|
|
from binaryninja.plugin import BackgroundTaskThread
|
|
|
|
logger = logging.getLogger("Lighthouse.API.Binja")
|
|
|
|
#------------------------------------------------------------------------------
|
|
# Utils
|
|
#------------------------------------------------------------------------------
|
|
|
|
def execute_sync(function):
|
|
"""
|
|
Synchronize with the disassembler for safe database access.
|
|
"""
|
|
|
|
@functools.wraps(function)
|
|
def wrapper(*args, **kwargs):
|
|
|
|
#
|
|
# in Binary Ninja, it is only safe to access the BNDB from a thread
|
|
# that is *not* the mainthread. if we appear to already be in a
|
|
# background thread of some sort, simply execute the given function
|
|
#
|
|
|
|
if not is_mainthread():
|
|
return function(*args, **kwargs)
|
|
|
|
#
|
|
# if we are in the mainthread, we need to schedule a background
|
|
# task to perform our database task/function instead
|
|
#
|
|
# this inline function definition is technically what will execute
|
|
# in a database-safe background thread. we use this thunk to
|
|
# capture any output the function may want to return to the user.
|
|
#
|
|
|
|
output = [None]
|
|
def thunk():
|
|
output[0] = function(*args, **kwargs)
|
|
return 1
|
|
|
|
class DatabaseRead(BackgroundTaskThread):
|
|
"""
|
|
A stub task to safely read from the BNDB.
|
|
"""
|
|
def __init__(self, text, function):
|
|
super(DatabaseRead, self).__init__(text, False)
|
|
self._task_to_run = function
|
|
def run(self):
|
|
self._task_to_run()
|
|
self.finish()
|
|
|
|
# schedule the databases read and wait for its completion
|
|
t = DatabaseRead("Accessing database...", thunk)
|
|
t.start()
|
|
t.join()
|
|
|
|
# return the output of the synchronized execution / read
|
|
return output[0]
|
|
return wrapper
|
|
|
|
#------------------------------------------------------------------------------
|
|
# Disassembler API
|
|
#------------------------------------------------------------------------------
|
|
|
|
class BinjaCoreAPI(DisassemblerCoreAPI):
|
|
NAME = "BINJA"
|
|
|
|
def __init__(self):
|
|
super(BinjaCoreAPI, self).__init__()
|
|
self._init_version()
|
|
|
|
def _init_version(self):
|
|
version_string = binaryninja.core_version()
|
|
|
|
# retrieve Binja's version #
|
|
if "-" in version_string: # dev
|
|
disassembler_version = version_string.split("-", 1)[0]
|
|
else: # commercial, personal
|
|
disassembler_version = version_string.split(" ", 1)[0]
|
|
|
|
major, minor, patch, *_= disassembler_version.split(".") + ['0']
|
|
|
|
# save the version number components for later use
|
|
self._version_major = major
|
|
self._version_minor = minor
|
|
self._version_patch = patch
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Properties
|
|
#--------------------------------------------------------------------------
|
|
|
|
@property
|
|
def headless(self):
|
|
return not(binaryninja.core_ui_enabled())
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Synchronization Decorators
|
|
#--------------------------------------------------------------------------
|
|
|
|
@staticmethod
|
|
def execute_read(function):
|
|
return execute_sync(function)
|
|
|
|
@staticmethod
|
|
def execute_write(function):
|
|
return execute_sync(function)
|
|
|
|
@staticmethod
|
|
def execute_ui(function):
|
|
|
|
@functools.wraps(function)
|
|
def wrapper(*args, **kwargs):
|
|
ff = functools.partial(function, *args, **kwargs)
|
|
|
|
# if we are already in the main (UI) thread, execute now
|
|
if is_mainthread():
|
|
ff()
|
|
return
|
|
|
|
# schedule the task to run in the main thread
|
|
binaryninja.execute_on_main_thread(ff)
|
|
|
|
return wrapper
|
|
|
|
#--------------------------------------------------------------------------
|
|
# API Shims
|
|
#--------------------------------------------------------------------------
|
|
|
|
def get_disassembler_user_directory(self):
|
|
return os.path.split(binaryninja.user_plugin_path())[0]
|
|
|
|
def get_disassembly_background_color(self):
|
|
return binaryninjaui.getThemeColor(binaryninjaui.ThemeColor.LinearDisassemblyBlockColor)
|
|
|
|
def is_msg_inited(self):
|
|
return True
|
|
|
|
@execute_ui.__func__
|
|
def warning(self, text):
|
|
super(BinjaCoreAPI, self).warning(text)
|
|
|
|
def message(self, message):
|
|
print(message)
|
|
|
|
#--------------------------------------------------------------------------
|
|
# UI API Shims
|
|
#--------------------------------------------------------------------------
|
|
|
|
def register_dockable(self, dockable_name, create_widget_callback):
|
|
dock_handler = DockHandler.getActiveDockHandler()
|
|
dock_handler.addDockWidget(dockable_name, create_widget_callback, QtCore.Qt.RightDockWidgetArea, QtCore.Qt.Horizontal, False)
|
|
|
|
def create_dockable_widget(self, parent, dockable_name):
|
|
return DockableWidget(parent, dockable_name)
|
|
|
|
def show_dockable(self, dockable_name):
|
|
dock_handler = DockHandler.getActiveDockHandler()
|
|
dock_handler.setVisible(dockable_name, True)
|
|
|
|
def hide_dockable(self, dockable_name):
|
|
dock_handler = DockHandler.getActiveDockHandler()
|
|
dock_handler.setVisible(dockable_name, False)
|
|
|
|
#--------------------------------------------------------------------------
|
|
# XXX Binja Specfic Helpers
|
|
#--------------------------------------------------------------------------
|
|
|
|
def binja_get_bv_from_dock(self):
|
|
dh = DockHandler.getActiveDockHandler()
|
|
if not dh:
|
|
return None
|
|
vf = dh.getViewFrame()
|
|
if not vf:
|
|
return None
|
|
vi = vf.getCurrentViewInterface()
|
|
bv = vi.getData()
|
|
return bv
|
|
|
|
class BinjaContextAPI(DisassemblerContextAPI):
|
|
|
|
def __init__(self, dctx):
|
|
super(BinjaContextAPI, self).__init__(dctx)
|
|
self.bv = dctx
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Properties
|
|
#--------------------------------------------------------------------------
|
|
|
|
@property
|
|
def busy(self):
|
|
return self.bv.analysis_info.state != binaryninja.enums.AnalysisState.IdleState
|
|
|
|
#--------------------------------------------------------------------------
|
|
# API Shims
|
|
#--------------------------------------------------------------------------
|
|
|
|
def get_current_address(self):
|
|
|
|
# TODO/V35: this doen't work because of the loss of context bug...
|
|
#ctx = UIContext.activeContext()
|
|
#ah = ctx.contentActionHandler()
|
|
#ac = ah.actionContext()
|
|
#return ac.address
|
|
|
|
dh = DockHandler.getActiveDockHandler()
|
|
if not dh:
|
|
return 0
|
|
vf = dh.getViewFrame()
|
|
if not vf:
|
|
return 0
|
|
ac = vf.actionContext()
|
|
if not ac:
|
|
return 0
|
|
return ac.address
|
|
|
|
@BinjaCoreAPI.execute_read
|
|
def get_database_directory(self):
|
|
return os.path.dirname(self.bv.file.filename)
|
|
|
|
@not_mainthread
|
|
def get_function_addresses(self):
|
|
return [x.start for x in self.bv.functions]
|
|
|
|
def get_function_name_at(self, address):
|
|
func = self.bv.get_function_at(address)
|
|
if not func:
|
|
return None
|
|
return func.symbol.short_name
|
|
|
|
@BinjaCoreAPI.execute_read
|
|
def get_function_raw_name_at(self, address):
|
|
func = self.bv.get_function_at(address)
|
|
if not func:
|
|
return None
|
|
return func.name
|
|
|
|
@not_mainthread
|
|
def get_imagebase(self):
|
|
return self.bv.start
|
|
|
|
@not_mainthread
|
|
def get_root_filename(self):
|
|
return os.path.basename(self.bv.file.original_filename)
|
|
|
|
def navigate(self, address):
|
|
return self.bv.navigate(self.bv.view, address)
|
|
|
|
def navigate_to_function(self, function_address, address):
|
|
|
|
#
|
|
# attempt a more 'precise' jump, that guarantees to place us within
|
|
# the given function. this is necessary when trying to jump to an
|
|
# an address/node that is shared between two functions
|
|
#
|
|
|
|
funcs = self.bv.get_functions_containing(address)
|
|
if not funcs:
|
|
return False
|
|
|
|
#
|
|
# try to find the function that contains our target (address) and has
|
|
# a matching function start...
|
|
#
|
|
|
|
for func in funcs:
|
|
if func.start == function_address:
|
|
break
|
|
|
|
# no matching function ???
|
|
else:
|
|
return False
|
|
|
|
dh = DockHandler.getActiveDockHandler()
|
|
vf = dh.getViewFrame()
|
|
vi = vf.getCurrentViewInterface()
|
|
|
|
return vi.navigateToFunction(func, address)
|
|
|
|
def set_function_name_at(self, function_address, new_name):
|
|
func = self.bv.get_function_at(function_address)
|
|
|
|
if not func:
|
|
return
|
|
|
|
if new_name == "":
|
|
new_name = None
|
|
|
|
state = self.bv.begin_undo_actions()
|
|
func.name = new_name
|
|
self.bv.commit_undo_actions(state)
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Hooks API
|
|
#--------------------------------------------------------------------------
|
|
|
|
def create_rename_hooks(self):
|
|
return RenameHooks(self.bv)
|
|
|
|
#------------------------------------------------------------------------------
|
|
# Function Prefix API
|
|
#------------------------------------------------------------------------------
|
|
|
|
PREFIX_SEPARATOR = "▁" # Unicode 0x2581
|
|
|
|
#------------------------------------------------------------------------------
|
|
# Hooking
|
|
#------------------------------------------------------------------------------
|
|
|
|
class RenameHooks(binaryview.BinaryDataNotification):
|
|
"""
|
|
A hooking class to catch symbol changes in Binary Ninja.
|
|
"""
|
|
|
|
def __init__(self, bv):
|
|
self._bv = bv
|
|
|
|
def hook(self):
|
|
self._bv.register_notification(self)
|
|
|
|
def unhook(self):
|
|
self._bv.unregister_notification(self)
|
|
|
|
def symbol_added(self, *args):
|
|
self.__symbol_handler(*args)
|
|
|
|
def symbol_updated(self, *args):
|
|
self.__symbol_handler(*args)
|
|
|
|
def symbol_removed(self, *args):
|
|
self.__symbol_handler(*args, True)
|
|
|
|
def __symbol_handler(self, view, symbol, removed=False):
|
|
|
|
func = self._bv.get_function_at(symbol.address)
|
|
if not func or not func.start == symbol.address:
|
|
return
|
|
|
|
if removed:
|
|
self.name_changed(symbol.address, "sub_%x" % symbol.address)
|
|
else:
|
|
self.name_changed(symbol.address, symbol.name)
|
|
|
|
def name_changed(self, address, name):
|
|
"""
|
|
A placeholder callback, which will get hooked / replaced once live.
|
|
"""
|
|
pass
|
|
|
|
#------------------------------------------------------------------------------
|
|
# UI
|
|
#------------------------------------------------------------------------------
|
|
|
|
if QT_AVAILABLE:
|
|
|
|
import binaryninjaui
|
|
from binaryninjaui import DockHandler, DockContextHandler, UIContext, UIActionHandler
|
|
|
|
class DockableWidget(QtWidgets.QWidget, DockContextHandler):
|
|
"""
|
|
A dockable Qt widget for Binary Ninja.
|
|
"""
|
|
|
|
def __init__(self, parent, name):
|
|
QtWidgets.QWidget.__init__(self, parent)
|
|
DockContextHandler.__init__(self, self, name)
|
|
|
|
self.actionHandler = UIActionHandler()
|
|
self.actionHandler.setupActionHandler(self)
|
|
|
|
self._active_view = None
|
|
self._visible_for_view = collections.defaultdict(lambda: False)
|
|
|
|
@property
|
|
def visible(self):
|
|
return self._visible_for_view[self._active_view]
|
|
|
|
@visible.setter
|
|
def visible(self, is_visible):
|
|
self._visible_for_view[self._active_view] = is_visible
|
|
|
|
def shouldBeVisible(self, view_frame):
|
|
if not view_frame:
|
|
return False
|
|
|
|
if USING_PYSIDE6:
|
|
import shiboken6 as shiboken
|
|
else:
|
|
import shiboken2 as shiboken
|
|
|
|
vf_ptr = shiboken.getCppPointer(view_frame)[0]
|
|
return self._visible_for_view[vf_ptr]
|
|
|
|
def notifyVisibilityChanged(self, is_visible):
|
|
self.visible = is_visible
|
|
|
|
def notifyViewChanged(self, view_frame):
|
|
if not view_frame:
|
|
self._active_view = None
|
|
return
|
|
|
|
if USING_PYSIDE6:
|
|
import shiboken6 as shiboken
|
|
else:
|
|
import shiboken2 as shiboken
|
|
|
|
self._active_view = shiboken.getCppPointer(view_frame)[0]
|
|
|
|
if self.visible:
|
|
dock_handler = DockHandler.getActiveDockHandler()
|
|
dock_handler.setVisible(self.m_name, True)
|