Commit 52fec143c47bd56fd325ebe19c75bc8be85b3b67
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: | ... | ... |