diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index 82e3771c..201c1c16 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -51,7 +51,6 @@ class CapaExplorerForm(idaapi.PluginForm): self.view_limit_results_by_function = None self.view_search_bar = None self.view_tree = None - self.view_summary = None self.view_attack = None self.view_tabs = None self.view_menu_bar = None @@ -94,20 +93,15 @@ class CapaExplorerForm(idaapi.PluginForm): self.search_model_proxy = CapaExplorerSearchProxyModel() self.search_model_proxy.setSourceModel(self.range_model_proxy) - # load tree self.view_tree = CapaExplorerQtreeView(self.search_model_proxy, self.parent) - - # load summary table - self.load_view_summary() self.load_view_attack() # load parent tab and children tab views self.load_view_tabs() self.load_view_checkbox_limit_by() self.load_view_search_bar() - self.load_view_summary_tab() - self.load_view_attack_tab() self.load_view_tree_tab() + self.load_view_attack_tab() # load menu bar and sub menus self.load_view_menu_bar() @@ -128,28 +122,6 @@ class CapaExplorerForm(idaapi.PluginForm): bar = QtWidgets.QMenuBar() self.view_menu_bar = bar - def load_view_summary(self): - """ load capa summary table """ - table_headers = [ - "Capability", - "Namespace", - ] - - table = QtWidgets.QTableWidget() - - table.setColumnCount(len(table_headers)) - table.verticalHeader().setVisible(False) - table.setSortingEnabled(False) - table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - table.setFocusPolicy(QtCore.Qt.NoFocus) - table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - table.setHorizontalHeaderLabels(table_headers) - table.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignLeft) - table.setShowGrid(False) - table.setStyleSheet("QTableWidget::item { padding: 25px; }") - - self.view_summary = table - def load_view_attack(self): """ load MITRE ATT&CK table """ table_headers = [ @@ -209,16 +181,6 @@ class CapaExplorerForm(idaapi.PluginForm): self.view_tabs.addTab(tab, "Tree View") - def load_view_summary_tab(self): - """ load capa summary tab view """ - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.view_summary) - - tab = QtWidgets.QWidget() - tab.setLayout(layout) - - self.view_tabs.addTab(tab, "Summary") - def load_view_attack_tab(self): """ load MITRE ATT&CK tab view """ layout = QtWidgets.QVBoxLayout() @@ -409,7 +371,6 @@ class CapaExplorerForm(idaapi.PluginForm): self.doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities) self.model_data.render_capa_doc(self.doc) - self.render_capa_doc_summary() self.render_capa_doc_mitre_summary() self.set_view_tree_default_sort_order() @@ -420,24 +381,6 @@ class CapaExplorerForm(idaapi.PluginForm): """ set capa tree view default sort order """ self.view_tree.sortByColumn(CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION, QtCore.Qt.AscendingOrder) - def render_capa_doc_summary(self): - """ render capa summary results """ - for (row, rule) in enumerate(rutils.capability_rules(self.doc)): - count = len(rule["matches"]) - - if count == 1: - capability = rule["meta"]["name"] - else: - capability = "%s (%d matches)" % (rule["meta"]["name"], count) - - self.view_summary.setRowCount(row + 1) - - self.view_summary.setItem(row, 0, self.render_new_table_header_item(capability)) - self.view_summary.setItem(row, 1, QtWidgets.QTableWidgetItem(rule["meta"]["namespace"])) - - # resize columns to content - self.view_summary.resizeColumnsToContents() - def render_capa_doc_mitre_summary(self): """ render capa MITRE ATT&CK results """ tactics = collections.defaultdict(set) @@ -511,7 +454,6 @@ class CapaExplorerForm(idaapi.PluginForm): self.range_model_proxy.invalidate() self.search_model_proxy.invalidate() self.model_data.clear() - self.view_summary.setRowCount(0) self.load_capa_results() logger.debug("%s reload completed", self.form_title) diff --git a/capa/ida/plugin/item.py b/capa/ida/plugin/item.py index 8cc9a1c5..46111633 100644 --- a/capa/ida/plugin/item.py +++ b/capa/ida/plugin/item.py @@ -147,10 +147,10 @@ class CapaExplorerRuleItem(CapaExplorerDataItem): fmt = "%s (%d matches)" - def __init__(self, parent, display, count, source): + def __init__(self, parent, name, namespace, count, source): """ """ - display = self.fmt % (display, count) if count > 1 else display - super(CapaExplorerRuleItem, self).__init__(parent, [display, "", ""]) + display = self.fmt % (name, count) if count > 1 else name + super(CapaExplorerRuleItem, self).__init__(parent, [display, "", namespace]) self._source = source @property diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index 7c951da4..0dadde19 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -450,7 +450,11 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel): self.beginResetModel() for rule in rutils.capability_rules(doc): - parent = CapaExplorerRuleItem(self.root_node, rule["meta"]["name"], len(rule["matches"]), rule["source"]) + rule_name = rule["meta"]["name"] + rule_namespace = rule["meta"].get("namespace") + parent = CapaExplorerRuleItem( + self.root_node, rule_name, rule_namespace, len(rule["matches"]), rule["source"] + ) for (location, match) in doc["rules"][rule["meta"]["name"]]["matches"].items(): if rule["meta"]["scope"] == capa.rules.FILE_SCOPE: diff --git a/capa/ida/plugin/proxy.py b/capa/ida/plugin/proxy.py index 349266c6..af9df7c1 100644 --- a/capa/ida/plugin/proxy.py +++ b/capa/ida/plugin/proxy.py @@ -116,7 +116,7 @@ class CapaExplorerRangeProxyModel(QtCore.QSortFilterProxyModel): class CapaExplorerSearchProxyModel(QtCore.QSortFilterProxyModel): """A SortFilterProxyModel that accepts rows with a substring match for a configurable query. - Looks for matches in the RULE_INFORMATION column (e.g. column 0). + Looks for matches in the text of all rows. Displays the entire tree row if any of the tree branches, that is, you can filter by rule name, or also filter by "characteristic(nzxor)" to filter matches with some feature. @@ -175,17 +175,25 @@ class CapaExplorerSearchProxyModel(QtCore.QSortFilterProxyModel): source_model = self.sourceModel() - index = source_model.index(row, 0, parent) - data = source_model.data(index, Qt.DisplayRole) + for column in ( + CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION, + CapaExplorerDataModel.COLUMN_INDEX_VIRTUAL_ADDRESS, + CapaExplorerDataModel.COLUMN_INDEX_DETAILS, + ): + index = source_model.index(row, column, parent) + data = source_model.data(index, Qt.DisplayRole) - if not data: - return False + if not data: + continue - if not isinstance(data, str): - # sanity check: should already be a string, but double check - return False + if not isinstance(data, str): + # sanity check: should already be a string, but double check + continue - return self.query in data + if self.query in data: + return True + + return False def set_query(self, query): self.query = query