Commit 52fec143c47bd56fd325ebe19c75bc8be85b3b67

Authored by decalage2
1 parent 6eb1efaa

olevba: added support for SLK files and XLM macros in SLK

Showing 1 changed file with 77 additions and 4 deletions
oletools/olevba.py
... ... @@ -219,8 +219,9 @@ from __future__ import print_function
219 219 # 2019-05-23 v0.55 PL: - added option --pcode to call pcodedmp and display P-code
220 220 # 2019-06-05 PL: - added VBA stomping detection
221 221 # 2019-09-24 PL: - included DridexUrlDecode into olevba (issue #485)
  222 +# 2019-12-03 PL: - added support for SLK files and XLM macros in SLK
222 223  
223   -__version__ = '0.55.dev5'
  224 +__version__ = '0.55'
224 225  
225 226 #------------------------------------------------------------------------------
226 227 # TODO:
... ... @@ -560,6 +561,7 @@ TYPE_Word2003_XML = 'Word2003_XML'
560 561 TYPE_MHTML = 'MHTML'
561 562 TYPE_TEXT = 'Text'
562 563 TYPE_PPT = 'PPT'
  564 +TYPE_SLK = 'SLK'
563 565  
564 566 # short tag to display file types in triage mode:
565 567 TYPE2TAG = {
... ... @@ -569,7 +571,8 @@ TYPE2TAG = {
569 571 TYPE_Word2003_XML: 'XML:',
570 572 TYPE_MHTML: 'MHT:',
571 573 TYPE_TEXT: 'TXT:',
572   - TYPE_PPT: 'PPT',
  574 + TYPE_PPT: 'PPT:',
  575 + TYPE_SLK: 'SLK:',
573 576 }
574 577  
575 578  
... ... @@ -687,6 +690,8 @@ SUSPICIOUS_KEYWORDS = {
687 690 ('Start-Process',),
688 691 'May run an executable file or a system command using Excel 4 Macros (XLM/XLF)':
689 692 ('EXEC',),
  693 + 'May call a DLL using Excel 4 Macros (XLM/XLF)':
  694 + ('REGISTER', 'CALL'),
690 695 'May hide the application':
691 696 ('Application.Visible', 'ShowWindow', 'SW_HIDE'),
692 697 'May create a directory':
... ... @@ -702,7 +707,7 @@ SUSPICIOUS_KEYWORDS = {
702 707 ('New-Object',),
703 708 'May run an application (if combined with CreateObject)':
704 709 ('Shell.Application',),
705   - 'May run an Excel 4 Macro (aka XLM/XLF)':
  710 + 'May run an Excel 4 Macro (aka XLM/XLF) from VBA':
706 711 ('ExecuteExcel4Macro',),
707 712 'May enumerate application windows (if combined with Shell.Application object)':
708 713 ('Windows', 'FindWindow'),
... ... @@ -853,7 +858,8 @@ re_hex_string = re.compile(r'(?:[0-9A-Fa-f]{2}){4,}')
853 858 BASE64_RE = r'(?:[A-Za-z0-9+/]{4}){1,}(?:[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?'
854 859 re_base64_string = re.compile('"' + BASE64_RE + '"')
855 860 # white list of common strings matching the base64 regex, but which are not base64 strings (all lowercase):
856   -BASE64_WHITELIST = set(['thisdocument', 'thisworkbook', 'test', 'temp', 'http', 'open', 'exit', 'kernel32'])
  861 +BASE64_WHITELIST = set(['thisdocument', 'thisworkbook', 'test', 'temp', 'http', 'open', 'exit', 'kernel32',
  862 + 'virtualalloc', 'createthread'])
857 863  
858 864 # regex to detect strings encoded with a specific Dridex algorithm
859 865 # (see https://github.com/JamesHabben/MalwareStuff)
... ... @@ -2720,6 +2726,10 @@ class VBA_Parser(object):
2720 2726 msg = '%s is RTF, which cannot contain VBA Macros. Please use rtfobj to analyse it.' % self.filename
2721 2727 log.info(msg)
2722 2728 raise FileOpenError(msg)
  2729 + # Check if it is a SLK/SYLK file - https://en.wikipedia.org/wiki/SYmbolic_LinK_(SYLK)
  2730 + # It must start with "ID" in uppercase, no whitespace or newline allowed before by Excel:
  2731 + if data.startswith(b'ID'):
  2732 + self.open_slk(data)
2723 2733 # Check if this is a plain text VBA or VBScript file:
2724 2734 # To avoid scanning binary files, we simply check for some control chars:
2725 2735 if self.type is None and b'\x00' not in data:
... ... @@ -2998,6 +3008,40 @@ class VBA_Parser(object):
2998 3008 log.debug("File appears not to be a ppt file (%s)" % exc)
2999 3009  
3000 3010  
  3011 + def open_slk(self, data):
  3012 + """
  3013 + Open a SLK file, which may contain XLM/Excel 4 macros
  3014 + :param data: file contents in a bytes string
  3015 + :return: nothing
  3016 + """
  3017 + # TODO: Those results should be stored as XLM macros, not VBA
  3018 + log.info('Opening SLK file %s' % self.filename)
  3019 + xlm_macro_found = False
  3020 + xlm_macros = []
  3021 + xlm_macros.append('Formulas and XLM/Excel 4 macros extracted from SLK file:')
  3022 + for line in data.splitlines(keepends=False):
  3023 + if line.startswith(b'O'):
  3024 + # Option: "O;E" indicates a macro sheet, must appear before NN and C rows
  3025 + for s in line.split(b';'):
  3026 + if s.startswith(b'E'):
  3027 + xlm_macro_found = True
  3028 + log.debug('SLK parser: found macro sheet')
  3029 + elif line.startswith(b'NN') and xlm_macro_found:
  3030 + # Name that can trigger a macro, for example "Auto_Open"
  3031 + for s in line.split(b';'):
  3032 + if s.startswith(b'N') and s.strip() != b'NN':
  3033 + xlm_macros.append('Named cell: %s' % bytes2str(s[1:]))
  3034 + elif line.startswith(b'C') and xlm_macro_found:
  3035 + # Cell
  3036 + for s in line.split(b';'):
  3037 + if s.startswith(b'E'):
  3038 + xlm_macros.append('Formula or Macro: %s' % bytes2str(s[1:]))
  3039 + if xlm_macro_found:
  3040 + self.contains_macros = True
  3041 + self.xlm_macros = xlm_macros
  3042 + self.type = TYPE_SLK
  3043 +
  3044 +
3001 3045 def open_text(self, data):
3002 3046 """
3003 3047 Open a text file containing VBA or VBScript source code
... ... @@ -3178,6 +3222,9 @@ class VBA_Parser(object):
3178 3222 return self.contains_macros
3179 3223  
3180 3224 def detect_xlm_macros(self):
  3225 + # if this is a SLK file, the analysis was done in open_slk:
  3226 + if self.type == TYPE_SLK:
  3227 + return self.contains_macros
3181 3228 from oletools.thirdparty.oledump.plugin_biff import cBIFF
3182 3229 self.xlm_macros = []
3183 3230 if self.ole_file is None:
... ... @@ -3227,6 +3274,12 @@ class VBA_Parser(object):
3227 3274 if self.type == TYPE_TEXT:
3228 3275 # This is a text file, yield the full code:
3229 3276 yield (self.filename, '', self.filename, self.vba_code_all_modules)
  3277 + elif self.type == TYPE_SLK:
  3278 + if self.xlm_macros:
  3279 + vba_code = ''
  3280 + for line in self.xlm_macros:
  3281 + vba_code += "' " + line + '\n'
  3282 + yield ('xlm_macro', 'xlm_macro', 'xlm_macro.txt', vba_code)
3230 3283 else:
3231 3284 # OpenXML/PPT: recursively yield results from each OLE subfile:
3232 3285 for ole_subfile in self.ole_subfiles:
... ... @@ -3513,6 +3566,10 @@ class VBA_Parser(object):
3513 3566 :return: VBA P-code disassembly
3514 3567 :rtype: str
3515 3568 """
  3569 + # Only run on OLE files
  3570 + if self.type != TYPE_OLE:
  3571 + self.pcodedmp_output = ''
  3572 + return ''
3516 3573 # only run it once:
3517 3574 if self.pcodedmp_output is None:
3518 3575 log.debug('Calling pcodedmp to extract and disassemble the VBA P-code')
... ... @@ -3571,6 +3628,10 @@ class VBA_Parser(object):
3571 3628 :return: True if VBA stomping detected, False otherwise
3572 3629 :rtype: bool
3573 3630 """
  3631 + # Only run on OLE files
  3632 + if self.type != TYPE_OLE:
  3633 + self.vba_stomping_detected = False
  3634 + return False
3574 3635 # only run it once:
3575 3636 if self.vba_stomping_detected is None:
3576 3637 log.debug('Analysing the P-code to detect VBA stomping')
... ... @@ -3840,6 +3901,18 @@ class VBA_Parser_CLI(VBA_Parser):
3840 3901 print('P-CODE disassembly:')
3841 3902 pcode = self.extract_pcode()
3842 3903 print(pcode)
  3904 + # if self.type == TYPE_SLK:
  3905 + # # TODO: clean up this code
  3906 + # slk_output = self.vba_code_all_modules
  3907 + # try:
  3908 + # # Colorize the interesting keywords in the output:
  3909 + # # (unless the output is redirected to a file)
  3910 + # if sys.stdout.isatty():
  3911 + # slk_output = colorclass.Color(self.colorize_keywords(slk_output))
  3912 + # except UnicodeError:
  3913 + # # TODO better handling of Unicode
  3914 + # log.debug('Unicode conversion to be fixed before colorizing the output')
  3915 + # print(slk_output)
3843 3916  
3844 3917 if not vba_code_only:
3845 3918 # analyse the code from all modules at once:
... ...