From 40b99d6be4e62a43de6307fa5f2323cedf16bf27 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Mon, 27 May 2019 16:08:28 +0200 Subject: [PATCH 01/10] ppt_parser: fix type of decompressed data Another py2/py3-error: returned data is str in py2, bytes in py3 --- oletools/ppt_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oletools/ppt_parser.py b/oletools/ppt_parser.py index fa1fd29a..93b75a4b 100644 --- a/oletools/ppt_parser.py +++ b/oletools/ppt_parser.py @@ -1615,7 +1615,7 @@ def iterative_decompress(stream, size, chunk_size=4096): decompressor = zlib.decompressobj() n_read = 0 - decomp = '' + decomp = b'' return_err = None try: From 30c84ddf95c48cb83cb8948952cbe29c02296a19 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Thu, 1 Aug 2019 15:08:07 +0200 Subject: [PATCH 02/10] olevba: Inherit VBA_Parser options for sub-parsers When creating a sub-parser, should inherit all the options, not just a few. To avoid repeating this error, create method for sub-parser creation. --- oletools/olevba.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index 9e0ed8df..8fd40900 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -2734,9 +2734,7 @@ def open_openxml(self, _file): with z.open(subfile) as file_handle: ole_data = file_handle.read() try: - self.ole_subfiles.append( - VBA_Parser(filename=subfile, data=ole_data, - relaxed=self.relaxed)) + self.append_subfile(filename=subfile, data=ole_data) except OlevbaBaseException as exc: if self.relaxed: log.info('%s is not a valid OLE file (%s)' % (subfile, exc)) @@ -2785,9 +2783,7 @@ def open_word2003xml(self, data): # TODO: handle different offsets => separate function try: ole_data = mso_file_extract(mso_data) - self.ole_subfiles.append( - VBA_Parser(filename=fname, data=ole_data, - relaxed=self.relaxed)) + self.append_subfile(filename=fname, data=ole_data) except OlevbaBaseException as exc: if self.relaxed: log.info('Error parsing subfile {0}: {1}' @@ -2832,9 +2828,7 @@ def open_flatopc(self, data): for bindata in pkgpart.iterfind(TAG_PKGBINDATA): try: ole_data = binascii.a2b_base64(bindata.text) - self.ole_subfiles.append( - VBA_Parser(filename=fname, data=ole_data, - relaxed=self.relaxed)) + self.append_subfile(filename=fname, data=ole_data) except OlevbaBaseException as exc: if self.relaxed: log.info('Error parsing subfile {0}: {1}' @@ -2905,9 +2899,7 @@ def open_mht(self, data): # TODO: check if it is actually an OLE file # TODO: get the MSO filename from content_location? - self.ole_subfiles.append( - VBA_Parser(filename=fname, data=ole_data, - relaxed=self.relaxed)) + self.append_subfile(filename=fname, data=ole_data) except OlevbaBaseException as exc: if self.relaxed: log.info('%s does not contain a valid OLE file (%s)' @@ -2946,8 +2938,7 @@ def open_ppt(self): try: ppt = ppt_parser.PptParser(self.ole_file, fast_fail=True) for vba_data in ppt.iter_vba_data(): - self.ole_subfiles.append(VBA_Parser(None, vba_data, - container='PptParser')) + self.append_subfile(None, vba_data, container='PptParser') log.info('File is PPT') self.ole_file.close() # just in case self.ole_file = None # required to make other methods look at ole_subfiles @@ -2975,6 +2966,13 @@ def open_text(self, data): # set type only if parsing succeeds self.type = TYPE_TEXT + def append_subfile(self, filename, data, container=None): + """ + Create sub-parser for given subfile/data and append to subfiles. + """ + self.ole_subfiles.append(VBA_Parser(filename, data, container, + relaxed=self.relaxed, + encoding=self.encoding)) def find_vba_projects(self): """ From 49d8474ee0269ef0d9ca772627c8634e1857d2cc Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Fri, 2 Aug 2019 15:51:26 +0200 Subject: [PATCH 03/10] olevba: Remove unnecessary catch-raise Reported by pylint --- oletools/olevba.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index 8fd40900..4a46a21b 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -4101,8 +4101,6 @@ def process_file(filename, data, container, options, crypto_nesting=0): log.info('Working on decrypted file') return process_file(decrypted_file, data, container or filename, options, crypto_nesting+1) - except Exception: - raise finally: # clean up try: log.debug('Removing crypt temp file {}'.format(decrypted_file)) From f1ce6b68bd4ddc537ebe0ea4724269e7056a0872 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Thu, 1 Aug 2019 14:39:06 +0200 Subject: [PATCH 04/10] olevba: Update optparse to argparse --- oletools/olevba.py | 129 +++++++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 52 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index 4a46a21b..a10687c4 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -262,7 +262,7 @@ import math import zipfile import re -import optparse +import argparse import binascii import base64 import zlib @@ -523,7 +523,7 @@ def __init__(self, stream_path, variable, expected, value): # return codes RETURN_OK = 0 RETURN_WARNINGS = 1 # (reserved, not used yet) -RETURN_WRONG_ARGS = 2 # (fixed, built into optparse) +RETURN_WRONG_ARGS = 2 # (fixed, built into argparse) RETURN_FILE_NOT_FOUND = 3 RETURN_XGLOB_ERR = 4 RETURN_OPEN_ERROR = 5 @@ -3943,57 +3943,82 @@ def parse_args(cmd_line_args=None): } usage = 'usage: olevba [options] [filename2 ...]' - parser = optparse.OptionParser(usage=usage) - # parser.add_option('-o', '--outfile', dest='outfile', + parser = argparse.ArgumentParser(usage=usage) + parser.add_argument('filenames', nargs='*', help='Files to analyze') + # parser.add_argument('-o', '--outfile', dest='outfile', # help='output file') - # parser.add_option('-c', '--csv', dest='csv', + # parser.add_argument('-c', '--csv', dest='csv', # help='export results to a CSV file') - parser.add_option("-r", action="store_true", dest="recursive", - help='find files recursively in subdirectories.') - parser.add_option("-z", "--zip", dest='zip_password', type='str', default=None, - help='if the file is a zip archive, open all files from it, using the provided password.') - parser.add_option("-p", "--password", type='str', action='append', - default=[], - help='if encrypted office files are encountered, try ' - 'decryption with this password. May be repeated.') - parser.add_option("-f", "--zipfname", dest='zip_fname', type='str', default='*', - help='if the file is a zip archive, file(s) to be opened within the zip. Wildcards * and ? are supported. (default:*)') - # output mode; could make this even simpler with add_option(type='choice') but that would make - # cmd line interface incompatible... - modes = optparse.OptionGroup(parser, title='Output mode (mutually exclusive)') - modes.add_option("-t", '--triage', action="store_const", dest="output_mode", - const='triage', default='unspecified', - help='triage mode, display results as a summary table (default for multiple files)') - modes.add_option("-d", '--detailed', action="store_const", dest="output_mode", - const='detailed', default='unspecified', - help='detailed mode, display full results (default for single file)') - modes.add_option("-j", '--json', action="store_const", dest="output_mode", - const='json', default='unspecified', - help='json mode, detailed in json format (never default)') - parser.add_option_group(modes) - parser.add_option("-a", '--analysis', action="store_false", dest="display_code", default=True, - help='display only analysis results, not the macro source code') - parser.add_option("-c", '--code', action="store_true", dest="vba_code_only", default=False, - help='display only VBA source code, do not analyze it') - parser.add_option("--decode", action="store_true", dest="show_decoded_strings", - help='display all the obfuscated strings with their decoded content (Hex, Base64, StrReverse, Dridex, VBA).') - parser.add_option("--attr", action="store_false", dest="hide_attributes", default=True, - help='display the attribute lines at the beginning of VBA source code') - parser.add_option("--reveal", action="store_true", dest="show_deobfuscated_code", - help='display the macro source code after replacing all the obfuscated strings by their decoded content.') - parser.add_option('-l', '--loglevel', dest="loglevel", action="store", default=DEFAULT_LOG_LEVEL, - help="logging level debug/info/warning/error/critical (default=%default)") - parser.add_option('--deobf', dest="deobfuscate", action="store_true", default=False, - help="Attempt to deobfuscate VBA expressions (slow)") - parser.add_option('--relaxed', dest="relaxed", action="store_true", default=False, - help="Do not raise errors if opening of substream fails") - parser.add_option('--pcode', dest="pcode", action="store_true", default=False, - help="Disassemble and display the P-code (using pcodedmp)") - - (options, args) = parser.parse_args(cmd_line_args) + parser.add_argument("-r", action="store_true", dest="recursive", + help='find files recursively in subdirectories.') + parser.add_argument("-z", "--zip", dest='zip_password', type=str, + default=None, + help='if the file is a zip archive, open all files ' + 'from it, using the provided password.') + parser.add_argument("-p", "--password", type=str, action='append', + default=[], + help='if encrypted office files are encountered, try ' + 'decryption with this password. May be repeated.') + parser.add_argument("-f", "--zipfname", dest='zip_fname', type=str, + default='*', + help='if the file is a zip archive, file(s) to be ' + 'opened within the zip. Wildcards * and ? are ' + 'supported. (default: %(default)s)') + modes = parser.add_argument_group(title='Output mode (mutually exclusive)') + modes.add_argument("-t", '--triage', action="store_const", + dest="output_mode", const='triage', + default='unspecified', + help='triage mode, display results as a summary table ' + '(default for multiple files)') + modes.add_argument("-d", '--detailed', action="store_const", + dest="output_mode", const='detailed', + default='unspecified', + help='detailed mode, display full results (default for ' + 'single file)') + modes.add_argument("-j", '--json', action="store_const", + dest="output_mode", const='json', default='unspecified', + help='json mode, detailed in json format ' + '(never default)') + parser.add_argument("-a", '--analysis', action="store_false", + dest="display_code", default=True, + help='display only analysis results, not the macro ' + 'source code') + parser.add_argument("-c", '--code', action="store_true", + dest="vba_code_only", default=False, + help='display only VBA source code, do not analyze it') + parser.add_argument("--decode", action="store_true", + dest="show_decoded_strings", + help='display all the obfuscated strings with their ' + 'decoded content (Hex, Base64, StrReverse, ' + 'Dridex, VBA).') + parser.add_argument("--attr", action="store_false", dest="hide_attributes", + default=True, + help='display the attribute lines at the beginning of ' + 'VBA source code') + parser.add_argument("--reveal", action="store_true", + dest="show_deobfuscated_code", + help='display the macro source code after replacing ' + 'all the obfuscated strings by their decoded ' + 'content.') + parser.add_argument('-l', '--loglevel', dest="loglevel", action="store", + default=DEFAULT_LOG_LEVEL, + help='logging level debug/info/warning/error/critical ' + '(default=%(default)s)') + parser.add_argument('--deobf', dest="deobfuscate", action="store_true", + default=False, + help="Attempt to deobfuscate VBA expressions (slow)") + parser.add_argument('--relaxed', dest="relaxed", action="store_true", + default=False, + help='Do not raise errors if opening of substream ' + 'fails') + parser.add_argument('--pcode', dest="pcode", action="store_true", + default=False, + help="Disassemble and display the P-code (using pcodedmp)") + + options = parser.parse_args(cmd_line_args) # Print help if no arguments are passed - if len(args) == 0: + if len(options.filenames) == 0: # print banner with version python_version = '%d.%d.%d' % sys.version_info[0:3] print('olevba %s on Python %s - http://decalage.info/python/oletools' % @@ -4004,7 +4029,7 @@ def parse_args(cmd_line_args=None): options.loglevel = LOG_LEVELS[options.loglevel] - return options, args + return options def process_file(filename, data, container, options, crypto_nesting=0): @@ -4119,7 +4144,7 @@ def main(cmd_line_args=None): in process_args. Per default (cmd_line_args=None), sys.argv is used. Option mainly added for unit-testing """ - options, args = parse_args(cmd_line_args) + options = parse_args(cmd_line_args) # provide info about tool and its version if options.output_mode == 'json': @@ -4148,7 +4173,7 @@ def main(cmd_line_args=None): # ignore directory names stored in zip files: all_input_info = tuple((container, filename, data) for container, filename, data in xglob.iter_files( - args, recursive=options.recursive, + options.filenames, recursive=options.recursive, zip_password=options.zip_password, zip_fname=options.zip_fname) if not (container and filename.endswith('/'))) From 755b28e7fd057accd93797c227b22ce4e4cc1f47 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Wed, 31 Jul 2019 16:34:39 +0200 Subject: [PATCH 05/10] olevba: Rename arg "pcode" to "show_pcode" The pcode will be extracted irrespective of this arg in detect_vba_stomping(). Adjust help of argument accordingly. --- oletools/olevba.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index a10687c4..bc5f1947 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -3713,7 +3713,7 @@ def colorize_keywords(self, vba_code): def process_file(self, show_decoded_strings=False, display_code=True, hide_attributes=True, vba_code_only=False, show_deobfuscated_code=False, - deobfuscate=False, pcode=False): + deobfuscate=False, show_pcode=False): """ Process a single file @@ -3725,7 +3725,7 @@ def process_file(self, show_decoded_strings=False, otherwise each module is analyzed separately (old behaviour) :param hide_attributes: bool, if True the first lines starting with "Attribute VB" are hidden (default) :param deobfuscate: bool, if True attempt to deobfuscate VBA expressions (slow) - :param pcode bool: if True, call pcodedmp to disassemble P-code and display it + :param show_pcode bool: if True, call pcodedmp to disassemble P-code and display it """ #TODO: replace print by writing to a provided output file (sys.stdout by default) # fix conflicting parameters: @@ -3795,7 +3795,7 @@ def process_file(self, show_decoded_strings=False, # display the exception with full stack trace for debugging log.info('Error parsing form: %s' % exc) log.debug('Traceback:', exc_info=True) - if pcode: + if show_pcode: print('-' * 79) print('P-CODE disassembly:') pcode = self.extract_pcode() @@ -4011,9 +4011,9 @@ def parse_args(cmd_line_args=None): default=False, help='Do not raise errors if opening of substream ' 'fails') - parser.add_argument('--pcode', dest="pcode", action="store_true", + parser.add_argument('--show-pcode', dest="show_pcode", action="store_true", default=False, - help="Disassemble and display the P-code (using pcodedmp)") + help="Show disassembled P-code (using pcodedmp)") options = parser.parse_args(cmd_line_args) @@ -4051,7 +4051,7 @@ def process_file(filename, data, container, options, crypto_nesting=0): display_code=options.display_code, hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only, show_deobfuscated_code=options.show_deobfuscated_code, - deobfuscate=options.deobfuscate, pcode=options.pcode) + deobfuscate=options.deobfuscate, show_pcode=options.show_pcode) elif options.output_mode == 'triage': # summarized output for triage: vba_parser.process_file_triage(show_decoded_strings=options.show_decoded_strings, From 0378d70c98dff7cefe776fa5d109e23d69d5749e Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Wed, 31 Jul 2019 17:23:45 +0200 Subject: [PATCH 06/10] olevba: Add missing doc string --- oletools/olevba.py | 1 + 1 file changed, 1 insertion(+) diff --git a/oletools/olevba.py b/oletools/olevba.py index bc5f1947..c84c3323 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -3835,6 +3835,7 @@ def process_file_json(self, show_decoded_strings=False, :param global_analysis: bool, if True all modules are merged for a single analysis (default), otherwise each module is analyzed separately (old behaviour) :param hide_attributes: bool, if True the first lines starting with "Attribute VB" are hidden (default) + :param show_deobfuscated_code: bool, if True add deobfuscated code to result :param deobfuscate: bool, if True attempt to deobfuscate VBA expressions (slow) """ #TODO: fix conflicting parameters (?) From e2659504cc73044ef2327e56164be2749ce9b2ee Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Wed, 31 Jul 2019 17:24:53 +0200 Subject: [PATCH 07/10] olevba: Add option to show pcode also in json output Option show_pcode sofar only implemented for "normal" mode. For triage it does not make sense to do this, but in json it might be useful --- oletools/olevba.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index c84c3323..19b13086 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -3822,7 +3822,7 @@ def process_file(self, show_decoded_strings=False, def process_file_json(self, show_decoded_strings=False, display_code=True, hide_attributes=True, vba_code_only=False, show_deobfuscated_code=False, - deobfuscate=False): + deobfuscate=False, show_pcode=False): """ Process a single file @@ -3837,6 +3837,7 @@ def process_file_json(self, show_decoded_strings=False, :param hide_attributes: bool, if True the first lines starting with "Attribute VB" are hidden (default) :param show_deobfuscated_code: bool, if True add deobfuscated code to result :param deobfuscate: bool, if True attempt to deobfuscate VBA expressions (slow) + :param show_pcode: bool, if True add extracted pcode to result """ #TODO: fix conflicting parameters (?) @@ -3854,6 +3855,7 @@ def process_file_json(self, show_decoded_strings=False, result['analysis'] = None result['code_deobfuscated'] = None result['do_deobfuscate'] = deobfuscate + result['show_pcode'] = show_pcode try: #TODO: handle olefile errors, when an OLE file is malformed @@ -3882,6 +3884,8 @@ def process_file_json(self, show_decoded_strings=False, deobfuscate) if show_deobfuscated_code: result['code_deobfuscated'] = self.reveal() + if show_pcode: + result['pcode'] = self.extract_pcode() result['macros'] = macros result['json_conversion_successful'] = True except Exception as exc: @@ -4063,7 +4067,7 @@ def process_file(filename, data, container, options, crypto_nesting=0): display_code=options.display_code, hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only, show_deobfuscated_code=options.show_deobfuscated_code, - deobfuscate=options.deobfuscate)) + deobfuscate=options.deobfuscate, pcode=pcode)) else: # (should be impossible) raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode)) From ac8b16f4ff7e80c035ee460cb1eed685e5faa5d1 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Thu, 1 Aug 2019 15:30:39 +0200 Subject: [PATCH 08/10] olevba: Add option to disable pcode extraction --- oletools/olevba.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index 19b13086..aa89e6f5 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -2571,7 +2571,8 @@ class VBA_Parser(object): Class to parse MS Office files, to detect VBA macros and extract VBA source code """ - def __init__(self, filename, data=None, container=None, relaxed=False, encoding=DEFAULT_API_ENCODING): + def __init__(self, filename, data=None, container=None, relaxed=False, encoding=DEFAULT_API_ENCODING, + disable_pcode=False): """ Constructor for VBA_Parser @@ -2629,6 +2630,7 @@ def __init__(self, filename, data=None, container=None, relaxed=False, encoding= self.encoding = encoding self.xlm_macros = [] #: Output from pcodedmp, disassembly of the VBA P-code + self.disable_pcode = disable_pcode self.pcodedmp_output = None #: Flag set to True/False if VBA stomping detected self.vba_stomping_detected = None @@ -2972,7 +2974,8 @@ def append_subfile(self, filename, data, container=None): """ self.ole_subfiles.append(VBA_Parser(filename, data, container, relaxed=self.relaxed, - encoding=self.encoding)) + encoding=self.encoding, + disable_pcode=self.disable_pcode)) def find_vba_projects(self): """ @@ -3474,6 +3477,9 @@ def extract_pcode(self): :rtype: str """ # only run it once: + if self.disable_pcode: + self.pcodedmp_output = '' + return '' if self.pcodedmp_output is None: log.debug('Calling pcodedmp to extract and disassemble the VBA P-code') # import pcodedmp here to avoid circular imports: @@ -4019,6 +4025,8 @@ def parse_args(cmd_line_args=None): parser.add_argument('--show-pcode', dest="show_pcode", action="store_true", default=False, help="Show disassembled P-code (using pcodedmp)") + parser.add_argument('--no-pcode', action='store_true', + help='Disable extraction and analysis of pcode') options = parser.parse_args(cmd_line_args) @@ -4048,7 +4056,8 @@ def process_file(filename, data, container, options, crypto_nesting=0): try: # Open the file vba_parser = VBA_Parser_CLI(filename, data=data, container=container, - relaxed=options.relaxed) + relaxed=options.relaxed, + disable_pcode=options.no_pcode) if options.output_mode == 'detailed': # fully detailed output @@ -4067,7 +4076,7 @@ def process_file(filename, data, container, options, crypto_nesting=0): display_code=options.display_code, hide_attributes=options.hide_attributes, vba_code_only=options.vba_code_only, show_deobfuscated_code=options.show_deobfuscated_code, - deobfuscate=options.deobfuscate, pcode=pcode)) + deobfuscate=options.deobfuscate, show_pcode=options.show_pcode)) else: # (should be impossible) raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode)) From 09c7e6cd4147b9181fcf2a960107cb253fa8c02b Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Fri, 2 Aug 2019 15:50:38 +0200 Subject: [PATCH 09/10] olevba: Warn at debug-level if trying to show pcode in triage mode Also move this test below the point where final output mode is determined. --- oletools/olevba.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index aa89e6f5..2703ce29 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -4180,8 +4180,6 @@ def main(cmd_line_args=None): if options.show_deobfuscated_code and not options.deobfuscate: log.debug('set --deobf because --reveal was set') options.deobfuscate = True - if options.output_mode == 'triage' and options.show_deobfuscated_code: - log.debug('ignoring option --reveal in triage output mode') # gather info on all files that must be processed # ignore directory names stored in zip files: @@ -4199,6 +4197,12 @@ def main(cmd_line_args=None): else: options.output_mode = 'triage' + if options.output_mode == 'triage': + if options.show_deobfuscated_code: + log.debug('ignoring option --reveal in triage output mode') + if options.show_pcode: + log.debug('ignoring option --show-pcode in triage output mode') + # Column headers for triage mode if options.output_mode == 'triage': print('%-12s %-65s' % ('Flags', 'Filename')) From d8f80b9d3b8d43c1b5cfb0f5b5ac7ee770e13655 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Fri, 2 Aug 2019 16:03:20 +0200 Subject: [PATCH 10/10] olevba: Disallow combination --show_pcode --no-pcode This could also be solved by creating just one single option --pcode=[OFF|DETECT|SHOW] (possibly also with option 'SHOW-VBA-STOMPED' for only showing if vba-stomping was detected). --- oletools/olevba.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oletools/olevba.py b/oletools/olevba.py index 2703ce29..eebf406d 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -4040,6 +4040,9 @@ def parse_args(cmd_line_args=None): parser.print_help() sys.exit(RETURN_WRONG_ARGS) + if options.show_pcode and options.no_pcode: + parser.error('You cannot combine options --no-pcode and --show-pcode') + options.loglevel = LOG_LEVELS[options.loglevel] return options