Commit 3cae86e62d99a29858721ca6f99c8c2cf15be8e4

Authored by decalage2
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,7 +235,7 @@ from __future__ import print_function
235 # for issue #619) 235 # for issue #619)
236 # 2021-04-14 PL: - added detection of Workbook_BeforeClose (issue #518) 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 # TODO: 241 # TODO:
@@ -310,6 +310,18 @@ import colorclass @@ -310,6 +310,18 @@ import colorclass
310 if os.name == 'nt': 310 if os.name == 'nt':
311 colorclass.Windows.enable(auto_colors=True) 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 # IMPORTANT: it should be possible to run oletools directly as scripts 326 # IMPORTANT: it should be possible to run oletools directly as scripts
315 # in any directory without installing them with pip or setup.py. 327 # in any directory without installing them with pip or setup.py.
@@ -326,17 +338,14 @@ if _parent_dir not in sys.path: @@ -326,17 +338,14 @@ if _parent_dir not in sys.path:
326 import olefile 338 import olefile
327 from oletools.thirdparty.tablestream import tablestream 339 from oletools.thirdparty.tablestream import tablestream
328 from oletools.thirdparty.xglob import xglob, PathNotFoundException 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 from oletools import ppt_parser 342 from oletools import ppt_parser
335 from oletools import oleform 343 from oletools import oleform
336 from oletools import rtfobj 344 from oletools import rtfobj
337 from oletools import crypto 345 from oletools import crypto
338 from oletools.common.io_encoding import ensure_stdout_handles_unicode 346 from oletools.common.io_encoding import ensure_stdout_handles_unicode
339 from oletools.common import codepages 347 from oletools.common import codepages
  348 +from oletools import ftguess
340 349
341 # === PYTHON 2+3 SUPPORT ====================================================== 350 # === PYTHON 2+3 SUPPORT ======================================================
342 351
@@ -2710,7 +2719,8 @@ class VBA_Parser(object): @@ -2710,7 +2719,8 @@ class VBA_Parser(object):
2710 self.type = None 2719 self.type = None
2711 self.vba_projects = None 2720 self.vba_projects = None
2712 self.vba_forms = None 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 self.vba_code_all_modules = None # to store the source code of all modules 2724 self.vba_code_all_modules = None # to store the source code of all modules
2715 # list of tuples for each module: (subfilename, stream_path, vba_filename, vba_code) 2725 # list of tuples for each module: (subfilename, stream_path, vba_filename, vba_code)
2716 self.modules = None 2726 self.modules = None
@@ -2736,9 +2746,13 @@ class VBA_Parser(object): @@ -2736,9 +2746,13 @@ class VBA_Parser(object):
2736 self.vba_stomping_detected = None 2746 self.vba_stomping_detected = None
2737 # will be set to True or False by detect_is_encrypted method 2747 # will be set to True or False by detect_is_encrypted method
2738 self.is_encrypted = False 2748 self.is_encrypted = False
  2749 + # TODO: those are disabled for now:
2739 self.xlm_macrosheet_found = False 2750 self.xlm_macrosheet_found = False
2740 self.template_injection_found = False 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 # if filename is None: 2756 # if filename is None:
2743 # if isinstance(_file, basestring): 2757 # if isinstance(_file, basestring):
2744 # if len(_file) < olefile.MINIMAL_OLEFILE_SIZE: 2758 # if len(_file) < olefile.MINIMAL_OLEFILE_SIZE:
@@ -2752,7 +2766,7 @@ class VBA_Parser(object): @@ -2752,7 +2766,7 @@ class VBA_Parser(object):
2752 self.open_ole(_file) 2766 self.open_ole(_file)
2753 2767
2754 # if this worked, try whether it is a ppt file (special ole file) 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 self.open_ppt() 2770 self.open_ppt()
2757 if self.type is None and zipfile.is_zipfile(_file): 2771 if self.type is None and zipfile.is_zipfile(_file):
2758 # Zip file, which may be an OpenXML document 2772 # Zip file, which may be an OpenXML document
@@ -2840,55 +2854,55 @@ class VBA_Parser(object): @@ -2840,55 +2854,55 @@ class VBA_Parser(object):
2840 #TODO: if the zip file is encrypted, suggest to use the -z option, or try '-z infected' automatically 2854 #TODO: if the zip file is encrypted, suggest to use the -z option, or try '-z infected' automatically
2841 # check each file within the zip if it is an OLE file, by reading its magic: 2855 # check each file within the zip if it is an OLE file, by reading its magic:
2842 for subfile in z.namelist(): 2856 for subfile in z.namelist():
2843 - log.debug("subfile {}".format(subfile)) 2857 + log.debug("OpenXML subfile {}".format(subfile))
2844 with z.open(subfile) as file_handle: 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 magic = file_handle.read(len(olefile.MAGIC)) 2862 magic = file_handle.read(len(olefile.MAGIC))
2849 if magic == olefile.MAGIC: 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 z.close() 2906 z.close()
2893 # set type only if parsing succeeds 2907 # set type only if parsing succeeds
2894 self.type = TYPE_OpenXML 2908 self.type = TYPE_OpenXML
@@ -3134,7 +3148,7 @@ class VBA_Parser(object): @@ -3134,7 +3148,7 @@ class VBA_Parser(object):
3134 if s.startswith(b'E'): 3148 if s.startswith(b'E'):
3135 xlm_macros.append('Formula or Macro: %s' % bytes2str(s[1:])) 3149 xlm_macros.append('Formula or Macro: %s' % bytes2str(s[1:]))
3136 if xlm_macro_found: 3150 if xlm_macro_found:
3137 - self.contains_macros = True 3151 + self.contains_xlm_macros = True
3138 self.xlm_macros = xlm_macros 3152 self.xlm_macros = xlm_macros
3139 self.type = TYPE_SLK 3153 self.type = TYPE_SLK
3140 3154
@@ -3150,7 +3164,7 @@ class VBA_Parser(object): @@ -3150,7 +3164,7 @@ class VBA_Parser(object):
3150 # On Python 2, store it as a raw bytes string 3164 # On Python 2, store it as a raw bytes string
3151 # On Python 3, convert it to unicode assuming it was encoded with UTF-8 3165 # On Python 3, convert it to unicode assuming it was encoded with UTF-8
3152 self.vba_code_all_modules = bytes2str(data) 3166 self.vba_code_all_modules = bytes2str(data)
3153 - self.contains_macros = True 3167 + self.contains_vba_macros = True
3154 # set type only if parsing succeeds 3168 # set type only if parsing succeeds
3155 self.type = TYPE_TEXT 3169 self.type = TYPE_TEXT
3156 3170
@@ -3257,6 +3271,20 @@ class VBA_Parser(object): @@ -3257,6 +3271,20 @@ class VBA_Parser(object):
3257 self.vba_projects.append((vba_root, project_path, dir_path)) 3271 self.vba_projects.append((vba_root, project_path, dir_path))
3258 return self.vba_projects 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 def detect_vba_macros(self): 3288 def detect_vba_macros(self):
3261 """ 3289 """
3262 Detect the potential presence of VBA macros in the file, by checking 3290 Detect the potential presence of VBA macros in the file, by checking
@@ -3273,28 +3301,28 @@ class VBA_Parser(object): @@ -3273,28 +3301,28 @@ class VBA_Parser(object):
3273 :return: bool, True if at least one VBA project has been found, False otherwise 3301 :return: bool, True if at least one VBA project has been found, False otherwise
3274 """ 3302 """
3275 log.debug("detect vba macros") 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 # if this method was already called, return the previous result: 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 # if OpenXML/PPT, check all the OLE subfiles: 3309 # if OpenXML/PPT, check all the OLE subfiles:
3282 if self.ole_file is None: 3310 if self.ole_file is None:
3283 for ole_subfile in self.ole_subfiles: 3311 for ole_subfile in self.ole_subfiles:
3284 log.debug("ole subfile {}".format(ole_subfile)) 3312 log.debug("ole subfile {}".format(ole_subfile))
3285 ole_subfile.no_xlm = self.no_xlm 3313 ole_subfile.no_xlm = self.no_xlm
3286 if ole_subfile.detect_vba_macros(): 3314 if ole_subfile.detect_vba_macros():
3287 - self.contains_macros = True 3315 + self.contains_vba_macros = True
3288 return True 3316 return True
3289 # otherwise, no macro found: 3317 # otherwise, no macro found:
3290 - self.contains_macros = False 3318 + self.contains_vba_macros = False
3291 return False 3319 return False
3292 # otherwise it's an OLE file, find VBA projects: 3320 # otherwise it's an OLE file, find VBA projects:
3293 vba_projects = self.find_vba_projects() 3321 vba_projects = self.find_vba_projects()
3294 if len(vba_projects) == 0: 3322 if len(vba_projects) == 0:
3295 - self.contains_macros = False 3323 + self.contains_vba_macros = False
3296 else: 3324 else:
3297 - self.contains_macros = True 3325 + self.contains_vba_macros = True
3298 # Also look for VBA code in any stream including orphans 3326 # Also look for VBA code in any stream including orphans
3299 # (happens in some malformed files) 3327 # (happens in some malformed files)
3300 ole = self.ole_file 3328 ole = self.ole_file
@@ -3318,26 +3346,100 @@ class VBA_Parser(object): @@ -3318,26 +3346,100 @@ class VBA_Parser(object):
3318 log.debug(repr(data)) 3346 log.debug(repr(data))
3319 if b'Attribut\x00' in data: 3347 if b'Attribut\x00' in data:
3320 log.debug('Found VBA compressed code') 3348 log.debug('Found VBA compressed code')
3321 - self.contains_macros = True 3349 + self.contains_vba_macros = True
3322 except IOError as exc: 3350 except IOError as exc:
3323 if self.relaxed: 3351 if self.relaxed:
3324 log.info('Error when reading OLE Stream %r' % d.name) 3352 log.info('Error when reading OLE Stream %r' % d.name)
3325 log.debug('Trace:', exc_trace=True) 3353 log.debug('Trace:', exc_trace=True)
3326 else: 3354 else:
3327 raise SubstreamOpenError(self.filename, d.name, exc) 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 def detect_xlm_macros(self): 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 log.debug("detect xlm macros") 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 # if this is a SLK file, the analysis was done in open_slk: 3373 # if this is a SLK file, the analysis was done in open_slk:
3335 if self.type == TYPE_SLK: 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 self.xlm_macros = [] 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 if self.ole_file is None: 3393 if self.ole_file is None:
  3394 + # TODO: handle OpenXML here
3340 return False 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 for excel_stream in ('Workbook', 'Book'): 3443 for excel_stream in ('Workbook', 'Book'):
3342 if self.ole_file.exists(excel_stream): 3444 if self.ole_file.exists(excel_stream):
3343 log.debug('Found Excel stream %r' % excel_stream) 3445 log.debug('Found Excel stream %r' % excel_stream)
@@ -3360,9 +3462,11 @@ class VBA_Parser(object): @@ -3360,9 +3462,11 @@ class VBA_Parser(object):
3360 # ref: https://inquest.net/blog/2020/03/18/Getting-Sneakier-Hidden-Sheets-Data-Connections-and-XLM-Macros 3462 # ref: https://inquest.net/blog/2020/03/18/Getting-Sneakier-Hidden-Sheets-Data-Connections-and-XLM-Macros
3361 biff_plugin = cBIFF(name=[excel_stream], stream=data, options='-o DCONN -s') 3463 biff_plugin = cBIFF(name=[excel_stream], stream=data, options='-o DCONN -s')
3362 self.xlm_macros += biff_plugin.Analyze() 3464 self.xlm_macros += biff_plugin.Analyze()
  3465 + self.contains_xlm_macros = True
3363 return True 3466 return True
3364 except: 3467 except:
3365 log.exception('Error when running oledump.plugin_biff, please report to %s' % URL_OLEVBA_ISSUES) 3468 log.exception('Error when running oledump.plugin_biff, please report to %s' % URL_OLEVBA_ISSUES)
  3469 + self.contains_xlm_macros = False
3366 return False 3470 return False
3367 3471
3368 def detect_is_encrypted(self): 3472 def detect_is_encrypted(self):
@@ -3420,6 +3524,12 @@ class VBA_Parser(object): @@ -3420,6 +3524,12 @@ class VBA_Parser(object):
3420 for ole_subfile in self.ole_subfiles: 3524 for ole_subfile in self.ole_subfiles:
3421 for results in ole_subfile.extract_macros(): 3525 for results in ole_subfile.extract_macros():
3422 yield results 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 else: 3533 else:
3424 # This is an OLE file: 3534 # This is an OLE file:
3425 self.find_vba_projects() 3535 self.find_vba_projects()
@@ -3528,12 +3638,16 @@ class VBA_Parser(object): @@ -3528,12 +3638,16 @@ class VBA_Parser(object):
3528 3638
3529 def analyze_macros(self, show_decoded_strings=False, deobfuscate=False): 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 found in the file. 3642 found in the file.
3533 All results are stored in self.analysis_results. 3643 All results are stored in self.analysis_results.
3534 If called more than once, simply returns the previous results. 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 # if the analysis was already done, avoid doing it twice: 3651 # if the analysis was already done, avoid doing it twice:
3538 if self.analysis_results is not None: 3652 if self.analysis_results is not None:
3539 return self.analysis_results 3653 return self.analysis_results
@@ -3552,12 +3666,13 @@ class VBA_Parser(object): @@ -3552,12 +3666,13 @@ class VBA_Parser(object):
3552 'this may have been used to hide malicious code' 3666 'this may have been used to hide malicious code'
3553 scanner.suspicious_keywords.append((keyword, description)) 3667 scanner.suspicious_keywords.append((keyword, description))
3554 scanner.results.append(('Suspicious', keyword, description)) 3668 scanner.results.append(('Suspicious', keyword, description))
3555 - if self.xlm_macrosheet_found: 3669 + if self.contains_xlm_macros:
3556 log.debug('adding XLM macrosheet found to suspicious keywords') 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 scanner.suspicious_keywords.append((keyword, description)) 3673 scanner.suspicious_keywords.append((keyword, description))
3560 scanner.results.append(('Suspicious', keyword, description)) 3674 scanner.results.append(('Suspicious', keyword, description))
  3675 + # TODO: this has been temporarily disabled
3561 if self.template_injection_found: 3676 if self.template_injection_found:
3562 log.debug('adding Template Injection to suspicious keywords') 3677 log.debug('adding Template Injection to suspicious keywords')
3563 keyword = 'Template Injection' 3678 keyword = 'Template Injection'
@@ -4029,7 +4144,7 @@ class VBA_Parser_CLI(VBA_Parser): @@ -4029,7 +4144,7 @@ class VBA_Parser_CLI(VBA_Parser):
4029 try: 4144 try:
4030 #TODO: handle olefile errors, when an OLE file is malformed 4145 #TODO: handle olefile errors, when an OLE file is malformed
4031 print('Type: %s'% self.type) 4146 print('Type: %s'% self.type)
4032 - if self.detect_vba_macros(): 4147 + if self.detect_macros():
4033 # run analysis before displaying VBA code, in order to colorize found keywords 4148 # run analysis before displaying VBA code, in order to colorize found keywords
4034 self.run_analysis(show_decoded_strings=show_decoded_strings, deobfuscate=deobfuscate) 4149 self.run_analysis(show_decoded_strings=show_decoded_strings, deobfuscate=deobfuscate)
4035 #print 'Contains VBA Macros:' 4150 #print 'Contains VBA Macros:'
@@ -4109,7 +4224,7 @@ class VBA_Parser_CLI(VBA_Parser): @@ -4109,7 +4224,7 @@ class VBA_Parser_CLI(VBA_Parser):
4109 print('MACRO SOURCE CODE WITH DEOBFUSCATED VBA STRINGS (EXPERIMENTAL):\n\n') 4224 print('MACRO SOURCE CODE WITH DEOBFUSCATED VBA STRINGS (EXPERIMENTAL):\n\n')
4110 print(self.reveal()) 4225 print(self.reveal())
4111 else: 4226 else:
4112 - print('No VBA macros found.') 4227 + print('No VBA or XLM macros found.')
4113 except OlevbaBaseException: 4228 except OlevbaBaseException:
4114 raise 4229 raise
4115 except Exception as exc: 4230 except Exception as exc:
@@ -4164,7 +4279,7 @@ class VBA_Parser_CLI(VBA_Parser): @@ -4164,7 +4279,7 @@ class VBA_Parser_CLI(VBA_Parser):
4164 #TODO: handle olefile errors, when an OLE file is malformed 4279 #TODO: handle olefile errors, when an OLE file is malformed
4165 result['type'] = self.type 4280 result['type'] = self.type
4166 macros = [] 4281 macros = []
4167 - if self.detect_vba_macros(): 4282 + if self.detect_macros():
4168 for (subfilename, stream_path, vba_filename, vba_code) in self.extract_all_macros(): 4283 for (subfilename, stream_path, vba_filename, vba_code) in self.extract_all_macros():
4169 curr_macro = {} 4284 curr_macro = {}
4170 if hide_attributes: 4285 if hide_attributes:
@@ -4207,7 +4322,7 @@ class VBA_Parser_CLI(VBA_Parser): @@ -4207,7 +4322,7 @@ class VBA_Parser_CLI(VBA_Parser):
4207 #TODO: replace print by writing to a provided output file (sys.stdout by default) 4322 #TODO: replace print by writing to a provided output file (sys.stdout by default)
4208 try: 4323 try:
4209 #TODO: handle olefile errors, when an OLE file is malformed 4324 #TODO: handle olefile errors, when an OLE file is malformed
4210 - if self.detect_vba_macros(): 4325 + if self.detect_macros():
4211 # print a waiting message only if the output is not redirected to a file: 4326 # print a waiting message only if the output is not redirected to a file:
4212 if sys.stdout.isatty(): 4327 if sys.stdout.isatty():
4213 print('Analysis...\r', end='') 4328 print('Analysis...\r', end='')
@@ -4216,7 +4331,7 @@ class VBA_Parser_CLI(VBA_Parser): @@ -4216,7 +4331,7 @@ class VBA_Parser_CLI(VBA_Parser):
4216 deobfuscate=deobfuscate) 4331 deobfuscate=deobfuscate)
4217 flags = TYPE2TAG[self.type] 4332 flags = TYPE2TAG[self.type]
4218 macros = autoexec = suspicious = iocs = hexstrings = base64obf = dridex = vba_obf = '-' 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 if self.nb_autoexec: autoexec = 'A' 4335 if self.nb_autoexec: autoexec = 'A'
4221 if self.nb_suspicious: suspicious = 'S' 4336 if self.nb_suspicious: suspicious = 'S'
4222 if self.nb_iocs: iocs = 'I' 4337 if self.nb_iocs: iocs = 'I'
@@ -4351,6 +4466,7 @@ def parse_args(cmd_line_args=None): @@ -4351,6 +4466,7 @@ def parse_args(cmd_line_args=None):
4351 def process_file(filename, data, container, options, crypto_nesting=0): 4466 def process_file(filename, data, container, options, crypto_nesting=0):
4352 """ 4467 """
4353 Part of main function that processes a single file. 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 This handles exceptions and encryption. 4471 This handles exceptions and encryption.
4356 4472