Commit d966bbbac88bc5ad4d1fcff7dffcc25fb89e6856
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): | ... | ... |