diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index d7e294fc..23c66fd6 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -32,24 +32,33 @@ settings = ida_settings.IDASettings("capa") class UserCancelledError(Exception): + """throw exception when user cancels action""" pass class CapaExplorerProgressIndicator(QtCore.QObject): + """implement progress signal, used during feature extraction""" progress = QtCore.pyqtSignal(str) def __init__(self): - """ """ + """initialize signal object""" super(CapaExplorerProgressIndicator, self).__init__() def update(self, text): - """ """ + """emit progress update + + check if user cancelled action, raise exception for parent function to catch + """ if ida_kernwin.user_cancelled(): raise UserCancelledError("user cancelled") self.progress.emit("extracting features from %s" % text) class CapaExplorerFeatureExtractor(capa.features.extractors.ida.IdaFeatureExtractor): + """subclass the IdaFeatureExtractor + + track progress during feature extraction, also allow user to cancel feature extraction + """ def __init__(self, progress): super(CapaExplorerFeatureExtractor, self).__init__() self.progress = progress @@ -77,6 +86,8 @@ class CapaExplorerForm(idaapi.PluginForm): self.form_title = name self.rule_path = "" + self.process_total = 0 + self.process_count = 0 self.parent = None self.ida_hooks = None @@ -396,21 +407,26 @@ class CapaExplorerForm(idaapi.PluginForm): self.slot_analyze() def load_capa_results(self): - """run capa analysis and render results in UI""" + """run capa analysis and render results in UI + + note: this function must always return, exception or not, in order for plugin to safely close the IDA + wait box + """ # new analysis, new doc self.doc = None self.process_total = 0 self.process_count = 0 + # default from view self.set_view_status_label("No rules loaded") self.disable_controls() def update_wait_box(text): - """ """ + """update the IDA wait box""" ida_kernwin.replace_wait_box("Processing; %s" % text) def slot_progress_feature_extraction(text): - """ """ + """slot function to handle feature extraction progress updates""" update_wait_box("%s (%d/%d)" % (text, self.process_count, self.process_total)) self.process_count += 1 @@ -423,7 +439,7 @@ class CapaExplorerForm(idaapi.PluginForm): try: self.process_total += len(tuple(extractor.get_functions())) except Exception as e: - logger.error("Failed to calculate analysis (Error: %s)." % e) + logger.error("Failed to calculate analysis (error: %s)." % e) return if ida_kernwin.user_cancelled(): @@ -448,7 +464,7 @@ class CapaExplorerForm(idaapi.PluginForm): self.rule_path = rule_path settings.user["rule_path"] = rule_path except Exception as e: - logger.error("Failed to load capa rules (Error: %s)." % e) + logger.error("Failed to load capa rules (error: %s)." % e) return if ida_kernwin.user_cancelled(): @@ -504,7 +520,7 @@ class CapaExplorerForm(idaapi.PluginForm): return except Exception as e: capa.ida.helpers.inform_user_ida_ui("Failed to load capa rules from %s" % self.rule_path) - logger.error("Failed to load rules from %s (Error: %s).", self.rule_path, e) + logger.error("Failed to load rules from %s (error: %s).", self.rule_path, e) logger.error("Make sure your file directory contains properly formatted capa rules. You can download the standard collection of capa rules from https://github.com/fireeye/capa-rules.") self.rule_path = "" settings.user.del_value("rule_path") @@ -514,7 +530,7 @@ class CapaExplorerForm(idaapi.PluginForm): logger.info("User cancelled analysis.") return - ida_kernwin.replace_wait_box("Processing; extracting features") + update_wait_box("extracting features") try: meta = capa.ida.helpers.collect_metadata() @@ -526,10 +542,10 @@ class CapaExplorerForm(idaapi.PluginForm): logger.info("User cancelled analysis.") return except Exception as e: - logger.error("Failed to extract capabilities from database (Error: %s)" % e) + logger.error("Failed to extract capabilities from database (error: %s)" % e) return - ida_kernwin.replace_wait_box("Processing; checking for file limitations") + update_wait_box("checking for file limitations") try: # support binary files specifically for x86/AMD64 shellcode @@ -553,7 +569,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 file limitation warnings during analysis") except Exception as e: - logger.error("Failed to check for file limitations (Error: %s)" % e) + logger.error("Failed to check for file limitations (error: %s)" % e) return if ida_kernwin.user_cancelled(): @@ -567,7 +583,7 @@ class CapaExplorerForm(idaapi.PluginForm): self.model_data.render_capa_doc(self.doc) self.render_capa_doc_mitre_summary() except Exception as e: - logger.error("Failed to render results (Error: %s)" % e) + logger.error("Failed to render results (error: %s)" % e) return self.enable_controls()