diff --git a/capa/ida/ida_capa_explorer.py b/capa/ida/ida_capa_explorer.py index 5996141d..1b9a64d5 100644 --- a/capa/ida/ida_capa_explorer.py +++ b/capa/ida/ida_capa_explorer.py @@ -1,6 +1,7 @@ # Copyright (C) 2020 FireEye, Inc. All Rights Reserved. import os +import json import logging import collections @@ -82,6 +83,7 @@ class CapaExplorerForm(idaapi.PluginForm): self.parent = None self.ida_hooks = None + self.doc = None # models self.model_data = None @@ -254,15 +256,28 @@ class CapaExplorerForm(idaapi.PluginForm): actions = ( ("Reset view", "Reset plugin view", self.reset), ("Run analysis", "Run capa analysis on current database", self.reload), + ("Export results...", "Export capa results as JSON file", self.export_json), ) menu = self.view_menu_bar.addMenu("File") - for (name, _, handle) in actions: action = QtWidgets.QAction(name, self.parent) action.triggered.connect(handle) menu.addAction(action) + def export_json(self): + """ export capa results as JSON file """ + if not self.doc: + idaapi.info("No capa results to export.") + return + path = idaapi.ask_file(True, "*.json", "Choose file") + if os.path.exists(path) and 1 != idaapi.ask_yn(1, "File already exists. Overwrite?"): + return + with open(path, "wb") as export_file: + export_file.write( + json.dumps(self.doc, sort_keys=True, cls=capa.render.CapaJsonObjectEncoder).encode("utf-8") + ) + def load_ida_hooks(self): """ load IDA Pro UI hooks """ action_hooks = { @@ -377,11 +392,11 @@ class CapaExplorerForm(idaapi.PluginForm): logger.info("analysis completed.") - 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(doc) - self.render_capa_doc_summary(doc) - self.render_capa_doc_mitre_summary(doc) + 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() @@ -391,12 +406,9 @@ 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, doc): - """ render capa summary results - - @param doc: capa doc - """ - for (row, rule) in enumerate(rutils.capability_rules(doc)): + 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: @@ -412,14 +424,11 @@ class CapaExplorerForm(idaapi.PluginForm): # resize columns to content self.view_summary.resizeColumnsToContents() - def render_capa_doc_mitre_summary(self, doc): - """ render capa MITRE ATT&CK results - - @param doc: capa doc - """ + def render_capa_doc_mitre_summary(self): + """ render capa MITRE ATT&CK results """ tactics = collections.defaultdict(set) - for rule in rutils.capability_rules(doc): + for rule in rutils.capability_rules(self.doc): if not rule["meta"].get("att&ck"): continue