Commit f189b26cd5029edccdaec4dc6d61d28972b45699

Authored by Philippe Lagadec
Committed by GitHub
2 parents 69b085b9 4274e151

Merge pull request #569 from mlodic/master

improvements to analysis of XLM macros (encrypted ones + contained in XLSM) + template injection
Showing 1 changed file with 112 additions and 14 deletions
oletools/olevba.py
... ... @@ -636,6 +636,9 @@ AUTOEXEC_KEYWORDS = {
636 636 # TODO: "Auto_Ope" is temporarily here because of a bug in plugin_biff, which misses the last byte in "Auto_Open"...
637 637 'Runs when the Excel Workbook is closed':
638 638 ('Auto_Close', 'Workbook_Close'),
  639 + #Worksheet_Calculate to Autoexec: see http://www.certego.net/en/news/advanced-vba-macros/
  640 + 'May run when an Excel WorkSheet is opened':
  641 + ('Worksheet_Calculate',),
639 642 }
640 643  
641 644 # Keywords to detect auto-executable macros
... ... @@ -652,15 +655,17 @@ AUTOEXEC_KEYWORDS_REGEX = {
652 655 r'\w+_FileDownload', r'\w+_NavigateComplete2', r'\w+_NavigateError',
653 656 r'\w+_ProgressChange', r'\w+_PropertyChange', r'\w+_SetSecureLockIcon',
654 657 r'\w+_StatusTextChange', r'\w+_TitleChange', r'\w+_MouseMove', r'\w+_MouseEnter',
655   - r'\w+_MouseLeave', r'\w+_Layout', r'\w+_OnConnecting'),
  658 + r'\w+_MouseLeave', r'\w+_Layout', r'\w+_OnConnecting', r'\w+_FollowHyperlink', r'\w+_ContentControlOnEnter'),
656 659 }
657 660  
658 661 # Suspicious Keywords that may be used by malware
659 662 # See VBA language reference: http://msdn.microsoft.com/en-us/library/office/jj692818%28v=office.15%29.aspx
660 663 SUSPICIOUS_KEYWORDS = {
661 664 #TODO: use regex to support variable whitespaces
  665 + #http://www.certego.net/en/news/advanced-vba-macros/
662 666 'May read system environment variables':
663   - ('Environ',),
  667 + ('Environ','Win32_Environment','Environment','ExpandEnvironmentStrings','HKCU\Environment',
  668 + 'HKEY_CURRENT_USER\Environment'),
664 669 'May open a file':
665 670 ('Open',),
666 671 'May write to a file (if combined with Open)':
... ... @@ -670,22 +675,35 @@ SUSPICIOUS_KEYWORDS = {
670 675 #TODO: regex to find Open+Binary on same line
671 676 ('Binary',),
672 677 'May copy a file':
673   - ('FileCopy', 'CopyFile'),
  678 + ('FileCopy', 'CopyFile','CopyHere','CopyFolder'),
674 679 #FileCopy: http://msdn.microsoft.com/en-us/library/office/gg264390%28v=office.15%29.aspx
675 680 #CopyFile: http://msdn.microsoft.com/en-us/library/office/gg264089%28v=office.15%29.aspx
  681 + #CopyHere, MoveHere, MoveHere and MoveFolder exploitation: see http://www.certego.net/en/news/advanced-vba-macros/
  682 + 'May move a file':
  683 + ('MoveHere', 'MoveFile', 'MoveFolder'),
676 684 'May delete a file':
677 685 ('Kill',),
678 686 'May create a text file':
679 687 ('CreateTextFile', 'ADODB.Stream', 'WriteText', 'SaveToFile'),
680 688 #CreateTextFile: http://msdn.microsoft.com/en-us/library/office/gg264617%28v=office.15%29.aspx
681 689 #ADODB.Stream sample: http://pastebin.com/Z4TMyuq6
682   - # ShellExecute: https://twitter.com/StanHacked/status/1075088449768693762
  690 + #ShellExecute: https://twitter.com/StanHacked/status/1075088449768693762
  691 + #InvokeVerb, InvokeVerbEx, DoIt and ControlPanelItem: see http://www.certego.net/en/news/advanced-vba-macros/
  692 +
683 693 'May run an executable file or a system command':
684 694 ('Shell', 'vbNormal', 'vbNormalFocus', 'vbHide', 'vbMinimizedFocus', 'vbMaximizedFocus', 'vbNormalNoFocus',
685   - 'vbMinimizedNoFocus', 'WScript.Shell', 'Run', 'ShellExecute', 'ShellExecuteA', 'shell32'),
  695 + 'vbMinimizedNoFocus', 'WScript.Shell', 'Run', 'ShellExecute', 'ShellExecuteA', 'shell32','InvokeVerb','InvokeVerbEx',
  696 + 'DoIt'),
  697 + 'May run a dll':
  698 + ('ControlPanelItem',),
  699 + # Win32_Process.Create https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process
  700 + 'May execute file or a system command through WMI':
  701 + ('Create',),
  702 + # WMI https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process
686 703 # MacScript: see https://msdn.microsoft.com/en-us/library/office/gg264812.aspx
  704 + # AppleScript: see https://docs.microsoft.com/en-us/office/vba/office-mac/applescripttask
687 705 'May run an executable file or a system command on a Mac':
688   - ('MacScript',),
  706 + ('MacScript','AppleScript'),
689 707 #Shell: http://msdn.microsoft.com/en-us/library/office/gg278437%28v=office.15%29.aspx
690 708 #WScript.Shell+Run sample: http://pastebin.com/Z4TMyuq6
691 709 'May run PowerShell commands':
... ... @@ -698,10 +716,8 @@ SUSPICIOUS_KEYWORDS = {
698 716 'invoke-command', 'scriptblock', 'Invoke-Expression', 'AuthorizationManager'),
699 717 'May run an executable file or a system command using PowerShell':
700 718 ('Start-Process',),
701   - 'May run an executable file or a system command using Excel 4 Macros (XLM/XLF)':
702   - ('EXEC',),
703 719 'May call a DLL using Excel 4 Macros (XLM/XLF)':
704   - ('REGISTER', 'CALL'),
  720 + ('CALL',),
705 721 'May hide the application':
706 722 ('Application.Visible', 'ShowWindow', 'SW_HIDE'),
707 723 'May create a directory':
... ... @@ -713,6 +729,9 @@ SUSPICIOUS_KEYWORDS = {
713 729 ('Application.AltStartupPath',),
714 730 'May create an OLE object':
715 731 ('CreateObject',),
  732 + #bypass CreateObject http://www.certego.net/en/news/advanced-vba-macros/
  733 + 'May get an OLE object with a running instance':
  734 + ('GetObject',),
716 735 'May create an OLE object using PowerShell':
717 736 ('New-Object',),
718 737 'May run an application (if combined with CreateObject)':
... ... @@ -727,8 +746,6 @@ SUSPICIOUS_KEYWORDS = {
727 746 'May run code from a library on a Mac':
728 747 #TODO: regex to find declare+lib on same line - see mraptor
729 748 ('libc.dylib', 'dylib'),
730   - 'May run code from a DLL using Excel 4 Macros (XLM/XLF)':
731   - ('REGISTER',),
732 749 'May inject code into another process':
733 750 ('CreateThread', 'CreateUserThread', 'VirtualAlloc', # (issue #9) suggested by Davy Douhine - used by MSF payload
734 751 'VirtualAllocEx', 'RtlMoveMemory', 'WriteProcessMemory',
... ... @@ -813,6 +830,14 @@ SUSPICIOUS_KEYWORDS_REGEX = {
813 830 ),
814 831 'May run an executable file or a system command on a Mac (if combined with libc.dylib)':
815 832 ('system', 'popen', r'exec[lv][ep]?'),
  833 + 'May run an executable file or a system command using Excel 4 Macros (XLM/XLF)':
  834 + (r'(?<!Could contain following functions: )EXEC',),
  835 + 'Could contain a function that allows to run an executable file or a system command using Excel 4 Macros (XLM/XLF)':
  836 + (r'Could contain following functions: EXEC',),
  837 + 'May call a DLL using Excel 4 Macros (XLM/XLF)':
  838 + (r'(?<!Could contain following functions: )REGISTER',),
  839 + 'Could contain a function that allows to call a DLL using Excel 4 Macros (XLM/XLF)':
  840 + (r'Could contain following functions: REGISTER',),
816 841 }
817 842  
818 843 # Suspicious Keywords to be searched for directly as strings, without regex
... ... @@ -842,6 +867,9 @@ URL_PATH = r&#39;(?:/[a-zA-Z0-9\-\._\?\,\&#39;/\\\+&amp;%\$#\=~]*)?&#39; # [^\.\,\)\(\s&quot;]
842 867 URL_RE = SCHEME + r'\://' + SERVER_PORT + URL_PATH
843 868 re_url = re.compile(URL_RE)
844 869  
  870 +EXCLUDE_URLS_PATTERNS = ["http://schemas.openxmlformats.org/",
  871 + "http://schemas.microsoft.com/",
  872 + ]
845 873  
846 874 # Patterns to be extracted (IP addresses, URLs, etc)
847 875 # From patterns.py in balbuzard
... ... @@ -2208,7 +2236,11 @@ def detect_patterns(vba_code, obfuscation=None):
2208 2236 for pattern_type, pattern_re in RE_PATTERNS:
2209 2237 for match in pattern_re.finditer(vba_code):
2210 2238 value = match.group()
2211   - if value not in found:
  2239 + exclude_pattern_found = False
  2240 + for url_exclude_pattern in EXCLUDE_URLS_PATTERNS:
  2241 + if value.startswith(url_exclude_pattern):
  2242 + exclude_pattern_found = True
  2243 + if value not in found and not exclude_pattern_found:
2212 2244 results.append((pattern_type + obf_text, value))
2213 2245 found.add(value)
2214 2246 return results
... ... @@ -2694,6 +2726,10 @@ class VBA_Parser(object):
2694 2726 self.pcodedmp_output = None
2695 2727 #: Flag set to True/False if VBA stomping detected
2696 2728 self.vba_stomping_detected = None
  2729 + # will be set to True or False by detect_is_encrypted method
  2730 + self.is_encrypted = False
  2731 + self.xlm_macrosheet_found = False
  2732 + self.template_injection_found = False
2697 2733  
2698 2734 # if filename is None:
2699 2735 # if isinstance(_file, basestring):
... ... @@ -2779,7 +2815,6 @@ class VBA_Parser(object):
2779 2815 log.info('Failed OLE parsing for file %r (%s)' % (self.filename, exc))
2780 2816 log.debug('Trace:', exc_info=True)
2781 2817  
2782   -
2783 2818 def open_openxml(self, _file):
2784 2819 """
2785 2820 Open an OpenXML file
... ... @@ -2797,9 +2832,42 @@ class VBA_Parser(object):
2797 2832 #TODO: if the zip file is encrypted, suggest to use the -z option, or try '-z infected' automatically
2798 2833 # check each file within the zip if it is an OLE file, by reading its magic:
2799 2834 for subfile in z.namelist():
  2835 + log.debug("subfile {}".format(subfile))
2800 2836 with z.open(subfile) as file_handle:
  2837 + found_ole = False
  2838 + template_injection_detected = False
  2839 + xml_macrosheet_found = False
2801 2840 magic = file_handle.read(len(olefile.MAGIC))
2802   - if magic == olefile.MAGIC:
  2841 + if magic == olefile.MAGIC:
  2842 + found_ole = True
  2843 + # in case we did not find an OLE file,
  2844 + # there could be a XLM macrosheet or a template injection attempt
  2845 + if not found_ole:
  2846 + read_all_file = file_handle.read()
  2847 + # try to detect template injection attempt
  2848 + # https://ired.team/offensive-security/initial-access/phishing-with-ms-office/inject-macros-from-a-remote-dotm-template-docx-with-macros
  2849 + subfile_that_can_contain_templates = "word/_rels/settings.xml.rels"
  2850 + if subfile == subfile_that_can_contain_templates:
  2851 + regex_template = b"Type=\"http://schemas\.openxmlformats\.org/officeDocument/\d{4}/relationships/attachedTemplate\"\s+Target=\"(.+?)\""
  2852 + template_injection_found = re.search(regex_template, read_all_file)
  2853 + if template_injection_found:
  2854 + injected_template_url = template_injection_found.group(1).decode()
  2855 + message = "Found injected template in subfile {}. Template URL: {}"\
  2856 + "".format(subfile_that_can_contain_templates, injected_template_url)
  2857 + log.info(message)
  2858 + template_injection_detected = True
  2859 + self.template_injection_found = True
  2860 + # try to find a XML macrosheet
  2861 + macro_sheet_footer = b"</xm:macrosheet>"
  2862 + len_macro_sheet_footer = len(macro_sheet_footer)
  2863 + last_bytes_to_check = read_all_file[-len_macro_sheet_footer:]
  2864 + if last_bytes_to_check == macro_sheet_footer:
  2865 + message = "Found XLM Macro in subfile: {}".format(subfile)
  2866 + log.info(message)
  2867 + xml_macrosheet_found = True
  2868 + self.xlm_macrosheet_found = True
  2869 +
  2870 + if found_ole or xml_macrosheet_found or template_injection_detected:
2803 2871 log.debug('Opening OLE file %s within zip' % subfile)
2804 2872 with z.open(subfile) as file_handle:
2805 2873 ole_data = file_handle.read()
... ... @@ -3196,6 +3264,7 @@ class VBA_Parser(object):
3196 3264  
3197 3265 :return: bool, True if at least one VBA project has been found, False otherwise
3198 3266 """
  3267 + log.debug("detect vba macros")
3199 3268 #TODO: return None or raise exception if format not supported
3200 3269 #TODO: return the number of VBA projects found instead of True/False?
3201 3270 # if this method was already called, return the previous result:
... ... @@ -3204,6 +3273,7 @@ class VBA_Parser(object):
3204 3273 # if OpenXML/PPT, check all the OLE subfiles:
3205 3274 if self.ole_file is None:
3206 3275 for ole_subfile in self.ole_subfiles:
  3276 + log.debug("ole subfile {}".format(ole_subfile))
3207 3277 ole_subfile.no_xlm = self.no_xlm
3208 3278 if ole_subfile.detect_vba_macros():
3209 3279 self.contains_macros = True
... ... @@ -3252,6 +3322,7 @@ class VBA_Parser(object):
3252 3322 return self.contains_macros
3253 3323  
3254 3324 def detect_xlm_macros(self):
  3325 + log.debug("detect xlm macros")
3255 3326 # if this is a SLK file, the analysis was done in open_slk:
3256 3327 if self.type == TYPE_SLK:
3257 3328 return self.contains_macros
... ... @@ -3286,6 +3357,20 @@ class VBA_Parser(object):
3286 3357 log.exception('Error when running oledump.plugin_biff, please report to %s' % URL_OLEVBA_ISSUES)
3287 3358 return False
3288 3359  
  3360 + def detect_is_encrypted(self):
  3361 + if self.ole_file:
  3362 + self.is_encrypted = crypto.is_encrypted(self.ole_file)
  3363 + return self.is_encrypted
  3364 +
  3365 + def decrypt_file(self, passwords_list=None):
  3366 + decrypted_file = None
  3367 + if self.detect_is_encrypted():
  3368 + passwords = crypto.DEFAULT_PASSWORDS
  3369 + if passwords_list and isinstance(passwords_list, list):
  3370 + passwords.extend(passwords_list)
  3371 + decrypted_file = crypto.decrypt(self.filename, passwords)
  3372 +
  3373 + return decrypted_file
3289 3374  
3290 3375 def encode_string(self, unicode_str):
3291 3376 """
... ... @@ -3457,6 +3542,19 @@ class VBA_Parser(object):
3457 3542 'this may have been used to hide malicious code'
3458 3543 scanner.suspicious_keywords.append((keyword, description))
3459 3544 scanner.results.append(('Suspicious', keyword, description))
  3545 + if self.xlm_macrosheet_found:
  3546 + log.debug('adding XLM macrosheet found to suspicious keywords')
  3547 + keyword = 'XLM macrosheet'
  3548 + description = 'XLM macrosheet found. It could contain malicious code'
  3549 + scanner.suspicious_keywords.append((keyword, description))
  3550 + scanner.results.append(('Suspicious', keyword, description))
  3551 + if self.template_injection_found:
  3552 + log.debug('adding Template Injection to suspicious keywords')
  3553 + keyword = 'Template Injection'
  3554 + description = 'Template injection found. A malicious template could have been uploaded ' \
  3555 + 'from a remote location'
  3556 + scanner.suspicious_keywords.append((keyword, description))
  3557 + scanner.results.append(('Suspicious', keyword, description))
3460 3558 autoexec, suspicious, iocs, hexstrings, base64strings, dridex, vbastrings = scanner.scan_summary()
3461 3559 self.nb_autoexec += autoexec
3462 3560 self.nb_suspicious += suspicious
... ...