From e70d04f052019bd092620e015e9c62c03abd0503 Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Fri, 16 Aug 2024 23:46:43 -0400 Subject: [PATCH 01/11] add warning / prevent auto-install on incompatible versions of IDA / Python fixes #10, #15, #16, #18 --- README.md | 2 ++ install.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 2d65e0c..bade5c2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Special thanks to [Hex-Rays](https://hex-rays.com/) for supporting the developme This plugin requires IDA 7.6 and Python 3. It supports Windows, Linux, and macOS. +*Please note, older versions of IDA (8.2 and below) are [not compatible](https://hex-rays.com/products/ida/news/8_2sp1/) with Python 3.11 and above.* + ## Easy Install Run the following line in the IDA console to automatically install the plugin: diff --git a/install.py b/install.py index 7bae9cb..67c299d 100644 --- a/install.py +++ b/install.py @@ -26,6 +26,22 @@ except: SUPPORTED_IDA = False +# +# XXX/NOTE: older versions of IDA have compatability issues with newer +# versions of Python. if the user is running IDA 8.2 or below and Python 3.11 +# or above, we will not proceed with installation. +# +# https://hex-rays.com/products/ida/news/8_2sp1/ +# https://github.com/gaasedelen/patching/issues/10 +# https://github.com/gaasedelen/patching/issues/16 +# ... +# + +if SUPPORTED_IDA and ida_pro.IDA_SDK_VERSION < 830: + SUPPORTED_PYTHON = sys.version_info[0] == 3 and sys.version_info[1] < 11 + if not SUPPORTED_PYTHON: + print("[i] IDA 8.2 and below do not support Python 3.11 and above") + # is this deemed to be a compatible environment for the plugin to load? SUPPORTED_ENVIRONMENT = bool(SUPPORTED_IDA and SUPPORTED_PYTHON) From 1d407f3b587ad2e48f0da3faaac64863866c72f4 Mon Sep 17 00:00:00 2001 From: kanren3 <760917197@qq.com> Date: Tue, 13 Aug 2024 00:49:43 +0800 Subject: [PATCH 02/11] bugfix: improve perf when calculating highlights near large items --- plugins/patching/core.py | 77 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/plugins/patching/core.py b/plugins/patching/core.py index ff2e99e..67ff30b 100644 --- a/plugins/patching/core.py +++ b/plugins/patching/core.py @@ -941,21 +941,87 @@ def _highlight_lines(self, out, widget, rin): IDA is drawing disassembly lines and requesting highlighting info. """ + # if there are no patches, there is nothing to highlight + if not self.patched_addresses: + return + # ignore line highlight events that are not for a disassembly view if ida_kernwin.get_widget_type(widget) != ida_kernwin.BWN_DISASM: return + # cache item heads that have been checked for patches + ignore_item_ea = set() + highlight_item_ea = set() + # highlight lines/addresses that have been patched by the user for section_lines in rin.sections_lines: for line in section_lines: line_ea = line.at.toea() + + # + # fast path to ignore entire items that have not been patched + # but may span multiple lines in the disassembly view + # + + item_head = ida_bytes.get_item_head(line_ea) + if item_head in ignore_item_ea: + continue + + # + # this is a fast-path to avoid having to re-check an entire + # item if the current line address has already been checked + # and determined to contain an applied patch. + # + + if line_ea in highlight_item_ea: + + # highlight the line if it is patched in some way + e = ida_kernwin.line_rendering_output_entry_t(line) + e.bg_color = ida_kernwin.CK_EXTRA2 + e.flags = ida_kernwin.LROEF_FULL_LINE + + # save the highlight to the output line highlight list + out.entries.push_back(e) + continue + + # + # for lines of IDA disas that normally have a small number of + # backing bytes (such as an instruction or simple data item) + # we explode it out to its individual addresses and use sets + # to check if any bytes within it have been patched + # + # this scales well to an infinite number of patched bytes + # + item_len = ida_bytes.get_item_size(line_ea) + end_ea = line_ea + item_len - # explode a line / instruction into individual addresses - line_addresses = set(range(line_ea, line_ea+item_len)) + if item_len <= 256: + line_addresses = set(range(line_ea, end_ea)) + if not(line_addresses & self.patched_addresses): + ignore_item_ea.add(line_ea) + continue - # if no patched bytes correspond to this line / instruction - if not(line_addresses & self.patched_addresses): + # + # for lines with items that are reportedly quite 'large' (maybe + # a struct, array, alignment directive, etc.) where a line may + # contribute to an item that's tens of thousands of bytes... + # + # we will instead loop through all of the patched addresses + # to see if any of them fall within the range of the line. + # + # it seems unlikely that the user will ever have very many + # patched bytes (maybe hundreds?) versus generating a large + # set and checking potentially tens of thousands of addresses + # that make up an item, like the above condition would + # + # NOTE: this was a added during a slight re-factor of this + # function / logic to help minimize the chance of notable lag + # when scrolling past large data structures in the disas view + # + + elif not any(line_ea <= ea < end_ea for ea in self.patched_addresses): + ignore_item_ea.add(line_ea) continue # highlight the line if it is patched in some way @@ -965,6 +1031,7 @@ def _highlight_lines(self, out, widget, rin): # save the highlight to the output line highlight list out.entries.push_back(e) + highlight_item_ea.add(line_ea) def _ida_undo_occurred(self, action_name, is_undo): """ @@ -1202,7 +1269,7 @@ def assemble_all(self): print("-"*50) print("(KNOWN) Unsupported Mnemonics") print("-"*50) - + for mnem, hits in unsupported_map.items(): print(" - %s - hits %u" % (mnem.ljust(10), hits)) From 5abbec6f26c27d1d19da5faa4c4f39568a750704 Mon Sep 17 00:00:00 2001 From: Harelon Date: Wed, 25 May 2022 22:06:33 +0300 Subject: [PATCH 03/11] Restore patch program alt shortcuts --- plugins/patching/actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/patching/actions.py b/plugins/patching/actions.py index a0e5018..3667771 100644 --- a/plugins/patching/actions.py +++ b/plugins/patching/actions.py @@ -110,7 +110,7 @@ def update(self, ctx): class AssembleAction(ida_kernwin.action_handler_t): NAME = 'patching:assemble' ICON = 'assemble.png' - TEXT = "Assemble..." + TEXT = "~A~ssemble..." TOOLTIP = "Assemble new instructions at the selected address" HOTKEY = None @@ -135,7 +135,7 @@ def update(self, ctx): class ApplyAction(ida_kernwin.action_handler_t): NAME = 'patching:apply' ICON = 'save.png' - TEXT = "Apply patches to..." + TEXT = "A~p~ply patches to..." TOOLTIP = "Select where to save the patched binary" HOTKEY = None From c4bad444708ee87bc1dec446b4bd993df3ad5166 Mon Sep 17 00:00:00 2001 From: kanren3 <32032670+kanren3@users.noreply.github.com> Date: Sat, 17 Aug 2024 12:17:19 -0700 Subject: [PATCH 04/11] Added support for ida9.0 beta (#21) * Added support for ida9.0 * update ARM arch name * change "inf.is_be" to "ida_ida.inf_is_be" * Using idc ensures compatibility with IDA 7.6 to IDA 9.0 beta. * defer import of idc to ensure patching.reload() still works --------- Co-authored-by: gaasedelen --- plugins/patching/asm.py | 32 ++++++++++++++++---------------- plugins/patching/core.py | 12 +++++------- plugins/patching/util/ida.py | 27 +++++++++++++-------------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/plugins/patching/asm.py b/plugins/patching/asm.py index 126624b..3480dd7 100644 --- a/plugins/patching/asm.py +++ b/plugins/patching/asm.py @@ -444,13 +444,13 @@ class AsmX86(KeystoneAssembler): 'REPE CMPSW', ] - def __init__(self, inf): + def __init__(self): arch = keystone.KS_ARCH_X86 - if inf.is_64bit(): + if ida_ida.inf_is_64bit(): mode = keystone.KS_MODE_64 self.MAX_PREVIEW_BYTES = 7 - elif inf.is_32bit(): + elif ida_ida.inf_is_32bit_exactly(): mode = keystone.KS_MODE_32 self.MAX_PREVIEW_BYTES = 6 else: @@ -644,13 +644,13 @@ class AsmARM(KeystoneAssembler): # TODO: MRS and MOV (32/64 bit) are semi-supported too ] - def __init__(self, inf): + def __init__(self): # ARM64 - if inf.is_64bit(): + if ida_ida.inf_is_64bit(): arch = keystone.KS_ARCH_ARM64 - if inf.is_be(): + if ida_ida.inf_is_be(): mode = keystone.KS_MODE_BIG_ENDIAN else: mode = keystone.KS_MODE_LITTLE_ENDIAN @@ -662,7 +662,7 @@ def __init__(self, inf): else: arch = keystone.KS_ARCH_ARM - if inf.is_be(): + if ida_ida.inf_is_be(): mode = keystone.KS_MODE_ARM | keystone.KS_MODE_BIG_ENDIAN self._ks_thumb = keystone.Ks(arch, keystone.KS_MODE_THUMB | keystone.KS_MODE_BIG_ENDIAN) else: @@ -810,10 +810,10 @@ def unalias(self, assembly): class AsmPPC(KeystoneAssembler): - def __init__(self, inf): + def __init__(self): arch = keystone.KS_ARCH_PPC - if inf.is_64bit(): + if ida_ida.inf_is_64bit(): mode = keystone.KS_MODE_PPC64 else: mode = keystone.KS_MODE_PPC32 @@ -831,15 +831,15 @@ def __init__(self, inf): class AsmMIPS(KeystoneAssembler): - def __init__(self, inf): + def __init__(self): arch = keystone.KS_ARCH_MIPS - if inf.is_64bit(): + if ida_ida.inf_is_64bit(): mode = keystone.KS_MODE_MIPS64 else: mode = keystone.KS_MODE_MIPS32 - if inf.is_be(): + if ida_ida.inf_is_be(): mode |= keystone.KS_MODE_BIG_ENDIAN else: mode |= keystone.KS_MODE_LITTLE_ENDIAN @@ -853,15 +853,15 @@ def __init__(self, inf): class AsmSPARC(KeystoneAssembler): - def __init__(self, inf): + def __init__(self): arch = keystone.KS_ARCH_SPARC - if inf.is_64bit(): + if ida_ida.inf_is_64bit(): mode = keystone.KS_MODE_SPARC64 else: mode = keystone.KS_MODE_SPARC32 - if inf.is_be(): + if ida_ida.inf_is_be(): mode |= keystone.KS_MODE_BIG_ENDIAN else: mode |= keystone.KS_MODE_LITTLE_ENDIAN @@ -875,5 +875,5 @@ def __init__(self, inf): class AsmSystemZ(KeystoneAssembler): - def __init__(self, inf): + def __init__(self): super(AsmSystemZ, self).__init__(keystone.KS_ARCH_SYSTEMZ, keystone.KS_MODE_BIG_ENDIAN) diff --git a/plugins/patching/core.py b/plugins/patching/core.py index 67ff30b..390a690 100644 --- a/plugins/patching/core.py +++ b/plugins/patching/core.py @@ -167,13 +167,12 @@ def _init_assembler(self): """ Initialize the assembly engine to be used for patching. """ - inf = ida_idaapi.get_inf_structure() - arch_name = inf.procname.lower() + arch_name = ida_ida.inf_get_procname() if arch_name == 'metapc': - assembler = AsmX86(inf) - elif arch_name.startswith('arm'): - assembler = AsmARM(inf) + assembler = AsmX86() + elif arch_name.startswith('arm') or arch_name.startswith('ARM'): + assembler = AsmARM() # # TODO: disabled until v0.2.0 @@ -1280,8 +1279,7 @@ def assemble_all(self): percent_truncated = percent[:percent.index('.')+3] # truncate! don't round this float... - inf = ida_idaapi.get_inf_structure() - arch_name = inf.procname.lower() + arch_name = ida_ida.inf_get_procname() total_failed = total - good unknown_fails = total_failed - unsupported diff --git a/plugins/patching/util/ida.py b/plugins/patching/util/ida.py index e5de1bb..0b835b0 100644 --- a/plugins/patching/util/ida.py +++ b/plugins/patching/util/ida.py @@ -10,7 +10,6 @@ import ida_bytes import ida_lines import ida_idaapi -import ida_struct import ida_kernwin import ida_segment @@ -301,6 +300,9 @@ def resolve_symbol(from_ea, name): 'multiple' potential values) """ + # XXX: deferred import to avoid breaking patching.reload() dev helper + import idc + # # first, we will attempt to parse the given symbol as a global # struct path. @@ -339,26 +341,23 @@ def resolve_symbol(from_ea, name): # get the struct info for the resolved global address sid = ida_nalt.get_strid(global_ea) - sptr = ida_struct.get_struc(sid) - - # - # walk through the rest of the struct path to compute the offset (and - # final address) of the referenced field eg. global.foo.bar - # offset = 0 - while struct_path and sptr != None: - + while struct_path and sid != -1: member_name, sep, struct_path = struct_path.partition('.') - member = ida_struct.get_member_by_name(sptr, member_name) + member_offset = idc.get_member_offset(sid, member_name) - if member is None: + if member_offset == -1: print(" - INVALID STRUCT MEMBER!", member_name) break - offset += member.get_soff() - sptr = ida_struct.get_sptr(member) - if not sptr: + offset += member_offset + sid = idc.get_member_strid(sid, member_offset) + + # The idc.get_member_strid function in IDA 9.0 beta has a bug. + # Even if a member is not a structure, it does not return -1. + # Therefore, it's necessary to use struct_path to determine whether the retrieval is complete. + if not struct_path or sid == -1: assert not('.' in struct_path), 'Expected end of struct path?' yield (global_ea+offset, name) resolved_paths += 1 From fa9e0a553d825aaee15ebe683455baca12f1d892 Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Sat, 17 Aug 2024 15:54:02 -0400 Subject: [PATCH 05/11] update range selection for IDA disassembly views to facilitate reverting individual instructions. this aligns with the current / intended behavior of the patch preview UI. related to #5 --- plugins/patching/core.py | 4 ++-- plugins/patching/util/ida.py | 40 +++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/plugins/patching/core.py b/plugins/patching/core.py index 390a690..ff2fe71 100644 --- a/plugins/patching/core.py +++ b/plugins/patching/core.py @@ -37,9 +37,9 @@ class PatchingCore(object): PLUGIN_NAME = 'Patching' - PLUGIN_VERSION = '0.1.2' + PLUGIN_VERSION = '0.2.0-DEV' PLUGIN_AUTHORS = 'Markus Gaasedelen' - PLUGIN_DATE = '2022' + PLUGIN_DATE = '2024' def __init__(self, defer_load=False): diff --git a/plugins/patching/util/ida.py b/plugins/patching/util/ida.py index 0b835b0..81eaa8e 100644 --- a/plugins/patching/util/ida.py +++ b/plugins/patching/util/ida.py @@ -816,7 +816,45 @@ def read_range_selection(ctx): # return the range of selected lines return (True, start_ea, end_ea) - # normal IDA view + # special tweak for IDA disassembly views + elif ida_kernwin.get_widget_type(ctx.widget) == ida_kernwin.BWN_DISASM: + + # extract the start/end cursor locations within the IDA disas view + p0 = ida_kernwin.twinpos_t() + p1 = ida_kernwin.twinpos_t() + + # + # this is where we do a special override such that a user can select a + # few characters on a single instruction / line .. and we will return + # the 'range' of just that single instruction + # + # with a few characters selected / highlighted, IDA will return True + # to the read_selection() call below + # + + if ida_kernwin.read_selection(ctx.widget, p0, p1): + start_ea = p0.at.toea() + end_ea = p1.at.toea() + + # + # if the start and end address are the same with a successful + # selection read, that means the user's selection is on a single + # line / instruction + # + # we will calculate an appropriate 'end_ea' ourselves to capture + # the length of the entire instruction and return this as our own + # custom / mini range selection + # + # this facilitates the ability for users to reverst individual + # instructions within a patch by selecting a few characters of + # the instruction in question + # + + if start_ea == end_ea: + end_ea = ida_bytes.get_item_end(end_ea) + return (True, start_ea, end_ea) + + # any other IDA widget / viewer return ida_kernwin.read_range_selection(ctx.widget) def remove_ida_actions(popup): From 30db65129b140e9d50cced9ca4fa1f1161aa986b Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Sat, 23 Nov 2024 11:40:20 -0500 Subject: [PATCH 06/11] fix regression introduced by PR #12 which causes the patching submenu to not properly group with the other patching actions. --- plugins/patching/util/ida.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/patching/util/ida.py b/plugins/patching/util/ida.py index 81eaa8e..6450d96 100644 --- a/plugins/patching/util/ida.py +++ b/plugins/patching/util/ida.py @@ -153,6 +153,13 @@ def attach_submenu_to_popup(popup_handle, submenu_name, prev_action_name): if not QT_AVAILABLE: return None + # + # convert IDA alt shortcut syntax to whatever they use in Qt Text menus + # eg: '~A~ssemble patches to...' --> '&Assemble patches to...' + # + + prev_action_name = re.sub(r'~(.)~', r'&\1', 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) From 761add1990431246e32d58273ed642c15d9a26ed Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Sat, 23 Nov 2024 14:24:17 -0500 Subject: [PATCH 07/11] fix issue where bytes patched through other means would not immediately highlight --- plugins/patching/core.py | 77 +++++++++++++++++++++++++----------- plugins/patching/util/ida.py | 2 + 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/plugins/patching/core.py b/plugins/patching/core.py index ff2fe71..891d417 100644 --- a/plugins/patching/core.py +++ b/plugins/patching/core.py @@ -54,18 +54,10 @@ def __init__(self, defer_load=False): # IDA 'Database' Hooks self._idb_hooks = IDBHooks() - self._idb_hooks.auto_empty_finally = self.load - - # - # the plugin only uses IDB hooks for IDA Batch mode. specifically, it - # will load the plugin when the initial auto analysis has finished - # - # TODO: does auto_empty_finally trigger if you are loading a - # pre-existing IDB in IDA batch mode? (probably not, hence TODO) - # - if ida_kernwin.cvar.batch: - self._idb_hooks.hook() + self._idb_hooks.auto_empty_finally = self.load + self._idb_hooks.byte_patched = self._ida_byte_patched + self._idb_hooks.hook() # the backing engine to assemble instructions for the plugin self.assembler = None @@ -88,6 +80,7 @@ def __init__(self, defer_load=False): # plugin events / callbacks self._patches_changed_callbacks = [] + self._refresh_timer = None # # defer fully loading the plugin core until the IDB and UI itself @@ -115,15 +108,6 @@ def load(self): Load the plugin core. """ - # - # IDB hooks are *only* ever used to load the patching plugin after - # initial auto-analysis completes in batch mode. so we should always - # unhook them here as they will not be used for anything else - # - - if ida_kernwin.cvar.batch: - self._idb_hooks.unhook() - # attempt to initialize an assembler engine matching the database self._init_assembler() @@ -140,7 +124,6 @@ def load(self): self._init_actions() self._idp_hooks.hook() self._refresh_patches() - ida_kernwin.refresh_idaview_anyway() print("[%s] Loaded v%s - (c) %s - %s" % (self.PLUGIN_NAME, self.PLUGIN_VERSION, self.PLUGIN_AUTHORS, self.PLUGIN_DATE)) @@ -158,6 +141,11 @@ def unload(self): print("[%s] Unloading v%s..." % (self.PLUGIN_NAME, self.PLUGIN_VERSION)) + if self._refresh_timer: + ida_kernwin.unregister_timer(self._refresh_timer) + self._refresh_timer = None + + self._idb_hooks.unhook() self._idp_hooks.unhook() self._ui_hooks.unhook() self._unregister_actions() @@ -478,8 +466,15 @@ def patch(self, ea, patch_data, fill_nop=True): self.nop_range(next_address, next_address+fill_size) ida_auto.auto_make_code(next_address) - # write the actual patch data to the database + # + # write the actual patch data to the database. we also unhook the IDB + # events to prevent the plugin from seeing the numerous 'patch' events + # that IDA will generate as we write the patch data to the database + # + + self._idb_hooks.unhook() ida_bytes.patch_bytes(ea, patch_data) + self._idb_hooks.hook() # # record the region of patched addresses @@ -740,6 +735,14 @@ def visitor(ea, file_offset, original_value, patched_value): self.patched_addresses = addresses ida_kernwin.execute_sync(self._notify_patches_changed, ida_kernwin.MFF_NOWAIT|ida_kernwin.MFF_WRITE) + def __deferred_refresh_callback(self): + """ + A deferred callback to refresh the list of patched addresses. + """ + self._refresh_timer = None + self._refresh_patches() + return -1 # unregisters the timer + #-------------------------------------------------------------------------- # Plugin Events #-------------------------------------------------------------------------- @@ -773,6 +776,9 @@ def _notify_patches_changed(self): notify_callback(self._patches_changed_callbacks) + # ensure the IDA views are refreshed so highlights are updated + ida_kernwin.refresh_idaview_anyway() + # for execute_sync(...) return 1 @@ -1045,6 +1051,27 @@ def _ida_undo_occurred(self, action_name, is_undo): self._refresh_patches() return 0 + + def _ida_byte_patched(self, ea, old_value): + """ + IDA is reporting a byte has been patched. + """ + + # + # if a timer already exists, unregister it so that we can register a + # new one. this is to effectively resest the timer as patched bytes + # are coming in 'rapidly' (eg. externally scripted patches, etc) + # + + if self._refresh_timer: + ida_kernwin.unregister_timer(self._refresh_timer) + + # + # register a timer to wait 200ms before doing a full reset of the + # patched addresses. this is to help 'batch' the changes + # + + self._refresh_timer = ida_kernwin.register_timer(200, self.__deferred_refresh_callback) #-------------------------------------------------------------------------- # Temp / DEV / Tests @@ -1125,6 +1152,9 @@ def assemble_all(self): fail_bytes = collections.defaultdict(set) alternates = set() + # unhook so the plugin doesn't try to handle a billion 'patch' events + self._idb_hooks.unhook() + for ea in all_instruction_addresses(start): # update the navbar cursor based on progress (only when in UI) @@ -1247,6 +1277,9 @@ def assemble_all(self): print(" - IDA: %s\n - ASM: %s" % (disas_hex, asm_hex)) #break + # re-hook the to re-enable the plugin's ability to see patch events + self._idb_hooks.hook() + print("-"*50) print("RESULTS") print("-"*50) diff --git a/plugins/patching/util/ida.py b/plugins/patching/util/ida.py index 6450d96..7957248 100644 --- a/plugins/patching/util/ida.py +++ b/plugins/patching/util/ida.py @@ -35,6 +35,8 @@ def ev_ending_undo(self, action_name, is_undo): class IDBHooks(ida_idp.IDB_Hooks): def auto_empty_finally(self): pass + def byte_patched(self, ea, value): + pass #------------------------------------------------------------------------------ # IDA Misc From f847ae2763c34866a48e78cdcb6d6584a5f40d26 Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Sat, 23 Nov 2024 14:24:59 -0500 Subject: [PATCH 08/11] maybe minor perf gain --- plugins/patching/util/ida.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/patching/util/ida.py b/plugins/patching/util/ida.py index 7957248..1e85978 100644 --- a/plugins/patching/util/ida.py +++ b/plugins/patching/util/ida.py @@ -827,6 +827,10 @@ def read_range_selection(ctx): # special tweak for IDA disassembly views elif ida_kernwin.get_widget_type(ctx.widget) == ida_kernwin.BWN_DISASM: + + # no active selection in the patching view, nothing to do... + if not(ctx.cur_flags & ida_kernwin.ACF_HAS_SELECTION): + return (False, ida_idaapi.BADADDR, ida_idaapi.BADADDR) # extract the start/end cursor locations within the IDA disas view p0 = ida_kernwin.twinpos_t() @@ -854,7 +858,7 @@ def read_range_selection(ctx): # the length of the entire instruction and return this as our own # custom / mini range selection # - # this facilitates the ability for users to reverst individual + # this facilitates the ability for users to revert individual # instructions within a patch by selecting a few characters of # the instruction in question # From 43a5755cafd01203caf73d1bf3ee235c0b5c3fbf Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Sat, 23 Nov 2024 14:35:53 -0500 Subject: [PATCH 09/11] update packaging yaml to use in support actions --- .github/workflows/package-plugin.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package-plugin.yaml b/.github/workflows/package-plugin.yaml index 07bfd96..52fc06f 100644 --- a/.github/workflows/package-plugin.yaml +++ b/.github/workflows/package-plugin.yaml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v2 - name: Download workflow artifact - uses: dawidd6/action-download-artifact@v2.16.0 + uses: dawidd6/action-download-artifact@v6 with: # the target repo for external artifacts (built libs) @@ -31,6 +31,6 @@ jobs: mkdir linux && cp -r ../plugins/* ./linux/ && cp -r ../artifact/keystone_linux/* ./linux/patching/keystone && cd ./linux && zip -r ../patching_linux.zip ./* && cd .. mkdir darwin && cp -r ../plugins/* ./darwin/ && cp -r ../artifact/keystone_darwin/* ./darwin/patching/keystone && cd ./darwin && zip -r ../patching_macos.zip ./* && cd .. - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: path: ${{ github.workspace }}/dist/*.zip \ No newline at end of file From 3691846accec0bd614183ef1b24b71bee81735aa Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Sat, 23 Nov 2024 15:56:14 -0500 Subject: [PATCH 10/11] maybe fix artifact fetching from keystone fork --- .github/workflows/package-plugin.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/package-plugin.yaml b/.github/workflows/package-plugin.yaml index 52fc06f..8a78c20 100644 --- a/.github/workflows/package-plugin.yaml +++ b/.github/workflows/package-plugin.yaml @@ -27,9 +27,9 @@ jobs: shell: bash run: | mkdir dist && cd dist - mkdir win32 && cp -r ../plugins/* ./win32/ && cp -r ../artifact/keystone_win32/* ./win32/patching/keystone && cd ./win32 && zip -r ../patching_win32.zip ./* && cd .. - mkdir linux && cp -r ../plugins/* ./linux/ && cp -r ../artifact/keystone_linux/* ./linux/patching/keystone && cd ./linux && zip -r ../patching_linux.zip ./* && cd .. - mkdir darwin && cp -r ../plugins/* ./darwin/ && cp -r ../artifact/keystone_darwin/* ./darwin/patching/keystone && cd ./darwin && zip -r ../patching_macos.zip ./* && cd .. + mkdir win32 && cp -r ../plugins/* ./win32/ && cp -r ../artifact_windows-latest/* ./win32/patching/keystone && cd ./win32 && zip -r ../patching_win32.zip ./* && cd .. + mkdir linux && cp -r ../plugins/* ./linux/ && cp -r ../artifact_ubuntu-latest/* ./linux/patching/keystone && cd ./linux && zip -r ../patching_linux.zip ./* && cd .. + mkdir darwin && cp -r ../plugins/* ./darwin/ && cp -r ../artifact_macos-latest/* ./darwin/patching/keystone && cd ./darwin && zip -r ../patching_macos.zip ./* && cd .. - uses: actions/upload-artifact@v4 with: From 6ee5917e71a07e2275eff55238367faf2c8ac4b0 Mon Sep 17 00:00:00 2001 From: gaasedelen Date: Sat, 23 Nov 2024 18:48:17 -0500 Subject: [PATCH 11/11] version bump, staging final tweaks for release --- .github/workflows/package-plugin.yaml | 2 +- README.md | 1 + plugins/patching/core.py | 2 +- plugins/patching/ui/preview.py | 2 -- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/package-plugin.yaml b/.github/workflows/package-plugin.yaml index 8a78c20..1acaa12 100644 --- a/.github/workflows/package-plugin.yaml +++ b/.github/workflows/package-plugin.yaml @@ -6,7 +6,7 @@ jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Download workflow artifact uses: dawidd6/action-download-artifact@v6 diff --git a/README.md b/README.md index bade5c2..ab23718 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Special thanks to [Hex-Rays](https://hex-rays.com/) for supporting the developme ## Releases +* v0.2 -- Important bugfixes, IDA 9 compatibility * v0.1 -- Initial release # Installation diff --git a/plugins/patching/core.py b/plugins/patching/core.py index 891d417..a4d41e4 100644 --- a/plugins/patching/core.py +++ b/plugins/patching/core.py @@ -37,7 +37,7 @@ class PatchingCore(object): PLUGIN_NAME = 'Patching' - PLUGIN_VERSION = '0.2.0-DEV' + PLUGIN_VERSION = '0.2.0' PLUGIN_AUTHORS = 'Markus Gaasedelen' PLUGIN_DATE = '2024' diff --git a/plugins/patching/ui/preview.py b/plugins/patching/ui/preview.py index d84eff8..77b2c5d 100644 --- a/plugins/patching/ui/preview.py +++ b/plugins/patching/ui/preview.py @@ -1,4 +1,3 @@ -import ida_nalt import ida_name import ida_bytes import ida_lines @@ -228,7 +227,6 @@ def _refresh_lines(self): """ instructions, current_address = [], self._address_origin - IMAGEBASE = ida_nalt.get_imagebase() PREV_INSTRUCTIONS = 50 NEXT_INSTRUCTIONS = 50 MAX_PREVIEW_BYTES = self.core.assembler.MAX_PREVIEW_BYTES