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,6 +636,9 @@ AUTOEXEC_KEYWORDS = {
636 # TODO: "Auto_Ope" is temporarily here because of a bug in plugin_biff, which misses the last byte in "Auto_Open"... 636 # TODO: "Auto_Ope" is temporarily here because of a bug in plugin_biff, which misses the last byte in "Auto_Open"...
637 'Runs when the Excel Workbook is closed': 637 'Runs when the Excel Workbook is closed':
638 ('Auto_Close', 'Workbook_Close'), 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 # Keywords to detect auto-executable macros 644 # Keywords to detect auto-executable macros
@@ -652,15 +655,17 @@ AUTOEXEC_KEYWORDS_REGEX = { @@ -652,15 +655,17 @@ AUTOEXEC_KEYWORDS_REGEX = {
652 r'\w+_FileDownload', r'\w+_NavigateComplete2', r'\w+_NavigateError', 655 r'\w+_FileDownload', r'\w+_NavigateComplete2', r'\w+_NavigateError',
653 r'\w+_ProgressChange', r'\w+_PropertyChange', r'\w+_SetSecureLockIcon', 656 r'\w+_ProgressChange', r'\w+_PropertyChange', r'\w+_SetSecureLockIcon',
654 r'\w+_StatusTextChange', r'\w+_TitleChange', r'\w+_MouseMove', r'\w+_MouseEnter', 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 # Suspicious Keywords that may be used by malware 661 # Suspicious Keywords that may be used by malware
659 # See VBA language reference: http://msdn.microsoft.com/en-us/library/office/jj692818%28v=office.15%29.aspx 662 # See VBA language reference: http://msdn.microsoft.com/en-us/library/office/jj692818%28v=office.15%29.aspx
660 SUSPICIOUS_KEYWORDS = { 663 SUSPICIOUS_KEYWORDS = {
661 #TODO: use regex to support variable whitespaces 664 #TODO: use regex to support variable whitespaces
  665 + #http://www.certego.net/en/news/advanced-vba-macros/
662 'May read system environment variables': 666 'May read system environment variables':
663 - ('Environ',), 667 + ('Environ','Win32_Environment','Environment','ExpandEnvironmentStrings','HKCU\Environment',
  668 + 'HKEY_CURRENT_USER\Environment'),
664 'May open a file': 669 'May open a file':
665 ('Open',), 670 ('Open',),
666 'May write to a file (if combined with Open)': 671 'May write to a file (if combined with Open)':
@@ -670,22 +675,35 @@ SUSPICIOUS_KEYWORDS = { @@ -670,22 +675,35 @@ SUSPICIOUS_KEYWORDS = {
670 #TODO: regex to find Open+Binary on same line 675 #TODO: regex to find Open+Binary on same line
671 ('Binary',), 676 ('Binary',),
672 'May copy a file': 677 'May copy a file':
673 - ('FileCopy', 'CopyFile'), 678 + ('FileCopy', 'CopyFile','CopyHere','CopyFolder'),
674 #FileCopy: http://msdn.microsoft.com/en-us/library/office/gg264390%28v=office.15%29.aspx 679 #FileCopy: http://msdn.microsoft.com/en-us/library/office/gg264390%28v=office.15%29.aspx
675 #CopyFile: http://msdn.microsoft.com/en-us/library/office/gg264089%28v=office.15%29.aspx 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 'May delete a file': 684 'May delete a file':
677 ('Kill',), 685 ('Kill',),
678 'May create a text file': 686 'May create a text file':
679 ('CreateTextFile', 'ADODB.Stream', 'WriteText', 'SaveToFile'), 687 ('CreateTextFile', 'ADODB.Stream', 'WriteText', 'SaveToFile'),
680 #CreateTextFile: http://msdn.microsoft.com/en-us/library/office/gg264617%28v=office.15%29.aspx 688 #CreateTextFile: http://msdn.microsoft.com/en-us/library/office/gg264617%28v=office.15%29.aspx
681 #ADODB.Stream sample: http://pastebin.com/Z4TMyuq6 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 'May run an executable file or a system command': 693 'May run an executable file or a system command':
684 ('Shell', 'vbNormal', 'vbNormalFocus', 'vbHide', 'vbMinimizedFocus', 'vbMaximizedFocus', 'vbNormalNoFocus', 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 # MacScript: see https://msdn.microsoft.com/en-us/library/office/gg264812.aspx 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 'May run an executable file or a system command on a Mac': 705 'May run an executable file or a system command on a Mac':
688 - ('MacScript',), 706 + ('MacScript','AppleScript'),
689 #Shell: http://msdn.microsoft.com/en-us/library/office/gg278437%28v=office.15%29.aspx 707 #Shell: http://msdn.microsoft.com/en-us/library/office/gg278437%28v=office.15%29.aspx
690 #WScript.Shell+Run sample: http://pastebin.com/Z4TMyuq6 708 #WScript.Shell+Run sample: http://pastebin.com/Z4TMyuq6
691 'May run PowerShell commands': 709 'May run PowerShell commands':
@@ -698,10 +716,8 @@ SUSPICIOUS_KEYWORDS = { @@ -698,10 +716,8 @@ SUSPICIOUS_KEYWORDS = {
698 'invoke-command', 'scriptblock', 'Invoke-Expression', 'AuthorizationManager'), 716 'invoke-command', 'scriptblock', 'Invoke-Expression', 'AuthorizationManager'),
699 'May run an executable file or a system command using PowerShell': 717 'May run an executable file or a system command using PowerShell':
700 ('Start-Process',), 718 ('Start-Process',),
701 - 'May run an executable file or a system command using Excel 4 Macros (XLM/XLF)':  
702 - ('EXEC',),  
703 'May call a DLL using Excel 4 Macros (XLM/XLF)': 719 'May call a DLL using Excel 4 Macros (XLM/XLF)':
704 - ('REGISTER', 'CALL'), 720 + ('CALL',),
705 'May hide the application': 721 'May hide the application':
706 ('Application.Visible', 'ShowWindow', 'SW_HIDE'), 722 ('Application.Visible', 'ShowWindow', 'SW_HIDE'),
707 'May create a directory': 723 'May create a directory':
@@ -713,6 +729,9 @@ SUSPICIOUS_KEYWORDS = { @@ -713,6 +729,9 @@ SUSPICIOUS_KEYWORDS = {
713 ('Application.AltStartupPath',), 729 ('Application.AltStartupPath',),
714 'May create an OLE object': 730 'May create an OLE object':
715 ('CreateObject',), 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 'May create an OLE object using PowerShell': 735 'May create an OLE object using PowerShell':
717 ('New-Object',), 736 ('New-Object',),
718 'May run an application (if combined with CreateObject)': 737 'May run an application (if combined with CreateObject)':
@@ -727,8 +746,6 @@ SUSPICIOUS_KEYWORDS = { @@ -727,8 +746,6 @@ SUSPICIOUS_KEYWORDS = {
727 'May run code from a library on a Mac': 746 'May run code from a library on a Mac':
728 #TODO: regex to find declare+lib on same line - see mraptor 747 #TODO: regex to find declare+lib on same line - see mraptor
729 ('libc.dylib', 'dylib'), 748 ('libc.dylib', 'dylib'),
730 - 'May run code from a DLL using Excel 4 Macros (XLM/XLF)':  
731 - ('REGISTER',),  
732 'May inject code into another process': 749 'May inject code into another process':
733 ('CreateThread', 'CreateUserThread', 'VirtualAlloc', # (issue #9) suggested by Davy Douhine - used by MSF payload 750 ('CreateThread', 'CreateUserThread', 'VirtualAlloc', # (issue #9) suggested by Davy Douhine - used by MSF payload
734 'VirtualAllocEx', 'RtlMoveMemory', 'WriteProcessMemory', 751 'VirtualAllocEx', 'RtlMoveMemory', 'WriteProcessMemory',
@@ -813,6 +830,14 @@ SUSPICIOUS_KEYWORDS_REGEX = { @@ -813,6 +830,14 @@ SUSPICIOUS_KEYWORDS_REGEX = {
813 ), 830 ),
814 'May run an executable file or a system command on a Mac (if combined with libc.dylib)': 831 'May run an executable file or a system command on a Mac (if combined with libc.dylib)':
815 ('system', 'popen', r'exec[lv][ep]?'), 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 # Suspicious Keywords to be searched for directly as strings, without regex 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,6 +867,9 @@ URL_PATH = r&#39;(?:/[a-zA-Z0-9\-\._\?\,\&#39;/\\\+&amp;%\$#\=~]*)?&#39; # [^\.\,\)\(\s&quot;]
842 URL_RE = SCHEME + r'\://' + SERVER_PORT + URL_PATH 867 URL_RE = SCHEME + r'\://' + SERVER_PORT + URL_PATH
843 re_url = re.compile(URL_RE) 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 # Patterns to be extracted (IP addresses, URLs, etc) 874 # Patterns to be extracted (IP addresses, URLs, etc)
847 # From patterns.py in balbuzard 875 # From patterns.py in balbuzard
@@ -2208,7 +2236,11 @@ def detect_patterns(vba_code, obfuscation=None): @@ -2208,7 +2236,11 @@ def detect_patterns(vba_code, obfuscation=None):
2208 for pattern_type, pattern_re in RE_PATTERNS: 2236 for pattern_type, pattern_re in RE_PATTERNS:
2209 for match in pattern_re.finditer(vba_code): 2237 for match in pattern_re.finditer(vba_code):
2210 value = match.group() 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 results.append((pattern_type + obf_text, value)) 2244 results.append((pattern_type + obf_text, value))
2213 found.add(value) 2245 found.add(value)
2214 return results 2246 return results
@@ -2694,6 +2726,10 @@ class VBA_Parser(object): @@ -2694,6 +2726,10 @@ class VBA_Parser(object):
2694 self.pcodedmp_output = None 2726 self.pcodedmp_output = None
2695 #: Flag set to True/False if VBA stomping detected 2727 #: Flag set to True/False if VBA stomping detected
2696 self.vba_stomping_detected = None 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 # if filename is None: 2734 # if filename is None:
2699 # if isinstance(_file, basestring): 2735 # if isinstance(_file, basestring):
@@ -2779,7 +2815,6 @@ class VBA_Parser(object): @@ -2779,7 +2815,6 @@ class VBA_Parser(object):
2779 log.info('Failed OLE parsing for file %r (%s)' % (self.filename, exc)) 2815 log.info('Failed OLE parsing for file %r (%s)' % (self.filename, exc))
2780 log.debug('Trace:', exc_info=True) 2816 log.debug('Trace:', exc_info=True)
2781 2817
2782 -  
2783 def open_openxml(self, _file): 2818 def open_openxml(self, _file):
2784 """ 2819 """
2785 Open an OpenXML file 2820 Open an OpenXML file
@@ -2797,9 +2832,42 @@ class VBA_Parser(object): @@ -2797,9 +2832,42 @@ class VBA_Parser(object):
2797 #TODO: if the zip file is encrypted, suggest to use the -z option, or try '-z infected' automatically 2832 #TODO: if the zip file is encrypted, suggest to use the -z option, or try '-z infected' automatically
2798 # check each file within the zip if it is an OLE file, by reading its magic: 2833 # check each file within the zip if it is an OLE file, by reading its magic:
2799 for subfile in z.namelist(): 2834 for subfile in z.namelist():
  2835 + log.debug("subfile {}".format(subfile))
2800 with z.open(subfile) as file_handle: 2836 with z.open(subfile) as file_handle:
  2837 + found_ole = False
  2838 + template_injection_detected = False
  2839 + xml_macrosheet_found = False
2801 magic = file_handle.read(len(olefile.MAGIC)) 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 log.debug('Opening OLE file %s within zip' % subfile) 2871 log.debug('Opening OLE file %s within zip' % subfile)
2804 with z.open(subfile) as file_handle: 2872 with z.open(subfile) as file_handle:
2805 ole_data = file_handle.read() 2873 ole_data = file_handle.read()
@@ -3196,6 +3264,7 @@ class VBA_Parser(object): @@ -3196,6 +3264,7 @@ class VBA_Parser(object):
3196 3264
3197 :return: bool, True if at least one VBA project has been found, False otherwise 3265 :return: bool, True if at least one VBA project has been found, False otherwise
3198 """ 3266 """
  3267 + log.debug("detect vba macros")
3199 #TODO: return None or raise exception if format not supported 3268 #TODO: return None or raise exception if format not supported
3200 #TODO: return the number of VBA projects found instead of True/False? 3269 #TODO: return the number of VBA projects found instead of True/False?
3201 # if this method was already called, return the previous result: 3270 # if this method was already called, return the previous result:
@@ -3204,6 +3273,7 @@ class VBA_Parser(object): @@ -3204,6 +3273,7 @@ class VBA_Parser(object):
3204 # if OpenXML/PPT, check all the OLE subfiles: 3273 # if OpenXML/PPT, check all the OLE subfiles:
3205 if self.ole_file is None: 3274 if self.ole_file is None:
3206 for ole_subfile in self.ole_subfiles: 3275 for ole_subfile in self.ole_subfiles:
  3276 + log.debug("ole subfile {}".format(ole_subfile))
3207 ole_subfile.no_xlm = self.no_xlm 3277 ole_subfile.no_xlm = self.no_xlm
3208 if ole_subfile.detect_vba_macros(): 3278 if ole_subfile.detect_vba_macros():
3209 self.contains_macros = True 3279 self.contains_macros = True
@@ -3252,6 +3322,7 @@ class VBA_Parser(object): @@ -3252,6 +3322,7 @@ class VBA_Parser(object):
3252 return self.contains_macros 3322 return self.contains_macros
3253 3323
3254 def detect_xlm_macros(self): 3324 def detect_xlm_macros(self):
  3325 + log.debug("detect xlm macros")
3255 # if this is a SLK file, the analysis was done in open_slk: 3326 # if this is a SLK file, the analysis was done in open_slk:
3256 if self.type == TYPE_SLK: 3327 if self.type == TYPE_SLK:
3257 return self.contains_macros 3328 return self.contains_macros
@@ -3286,6 +3357,20 @@ class VBA_Parser(object): @@ -3286,6 +3357,20 @@ class VBA_Parser(object):
3286 log.exception('Error when running oledump.plugin_biff, please report to %s' % URL_OLEVBA_ISSUES) 3357 log.exception('Error when running oledump.plugin_biff, please report to %s' % URL_OLEVBA_ISSUES)
3287 return False 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 def encode_string(self, unicode_str): 3375 def encode_string(self, unicode_str):
3291 """ 3376 """
@@ -3457,6 +3542,19 @@ class VBA_Parser(object): @@ -3457,6 +3542,19 @@ class VBA_Parser(object):
3457 'this may have been used to hide malicious code' 3542 'this may have been used to hide malicious code'
3458 scanner.suspicious_keywords.append((keyword, description)) 3543 scanner.suspicious_keywords.append((keyword, description))
3459 scanner.results.append(('Suspicious', keyword, description)) 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 autoexec, suspicious, iocs, hexstrings, base64strings, dridex, vbastrings = scanner.scan_summary() 3558 autoexec, suspicious, iocs, hexstrings, base64strings, dridex, vbastrings = scanner.scan_summary()
3461 self.nb_autoexec += autoexec 3559 self.nb_autoexec += autoexec
3462 self.nb_suspicious += suspicious 3560 self.nb_suspicious += suspicious