Commit f028496d15715f6a5605e427554472ba3889049c

Authored by decalage2
1 parent 3056212d

crypto: fixed a bug in is_encrypted_ole

oletools/crypto.py
@@ -89,8 +89,9 @@ http://www.decalage.info/python/oletools @@ -89,8 +89,9 @@ http://www.decalage.info/python/oletools
89 # ----------------------------------------------------------------------------- 89 # -----------------------------------------------------------------------------
90 # CHANGELOG: 90 # CHANGELOG:
91 # 2019-02-14 v0.01 CH: - first version with encryption check from oleid 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 import sys 96 import sys
96 import struct 97 import struct
@@ -98,8 +99,8 @@ import os @@ -98,8 +99,8 @@ import os
98 from os.path import splitext, isfile 99 from os.path import splitext, isfile
99 from tempfile import mkstemp 100 from tempfile import mkstemp
100 import zipfile 101 import zipfile
101 -from oletools.common.errors import CryptoErrorBase, WrongEncryptionPassword, \  
102 - UnsupportedEncryptionError, MaxCryptoNestingReached, CryptoLibNotImported 102 +import logging
  103 +
103 from olefile import OleFileIO 104 from olefile import OleFileIO
104 105
105 try: 106 try:
@@ -107,11 +108,65 @@ try: @@ -107,11 +108,65 @@ try:
107 except ImportError: 108 except ImportError:
108 msoffcrypto = None 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 #: if there is an encrypted file embedded in an encrypted file, 126 #: if there is an encrypted file embedded in an encrypted file,
112 #: how deep down do we go 127 #: how deep down do we go
113 MAX_NESTING_DEPTH = 10 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 def is_encrypted(some_file): 171 def is_encrypted(some_file):
117 """ 172 """
@@ -141,7 +196,8 @@ def is_encrypted(some_file): @@ -141,7 +196,8 @@ def is_encrypted(some_file):
141 :type some_file: :py:class:`olefile.OleFileIO` or `str` 196 :type some_file: :py:class:`olefile.OleFileIO` or `str`
142 :returns: True if (and only if) the file contains encrypted content 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 return is_encrypted_ole(some_file) # assume it is OleFileIO 201 return is_encrypted_ole(some_file) # assume it is OleFileIO
146 if zipfile.is_zipfile(some_file): 202 if zipfile.is_zipfile(some_file):
147 return is_encrypted_zip(some_file) 203 return is_encrypted_zip(some_file)
@@ -151,6 +207,8 @@ def is_encrypted(some_file): @@ -151,6 +207,8 @@ def is_encrypted(some_file):
151 207
152 def is_encrypted_zip(filename): 208 def is_encrypted_zip(filename):
153 """Specialization of :py:func:`is_encrypted` for zip-based files.""" 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 # try to decrypt a few bytes from first entry 212 # try to decrypt a few bytes from first entry
155 with zipfile.ZipFile(filename, 'r') as zipper: 213 with zipfile.ZipFile(filename, 'r') as zipper:
156 first_entry = zipper.infolist()[0] 214 first_entry = zipper.infolist()[0]
@@ -164,6 +222,7 @@ def is_encrypted_zip(filename): @@ -164,6 +222,7 @@ def is_encrypted_zip(filename):
164 222
165 def is_encrypted_ole(ole): 223 def is_encrypted_ole(ole):
166 """Specialization of :py:func:`is_encrypted` for ole files.""" 224 """Specialization of :py:func:`is_encrypted` for ole files."""
  225 + log.debug('is_encrypted_ole')
167 # check well known property for password protection 226 # check well known property for password protection
168 # (this field may be missing for Powerpoint2000, for example) 227 # (this field may be missing for Powerpoint2000, for example)
169 # TODO: check whether password protection always implies encryption. Could 228 # TODO: check whether password protection always implies encryption. Could
@@ -176,10 +235,11 @@ def is_encrypted_ole(ole): @@ -176,10 +235,11 @@ def is_encrypted_ole(ole):
176 # check a few stream names 235 # check a few stream names
177 # TODO: check whether these actually contain data and whether other 236 # TODO: check whether these actually contain data and whether other
178 # necessary properties exist / are set 237 # necessary properties exist / are set
179 - elif ole.exists('EncryptionInfo'): 238 + if ole.exists('EncryptionInfo'):
  239 + log.debug('found stream EncryptionInfo')
180 return True 240 return True
181 # or an encrypted ppt file 241 # or an encrypted ppt file
182 - elif ole.exists('EncryptedSummary') and \ 242 + if ole.exists('EncryptedSummary') and \
183 not ole.exists('SummaryInformation'): 243 not ole.exists('SummaryInformation'):
184 return True 244 return True
185 245
oletools/olevba.py
@@ -216,7 +216,7 @@ from __future__ import print_function @@ -216,7 +216,7 @@ from __future__ import print_function
216 # 2019-03-18 PL: - added XLM/XLF macros detection for Excel OLE files 216 # 2019-03-18 PL: - added XLM/XLF macros detection for Excel OLE files
217 # 2019-03-25 CH: - added decryption of password-protected files 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 # TODO: 222 # TODO:
@@ -436,6 +436,7 @@ def enable_logging(): @@ -436,6 +436,7 @@ def enable_logging():
436 log.setLevel(logging.NOTSET) 436 log.setLevel(logging.NOTSET)
437 # Also enable logging in the ppt_parser module: 437 # Also enable logging in the ppt_parser module:
438 ppt_parser.enable_logging() 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,11 +3826,12 @@ def process_file(filename, data, container, options, crypto_nesting=0):
3825 raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode)) 3826 raise ValueError('unexpected output mode: "{0}"!'.format(options.output_mode))
3826 3827
3827 # even if processing succeeds, file might still be encrypted 3828 # even if processing succeeds, file might still be encrypted
3828 - log.debug('Checking for encryption') 3829 + log.debug('Checking for encryption (normal)')
3829 if not crypto.is_encrypted(filename): 3830 if not crypto.is_encrypted(filename):
  3831 + log.debug('no encryption detected')
3830 return RETURN_OK 3832 return RETURN_OK
3831 except Exception as exc: 3833 except Exception as exc:
3832 - log.debug('Checking for encryption') 3834 + log.debug('Checking for encryption (after exception)')
3833 if crypto.is_encrypted(filename): 3835 if crypto.is_encrypted(filename):
3834 pass # deal with this below 3836 pass # deal with this below
3835 else: 3837 else:
setup.py
@@ -48,7 +48,7 @@ import os, fnmatch @@ -48,7 +48,7 @@ import os, fnmatch
48 #--- METADATA ----------------------------------------------------------------- 48 #--- METADATA -----------------------------------------------------------------
49 49
50 name = "oletools" 50 name = "oletools"
51 -version = '0.54dev12' 51 +version = '0.54dev13'
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" 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 long_desc = open('oletools/README.rst').read() 53 long_desc = open('oletools/README.rst').read()
54 author = "Philippe Lagadec" 54 author = "Philippe Lagadec"