From 4890bf503ec517ec96040f23a597a8b26b76d96f Mon Sep 17 00:00:00 2001 From: vee1e Date: Wed, 15 Apr 2026 20:51:00 +0530 Subject: [PATCH 1/2] ida: show addresses for file level features in rulegen --- CHANGELOG.md | 3 ++- capa/ida/plugin/view.py | 36 +++++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 601cf5f6d..16d64ad6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### capa Explorer Web ### capa Explorer IDA Pro plugin +- ida: display file-scope feature addresses (`file:0x...`) in rule generator tree and safely handle file-offset navigation/filtering @vee1e #3009 ### Development - ci: use explicit and per job permissions @mike-hunhoff #3002 @@ -165,7 +166,7 @@ Additionally a Binary Ninja bug has been fixed. Released binaries now include AR ### Bug Fixes -- binja: fix a crash during feature extraction when the MLIL is unavailable @xusheng6 #2714 +- binja: fix a crash during feature extraction when the MLIL is unavailable @xusheng6 #2714 ### capa Explorer Web diff --git a/capa/ida/plugin/view.py b/capa/ida/plugin/view.py index 4c21de1c9..ceebf6c6e 100644 --- a/capa/ida/plugin/view.py +++ b/capa/ida/plugin/view.py @@ -18,6 +18,7 @@ import idc import idaapi +import ida_loader import capa.rules import capa.engine @@ -25,7 +26,7 @@ import capa.features.common import capa.features.basicblock from capa.ida.plugin.item import CapaExplorerFunctionItem -from capa.features.address import AbsoluteVirtualAddress, _NoAddress +from capa.features.address import FileOffsetAddress, AbsoluteVirtualAddress, _NoAddress from capa.ida.plugin.model import CapaExplorerDataModel from capa.ida.plugin.qt_compat import QtGui, QtCore, Signal, QAction, QtWidgets @@ -905,7 +906,22 @@ def slot_custom_context_menu_requested(self, pos): def slot_item_double_clicked(self, o, column): """ """ if column == CapaExplorerRulegenFeatures.get_column_address_index() and o.text(column): - idc.jumpto(int(o.text(column), 0x10)) + addr_text = o.text(column).strip() + + if addr_text.startswith("file:"): + try: + file_offset = int(addr_text[len("file:") :], 16) + except (ValueError, TypeError): + return + + ea = ida_loader.get_fileregion_ea(file_offset) + if ea != idc.BADADDR: + idc.jumpto(ea) + else: + try: + idc.jumpto(int(addr_text, 16)) + except (ValueError, TypeError): + return elif o.capa_type == CapaExplorerRulegenFeatures.get_node_type_leaf(): self.editor.update_features([o.data(0, 0x100)]) @@ -955,13 +971,18 @@ def show_item_and_parents(_o): # read ea from "Address" column o_ea = o.text(CapaExplorerRulegenFeatures.get_column_address_index()) - if o_ea == "": - # ea may be empty, hide by default + if o_ea == "" or o_ea.startswith("file:"): + # ea may be empty or a file offset, hide by default when filtering by VA if not o.isHidden(): o.setHidden(True) continue - o_ea = int(o_ea, 16) + try: + o_ea = int(o_ea, 16) + except (ValueError, TypeError): + if not o.isHidden(): + o.setHidden(True) + continue if max_ea is not None and min_ea <= o_ea <= max_ea: show_item_and_parents(o) @@ -1045,8 +1066,9 @@ def parse_features_for_tree(self, parent, features): def format_address(e): if isinstance(e, AbsoluteVirtualAddress): return f"{hex(int(e))}" - else: - return "" + if isinstance(e, FileOffsetAddress): + return f"file:{hex(int(e))}" + return "" def format_feature(feature): """ """ From 3432ba98ec747bec3e1b7dd02f8b7e2fa8d10f08 Mon Sep 17 00:00:00 2001 From: yuno <51952975+vee1e@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:12:27 +0530 Subject: [PATCH 2/2] ida(ai): remove redundant `int` conversion Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- capa/ida/plugin/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capa/ida/plugin/view.py b/capa/ida/plugin/view.py index ceebf6c6e..bc1568d86 100644 --- a/capa/ida/plugin/view.py +++ b/capa/ida/plugin/view.py @@ -1067,7 +1067,7 @@ def format_address(e): if isinstance(e, AbsoluteVirtualAddress): return f"{hex(int(e))}" if isinstance(e, FileOffsetAddress): - return f"file:{hex(int(e))}" + return f"file:{hex(e)}" return "" def format_feature(feature):