Commit d966bbbac88bc5ad4d1fcff7dffcc25fb89e6856

Authored by Christian Herdtweck
1 parent d993998a

olevba: integrate crypto

Showing 1 changed file with 69 additions and 49 deletions
oletools/olevba.py
@@ -312,8 +312,7 @@ from pyparsing import \ @@ -312,8 +312,7 @@ from pyparsing import \
312 from oletools import ppt_parser 312 from oletools import ppt_parser
313 from oletools import oleform 313 from oletools import oleform
314 from oletools import rtfobj 314 from oletools import rtfobj
315 -from oletools import oleid  
316 -from oletools.common.errors import FileIsEncryptedError 315 +from oletools import crypto
317 from oletools.common import codepages 316 from oletools.common import codepages
318 317
319 # monkeypatch email to fix issue #32: 318 # monkeypatch email to fix issue #32:
@@ -2585,12 +2584,6 @@ class VBA_Parser(object): @@ -2585,12 +2584,6 @@ class VBA_Parser(object):
2585 # This looks like an OLE file 2584 # This looks like an OLE file
2586 self.open_ole(_file) 2585 self.open_ole(_file)
2587 2586
2588 - # check whether file is encrypted (need to do this before try ppt)  
2589 - log.debug('Check encryption of ole file')  
2590 - crypt_indicator = oleid.OleID(self.ole_file).check_encrypted()  
2591 - if crypt_indicator.value:  
2592 - raise FileIsEncryptedError(filename)  
2593 -  
2594 # if this worked, try whether it is a ppt file (special ole file) 2587 # if this worked, try whether it is a ppt file (special ole file)
2595 self.open_ppt() 2588 self.open_ppt()
2596 if self.type is None and zipfile.is_zipfile(_file): 2589 if self.type is None and zipfile.is_zipfile(_file):
@@ -3738,6 +3731,10 @@ def parse_args(cmd_line_args=None): @@ -3738,6 +3731,10 @@ def parse_args(cmd_line_args=None):
3738 help='find files recursively in subdirectories.') 3731 help='find files recursively in subdirectories.')
3739 parser.add_option("-z", "--zip", dest='zip_password', type='str', default=None, 3732 parser.add_option("-z", "--zip", dest='zip_password', type='str', default=None,
3740 help='if the file is a zip archive, open all files from it, using the provided password.') 3733 help='if the file is a zip archive, open all files from it, using the provided password.')
  3734 + parser.add_option("-p", "--password", type='str', action='append',
  3735 + default=[],
  3736 + help='if encrypted office files are encountered, try '
  3737 + 'decryption with this password. May be repeated.')
3741 parser.add_option("-f", "--zipfname", dest='zip_fname', type='str', default='*', 3738 parser.add_option("-f", "--zipfname", dest='zip_fname', type='str', default='*',
3742 help='if the file is a zip archive, file(s) to be opened within the zip. Wildcards * and ? are supported. (default:*)') 3739 help='if the file is a zip archive, file(s) to be opened within the zip. Wildcards * and ? are supported. (default:*)')
3743 # output mode; could make this even simpler with add_option(type='choice') but that would make 3740 # output mode; could make this even simpler with add_option(type='choice') but that would make
@@ -3787,7 +3784,7 @@ def parse_args(cmd_line_args=None): @@ -3787,7 +3784,7 @@ def parse_args(cmd_line_args=None):
3787 return options, args 3784 return options, args
3788 3785
3789 3786
3790 -def process_file(filename, data, container, options): 3787 +def process_file(filename, data, container, options, crypto_nesting=0):
3791 """ 3788 """
3792 Part of main function that processes a single file. 3789 Part of main function that processes a single file.
3793 3790
@@ -3821,47 +3818,70 @@ def process_file(filename, data, container, options): @@ -3821,47 +3818,70 @@ def process_file(filename, data, container, options):
3821 else: # (should be impossible) 3818 else: # (should be impossible)
3822 raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode)) 3819 raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode))
3823 3820
3824 - except (SubstreamOpenError, UnexpectedDataError) as exc:  
3825 - if options.output_mode == 'triage':  
3826 - print('%-12s %s - Error opening substream or uenxpected ' \  
3827 - 'content' % ('?', filename))  
3828 - elif options.output_mode == 'json':  
3829 - print_json(file=filename, type='error',  
3830 - error=type(exc).__name__, message=str(exc))  
3831 - else:  
3832 - log.exception('Error opening substream or unexpected '  
3833 - 'content in %s' % filename)  
3834 - return RETURN_OPEN_ERROR  
3835 - except FileOpenError as exc:  
3836 - if options.output_mode == 'triage':  
3837 - print('%-12s %s - File format not supported' % ('?', filename))  
3838 - elif options.output_mode == 'json':  
3839 - print_json(file=filename, type='error',  
3840 - error=type(exc).__name__, message=str(exc))  
3841 - else:  
3842 - log.exception('Failed to open %s -- probably not supported!' % filename)  
3843 - return RETURN_OPEN_ERROR  
3844 - except ProcessingError as exc:  
3845 - if options.output_mode == 'triage':  
3846 - print('%-12s %s - %s' % ('!ERROR', filename, exc.orig_exc))  
3847 - elif options.output_mode == 'json':  
3848 - print_json(file=filename, type='error',  
3849 - error=type(exc).__name__,  
3850 - message=str(exc.orig_exc))  
3851 - else:  
3852 - log.exception('Error processing file %s (%s)!'  
3853 - % (filename, exc.orig_exc))  
3854 - return RETURN_PARSE_ERROR  
3855 - except FileIsEncryptedError as exc:  
3856 - if options.output_mode == 'triage':  
3857 - print('%-12s %s - File is encrypted' % ('!ERROR', filename))  
3858 - elif options.output_mode == 'json':  
3859 - print_json(file=filename, type='error',  
3860 - error=type(exc).__name__, message=str(exc)) 3821 + # even if processing succeeds, file might still be encrypted
  3822 + log.debug('Checking for encryption')
  3823 + if not crypto.is_encrypted(filename):
  3824 + return RETURN_OK
  3825 + except Exception as exc:
  3826 + log.debug('Checking for encryption')
  3827 + if crypto.is_encrypted(filename):
  3828 + pass # deal with this below
3861 else: 3829 else:
3862 - log.exception('File %s is encrypted!' % (filename))  
3863 - return RETURN_ENCRYPTED  
3864 - return RETURN_OK 3830 + if isinstance(exc, (SubstreamOpenError, UnexpectedDataError)):
  3831 + if options.output_mode in ('triage', 'unspecified'):
  3832 + print('%-12s %s - Error opening substream or uenxpected ' \
  3833 + 'content' % ('?', filename))
  3834 + elif options.output_mode == 'json':
  3835 + print_json(file=filename, type='error',
  3836 + error=type(exc).__name__, message=str(exc))
  3837 + else:
  3838 + log.exception('Error opening substream or unexpected '
  3839 + 'content in %s' % filename)
  3840 + return RETURN_OPEN_ERROR
  3841 + elif isinstance(exc, FileOpenError):
  3842 + if options.output_mode in ('triage', 'unspecified'):
  3843 + print('%-12s %s - File format not supported' % ('?', filename))
  3844 + elif options.output_mode == 'json':
  3845 + print_json(file=filename, type='error',
  3846 + error=type(exc).__name__, message=str(exc))
  3847 + else:
  3848 + log.exception('Failed to open %s -- probably not supported!' % filename)
  3849 + return RETURN_OPEN_ERROR
  3850 + elif isinstance(exc, ProcessingError):
  3851 + if options.output_mode in ('triage', 'unspecified'):
  3852 + print('%-12s %s - %s' % ('!ERROR', filename, exc.orig_exc))
  3853 + elif options.output_mode == 'json':
  3854 + print_json(file=filename, type='error',
  3855 + error=type(exc).__name__,
  3856 + message=str(exc.orig_exc))
  3857 + else:
  3858 + log.exception('Error processing file %s (%s)!'
  3859 + % (filename, exc.orig_exc))
  3860 + return RETURN_PARSE_ERROR
  3861 + else:
  3862 + raise # let caller deal with this
  3863 +
  3864 + # we reach this point only if file is encrypted
  3865 + # check if this is an encrypted file in an encrypted file in an ...
  3866 + if crypto_nesting >= crypto.MAX_NESTING_DEPTH:
  3867 + raise crypto.MaxCryptoNestingReached(crypto_nesting, filename)
  3868 +
  3869 + decrypted_file = None
  3870 + try:
  3871 + log.debug('Checking encryption passwords {}'.format(options.password))
  3872 + passwords = options.password + \
  3873 + [crypto.WRITE_PROTECT_ENCRYPTION_PASSWORD, ]
  3874 + decrypted_file = crypto.decrypt(filename, passwords)
  3875 + if not decrypted_file:
  3876 + raise crypto.WrongEncryptionPassword(filename)
  3877 + log.info('Working on decrypted file')
  3878 + return process_file(decrypted_file, data, container or filename,
  3879 + options, crypto_nesting+1)
  3880 + except Exception:
  3881 + raise
  3882 + finally: # clean up
  3883 + if decrypted_file is not None and os.path.isfile(decrypted_file):
  3884 + os.unlink(decrypted_file)
3865 3885
3866 3886
3867 def main(cmd_line_args=None): 3887 def main(cmd_line_args=None):