Commit d993998ada1eadd166f3408a0f6a516adb87bd06

Authored by Christian Herdtweck
1 parent 1f04b96d

olevba: move processing & error handling from main to separate function

Code is almost functionally identical.
Only difference is that iteration of xglob is saved in a tuple to
determine beforehand whether there is only one single file or several.
This allows choosing the output if handling a single file with unspecified
output mode and thus greatly simplifies handling of vba_parser
Showing 1 changed file with 116 additions and 102 deletions
oletools/olevba.py
... ... @@ -3787,6 +3787,83 @@ def parse_args(cmd_line_args=None):
3787 3787 return options, args
3788 3788  
3789 3789  
  3790 +def process_file(filename, data, container, options):
  3791 + """
  3792 + Part of main function that processes a single file.
  3793 +
  3794 + This handles exceptions and encryption.
  3795 +
  3796 + Returns a single code summarizing the status of processing of this file
  3797 + """
  3798 + try:
  3799 + # Open the file
  3800 + vba_parser = VBA_Parser_CLI(filename, data=data, container=container,
  3801 + relaxed=options.relaxed)
  3802 +
  3803 + if options.output_mode == 'detailed':
  3804 + # fully detailed output
  3805 + vba_parser.process_file(show_decoded_strings=options.show_decoded_strings,
  3806 + display_code=options.display_code,
  3807 + hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only,
  3808 + show_deobfuscated_code=options.show_deobfuscated_code,
  3809 + deobfuscate=options.deobfuscate)
  3810 + elif options.output_mode == 'triage':
  3811 + # summarized output for triage:
  3812 + vba_parser.process_file_triage(show_decoded_strings=options.show_decoded_strings,
  3813 + deobfuscate=options.deobfuscate)
  3814 + elif options.output_mode == 'json':
  3815 + print_json(
  3816 + vba_parser.process_file_json(show_decoded_strings=options.show_decoded_strings,
  3817 + display_code=options.display_code,
  3818 + hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only,
  3819 + show_deobfuscated_code=options.show_deobfuscated_code,
  3820 + deobfuscate=options.deobfuscate))
  3821 + else: # (should be impossible)
  3822 + raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode))
  3823 +
  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))
  3861 + else:
  3862 + log.exception('File %s is encrypted!' % (filename))
  3863 + return RETURN_ENCRYPTED
  3864 + return RETURN_OK
  3865 +
  3866 +
3790 3867 def main(cmd_line_args=None):
3791 3868 """
3792 3869 Main function, called when olevba is run from the command line
... ... @@ -3821,35 +3898,44 @@ def main(cmd_line_args=None):
3821 3898 if options.output_mode == 'triage' and options.show_deobfuscated_code:
3822 3899 log.info('ignoring option --reveal in triage output mode')
3823 3900  
3824   - # Column headers (do not know how many files there will be yet, so if no output_mode
3825   - # was specified, we will print triage for first file --> need these headers)
3826   - if options.output_mode in ('triage', 'unspecified'):
  3901 + # gather info on all files that must be processed
  3902 + # ignore directory names stored in zip files:
  3903 + all_input_info = tuple((container, filename, data) for
  3904 + container, filename, data in xglob.iter_files(
  3905 + args, recursive=options.recursive,
  3906 + zip_password=options.zip_password,
  3907 + zip_fname=options.zip_fname)
  3908 + if not (container and filename.endswith('/')))
  3909 +
  3910 + # specify output mode if options -t, -d and -j were not specified
  3911 + if options.output_mode == 'unspecified':
  3912 + if len(all_input_info) == 1:
  3913 + options.output_mode = 'detailed'
  3914 + else:
  3915 + options.output_mode = 'triage'
  3916 +
  3917 + # Column headers for triage mode
  3918 + if options.output_mode == 'triage':
3827 3919 print('%-12s %-65s' % ('Flags', 'Filename'))
3828 3920 print('%-12s %-65s' % ('-' * 11, '-' * 65))
3829 3921  
3830 3922 previous_container = None
3831 3923 count = 0
3832 3924 container = filename = data = None
3833   - vba_parser = None
3834 3925 return_code = RETURN_OK
3835 3926 try:
3836   - for container, filename, data in xglob.iter_files(args, recursive=options.recursive,
3837   - zip_password=options.zip_password, zip_fname=options.zip_fname):
3838   - # ignore directory names stored in zip files:
3839   - if container and filename.endswith('/'):
3840   - continue
3841   -
  3927 + for container, filename, data in all_input_info:
3842 3928 # handle errors from xglob
3843 3929 if isinstance(data, Exception):
3844 3930 if isinstance(data, PathNotFoundException):
3845   - if options.output_mode in ('triage', 'unspecified'):
  3931 + if options.output_mode == 'triage':
3846 3932 print('%-12s %s - File not found' % ('?', filename))
3847 3933 elif options.output_mode != 'json':
3848 3934 log.error('Given path %r does not exist!' % filename)
3849 3935 return_code = RETURN_FILE_NOT_FOUND if return_code == 0 \
3850 3936 else RETURN_SEVERAL_ERRS
3851 3937 else:
3852   - if options.output_mode in ('triage', 'unspecified'):
  3938 + if options.output_mode == 'triage':
3853 3939 print('%-12s %s - Failed to read from zip file %s' % ('?', filename, container))
3854 3940 elif options.output_mode != 'json':
3855 3941 log.error('Exception opening/reading %r from zip file %r: %s'
... ... @@ -3861,102 +3947,30 @@ def main(cmd_line_args=None):
3861 3947 error=type(data).__name__, message=str(data))
3862 3948 continue
3863 3949  
3864   - try:
3865   - # close the previous file if analyzing several:
3866   - # (this must be done here to avoid closing the file if there is only 1,
3867   - # to fix issue #219)
3868   - if vba_parser is not None:
3869   - vba_parser.close()
3870   - # Open the file
3871   - vba_parser = VBA_Parser_CLI(filename, data=data, container=container,
3872   - relaxed=options.relaxed)
3873   -
3874   - if options.output_mode == 'detailed':
3875   - # fully detailed output
3876   - vba_parser.process_file(show_decoded_strings=options.show_decoded_strings,
3877   - display_code=options.display_code,
3878   - hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only,
3879   - show_deobfuscated_code=options.show_deobfuscated_code,
3880   - deobfuscate=options.deobfuscate)
3881   - elif options.output_mode in ('triage', 'unspecified'):
3882   - # print container name when it changes:
3883   - if container != previous_container:
3884   - if container is not None:
3885   - print('\nFiles in %s:' % container)
3886   - previous_container = container
3887   - # summarized output for triage:
3888   - vba_parser.process_file_triage(show_decoded_strings=options.show_decoded_strings,
3889   - deobfuscate=options.deobfuscate)
3890   - elif options.output_mode == 'json':
3891   - print_json(
3892   - vba_parser.process_file_json(show_decoded_strings=options.show_decoded_strings,
3893   - display_code=options.display_code,
3894   - hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only,
3895   - show_deobfuscated_code=options.show_deobfuscated_code,
3896   - deobfuscate=options.deobfuscate))
3897   - else: # (should be impossible)
3898   - raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode))
3899   - count += 1
3900   -
3901   - except (SubstreamOpenError, UnexpectedDataError) as exc:
3902   - if options.output_mode in ('triage', 'unspecified'):
3903   - print('%-12s %s - Error opening substream or uenxpected ' \
3904   - 'content' % ('?', filename))
3905   - elif options.output_mode == 'json':
3906   - print_json(file=filename, type='error',
3907   - error=type(exc).__name__, message=str(exc))
3908   - else:
3909   - log.exception('Error opening substream or unexpected '
3910   - 'content in %s' % filename)
3911   - return_code = RETURN_OPEN_ERROR if return_code == 0 \
3912   - else RETURN_SEVERAL_ERRS
3913   - except FileOpenError as exc:
3914   - if options.output_mode in ('triage', 'unspecified'):
3915   - print('%-12s %s - File format not supported' % ('?', filename))
3916   - elif options.output_mode == 'json':
3917   - print_json(file=filename, type='error',
3918   - error=type(exc).__name__, message=str(exc))
3919   - else:
3920   - log.exception('Failed to open %s -- probably not supported!' % filename)
3921   - return_code = RETURN_OPEN_ERROR if return_code == 0 \
3922   - else RETURN_SEVERAL_ERRS
3923   - except ProcessingError as exc:
3924   - if options.output_mode in ('triage', 'unspecified'):
3925   - print('%-12s %s - %s' % ('!ERROR', filename, exc.orig_exc))
3926   - elif options.output_mode == 'json':
3927   - print_json(file=filename, type='error',
3928   - error=type(exc).__name__,
3929   - message=str(exc.orig_exc))
3930   - else:
3931   - log.exception('Error processing file %s (%s)!'
3932   - % (filename, exc.orig_exc))
3933   - return_code = RETURN_PARSE_ERROR if return_code == 0 \
3934   - else RETURN_SEVERAL_ERRS
3935   - except FileIsEncryptedError as exc:
3936   - if options.output_mode in ('triage', 'unspecified'):
3937   - print('%-12s %s - File is encrypted' % ('!ERROR', filename))
3938   - elif options.output_mode == 'json':
3939   - print_json(file=filename, type='error',
3940   - error=type(exc).__name__, message=str(exc))
3941   - else:
3942   - log.exception('File %s is encrypted!' % (filename))
3943   - return_code = RETURN_ENCRYPTED if return_code == 0 \
3944   - else RETURN_SEVERAL_ERRS
3945   - # Here we do not close the vba_parser, because process_file may need it below.
  3950 + if options.output_mode == 'triage':
  3951 + # print container name when it changes:
  3952 + if container != previous_container:
  3953 + if container is not None:
  3954 + print('\nFiles in %s:' % container)
  3955 + previous_container = container
  3956 +
  3957 + # process the file, handling errors and encryption
  3958 + curr_return_code = process_file(filename, data, container, options)
  3959 + count += 1
  3960 +
  3961 + # adjust overall return code
  3962 + if curr_return_code == RETURN_OK:
  3963 + continue # do not modify overall return code
  3964 + if return_code == RETURN_OK:
  3965 + return_code = curr_return_code # first error return code
  3966 + else:
  3967 + return_code = RETURN_SEVERAL_ERRS # several errors
3946 3968  
3947 3969 if options.output_mode == 'triage':
3948 3970 print('\n(Flags: OpX=OpenXML, XML=Word2003XML, FlX=FlatOPC XML, MHT=MHTML, TXT=Text, M=Macros, ' \
3949 3971 'A=Auto-executable, S=Suspicious keywords, I=IOCs, H=Hex strings, ' \
3950 3972 'B=Base64 strings, D=Dridex strings, V=VBA strings, ?=Unknown)\n')
3951 3973  
3952   - if count == 1 and options.output_mode == 'unspecified':
3953   - # if options -t, -d and -j were not specified and it's a single file, print details:
3954   - vba_parser.process_file(show_decoded_strings=options.show_decoded_strings,
3955   - display_code=options.display_code,
3956   - hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only,
3957   - show_deobfuscated_code=options.show_deobfuscated_code,
3958   - deobfuscate=options.deobfuscate)
3959   -
3960 3974 if options.output_mode == 'json':
3961 3975 # print last json entry (a last one without a comma) and closing ]
3962 3976 print_json(type='MetaInformation', return_code=return_code,
... ...