Commit 3cae86e62d99a29858721ca6f99c8c2cf15be8e4
1 parent
e28b2001
olevba: if XLMMacroDeobfuscator is available, use it to extract and deobfuscate XLM macros
Showing
1 changed file
with
196 additions
and
80 deletions
oletools/olevba.py
| ... | ... | @@ -235,7 +235,7 @@ from __future__ import print_function |
| 235 | 235 | # for issue #619) |
| 236 | 236 | # 2021-04-14 PL: - added detection of Workbook_BeforeClose (issue #518) |
| 237 | 237 | |
| 238 | -__version__ = '0.56.2' | |
| 238 | +__version__ = '0.60.dev2' | |
| 239 | 239 | |
| 240 | 240 | #------------------------------------------------------------------------------ |
| 241 | 241 | # TODO: |
| ... | ... | @@ -310,6 +310,18 @@ import colorclass |
| 310 | 310 | if os.name == 'nt': |
| 311 | 311 | colorclass.Windows.enable(auto_colors=True) |
| 312 | 312 | |
| 313 | +from pyparsing import \ | |
| 314 | + CaselessKeyword, CaselessLiteral, Combine, Forward, Literal, \ | |
| 315 | + Optional, QuotedString,Regex, Suppress, Word, WordStart, \ | |
| 316 | + alphanums, alphas, hexnums,nums, opAssoc, srange, \ | |
| 317 | + infixNotation, ParserElement | |
| 318 | + | |
| 319 | +# attempt to import XLMMacroDeobfuscator (optional) | |
| 320 | +try: | |
| 321 | + from XLMMacroDeobfuscator import deobfuscator as xlmdeobfuscator | |
| 322 | + XLMDEOBFUSCATOR = True | |
| 323 | +except ImportError: | |
| 324 | + XLMDEOBFUSCATOR = False | |
| 313 | 325 | |
| 314 | 326 | # IMPORTANT: it should be possible to run oletools directly as scripts |
| 315 | 327 | # in any directory without installing them with pip or setup.py. |
| ... | ... | @@ -326,17 +338,14 @@ if _parent_dir not in sys.path: |
| 326 | 338 | import olefile |
| 327 | 339 | from oletools.thirdparty.tablestream import tablestream |
| 328 | 340 | from oletools.thirdparty.xglob import xglob, PathNotFoundException |
| 329 | -from pyparsing import \ | |
| 330 | - CaselessKeyword, CaselessLiteral, Combine, Forward, Literal, \ | |
| 331 | - Optional, QuotedString,Regex, Suppress, Word, WordStart, \ | |
| 332 | - alphanums, alphas, hexnums,nums, opAssoc, srange, \ | |
| 333 | - infixNotation, ParserElement | |
| 341 | +from oletools.thirdparty.oledump.plugin_biff import cBIFF | |
| 334 | 342 | from oletools import ppt_parser |
| 335 | 343 | from oletools import oleform |
| 336 | 344 | from oletools import rtfobj |
| 337 | 345 | from oletools import crypto |
| 338 | 346 | from oletools.common.io_encoding import ensure_stdout_handles_unicode |
| 339 | 347 | from oletools.common import codepages |
| 348 | +from oletools import ftguess | |
| 340 | 349 | |
| 341 | 350 | # === PYTHON 2+3 SUPPORT ====================================================== |
| 342 | 351 | |
| ... | ... | @@ -2710,7 +2719,8 @@ class VBA_Parser(object): |
| 2710 | 2719 | self.type = None |
| 2711 | 2720 | self.vba_projects = None |
| 2712 | 2721 | self.vba_forms = None |
| 2713 | - self.contains_macros = None # will be set to True or False by detect_macros | |
| 2722 | + self.contains_vba_macros = None # will be set to True or False by detect_vba_macros | |
| 2723 | + self.contains_xlm_macros = None # will be set to True or False by detect_xlm_macros | |
| 2714 | 2724 | self.vba_code_all_modules = None # to store the source code of all modules |
| 2715 | 2725 | # list of tuples for each module: (subfilename, stream_path, vba_filename, vba_code) |
| 2716 | 2726 | self.modules = None |
| ... | ... | @@ -2736,9 +2746,13 @@ class VBA_Parser(object): |
| 2736 | 2746 | self.vba_stomping_detected = None |
| 2737 | 2747 | # will be set to True or False by detect_is_encrypted method |
| 2738 | 2748 | self.is_encrypted = False |
| 2749 | + # TODO: those are disabled for now: | |
| 2739 | 2750 | self.xlm_macrosheet_found = False |
| 2740 | 2751 | self.template_injection_found = False |
| 2741 | 2752 | |
| 2753 | + # call ftguess to identify file type: | |
| 2754 | + self.ftg = ftguess.FileTypeGuesser(self.filename, data=data) | |
| 2755 | + log.debug('ftguess: file type=%s - container=%s' % (self.ftg.ftype.name, self.ftg.container)) | |
| 2742 | 2756 | # if filename is None: |
| 2743 | 2757 | # if isinstance(_file, basestring): |
| 2744 | 2758 | # if len(_file) < olefile.MINIMAL_OLEFILE_SIZE: |
| ... | ... | @@ -2752,7 +2766,7 @@ class VBA_Parser(object): |
| 2752 | 2766 | self.open_ole(_file) |
| 2753 | 2767 | |
| 2754 | 2768 | # if this worked, try whether it is a ppt file (special ole file) |
| 2755 | - # TODO: instead of this we should have a function to test if it is a PPT | |
| 2769 | + # TODO: instead of this we should have a function to test if it is a PPT (e.g. using ftguess) | |
| 2756 | 2770 | self.open_ppt() |
| 2757 | 2771 | if self.type is None and zipfile.is_zipfile(_file): |
| 2758 | 2772 | # Zip file, which may be an OpenXML document |
| ... | ... | @@ -2840,55 +2854,55 @@ class VBA_Parser(object): |
| 2840 | 2854 | #TODO: if the zip file is encrypted, suggest to use the -z option, or try '-z infected' automatically |
| 2841 | 2855 | # check each file within the zip if it is an OLE file, by reading its magic: |
| 2842 | 2856 | for subfile in z.namelist(): |
| 2843 | - log.debug("subfile {}".format(subfile)) | |
| 2857 | + log.debug("OpenXML subfile {}".format(subfile)) | |
| 2844 | 2858 | with z.open(subfile) as file_handle: |
| 2845 | - found_ole = False | |
| 2846 | - template_injection_detected = False | |
| 2847 | - xml_macrosheet_found = False | |
| 2859 | + # found_ole = False | |
| 2860 | + # template_injection_detected = False | |
| 2861 | + # xml_macrosheet_found = False | |
| 2848 | 2862 | magic = file_handle.read(len(olefile.MAGIC)) |
| 2849 | 2863 | if magic == olefile.MAGIC: |
| 2850 | - found_ole = True | |
| 2851 | - # in case we did not find an OLE file, | |
| 2852 | - # there could be a XLM macrosheet or a template injection attempt | |
| 2853 | - if not found_ole: | |
| 2854 | - read_all_file = file_handle.read() | |
| 2855 | - # try to detect template injection attempt | |
| 2856 | - # https://ired.team/offensive-security/initial-access/phishing-with-ms-office/inject-macros-from-a-remote-dotm-template-docx-with-macros | |
| 2857 | - subfile_that_can_contain_templates = "word/_rels/settings.xml.rels" | |
| 2858 | - if subfile == subfile_that_can_contain_templates: | |
| 2859 | - regex_template = b"Type=\"http://schemas\.openxmlformats\.org/officeDocument/\d{4}/relationships/attachedTemplate\"\s+Target=\"(.+?)\"" | |
| 2860 | - template_injection_found = re.search(regex_template, read_all_file) | |
| 2861 | - if template_injection_found: | |
| 2862 | - injected_template_url = template_injection_found.group(1).decode() | |
| 2863 | - message = "Found injected template in subfile {}. Template URL: {}"\ | |
| 2864 | - "".format(subfile_that_can_contain_templates, injected_template_url) | |
| 2865 | - log.info(message) | |
| 2866 | - template_injection_detected = True | |
| 2867 | - self.template_injection_found = True | |
| 2868 | - # try to find a XML macrosheet | |
| 2869 | - macro_sheet_footer = b"</xm:macrosheet>" | |
| 2870 | - len_macro_sheet_footer = len(macro_sheet_footer) | |
| 2871 | - last_bytes_to_check = read_all_file[-len_macro_sheet_footer:] | |
| 2872 | - if last_bytes_to_check == macro_sheet_footer: | |
| 2873 | - message = "Found XLM Macro in subfile: {}".format(subfile) | |
| 2874 | - log.info(message) | |
| 2875 | - xml_macrosheet_found = True | |
| 2876 | - self.xlm_macrosheet_found = True | |
| 2877 | - | |
| 2878 | - if found_ole or xml_macrosheet_found or template_injection_detected: | |
| 2879 | - log.debug('Opening OLE file %s within zip' % subfile) | |
| 2880 | - with z.open(subfile) as file_handle: | |
| 2881 | - ole_data = file_handle.read() | |
| 2882 | - try: | |
| 2883 | - self.append_subfile(filename=subfile, data=ole_data) | |
| 2884 | - except OlevbaBaseException as exc: | |
| 2885 | - if self.relaxed: | |
| 2886 | - log.info('%s is not a valid OLE file (%s)' % (subfile, exc)) | |
| 2887 | - log.debug('Trace:', exc_info=True) | |
| 2888 | - continue | |
| 2889 | - else: | |
| 2890 | - raise SubstreamOpenError(self.filename, subfile, | |
| 2891 | - exc) | |
| 2864 | + # found_ole = True | |
| 2865 | + # # in case we did not find an OLE file, | |
| 2866 | + # # there could be a XLM macrosheet or a template injection attempt | |
| 2867 | + # if not found_ole: | |
| 2868 | + # read_all_file = file_handle.read() | |
| 2869 | + # # try to detect template injection attempt | |
| 2870 | + # # https://ired.team/offensive-security/initial-access/phishing-with-ms-office/inject-macros-from-a-remote-dotm-template-docx-with-macros | |
| 2871 | + # subfile_that_can_contain_templates = "word/_rels/settings.xml.rels" | |
| 2872 | + # if subfile == subfile_that_can_contain_templates: | |
| 2873 | + # regex_template = b"Type=\"http://schemas\.openxmlformats\.org/officeDocument/\d{4}/relationships/attachedTemplate\"\s+Target=\"(.+?)\"" | |
| 2874 | + # template_injection_found = re.search(regex_template, read_all_file) | |
| 2875 | + # if template_injection_found: | |
| 2876 | + # injected_template_url = template_injection_found.group(1).decode() | |
| 2877 | + # message = "Found injected template in subfile {}. Template URL: {}"\ | |
| 2878 | + # "".format(subfile_that_can_contain_templates, injected_template_url) | |
| 2879 | + # log.info(message) | |
| 2880 | + # template_injection_detected = True | |
| 2881 | + # self.template_injection_found = True | |
| 2882 | + # # try to find a XML macrosheet | |
| 2883 | + # macro_sheet_footer = b"</xm:macrosheet>" | |
| 2884 | + # len_macro_sheet_footer = len(macro_sheet_footer) | |
| 2885 | + # last_bytes_to_check = read_all_file[-len_macro_sheet_footer:] | |
| 2886 | + # if last_bytes_to_check == macro_sheet_footer: | |
| 2887 | + # message = "Found XLM Macro in subfile: {}".format(subfile) | |
| 2888 | + # log.info(message) | |
| 2889 | + # xml_macrosheet_found = True | |
| 2890 | + # self.xlm_macrosheet_found = True | |
| 2891 | + # | |
| 2892 | + # if found_ole or xml_macrosheet_found or template_injection_detected: | |
| 2893 | + log.debug('Opening OLE file %s within zip' % subfile) | |
| 2894 | + with z.open(subfile) as file_handle: | |
| 2895 | + ole_data = file_handle.read() | |
| 2896 | + try: | |
| 2897 | + self.append_subfile(filename=subfile, data=ole_data) | |
| 2898 | + except OlevbaBaseException as exc: | |
| 2899 | + if self.relaxed: | |
| 2900 | + log.info('%s is not a valid OLE file (%s)' % (subfile, exc)) | |
| 2901 | + log.debug('Trace:', exc_info=True) | |
| 2902 | + continue | |
| 2903 | + else: | |
| 2904 | + raise SubstreamOpenError(self.filename, subfile, | |
| 2905 | + exc) | |
| 2892 | 2906 | z.close() |
| 2893 | 2907 | # set type only if parsing succeeds |
| 2894 | 2908 | self.type = TYPE_OpenXML |
| ... | ... | @@ -3134,7 +3148,7 @@ class VBA_Parser(object): |
| 3134 | 3148 | if s.startswith(b'E'): |
| 3135 | 3149 | xlm_macros.append('Formula or Macro: %s' % bytes2str(s[1:])) |
| 3136 | 3150 | if xlm_macro_found: |
| 3137 | - self.contains_macros = True | |
| 3151 | + self.contains_xlm_macros = True | |
| 3138 | 3152 | self.xlm_macros = xlm_macros |
| 3139 | 3153 | self.type = TYPE_SLK |
| 3140 | 3154 | |
| ... | ... | @@ -3150,7 +3164,7 @@ class VBA_Parser(object): |
| 3150 | 3164 | # On Python 2, store it as a raw bytes string |
| 3151 | 3165 | # On Python 3, convert it to unicode assuming it was encoded with UTF-8 |
| 3152 | 3166 | self.vba_code_all_modules = bytes2str(data) |
| 3153 | - self.contains_macros = True | |
| 3167 | + self.contains_vba_macros = True | |
| 3154 | 3168 | # set type only if parsing succeeds |
| 3155 | 3169 | self.type = TYPE_TEXT |
| 3156 | 3170 | |
| ... | ... | @@ -3257,6 +3271,20 @@ class VBA_Parser(object): |
| 3257 | 3271 | self.vba_projects.append((vba_root, project_path, dir_path)) |
| 3258 | 3272 | return self.vba_projects |
| 3259 | 3273 | |
| 3274 | + def detect_macros(self): | |
| 3275 | + """ | |
| 3276 | + Detect the potential presence of VBA or Excel4/XLM macros in the file, | |
| 3277 | + by calling detect_vba_macros and detect_xlm_macros. | |
| 3278 | + (if the no_xlm option is set, XLM macros are not checked) | |
| 3279 | + | |
| 3280 | + :return: bool, True if at least one VBA project has been found, False otherwise | |
| 3281 | + """ | |
| 3282 | + vba = self.detect_vba_macros() | |
| 3283 | + xlm = False | |
| 3284 | + if not self.no_xlm: | |
| 3285 | + xlm = self.detect_xlm_macros() | |
| 3286 | + return (vba or xlm) | |
| 3287 | + | |
| 3260 | 3288 | def detect_vba_macros(self): |
| 3261 | 3289 | """ |
| 3262 | 3290 | Detect the potential presence of VBA macros in the file, by checking |
| ... | ... | @@ -3273,28 +3301,28 @@ class VBA_Parser(object): |
| 3273 | 3301 | :return: bool, True if at least one VBA project has been found, False otherwise |
| 3274 | 3302 | """ |
| 3275 | 3303 | log.debug("detect vba macros") |
| 3276 | - #TODO: return None or raise exception if format not supported | |
| 3277 | - #TODO: return the number of VBA projects found instead of True/False? | |
| 3304 | + # TODO: return None or raise exception if format not supported | |
| 3305 | + # TODO: return the number of VBA projects found instead of True/False? | |
| 3278 | 3306 | # if this method was already called, return the previous result: |
| 3279 | - if self.contains_macros is not None: | |
| 3280 | - return self.contains_macros | |
| 3307 | + if self.contains_vba_macros is not None: | |
| 3308 | + return self.contains_vba_macros | |
| 3281 | 3309 | # if OpenXML/PPT, check all the OLE subfiles: |
| 3282 | 3310 | if self.ole_file is None: |
| 3283 | 3311 | for ole_subfile in self.ole_subfiles: |
| 3284 | 3312 | log.debug("ole subfile {}".format(ole_subfile)) |
| 3285 | 3313 | ole_subfile.no_xlm = self.no_xlm |
| 3286 | 3314 | if ole_subfile.detect_vba_macros(): |
| 3287 | - self.contains_macros = True | |
| 3315 | + self.contains_vba_macros = True | |
| 3288 | 3316 | return True |
| 3289 | 3317 | # otherwise, no macro found: |
| 3290 | - self.contains_macros = False | |
| 3318 | + self.contains_vba_macros = False | |
| 3291 | 3319 | return False |
| 3292 | 3320 | # otherwise it's an OLE file, find VBA projects: |
| 3293 | 3321 | vba_projects = self.find_vba_projects() |
| 3294 | 3322 | if len(vba_projects) == 0: |
| 3295 | - self.contains_macros = False | |
| 3323 | + self.contains_vba_macros = False | |
| 3296 | 3324 | else: |
| 3297 | - self.contains_macros = True | |
| 3325 | + self.contains_vba_macros = True | |
| 3298 | 3326 | # Also look for VBA code in any stream including orphans |
| 3299 | 3327 | # (happens in some malformed files) |
| 3300 | 3328 | ole = self.ole_file |
| ... | ... | @@ -3318,26 +3346,100 @@ class VBA_Parser(object): |
| 3318 | 3346 | log.debug(repr(data)) |
| 3319 | 3347 | if b'Attribut\x00' in data: |
| 3320 | 3348 | log.debug('Found VBA compressed code') |
| 3321 | - self.contains_macros = True | |
| 3349 | + self.contains_vba_macros = True | |
| 3322 | 3350 | except IOError as exc: |
| 3323 | 3351 | if self.relaxed: |
| 3324 | 3352 | log.info('Error when reading OLE Stream %r' % d.name) |
| 3325 | 3353 | log.debug('Trace:', exc_trace=True) |
| 3326 | 3354 | else: |
| 3327 | 3355 | raise SubstreamOpenError(self.filename, d.name, exc) |
| 3328 | - if (not self.no_xlm) and self.detect_xlm_macros(): | |
| 3329 | - self.contains_macros = True | |
| 3330 | - return self.contains_macros | |
| 3356 | + return self.contains_vba_macros | |
| 3331 | 3357 | |
| 3332 | 3358 | def detect_xlm_macros(self): |
| 3359 | + """ | |
| 3360 | + Detect the potential presence of Excel 4/XLM macros in the file, by checking | |
| 3361 | + if it contains a macro worksheet. Both OLE and OpenXML files are supported. | |
| 3362 | + Only Excel files may contain XLM macros, and also SLK and CSV files. | |
| 3363 | + | |
| 3364 | + If XLMMacroDeobfuscator is available, it will be used. Otherwise plugin_biff | |
| 3365 | + is used as fallback (plugin_biff only supports OLE files, not XLSX or XLSB) | |
| 3366 | + | |
| 3367 | + :return: bool, True if at least one macro worksheet has been found, False otherwise | |
| 3368 | + """ | |
| 3333 | 3369 | log.debug("detect xlm macros") |
| 3370 | + # if this method was already called, return the previous result: | |
| 3371 | + if self.contains_xlm_macros is not None: | |
| 3372 | + return self.contains_xlm_macros | |
| 3334 | 3373 | # if this is a SLK file, the analysis was done in open_slk: |
| 3335 | 3374 | if self.type == TYPE_SLK: |
| 3336 | - return self.contains_macros | |
| 3337 | - from oletools.thirdparty.oledump.plugin_biff import cBIFF | |
| 3375 | + return self.contains_xlm_macros | |
| 3376 | + # TODO: check also CSV files for formulas? | |
| 3338 | 3377 | self.xlm_macros = [] |
| 3378 | + # check if the file is Excel, otherwise return False | |
| 3379 | + if not self.ftg.is_excel(): | |
| 3380 | + self.contains_xlm_macros = False | |
| 3381 | + return False | |
| 3382 | + if XLMDEOBFUSCATOR: | |
| 3383 | + # XLMMacroDeobfuscator is available, use it: | |
| 3384 | + # But it only works with files on disk for now | |
| 3385 | + if not self.file_on_disk: | |
| 3386 | + log.warning('XLMMacroDeobfuscator only works with files on disk, not in memory. Analysis might be less complete.') | |
| 3387 | + else: | |
| 3388 | + try: | |
| 3389 | + return self._extract_xlm_xlmdeobf() | |
| 3390 | + except Exception: | |
| 3391 | + log.error('Error when running XLMMacroDeobfuscator') | |
| 3392 | + # fall back to plugin_biff: | |
| 3339 | 3393 | if self.ole_file is None: |
| 3394 | + # TODO: handle OpenXML here | |
| 3340 | 3395 | return False |
| 3396 | + return self._extract_xlm_plugin_biff() | |
| 3397 | + | |
| 3398 | + def _extract_xlm_xlmdeobf(self): | |
| 3399 | + """ | |
| 3400 | + Run XLMMacroDeobfuscator to detect and extract XLM macros | |
| 3401 | + :return: bool, True if at least one macro worksheet has been found, False otherwise | |
| 3402 | + """ | |
| 3403 | + log.debug('Calling XLMMacroDeobfuscator to detect and extract XLM macros') | |
| 3404 | + xlmdeobfuscator.SILENT = True | |
| 3405 | + # we build the output as a list of strings: | |
| 3406 | + xlm = ["RAW EXCEL4/XLM MACRO FORMULAS:"] | |
| 3407 | + # First, extract only formulas without emulation | |
| 3408 | + result = xlmdeobfuscator.process_file(file=self.filename, | |
| 3409 | + noninteractive=True, | |
| 3410 | + noindent=True, | |
| 3411 | + # output_formula_format='CELL:[[CELL_ADDR]], [[INT-FORMULA]]', | |
| 3412 | + return_deobfuscated=True, | |
| 3413 | + timeout=30, | |
| 3414 | + extract_only=True, | |
| 3415 | + ) | |
| 3416 | + if len(result) == 0: | |
| 3417 | + # no XLM macro was found | |
| 3418 | + self.contains_xlm_macros = False | |
| 3419 | + return False | |
| 3420 | + xlm += result | |
| 3421 | + xlm.append('- ' * 38) | |
| 3422 | + xlm.append('EMULATION - DEOBFUSCATED EXCEL4/XLM MACRO FORMULAS:') | |
| 3423 | + result = xlmdeobfuscator.process_file(file=sys.argv[1], | |
| 3424 | + noninteractive=True, | |
| 3425 | + noindent=True, | |
| 3426 | + # output_formula_format='CELL:[[CELL_ADDR]], [[INT-FORMULA]]', | |
| 3427 | + return_deobfuscated=True, | |
| 3428 | + timeout=30, | |
| 3429 | + ) | |
| 3430 | + xlm += result | |
| 3431 | + log.debug(xlm) | |
| 3432 | + self.xlm_macros = xlm | |
| 3433 | + self.contains_xlm_macros = True | |
| 3434 | + return True | |
| 3435 | + | |
| 3436 | + | |
| 3437 | + def _extract_xlm_plugin_biff(self): | |
| 3438 | + """ | |
| 3439 | + Run plugin_biff to detect and extract XLM macros | |
| 3440 | + :return: bool, True if at least one macro worksheet has been found, False otherwise | |
| 3441 | + """ | |
| 3442 | + log.debug('_extract_xlm_plugin_biff') | |
| 3341 | 3443 | for excel_stream in ('Workbook', 'Book'): |
| 3342 | 3444 | if self.ole_file.exists(excel_stream): |
| 3343 | 3445 | log.debug('Found Excel stream %r' % excel_stream) |
| ... | ... | @@ -3360,9 +3462,11 @@ class VBA_Parser(object): |
| 3360 | 3462 | # ref: https://inquest.net/blog/2020/03/18/Getting-Sneakier-Hidden-Sheets-Data-Connections-and-XLM-Macros |
| 3361 | 3463 | biff_plugin = cBIFF(name=[excel_stream], stream=data, options='-o DCONN -s') |
| 3362 | 3464 | self.xlm_macros += biff_plugin.Analyze() |
| 3465 | + self.contains_xlm_macros = True | |
| 3363 | 3466 | return True |
| 3364 | 3467 | except: |
| 3365 | 3468 | log.exception('Error when running oledump.plugin_biff, please report to %s' % URL_OLEVBA_ISSUES) |
| 3469 | + self.contains_xlm_macros = False | |
| 3366 | 3470 | return False |
| 3367 | 3471 | |
| 3368 | 3472 | def detect_is_encrypted(self): |
| ... | ... | @@ -3420,6 +3524,12 @@ class VBA_Parser(object): |
| 3420 | 3524 | for ole_subfile in self.ole_subfiles: |
| 3421 | 3525 | for results in ole_subfile.extract_macros(): |
| 3422 | 3526 | yield results |
| 3527 | + # we also need to yield XLM macros | |
| 3528 | + if self.xlm_macros: | |
| 3529 | + vba_code = '' | |
| 3530 | + for line in self.xlm_macros: | |
| 3531 | + vba_code += "' " + line + '\n' | |
| 3532 | + yield ('xlm_macro', 'xlm_macro', 'xlm_macro.txt', vba_code) | |
| 3423 | 3533 | else: |
| 3424 | 3534 | # This is an OLE file: |
| 3425 | 3535 | self.find_vba_projects() |
| ... | ... | @@ -3528,12 +3638,16 @@ class VBA_Parser(object): |
| 3528 | 3638 | |
| 3529 | 3639 | def analyze_macros(self, show_decoded_strings=False, deobfuscate=False): |
| 3530 | 3640 | """ |
| 3531 | - runs extract_macros and analyze the source code of all VBA macros | |
| 3641 | + runs extract_macros and analyze the source code of all VBA+XLM macros | |
| 3532 | 3642 | found in the file. |
| 3533 | 3643 | All results are stored in self.analysis_results. |
| 3534 | 3644 | If called more than once, simply returns the previous results. |
| 3645 | + | |
| 3646 | + :return: list of tuples (type, keyword, description) | |
| 3647 | + (type = 'AutoExec', 'Suspicious', 'IOC', 'Hex String', 'Base64 String' or 'Dridex String') | |
| 3535 | 3648 | """ |
| 3536 | - if self.detect_vba_macros(): | |
| 3649 | + # Check if there are VBA or XLM macros: | |
| 3650 | + if self.detect_macros(): | |
| 3537 | 3651 | # if the analysis was already done, avoid doing it twice: |
| 3538 | 3652 | if self.analysis_results is not None: |
| 3539 | 3653 | return self.analysis_results |
| ... | ... | @@ -3552,12 +3666,13 @@ class VBA_Parser(object): |
| 3552 | 3666 | 'this may have been used to hide malicious code' |
| 3553 | 3667 | scanner.suspicious_keywords.append((keyword, description)) |
| 3554 | 3668 | scanner.results.append(('Suspicious', keyword, description)) |
| 3555 | - if self.xlm_macrosheet_found: | |
| 3669 | + if self.contains_xlm_macros: | |
| 3556 | 3670 | log.debug('adding XLM macrosheet found to suspicious keywords') |
| 3557 | - keyword = 'XLM macrosheet' | |
| 3558 | - description = 'XLM macrosheet found. It could contain malicious code' | |
| 3671 | + keyword = 'XLM macro' | |
| 3672 | + description = 'XLM macro found. It may contain malicious code' | |
| 3559 | 3673 | scanner.suspicious_keywords.append((keyword, description)) |
| 3560 | 3674 | scanner.results.append(('Suspicious', keyword, description)) |
| 3675 | + # TODO: this has been temporarily disabled | |
| 3561 | 3676 | if self.template_injection_found: |
| 3562 | 3677 | log.debug('adding Template Injection to suspicious keywords') |
| 3563 | 3678 | keyword = 'Template Injection' |
| ... | ... | @@ -4029,7 +4144,7 @@ class VBA_Parser_CLI(VBA_Parser): |
| 4029 | 4144 | try: |
| 4030 | 4145 | #TODO: handle olefile errors, when an OLE file is malformed |
| 4031 | 4146 | print('Type: %s'% self.type) |
| 4032 | - if self.detect_vba_macros(): | |
| 4147 | + if self.detect_macros(): | |
| 4033 | 4148 | # run analysis before displaying VBA code, in order to colorize found keywords |
| 4034 | 4149 | self.run_analysis(show_decoded_strings=show_decoded_strings, deobfuscate=deobfuscate) |
| 4035 | 4150 | #print 'Contains VBA Macros:' |
| ... | ... | @@ -4109,7 +4224,7 @@ class VBA_Parser_CLI(VBA_Parser): |
| 4109 | 4224 | print('MACRO SOURCE CODE WITH DEOBFUSCATED VBA STRINGS (EXPERIMENTAL):\n\n') |
| 4110 | 4225 | print(self.reveal()) |
| 4111 | 4226 | else: |
| 4112 | - print('No VBA macros found.') | |
| 4227 | + print('No VBA or XLM macros found.') | |
| 4113 | 4228 | except OlevbaBaseException: |
| 4114 | 4229 | raise |
| 4115 | 4230 | except Exception as exc: |
| ... | ... | @@ -4164,7 +4279,7 @@ class VBA_Parser_CLI(VBA_Parser): |
| 4164 | 4279 | #TODO: handle olefile errors, when an OLE file is malformed |
| 4165 | 4280 | result['type'] = self.type |
| 4166 | 4281 | macros = [] |
| 4167 | - if self.detect_vba_macros(): | |
| 4282 | + if self.detect_macros(): | |
| 4168 | 4283 | for (subfilename, stream_path, vba_filename, vba_code) in self.extract_all_macros(): |
| 4169 | 4284 | curr_macro = {} |
| 4170 | 4285 | if hide_attributes: |
| ... | ... | @@ -4207,7 +4322,7 @@ class VBA_Parser_CLI(VBA_Parser): |
| 4207 | 4322 | #TODO: replace print by writing to a provided output file (sys.stdout by default) |
| 4208 | 4323 | try: |
| 4209 | 4324 | #TODO: handle olefile errors, when an OLE file is malformed |
| 4210 | - if self.detect_vba_macros(): | |
| 4325 | + if self.detect_macros(): | |
| 4211 | 4326 | # print a waiting message only if the output is not redirected to a file: |
| 4212 | 4327 | if sys.stdout.isatty(): |
| 4213 | 4328 | print('Analysis...\r', end='') |
| ... | ... | @@ -4216,7 +4331,7 @@ class VBA_Parser_CLI(VBA_Parser): |
| 4216 | 4331 | deobfuscate=deobfuscate) |
| 4217 | 4332 | flags = TYPE2TAG[self.type] |
| 4218 | 4333 | macros = autoexec = suspicious = iocs = hexstrings = base64obf = dridex = vba_obf = '-' |
| 4219 | - if self.contains_macros: macros = 'M' | |
| 4334 | + if self.contains_vba_macros: macros = 'M' | |
| 4220 | 4335 | if self.nb_autoexec: autoexec = 'A' |
| 4221 | 4336 | if self.nb_suspicious: suspicious = 'S' |
| 4222 | 4337 | if self.nb_iocs: iocs = 'I' |
| ... | ... | @@ -4351,6 +4466,7 @@ def parse_args(cmd_line_args=None): |
| 4351 | 4466 | def process_file(filename, data, container, options, crypto_nesting=0): |
| 4352 | 4467 | """ |
| 4353 | 4468 | Part of main function that processes a single file. |
| 4469 | + This is meant to be used only for the command-line interface of olevba | |
| 4354 | 4470 | |
| 4355 | 4471 | This handles exceptions and encryption. |
| 4356 | 4472 | ... | ... |