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 312 from oletools import ppt_parser
313 313 from oletools import oleform
314 314 from oletools import rtfobj
315   -from oletools import oleid
316   -from oletools.common.errors import FileIsEncryptedError
  315 +from oletools import crypto
317 316 from oletools.common import codepages
318 317  
319 318 # monkeypatch email to fix issue #32:
... ... @@ -2585,12 +2584,6 @@ class VBA_Parser(object):
2585 2584 # This looks like an OLE file
2586 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 2587 # if this worked, try whether it is a ppt file (special ole file)
2595 2588 self.open_ppt()
2596 2589 if self.type is None and zipfile.is_zipfile(_file):
... ... @@ -3738,6 +3731,10 @@ def parse_args(cmd_line_args=None):
3738 3731 help='find files recursively in subdirectories.')
3739 3732 parser.add_option("-z", "--zip", dest='zip_password', type='str', default=None,
3740 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 3738 parser.add_option("-f", "--zipfname", dest='zip_fname', type='str', default='*',
3742 3739 help='if the file is a zip archive, file(s) to be opened within the zip. Wildcards * and ? are supported. (default:*)')
3743 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 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 3789 Part of main function that processes a single file.
3793 3790  
... ... @@ -3821,47 +3818,70 @@ def process_file(filename, data, container, options):
3821 3818 else: # (should be impossible)
3822 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 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 3887 def main(cmd_line_args=None):
... ...