Commit f028496d15715f6a5605e427554472ba3889049c
1 parent
3056212d
crypto: fixed a bug in is_encrypted_ole
Showing
3 changed files
with
72 additions
and
10 deletions
oletools/crypto.py
| ... | ... | @@ -89,8 +89,9 @@ http://www.decalage.info/python/oletools |
| 89 | 89 | # ----------------------------------------------------------------------------- |
| 90 | 90 | # CHANGELOG: |
| 91 | 91 | # 2019-02-14 v0.01 CH: - first version with encryption check from oleid |
| 92 | +# 2019-04-01 v0.54 PL: - fixed bug in is_encrypted_ole | |
| 92 | 93 | |
| 93 | -__version__ = '0.01' | |
| 94 | +__version__ = '0.54dev13' | |
| 94 | 95 | |
| 95 | 96 | import sys |
| 96 | 97 | import struct |
| ... | ... | @@ -98,8 +99,8 @@ import os |
| 98 | 99 | from os.path import splitext, isfile |
| 99 | 100 | from tempfile import mkstemp |
| 100 | 101 | import zipfile |
| 101 | -from oletools.common.errors import CryptoErrorBase, WrongEncryptionPassword, \ | |
| 102 | - UnsupportedEncryptionError, MaxCryptoNestingReached, CryptoLibNotImported | |
| 102 | +import logging | |
| 103 | + | |
| 103 | 104 | from olefile import OleFileIO |
| 104 | 105 | |
| 105 | 106 | try: |
| ... | ... | @@ -107,11 +108,65 @@ try: |
| 107 | 108 | except ImportError: |
| 108 | 109 | msoffcrypto = None |
| 109 | 110 | |
| 111 | +# IMPORTANT: it should be possible to run oletools directly as scripts | |
| 112 | +# in any directory without installing them with pip or setup.py. | |
| 113 | +# In that case, relative imports are NOT usable. | |
| 114 | +# And to enable Python 2+3 compatibility, we need to use absolute imports, | |
| 115 | +# so we add the oletools parent folder to sys.path (absolute+normalized path): | |
| 116 | +_thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) | |
| 117 | +_parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..')) | |
| 118 | +if _parent_dir not in sys.path: | |
| 119 | + sys.path.insert(0, _parent_dir) | |
| 120 | + | |
| 121 | +from oletools.common.errors import CryptoErrorBase, WrongEncryptionPassword, \ | |
| 122 | + UnsupportedEncryptionError, MaxCryptoNestingReached, CryptoLibNotImported | |
| 123 | +from oletools.common.log_helper import log_helper | |
| 124 | + | |
| 110 | 125 | |
| 111 | 126 | #: if there is an encrypted file embedded in an encrypted file, |
| 112 | 127 | #: how deep down do we go |
| 113 | 128 | MAX_NESTING_DEPTH = 10 |
| 114 | 129 | |
| 130 | +# === LOGGING ================================================================= | |
| 131 | + | |
| 132 | +# TODO: use log_helper instead | |
| 133 | + | |
| 134 | +def get_logger(name, level=logging.CRITICAL+1): | |
| 135 | + """ | |
| 136 | + Create a suitable logger object for this module. | |
| 137 | + The goal is not to change settings of the root logger, to avoid getting | |
| 138 | + other modules' logs on the screen. | |
| 139 | + If a logger exists with same name, reuse it. (Else it would have duplicate | |
| 140 | + handlers and messages would be doubled.) | |
| 141 | + The level is set to CRITICAL+1 by default, to avoid any logging. | |
| 142 | + """ | |
| 143 | + # First, test if there is already a logger with the same name, else it | |
| 144 | + # will generate duplicate messages (due to duplicate handlers): | |
| 145 | + if name in logging.Logger.manager.loggerDict: | |
| 146 | + # NOTE: another less intrusive but more "hackish" solution would be to | |
| 147 | + # use getLogger then test if its effective level is not default. | |
| 148 | + logger = logging.getLogger(name) | |
| 149 | + # make sure level is OK: | |
| 150 | + logger.setLevel(level) | |
| 151 | + return logger | |
| 152 | + # get a new logger: | |
| 153 | + logger = logging.getLogger(name) | |
| 154 | + # only add a NullHandler for this logger, it is up to the application | |
| 155 | + # to configure its own logging: | |
| 156 | + logger.addHandler(logging.NullHandler()) | |
| 157 | + logger.setLevel(level) | |
| 158 | + return logger | |
| 159 | + | |
| 160 | +# a global logger object used for debugging: | |
| 161 | +log = get_logger('crypto') | |
| 162 | + | |
| 163 | +def enable_logging(): | |
| 164 | + """ | |
| 165 | + Enable logging for this module (disabled by default). | |
| 166 | + This will set the module-specific logger level to NOTSET, which | |
| 167 | + means the main application controls the actual logging level. | |
| 168 | + """ | |
| 169 | + log.setLevel(logging.NOTSET) | |
| 115 | 170 | |
| 116 | 171 | def is_encrypted(some_file): |
| 117 | 172 | """ |
| ... | ... | @@ -141,7 +196,8 @@ def is_encrypted(some_file): |
| 141 | 196 | :type some_file: :py:class:`olefile.OleFileIO` or `str` |
| 142 | 197 | :returns: True if (and only if) the file contains encrypted content |
| 143 | 198 | """ |
| 144 | - if not isinstance(some_file, str): | |
| 199 | + log.debug('is_encrypted') | |
| 200 | + if isinstance(some_file, OleFileIO): | |
| 145 | 201 | return is_encrypted_ole(some_file) # assume it is OleFileIO |
| 146 | 202 | if zipfile.is_zipfile(some_file): |
| 147 | 203 | return is_encrypted_zip(some_file) |
| ... | ... | @@ -151,6 +207,8 @@ def is_encrypted(some_file): |
| 151 | 207 | |
| 152 | 208 | def is_encrypted_zip(filename): |
| 153 | 209 | """Specialization of :py:func:`is_encrypted` for zip-based files.""" |
| 210 | + log.debug('is_encrypted_zip') | |
| 211 | + # TODO: distinguish OpenXML from normal zip files | |
| 154 | 212 | # try to decrypt a few bytes from first entry |
| 155 | 213 | with zipfile.ZipFile(filename, 'r') as zipper: |
| 156 | 214 | first_entry = zipper.infolist()[0] |
| ... | ... | @@ -164,6 +222,7 @@ def is_encrypted_zip(filename): |
| 164 | 222 | |
| 165 | 223 | def is_encrypted_ole(ole): |
| 166 | 224 | """Specialization of :py:func:`is_encrypted` for ole files.""" |
| 225 | + log.debug('is_encrypted_ole') | |
| 167 | 226 | # check well known property for password protection |
| 168 | 227 | # (this field may be missing for Powerpoint2000, for example) |
| 169 | 228 | # TODO: check whether password protection always implies encryption. Could |
| ... | ... | @@ -176,10 +235,11 @@ def is_encrypted_ole(ole): |
| 176 | 235 | # check a few stream names |
| 177 | 236 | # TODO: check whether these actually contain data and whether other |
| 178 | 237 | # necessary properties exist / are set |
| 179 | - elif ole.exists('EncryptionInfo'): | |
| 238 | + if ole.exists('EncryptionInfo'): | |
| 239 | + log.debug('found stream EncryptionInfo') | |
| 180 | 240 | return True |
| 181 | 241 | # or an encrypted ppt file |
| 182 | - elif ole.exists('EncryptedSummary') and \ | |
| 242 | + if ole.exists('EncryptedSummary') and \ | |
| 183 | 243 | not ole.exists('SummaryInformation'): |
| 184 | 244 | return True |
| 185 | 245 | ... | ... |
oletools/olevba.py
| ... | ... | @@ -216,7 +216,7 @@ from __future__ import print_function |
| 216 | 216 | # 2019-03-18 PL: - added XLM/XLF macros detection for Excel OLE files |
| 217 | 217 | # 2019-03-25 CH: - added decryption of password-protected files |
| 218 | 218 | |
| 219 | -__version__ = '0.54dev12' | |
| 219 | +__version__ = '0.54dev13' | |
| 220 | 220 | |
| 221 | 221 | #------------------------------------------------------------------------------ |
| 222 | 222 | # TODO: |
| ... | ... | @@ -436,6 +436,7 @@ def enable_logging(): |
| 436 | 436 | log.setLevel(logging.NOTSET) |
| 437 | 437 | # Also enable logging in the ppt_parser module: |
| 438 | 438 | ppt_parser.enable_logging() |
| 439 | + crypto.enable_logging() | |
| 439 | 440 | |
| 440 | 441 | |
| 441 | 442 | |
| ... | ... | @@ -3825,11 +3826,12 @@ def process_file(filename, data, container, options, crypto_nesting=0): |
| 3825 | 3826 | raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode)) |
| 3826 | 3827 | |
| 3827 | 3828 | # even if processing succeeds, file might still be encrypted |
| 3828 | - log.debug('Checking for encryption') | |
| 3829 | + log.debug('Checking for encryption (normal)') | |
| 3829 | 3830 | if not crypto.is_encrypted(filename): |
| 3831 | + log.debug('no encryption detected') | |
| 3830 | 3832 | return RETURN_OK |
| 3831 | 3833 | except Exception as exc: |
| 3832 | - log.debug('Checking for encryption') | |
| 3834 | + log.debug('Checking for encryption (after exception)') | |
| 3833 | 3835 | if crypto.is_encrypted(filename): |
| 3834 | 3836 | pass # deal with this below |
| 3835 | 3837 | else: | ... | ... |
setup.py
| ... | ... | @@ -48,7 +48,7 @@ import os, fnmatch |
| 48 | 48 | #--- METADATA ----------------------------------------------------------------- |
| 49 | 49 | |
| 50 | 50 | name = "oletools" |
| 51 | -version = '0.54dev12' | |
| 51 | +version = '0.54dev13' | |
| 52 | 52 | desc = "Python tools to analyze security characteristics of MS Office and OLE files (also called Structured Storage, Compound File Binary Format or Compound Document File Format), for Malware Analysis and Incident Response #DFIR" |
| 53 | 53 | long_desc = open('oletools/README.rst').read() |
| 54 | 54 | author = "Philippe Lagadec" | ... | ... |