diff --git a/capa/ida/helpers/__init__.py b/capa/ida/helpers/__init__.py index 78f0d93e..be6402e3 100644 --- a/capa/ida/helpers/__init__.py +++ b/capa/ida/helpers/__init__.py @@ -1,6 +1,36 @@ +import logging + import idaapi import idc +logger = logging.getLogger('capa') + +# file type names as returned by idaapi.get_file_type_name() +SUPPORTED_FILE_TYPES = [ + 'Portable executable for 80386 (PE)', + 'Portable executable for AMD64 (PE)', + 'Binary file' # x86/AMD64 shellcode support +] + + +def inform_user_ida_ui(message): + idaapi.info('%s. Please refer to IDA Output window for more information.' % message) + + +def is_supported_file_type(): + file_type = idaapi.get_file_type_name() + if file_type not in SUPPORTED_FILE_TYPES: + logger.error('-' * 80) + logger.error(' Input file does not appear to be a PE file.') + logger.error(' ') + logger.error( + ' capa currently only supports analyzing PE files (or binary files containing x86/AMD64 shellcode) with IDA.') + logger.error(' If you don\'t know the input file type, you can try using the `file` utility to guess it.') + logger.error('-' * 80) + inform_user_ida_ui('capa does not support the format of this file') + return False + return True + def get_disasm_line(va): ''' ''' diff --git a/capa/ida/ida_capa_explorer.py b/capa/ida/ida_capa_explorer.py index d40c7ab8..c322a274 100644 --- a/capa/ida/ida_capa_explorer.py +++ b/capa/ida/ida_capa_explorer.py @@ -26,16 +26,15 @@ import idaapi import capa.main import capa.rules import capa.features.extractors.ida +import capa.ida.helpers from capa.ida.explorer.view import CapaExplorerQtreeView from capa.ida.explorer.model import CapaExplorerDataModel from capa.ida.explorer.proxy import CapaExplorerSortFilterProxyModel +PLUGIN_NAME = 'capa explorer' -PLUGIN_NAME = 'capaex' - - -logger = logging.getLogger(PLUGIN_NAME) +logger = logging.getLogger('capa') class CapaExplorerIdaHooks(idaapi.UI_Hooks): @@ -326,12 +325,31 @@ class CapaExplorerForm(idaapi.PluginForm): rules_path = os.path.join(os.path.dirname(self._file_loc), '../..', 'rules') rules = capa.main.get_rules(rules_path) rules = capa.rules.RuleSet(rules) - results = capa.main.find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor(), True) + capabilities = capa.main.find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor(), True) + + # support binary files specifically for x86/AMD64 shellcode + # warn user binary file is loaded but still allow capa to process it + # TODO: check specific architecture of binary files based on how user configured IDA processors + if idaapi.get_file_type_name() == 'Binary file': + logger.warning('-' * 80) + logger.warning(' Input file appears to be a binary file.') + logger.warning(' ') + logger.warning( + ' capa currently only supports analyzing binary files containing x86/AMD64 shellcode with IDA.') + logger.warning( + ' This means the results may be misleading or incomplete if the binary file loaded in IDA is not x86/AMD64.') + logger.warning(' If you don\'t know the input file type, you can try using the `file` utility to guess it.') + logger.warning('-' * 80) + + capa.ida.helpers.inform_user_ida_ui('capa encountered warnings during analysis') + + if capa.main.is_file_limitation(rules, capabilities, is_standalone=False): + capa.ida.helpers.inform_user_ida_ui('capa encountered warnings during analysis') logger.info('analysis completed.') - self._model_data.render_capa_results(rules, results) - self._render_capa_summary(rules, results) + self._model_data.render_capa_results(rules, capabilities) + self._render_capa_summary(rules, capabilities) logger.info('render views completed.') @@ -441,6 +459,9 @@ def main(): ''' TODO: move to idaapi.plugin_t class ''' logging.basicConfig(level=logging.INFO) + if not capa.ida.helpers.is_supported_file_type(): + return -1 + global CAPA_EXPLORER_FORM try: diff --git a/capa/main.py b/capa/main.py index 33d91aac..093ebe6d 100644 --- a/capa/main.py +++ b/capa/main.py @@ -409,6 +409,60 @@ def appears_rule_cat(rules, capabilities, rule_cat): return False +def is_file_limitation(rules, capabilities, is_standalone=True): + file_limitations = { + # capa will likely detect installer specific functionality. + # this is probably not what the user wants. + 'other-features/installer/': [ + ' This sample appears to be an installer.', + ' ', + ' capa cannot handle installers well. This means the results may be misleading or incomplete.' + ' You should try to understand the install mechanism and analyze created files with capa.' + ], + # capa won't detect much in .NET samples. + # it might match some file-level things. + # for consistency, bail on things that we don't support. + 'other-features/compiled-to-dot-net': [ + ' This sample appears to be a .NET module.', + ' ', + ' .NET is a cross-platform framework for running managed applications.', + ' capa cannot handle non-native files. This means that the results may be misleading or incomplete.', + ' You may have to analyze the file manually, using a tool like the .NET decompiler dnSpy.' + ], + # capa will detect dozens of capabilities for AutoIt samples, + # but these are due to the AutoIt runtime, not the payload script. + # so, don't confuse the user with FP matches - bail instead + 'other-features/compiled-with-autoit': [ + ' This sample appears to be compiled with AutoIt.', + ' ', + ' AutoIt is a freeware BASIC-like scripting language designed for automating the Windows GUI.', + ' capa cannot handle AutoIt scripts. This means that the results will be misleading or incomplete.', + ' You may have to analyze the file manually, using a tool like the AutoIt decompiler MyAut2Exe.' + ], + # capa won't detect much in packed samples + 'anti-analysis/packing/': [ + ' This sample appears to be packed.', + ' ', + ' Packed samples have often been obfuscated to hide their logic.', + ' capa cannot handle obfuscation well. This means the results may be misleading or incomplete.', + ' If possible, you should try to unpack this input file before analyzing it with capa.' + ] + } + + for category, dialogue in file_limitations.items(): + if not appears_rule_cat(rules, capabilities, category): + continue + logger.warning('-' * 80) + for line in dialogue: + logger.warning(line) + if is_standalone: + logger.warning(' ') + logger.warning(' Use -v or -vv if you really want to see the capabilities identified by capa.') + logger.warning('-' * 80) + return True + return False + + def is_supported_file_type(sample): ''' Return if this is a supported file based on magic header values @@ -662,70 +716,12 @@ def main(argv=None): capabilities = find_capabilities(rules, extractor) - if appears_rule_cat(rules, capabilities, 'other-features/installer/'): - logger.warning('-' * 80) - logger.warning(' This sample appears to be an installer.') - logger.warning(' ') - logger.warning(' capa cannot handle installers well. This means the results may be misleading or incomplete.') - logger.warning(' You should try to understand the install mechanism and analyze created files with capa.') - logger.warning(' ') - logger.warning(' Use -v or -vv if you really want to see the capabilities identified by capa.') - logger.warning('-' * 80) - # capa will likely detect installer specific functionality. - # this is probably not what the user wants. - # + if is_file_limitation(rules, capabilities): + # bail if capa encountered file limitation e.g. a packed binary # do show the output in verbose mode, though. if not (args.verbose or args.vverbose): return -1 - if appears_rule_cat(rules, capabilities, 'other-features/compiled-to-dot-net'): - logger.warning('-' * 80) - logger.warning(' This sample appears to be a .NET module.') - logger.warning(' ') - logger.warning(' .NET is a cross-platform framework for running managed applications.') - logger.warning( - ' capa cannot handle non-native files. This means that the results may be misleading or incomplete.') - logger.warning(' You may have to analyze the file manually, using a tool like the .NET decompiler dnSpy.') - logger.warning(' ') - logger.warning(' Use -v or -vv if you really want to see the capabilities identified by capa.') - logger.warning('-' * 80) - - # capa won't detect much in .NET samples. - # it might match some file-level things. - # for consistency, bail on things that we don't support. - # - # do show the output in verbose mode, though. - if not (args.verbose or args.vverbose): - return -1 - - if appears_rule_cat(rules, capabilities, 'other-features/compiled-with-autoit'): - logger.warning('-' * 80) - logger.warning(' This sample appears to be compiled with AutoIt.') - logger.warning(' ') - logger.warning(' AutoIt is a freeware BASIC-like scripting language designed for automating the Windows GUI.') - logger.warning( - ' capa cannot handle AutoIt scripts. This means that the results will be misleading or incomplete.') - logger.warning(' You may have to analyze the file manually, using a tool like the AutoIt decompiler MyAut2Exe.') - logger.warning(' ') - logger.warning(' Use -v or -vv if you really want to see the capabilities identified by capa.') - logger.warning('-' * 80) - # capa will detect dozens of capabilities for AutoIt samples, - # but these are due to the AutoIt runtime, not the payload script. - # so, don't confuse the user with FP matches - bail instead - # - # do show the output in verbose mode, though. - if not (args.verbose or args.vverbose): - return -1 - - if appears_rule_cat(rules, capabilities, 'anti-analysis/packing/'): - logger.warning('-' * 80) - logger.warning(' This sample appears packed.') - logger.warning(' ') - logger.warning(' Packed samples have often been obfuscated to hide their logic.') - logger.warning(' capa cannot handle obfuscation well. This means the results may be misleading or incomplete.') - logger.warning(' If possible, you should try to unpack this input file before analyzing it with capa.') - logger.warning('-' * 80) - if args.vverbose: render_capabilities_vverbose(rules, capabilities) elif args.verbose: @@ -742,6 +738,10 @@ def ida_main(): logging.basicConfig(level=logging.INFO) logging.getLogger().setLevel(logging.INFO) + import capa.ida.helpers + if not capa.ida.helpers.is_supported_file_type(): + return -1 + logger.info('-' * 80) logger.info(' Using default embedded rules.') logger.info(' ') @@ -764,6 +764,10 @@ def ida_main(): import capa.features.extractors.ida capabilities = find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor()) + + if is_file_limitation(rules, capabilities, is_standalone=False): + capa.ida.helpers.inform_user_ida_ui('capa encountered warnings during analysis') + render_capabilities_default(rules, capabilities)