Merge pull request #287 from fireeye/fix-286

fix 286
This commit is contained in:
Willi Ballenthin
2020-09-02 14:50:24 -06:00
committed by GitHub
4 changed files with 26 additions and 72 deletions

View File

@@ -51,7 +51,6 @@ class CapaExplorerForm(idaapi.PluginForm):
self.view_limit_results_by_function = None self.view_limit_results_by_function = None
self.view_search_bar = None self.view_search_bar = None
self.view_tree = None self.view_tree = None
self.view_summary = None
self.view_attack = None self.view_attack = None
self.view_tabs = None self.view_tabs = None
self.view_menu_bar = None self.view_menu_bar = None
@@ -94,20 +93,15 @@ class CapaExplorerForm(idaapi.PluginForm):
self.search_model_proxy = CapaExplorerSearchProxyModel() self.search_model_proxy = CapaExplorerSearchProxyModel()
self.search_model_proxy.setSourceModel(self.range_model_proxy) self.search_model_proxy.setSourceModel(self.range_model_proxy)
# load tree
self.view_tree = CapaExplorerQtreeView(self.search_model_proxy, self.parent) self.view_tree = CapaExplorerQtreeView(self.search_model_proxy, self.parent)
# load summary table
self.load_view_summary()
self.load_view_attack() self.load_view_attack()
# load parent tab and children tab views # load parent tab and children tab views
self.load_view_tabs() self.load_view_tabs()
self.load_view_checkbox_limit_by() self.load_view_checkbox_limit_by()
self.load_view_search_bar() self.load_view_search_bar()
self.load_view_summary_tab()
self.load_view_attack_tab()
self.load_view_tree_tab() self.load_view_tree_tab()
self.load_view_attack_tab()
# load menu bar and sub menus # load menu bar and sub menus
self.load_view_menu_bar() self.load_view_menu_bar()
@@ -128,28 +122,6 @@ class CapaExplorerForm(idaapi.PluginForm):
bar = QtWidgets.QMenuBar() bar = QtWidgets.QMenuBar()
self.view_menu_bar = bar 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): def load_view_attack(self):
""" load MITRE ATT&CK table """ """ load MITRE ATT&CK table """
table_headers = [ table_headers = [
@@ -209,16 +181,6 @@ class CapaExplorerForm(idaapi.PluginForm):
self.view_tabs.addTab(tab, "Tree View") 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): def load_view_attack_tab(self):
""" load MITRE ATT&CK tab view """ """ load MITRE ATT&CK tab view """
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
@@ -409,7 +371,6 @@ class CapaExplorerForm(idaapi.PluginForm):
self.doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities) self.doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities)
self.model_data.render_capa_doc(self.doc) self.model_data.render_capa_doc(self.doc)
self.render_capa_doc_summary()
self.render_capa_doc_mitre_summary() self.render_capa_doc_mitre_summary()
self.set_view_tree_default_sort_order() self.set_view_tree_default_sort_order()
@@ -420,24 +381,6 @@ class CapaExplorerForm(idaapi.PluginForm):
""" set capa tree view default sort order """ """ set capa tree view default sort order """
self.view_tree.sortByColumn(CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION, QtCore.Qt.AscendingOrder) 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): def render_capa_doc_mitre_summary(self):
""" render capa MITRE ATT&CK results """ """ render capa MITRE ATT&CK results """
tactics = collections.defaultdict(set) tactics = collections.defaultdict(set)
@@ -511,7 +454,6 @@ class CapaExplorerForm(idaapi.PluginForm):
self.range_model_proxy.invalidate() self.range_model_proxy.invalidate()
self.search_model_proxy.invalidate() self.search_model_proxy.invalidate()
self.model_data.clear() self.model_data.clear()
self.view_summary.setRowCount(0)
self.load_capa_results() self.load_capa_results()
logger.debug("%s reload completed", self.form_title) logger.debug("%s reload completed", self.form_title)

View File

@@ -147,10 +147,10 @@ class CapaExplorerRuleItem(CapaExplorerDataItem):
fmt = "%s (%d matches)" 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 display = self.fmt % (name, count) if count > 1 else name
super(CapaExplorerRuleItem, self).__init__(parent, [display, "", ""]) super(CapaExplorerRuleItem, self).__init__(parent, [display, "", namespace])
self._source = source self._source = source
@property @property

View File

@@ -450,7 +450,11 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
self.beginResetModel() self.beginResetModel()
for rule in rutils.capability_rules(doc): 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(): for (location, match) in doc["rules"][rule["meta"]["name"]]["matches"].items():
if rule["meta"]["scope"] == capa.rules.FILE_SCOPE: if rule["meta"]["scope"] == capa.rules.FILE_SCOPE:

View File

@@ -116,7 +116,7 @@ class CapaExplorerRangeProxyModel(QtCore.QSortFilterProxyModel):
class CapaExplorerSearchProxyModel(QtCore.QSortFilterProxyModel): class CapaExplorerSearchProxyModel(QtCore.QSortFilterProxyModel):
"""A SortFilterProxyModel that accepts rows with a substring match for a configurable query. """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, Displays the entire tree row if any of the tree branches,
that is, you can filter by rule name, or also that is, you can filter by rule name, or also
filter by "characteristic(nzxor)" to filter matches with some feature. filter by "characteristic(nzxor)" to filter matches with some feature.
@@ -175,17 +175,25 @@ class CapaExplorerSearchProxyModel(QtCore.QSortFilterProxyModel):
source_model = self.sourceModel() source_model = self.sourceModel()
index = source_model.index(row, 0, parent) for column in (
data = source_model.data(index, Qt.DisplayRole) 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: if not data:
return False continue
if not isinstance(data, str): if not isinstance(data, str):
# sanity check: should already be a string, but double check # sanity check: should already be a string, but double check
return False continue
return self.query in data if self.query in data:
return True
return False
def set_query(self, query): def set_query(self, query):
self.query = query self.query = query