From 98ed862d3cb0770f5dfca0825155f6b90bde85a3 Mon Sep 17 00:00:00 2001 From: Moritz Raabe Date: Tue, 1 Sep 2020 23:01:27 +0200 Subject: [PATCH] add form icon and other cosmetic changes --- capa/ida/plugin/__init__.py | 20 ++++++++--------- capa/ida/plugin/form.py | 36 +++++++++++++++++------------- capa/ida/plugin/img/capa_icon.png | Bin 0 -> 15024 bytes tests/data | 2 +- 4 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 capa/ida/plugin/img/capa_icon.png diff --git a/capa/ida/plugin/__init__.py b/capa/ida/plugin/__init__.py index d75cfc1e..da64ece2 100644 --- a/capa/ida/plugin/__init__.py +++ b/capa/ida/plugin/__init__.py @@ -13,22 +13,22 @@ import idaapi from capa.ida.helpers import is_supported_file_type, is_supported_ida_version from capa.ida.plugin.form import CapaExplorerForm -logger = logging.getLogger("capa") +logger = logging.getLogger(__name__) class CapaExplorerPlugin(idaapi.plugin_t): # Mandatory definitions - PLUGIN_NAME = "capa explorer" + PLUGIN_NAME = "FLARE capa plugin" PLUGIN_VERSION = "1.0.0" - PLUGIN_AUTHORS = "" + PLUGIN_AUTHORS = "michael.hunhoff@mandiant.com, william.ballenthin@mandiant.com, moritz.raabe@mandiant.com" wanted_name = PLUGIN_NAME - comment = "IDA plugin for capa analysis framework" + wanted_hotkey = "ALT-F5" + comment = "IDA Pro plugin for the FLARE team's capa tool to identify capabilities in executable files." + website = "https://github.com/fireeye/capa" + help = "See https://github.com/fireeye/capa/blob/master/doc/usage.md" version = "" - website = "" - help = "" - wanted_hotkey = "" flags = 0 def __init__(self): @@ -41,13 +41,13 @@ class CapaExplorerPlugin(idaapi.plugin_t): """ logging.basicConfig(level=logging.INFO) - # check IDA version and database compat + # check IDA version and database compatibility if not is_supported_ida_version(): return idaapi.PLUGIN_SKIP if not is_supported_file_type(): return idaapi.PLUGIN_SKIP - logger.info("plugin initialized.") + logger.debug("plugin initialized") return idaapi.PLUGIN_KEEP @@ -55,7 +55,7 @@ class CapaExplorerPlugin(idaapi.plugin_t): """ called when IDA is unloading the plugin """ - logger.info("plugin closed.") + logger.debug("plugin terminated") def run(self, arg): """ diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index 26498370..fb7fe972 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -24,7 +24,10 @@ from capa.ida.plugin.hooks import CapaExplorerIdaHooks from capa.ida.plugin.model import CapaExplorerDataModel from capa.ida.plugin.proxy import CapaExplorerSortFilterProxyModel -logger = logging.getLogger("capa") +logger = logging.getLogger(__name__) + + +ICON_PATH = os.path.join(os.path.dirname(__file__), "img", "capa_icon.png") class CapaExplorerForm(idaapi.PluginForm): @@ -54,17 +57,18 @@ class CapaExplorerForm(idaapi.PluginForm): def OnCreate(self, form): """ """ self.parent = self.FormToPyQtWidget(form) + self.parent.setWindowIcon(QtGui.QIcon(ICON_PATH)) self.load_interface() self.load_capa_results() self.load_ida_hooks() self.view_tree.reset() - logger.info("form created.") + logger.debug("form created") def Show(self): """ """ - logger.info("form show.") + logger.debug("form show") return idaapi.PluginForm.Show( self, self.form_title, options=(idaapi.PluginForm.WOPN_TAB | idaapi.PluginForm.WCLS_CLOSE_LATER) ) @@ -73,7 +77,7 @@ class CapaExplorerForm(idaapi.PluginForm): """ form is closed """ self.unload_ida_hooks() self.ida_reset() - logger.info("form closed.") + logger.debug("form closed") def load_interface(self): """ load user interface """ @@ -310,19 +314,19 @@ class CapaExplorerForm(idaapi.PluginForm): return self.rule_path = rule_path - logger.info("-" * 80) - logger.info(" Using rules from %s." % self.rule_path) - logger.info(" ") - logger.info(" You can see the current default rule set here:") - logger.info(" https://github.com/fireeye/capa-rules") - logger.info("-" * 80) + logger.debug("-" * 80) + logger.debug(" Using rules from %s.", self.rule_path) + logger.debug(" ") + logger.debug(" You can see the current default rule set here:") + logger.debug(" https://github.com/fireeye/capa-rules") + logger.debug("-" * 80) try: rules = capa.main.get_rules(self.rule_path) rules = capa.rules.RuleSet(rules) except (IOError, capa.rules.InvalidRule, capa.rules.InvalidRuleSet) as e: capa.ida.helpers.inform_user_ida_ui("Failed to load rules from %s" % self.rule_path) - logger.error("failed to load rules from %s (%s)" % (self.rule_path, e)) + logger.error("failed to load rules from %s (%s)", self.rule_path, e) self.rule_path = "" return @@ -354,7 +358,7 @@ class CapaExplorerForm(idaapi.PluginForm): if capa.main.has_file_limitation(rules, capabilities, is_standalone=False): capa.ida.helpers.inform_user_ida_ui("capa encountered warnings during analysis") - logger.info("analysis completed.") + logger.debug("analysis completed.") self.doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities) @@ -364,7 +368,7 @@ class CapaExplorerForm(idaapi.PluginForm): self.set_view_tree_default_sort_order() - logger.info("render views completed.") + logger.debug("render views completed.") def set_view_tree_default_sort_order(self): """ set capa tree view default sort order """ @@ -462,7 +466,7 @@ class CapaExplorerForm(idaapi.PluginForm): self.view_summary.setRowCount(0) self.load_capa_results() - logger.info("reload complete.") + logger.debug("%s reload completed", self.form_title) idaapi.info("%s reload completed." % self.form_title) def reset(self, checked): @@ -472,8 +476,8 @@ class CapaExplorerForm(idaapi.PluginForm): """ self.ida_reset() - logger.info("reset completed.") - idaapi.info("%s reset completed." % self.form_title) + logger.debug("%s reset completed", self.form_title) + idaapi.info("%s reset completed" % self.form_title) def slot_menu_bar_hovered(self, action): """display menu action tooltip diff --git a/capa/ida/plugin/img/capa_icon.png b/capa/ida/plugin/img/capa_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ae38674b16a8ecea865bb3f6dcd4c76e570deb46 GIT binary patch literal 15024 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 literal 0 HcmV?d00001 diff --git a/tests/data b/tests/data index c3a35d4b..afd4177a 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit c3a35d4b6430ed61ffef59d672a2a8b6061e23fe +Subproject commit afd4177aa9da0daefaee69f5cbc4a86fcc072a4d