Commit 3ac3fd00d6be623db64e6e3548a2feb5733fb060

Authored by Christian Herdtweck
1 parent 989984d9

added option --no-deobfuscate and argument skip_deobufscate to many functions to…

… avoid call to slow detect_vba_strings
Showing 1 changed file with 34 additions and 16 deletions
oletools/olevba.py
... ... @@ -1687,13 +1687,14 @@ class VBA_Scanner(object):
1687 1687 self.vba_strings = None
1688 1688  
1689 1689  
1690   - def scan(self, include_decoded_strings=False):
  1690 + def scan(self, include_decoded_strings=False, skip_deobfuscate=False):
1691 1691 """
1692 1692 Analyze the provided VBA code to detect suspicious keywords,
1693 1693 auto-executable macros, IOC patterns, obfuscation patterns
1694 1694 such as hex-encoded strings.
1695 1695  
1696 1696 :param include_decoded_strings: bool, if True, all encoded strings will be included with their decoded content.
  1697 + :param skip_deobfuscate: bool, if True do not try to deobfuscate code (faster but less secure)
1697 1698 :return: list of tuples (type, keyword, description)
1698 1699 (type = 'AutoExec', 'Suspicious', 'IOC', 'Hex String', 'Base64 String' or 'Dridex String')
1699 1700 """
... ... @@ -1722,7 +1723,10 @@ class VBA_Scanner(object):
1722 1723 for encoded, decoded in self.dridex_strings:
1723 1724 self.code_dridex += '\n' + decoded
1724 1725 # Detect obfuscated strings in VBA expressions
1725   - self.vba_strings = detect_vba_strings(self.code)
  1726 + if skip_deobfuscate:
  1727 + self.vba_strings = []
  1728 + else:
  1729 + self.vba_strings = detect_vba_strings(self.code)
1726 1730 for encoded, decoded in self.vba_strings:
1727 1731 self.code_vba += '\n' + decoded
1728 1732 results = []
... ... @@ -1806,7 +1810,7 @@ class VBA_Scanner(object):
1806 1810 len(self.dridex_strings), len(self.vba_strings))
1807 1811  
1808 1812  
1809   -def scan_vba(vba_code, include_decoded_strings):
  1813 +def scan_vba(vba_code, include_decoded_strings, skip_deobfuscate=False):
1810 1814 """
1811 1815 Analyze the provided VBA code to detect suspicious keywords,
1812 1816 auto-executable macros, IOC patterns, obfuscation patterns
... ... @@ -1815,10 +1819,11 @@ def scan_vba(vba_code, include_decoded_strings):
1815 1819  
1816 1820 :param vba_code: str, VBA source code to be analyzed
1817 1821 :param include_decoded_strings: bool, if True all encoded strings will be included with their decoded content.
  1822 + :param skip_deobfuscate: do not deobfuscate code; much faster but less secure
1818 1823 :return: list of tuples (type, keyword, description)
1819 1824 (type = 'AutoExec', 'Suspicious', 'IOC', 'Hex String', 'Base64 String' or 'Dridex String')
1820 1825 """
1821   - return VBA_Scanner(vba_code).scan(include_decoded_strings)
  1826 + return VBA_Scanner(vba_code).scan(include_decoded_strings, skip_deobfuscate)
1822 1827  
1823 1828  
1824 1829 #=== CLASSES =================================================================
... ... @@ -2294,7 +2299,7 @@ class VBA_Parser(object):
2294 2299  
2295 2300  
2296 2301  
2297   - def analyze_macros(self, show_decoded_strings=False):
  2302 + def analyze_macros(self, show_decoded_strings=False, skip_deobfuscate=False):
2298 2303 """
2299 2304 runs extract_macros and analyze the source code of all VBA macros
2300 2305 found in the file.
... ... @@ -2313,7 +2318,7 @@ class VBA_Parser(object):
2313 2318 self.vba_code_all_modules += form_string + '\n'
2314 2319 # Analyze the whole code at once:
2315 2320 scanner = VBA_Scanner(self.vba_code_all_modules)
2316   - self.analysis_results = scanner.scan(show_decoded_strings)
  2321 + self.analysis_results = scanner.scan(show_decoded_strings, skip_deobfuscate)
2317 2322 autoexec, suspicious, iocs, hexstrings, base64strings, dridex, vbastrings = scanner.scan_summary()
2318 2323 self.nb_autoexec += autoexec
2319 2324 self.nb_suspicious += suspicious
... ... @@ -2476,19 +2481,20 @@ class VBA_Parser_CLI(VBA_Parser):
2476 2481 pass
2477 2482  
2478 2483  
2479   - def print_analysis(self, show_decoded_strings=False):
  2484 + def print_analysis(self, show_decoded_strings=False, skip_deobfuscate=False):
2480 2485 """
2481 2486 Analyze the provided VBA code, and print the results in a table
2482 2487  
2483 2488 :param vba_code: str, VBA source code to be analyzed
2484 2489 :param show_decoded_strings: bool, if True hex-encoded strings will be displayed with their decoded content.
  2490 + :param skip_deobfuscate: bool, if True do not try to deobfuscate code (faster but less secure)
2485 2491 :return: None
2486 2492 """
2487 2493 # print a waiting message only if the output is not redirected to a file:
2488 2494 if sys.stdout.isatty():
2489 2495 print 'Analysis...\r',
2490 2496 sys.stdout.flush()
2491   - results = self.analyze_macros(show_decoded_strings)
  2497 + results = self.analyze_macros(show_decoded_strings, skip_deobfuscate)
2492 2498 if results:
2493 2499 t = prettytable.PrettyTable(('Type', 'Keyword', 'Description'))
2494 2500 t.align = 'l'
... ... @@ -2509,7 +2515,8 @@ class VBA_Parser_CLI(VBA_Parser):
2509 2515  
2510 2516 def process_file(self, show_decoded_strings=False,
2511 2517 display_code=True, global_analysis=True, hide_attributes=True,
2512   - vba_code_only=False, show_deobfuscated_code=False):
  2518 + vba_code_only=False, show_deobfuscated_code=False,
  2519 + skip_deobfuscate=False):
2513 2520 """
2514 2521 Process a single file
2515 2522  
... ... @@ -2520,6 +2527,7 @@ class VBA_Parser_CLI(VBA_Parser):
2520 2527 :param global_analysis: bool, if True all modules are merged for a single analysis (default),
2521 2528 otherwise each module is analyzed separately (old behaviour)
2522 2529 :param hide_attributes: bool, if True the first lines starting with "Attribute VB" are hidden (default)
  2530 + :param skip_deobfuscate: bool, if True do not try to deobfuscate code (faster but less secure)
2523 2531 """
2524 2532 #TODO: replace print by writing to a provided output file (sys.stdout by default)
2525 2533 # fix conflicting parameters:
... ... @@ -2558,7 +2566,7 @@ class VBA_Parser_CLI(VBA_Parser):
2558 2566 print '- ' * 39
2559 2567 print 'ANALYSIS:'
2560 2568 # analyse each module's code, filtered to avoid false positives:
2561   - self.print_analysis(show_decoded_strings)
  2569 + self.print_analysis(show_decoded_strings, skip_deobfuscate)
2562 2570 for (subfilename, stream_path, form_string) in self.extract_form_strings():
2563 2571 print '-' * 79
2564 2572 print 'VBA FORM STRING IN %r - OLE stream: %r' % (subfilename, stream_path)
... ... @@ -2566,7 +2574,7 @@ class VBA_Parser_CLI(VBA_Parser):
2566 2574 print form_string
2567 2575 if global_analysis and not vba_code_only:
2568 2576 # analyse the code from all modules at once:
2569   - self.print_analysis(show_decoded_strings)
  2577 + self.print_analysis(show_decoded_strings, skip_deobfuscate)
2570 2578 if show_deobfuscated_code:
2571 2579 print 'MACRO SOURCE CODE WITH DEOBFUSCATED VBA STRINGS (EXPERIMENTAL):\n\n'
2572 2580 print self.reveal()
... ... @@ -2584,7 +2592,7 @@ class VBA_Parser_CLI(VBA_Parser):
2584 2592 print ''
2585 2593  
2586 2594  
2587   - def process_file_triage(self, show_decoded_strings=False):
  2595 + def process_file_triage(self, show_decoded_strings=False, skip_deobfuscate=False):
2588 2596 """
2589 2597 Process a file in triage mode, showing only summary results on one line.
2590 2598 """
... ... @@ -2598,7 +2606,8 @@ class VBA_Parser_CLI(VBA_Parser):
2598 2606 if sys.stdout.isatty():
2599 2607 print 'Analysis...\r',
2600 2608 sys.stdout.flush()
2601   - self.analyze_macros(show_decoded_strings=show_decoded_strings)
  2609 + self.analyze_macros(show_decoded_strings=show_decoded_strings,
  2610 + skip_deobfuscate=skip_deobfuscate)
2602 2611 flags = TYPE2TAG[self.type]
2603 2612 macros = autoexec = suspicious = iocs = hexstrings = base64obf = dridex = vba_obf = '-'
2604 2613 if self.contains_macros: macros = 'M'
... ... @@ -2698,6 +2707,8 @@ def main():
2698 2707 help='display the macro source code after replacing all the obfuscated strings by their decoded content.')
2699 2708 parser.add_option('-l', '--loglevel', dest="loglevel", action="store", default=DEFAULT_LOG_LEVEL,
2700 2709 help="logging level debug/info/warning/error/critical (default=%default)")
  2710 + parser.add_option('-n', '--no-deobfuscate', dest="skip_deobfuscate", action="store_true", default=False,
  2711 + help="skip deobfuscation (much faster but less secure)")
2701 2712  
2702 2713 # Disabled options:
2703 2714 # parser.add_option("--each", action="store_false", dest="global_analysis", default=True,
... ... @@ -2729,12 +2740,16 @@ def main():
2729 2740 # print 'Analysis of VBA source code from %s:' % options.input
2730 2741 # vba_code = open(options.input).read()
2731 2742 # print_analysis(vba_code, show_decoded_strings=options.show_decoded_strings)
  2743 + # skip_deobfuscate=options.skip_deobfuscate)
2732 2744 # sys.exit()
2733 2745  
2734 2746 # Old display with number of items detected:
2735 2747 # print '%-8s %-7s %-7s %-7s %-7s %-7s' % ('Type', 'Macros', 'AutoEx', 'Susp.', 'IOCs', 'HexStr')
2736 2748 # print '%-8s %-7s %-7s %-7s %-7s %-7s' % ('-'*8, '-'*7, '-'*7, '-'*7, '-'*7, '-'*7)
2737 2749  
  2750 + if options.skip_deobfuscate and options.show_deobfuscated_code:
  2751 + logging.warning('Ignoring option --reveal since option -n / --no-deobfuscate is present!')
  2752 +
2738 2753 # Column headers (except if detailed mode)
2739 2754 if not options.detailed_mode or options.triage_mode:
2740 2755 print '%-12s %-65s' % ('Flags', 'Filename')
... ... @@ -2756,7 +2771,8 @@ def main():
2756 2771 vba_parser.process_file(show_decoded_strings=options.show_decoded_strings,
2757 2772 display_code=options.display_code, global_analysis=True, #options.global_analysis,
2758 2773 hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only,
2759   - show_deobfuscated_code=options.show_deobfuscated_code)
  2774 + show_deobfuscated_code=options.show_deobfuscated_code,
  2775 + skip_deobfuscate=options.skip_deobfuscate)
2760 2776 else:
2761 2777 # print container name when it changes:
2762 2778 if container != previous_container:
... ... @@ -2764,7 +2780,8 @@ def main():
2764 2780 print '\nFiles in %s:' % container
2765 2781 previous_container = container
2766 2782 # summarized output for triage:
2767   - vba_parser.process_file_triage(show_decoded_strings=options.show_decoded_strings)
  2783 + vba_parser.process_file_triage(show_decoded_strings=options.show_decoded_strings,
  2784 + skip_deobfuscate=options.skip_deobfuscate)
2768 2785 count += 1
2769 2786 if not options.detailed_mode or options.triage_mode:
2770 2787 print '\n(Flags: OpX=OpenXML, XML=Word2003XML, MHT=MHTML, TXT=Text, M=Macros, ' \
... ... @@ -2776,7 +2793,8 @@ def main():
2776 2793 vba_parser.process_file(show_decoded_strings=options.show_decoded_strings,
2777 2794 display_code=options.display_code, global_analysis=True, #options.global_analysis,
2778 2795 hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only,
2779   - show_deobfuscated_code=options.show_deobfuscated_code)
  2796 + show_deobfuscated_code=options.show_deobfuscated_code,
  2797 + skip_deobfuscate=options.skip_deobfuscate)
2780 2798  
2781 2799  
2782 2800 if __name__ == '__main__':
... ...