diff --git a/plugins/ida-plugin.json b/plugins/ida-plugin.json new file mode 100644 index 0000000..8189eea --- /dev/null +++ b/plugins/ida-plugin.json @@ -0,0 +1,12 @@ +{ + "IDAMetadataDescriptorVersion": 1, + "plugin": { + "name": "Patching", + "entryPoint": "patching.py", + "categories": ["api-scripting-and-automation"], + "logoPath": "logo.png", + "idaVersions": ">=7.6", + "description" : "Interactive Binary Patching for IDA Pro", + "version": "0.3.0" + } +} diff --git a/plugins/patching.py b/plugins/patching.py index abbe262..7973b4f 100644 --- a/plugins/patching.py +++ b/plugins/patching.py @@ -22,8 +22,8 @@ # this plugin requires IDA 7.6 or newer try: + import idaapi import ida_pro - import ida_idaapi IDA_GLOBAL_SCOPE = sys.modules['__main__'] SUPPORTED_IDA = ida_pro.IDA_SDK_VERSION >= 760 except: @@ -48,7 +48,7 @@ def PLUGIN_ENTRY(): """ return PatchingPlugin() -class PatchingPlugin(ida_idaapi.plugin_t): +class PatchingPlugin(idaapi.plugin_t): """ The IDA Patching plugin stub. """ @@ -60,7 +60,7 @@ class PatchingPlugin(ida_idaapi.plugin_t): # - PLUGIN_UNL: Unload the plugin after calling run() # - flags = ida_idaapi.PLUGIN_PROC | ida_idaapi.PLUGIN_HIDE | ida_idaapi.PLUGIN_UNL + flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE | idaapi.PLUGIN_UNL comment = "A plugin to enable binary patching in IDA" help = "" wanted_name = "Patching" @@ -78,7 +78,7 @@ def init(self): This is called by IDA when it is loading the plugin. """ if not SUPPORTED_ENVIRONMENT or self.__updated: - return ida_idaapi.PLUGIN_SKIP + return idaapi.PLUGIN_SKIP # load the plugin core self.core = patching.PatchingCore(defer_load=True) @@ -86,8 +86,16 @@ def init(self): # inject a reference to the plugin context into the IDA console scope IDA_GLOBAL_SCOPE.patching = self + addon = idaapi.addon_info_t() + addon.id = "github.gaasedelen.patching" + addon.name = "Patching" + addon.producer = "Markus Gaasedelen" + addon.url = "https://github.com/gaasedelen/patching" + addon.version = "0.3.0.0" + idaapi.register_addon(addon) + # mark the plugin as loaded - return ida_idaapi.PLUGIN_KEEP + return idaapi.PLUGIN_KEEP def run(self, arg): """ diff --git a/plugins/patching/core.py b/plugins/patching/core.py index a4d41e4..a5a9c6d 100644 --- a/plugins/patching/core.py +++ b/plugins/patching/core.py @@ -37,9 +37,9 @@ class PatchingCore(object): PLUGIN_NAME = 'Patching' - PLUGIN_VERSION = '0.2.0' + PLUGIN_VERSION = '0.3.0' PLUGIN_AUTHORS = 'Markus Gaasedelen' - PLUGIN_DATE = '2024' + PLUGIN_DATE = '2025' def __init__(self, defer_load=False): diff --git a/plugins/patching/util/ida.py b/plugins/patching/util/ida.py index 1e85978..693a6e8 100644 --- a/plugins/patching/util/ida.py +++ b/plugins/patching/util/ida.py @@ -164,7 +164,10 @@ def attach_submenu_to_popup(popup_handle, submenu_name, prev_action_name): # cast an IDA 'popup handle' pointer back to a QMenu object p_qmenu = ctypes.cast(int(popup_handle), ctypes.POINTER(ctypes.c_void_p))[0] - qmenu = sip.wrapinstance(int(p_qmenu), QtWidgets.QMenu) + if ida_pro.IDA_SDK_VERSION >= 920: + qmenu = shiboken6.wrapInstance(int(p_qmenu), QtWidgets.QMenu) + else: + qmenu = sip.wrapinstance(int(p_qmenu), QtWidgets.QMenu) # create a Qt (sub)menu that can be injected into an IDA-originating menu submenu = QtWidgets.QMenu(submenu_name) @@ -893,7 +896,7 @@ def remove_ida_actions(popup): class FilterMenu(QtCore.QObject): def __init__(self, qmenu): - super(QtCore.QObject, self).__init__() + super(FilterMenu, self).__init__() self.qmenu = qmenu def eventFilter(self, obj, event): @@ -901,13 +904,16 @@ def eventFilter(self, obj, event): return False for action in self.qmenu.actions(): if action.text() in ["&Font...", "&Synchronize with"]: # lol.. - qmenu.removeAction(action) + self.qmenu.removeAction(action) self.qmenu.removeEventFilter(self) self.qmenu = None return True p_qmenu = ctypes.cast(int(popup), ctypes.POINTER(ctypes.c_void_p))[0] - qmenu = sip.wrapinstance(int(p_qmenu), QtWidgets.QMenu) + if ida_pro.IDA_SDK_VERSION >= 920: + qmenu = shiboken6.wrapInstance(int(p_qmenu), QtWidgets.QMenu) + else: + qmenu = sip.wrapinstance(int(p_qmenu), QtWidgets.QMenu) filter = FilterMenu(qmenu) qmenu.installEventFilter(filter) diff --git a/plugins/patching/util/qt.py b/plugins/patching/util/qt.py index bf0093b..f3e8ac7 100644 --- a/plugins/patching/util/qt.py +++ b/plugins/patching/util/qt.py @@ -5,23 +5,44 @@ QT_AVAILABLE = False -# attempt to load PyQt5 -try: - import PyQt5.QtGui as QtGui - import PyQt5.QtCore as QtCore - import PyQt5.QtWidgets as QtWidgets - from PyQt5 import sip - - # importing PyQt5 went okay, let's see if we're in an IDA Qt context +import ida_pro + +if ida_pro.IDA_SDK_VERSION >= 920: + # attempt to load PySide6 try: - import ida_kernwin - QT_AVAILABLE = ida_kernwin.is_idaq() + import PySide6.QtGui as QtGui + import PySide6.QtCore as QtCore + import PySide6.QtWidgets as QtWidgets + import shiboken6 + + # importing PySide6 went okay, let's see if we're in an IDA Qt context + try: + import ida_kernwin + QT_AVAILABLE = ida_kernwin.is_idaq() + except ImportError: + pass + + # import failed, PySide6 is not available except ImportError: pass +else: + # attempt to load PyQt5 + try: + import PyQt5.QtGui as QtGui + import PyQt5.QtCore as QtCore + import PyQt5.QtWidgets as QtWidgets + from PyQt5 import sip -# import failed, PyQt5 is not available -except ImportError: - pass + # importing PyQt5 went okay, let's see if we're in an IDA Qt context + try: + import ida_kernwin + QT_AVAILABLE = ida_kernwin.is_idaq() + except ImportError: + pass + + # import failed, PyQt5 is not available + except ImportError: + pass #-------------------------------------------------------------------------- # Qt Misc Helpers