import io
import logging
import shutil
import tempfile
from collections import namedtuple
from struct import pack, unpack
import olefile
from msoffcrypto import exceptions
from msoffcrypto.format import base
from msoffcrypto.format.common import _parse_encryptionheader, _parse_encryptionverifier
from msoffcrypto.method.rc4_cryptoapi import DocumentRC4CryptoAPI
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
RecordHeader = namedtuple(
"RecordHeader",
[
"recVer",
"recInstance",
"recType",
"recLen",
],
)
def _parseRecordHeader(blob):
# RecordHeader: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/df201194-0cd0-4dfb-bf10-eea353d8eabc
getBitSlice = lambda bits, i, w: (bits & (2**w - 1 << i)) >> i
blob.seek(0)
(buf,) = unpack("<H", blob.read(2))
recVer = getBitSlice(buf, 0, 4)
recInstance = getBitSlice(buf, 4, 12)
(recType,) = unpack("<H", blob.read(2))
(recLen,) = unpack("<I", blob.read(4))
rh = RecordHeader(
recVer=recVer,
recInstance=recInstance,
recType=recType,
recLen=recLen,
)
return rh
def _packRecordHeader(rh):
setBitSlice = lambda bits, i, w, v: (bits & ~((2**w - 1) << i)) | ((v & (2**w - 1)) << i)
blob = io.BytesIO()
_buf = 0xFFFF
_buf = setBitSlice(_buf, 0, 4, rh.recVer)
_buf = setBitSlice(_buf, 4, 12, rh.recInstance)
buf = pack("<H", _buf)
blob.write(buf)
buf = pack("<H", rh.recType)
blob.write(buf)
buf = pack("<I", rh.recLen)
blob.write(buf)
blob.seek(0)
return blob
CurrentUserAtom = namedtuple(
"CurrentUserAtom",
[
"rh",
"size",
"headerToken",
"offsetToCurrentEdit",
"lenUserName",
"docFileVersion",
"majorVersion",
"minorVersion",
"unused",
"ansiUserName",
"relVersion",
"unicodeUserName",
],
)
def _parseCurrentUserAtom(blob):
# CurrentUserAtom: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/940d5700-e4d7-4fc0-ab48-fed5dbc48bc1
# rh (8 bytes): A RecordHeader structure...
buf = io.BytesIO(blob.read(8))
rh = _parseRecordHeader(buf)
# logger.debug(rh)
# ...Sub-fields are further specified in the following table.
assert rh.recVer == 0x0
assert rh.recInstance == 0x000
assert rh.recType == 0x0FF6
(size,) = unpack("<I", blob.read(4))
# logger.debug(hex(size))
# size (4 bytes): ...It MUST be 0x00000014.
assert size == 0x00000014
# headerToken (4 bytes): An unsigned integer that specifies
# a token used to identify whether the file is encrypted.
(headerToken,) = unpack("<I", blob.read(4))
# TODO: Check headerToken value
(offsetToCurrentEdit,) = unpack("<I", blob.read(4))
(lenUserName,) = unpack("<H", blob.read(2))
(docFileVersion,) = unpack("<H", blob.read(2))
(
majorVersion,
minorVersion,
) = unpack("<BB", blob.read(2))
unused = blob.read(2)
ansiUserName = blob.read(lenUserName)
(relVersion,) = unpack("<I", blob.read(4))
unicodeUserName = blob.read(2 * lenUserName)
return CurrentUserAtom(
rh=rh,
size=size,
headerToken=headerToken,
offsetToCurrentEdit=offsetToCurrentEdit,
lenUserName=lenUserName,
docFileVersion=docFileVersion,
majorVersion=majorVersion,
minorVersion=minorVersion,
unused=unused,
ansiUserName=ansiUserName,
relVersion=relVersion,
unicodeUserName=unicodeUserName,
)
def _packCurrentUserAtom(currentuseratom):
blob = io.BytesIO()
buf = _packRecordHeader(currentuseratom.rh).read()
blob.write(buf)
buf = pack("<I", currentuseratom.size)
blob.write(buf)
buf = pack("<I", currentuseratom.headerToken)
blob.write(buf)
buf = pack("<I", currentuseratom.offsetToCurrentEdit)
blob.write(buf)
buf = pack("<H", currentuseratom.lenUserName)
blob.write(buf)
buf = pack("<H", currentuseratom.docFileVersion)
blob.write(buf)
buf = pack("<BB", currentuseratom.majorVersion, currentuseratom.minorVersion)
blob.write(buf)
buf = currentuseratom.unused
blob.write(buf)
buf = currentuseratom.ansiUserName
blob.write(buf)
buf = pack("<I", currentuseratom.relVersion)
blob.write(buf)
buf = currentuseratom.unicodeUserName
blob.write(buf)
blob.seek(0)
return blob
CurrentUser = namedtuple("CurrentUser", ["currentuseratom"])
def _parseCurrentUser(blob):
# Current User Stream: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/76cfa657-07a6-464b-81ab-4c017c611f64
currentuser = CurrentUser(currentuseratom=_parseCurrentUserAtom(blob))
return currentuser
def _packCurrentUser(currentuser):
blob = io.BytesIO()
buf = _packCurrentUserAtom(currentuser.currentuseratom).read()
blob.write(buf)
blob.seek(0)
return blob
UserEditAtom = namedtuple(
"UserEditAtom",
[
"rh",
"lastSlideIdRef",
"version",
"minorVersion",
"majorVersion",
"offsetLastEdit",
"offsetPersistDirectory",
"docPersistIdRef",
"persistIdSeed",
"lastView",
"unused",
"encryptSessionPersistIdRef",
],
)
def _parseUserEditAtom(blob):
# UserEditAtom: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/3ffb3fab-95de-4873-98aa-d508fbbac981
# rh (8 bytes): A RecordHeader structure...
buf = io.BytesIO(blob.read(8))
rh = _parseRecordHeader(buf)
# logger.debug(rh)
# ...Sub-fields are further specified in the following table.
assert rh.recVer == 0x0
assert rh.recInstance == 0x000
assert rh.recType == 0x0FF5
assert rh.recLen == 0x0000001C or rh.recLen == 0x00000020 # 0x0000001c + len(encryptSessionPersistIdRef)
(lastSlideIdRef,) = unpack("<I", blob.read(4))
(version,) = unpack("<H", blob.read(2))
(
minorVersion,
majorVersion,
) = unpack("<BB", blob.read(2))
# majorVersion, minorVersion, = unpack("<BB", blob.read(2))
(offsetLastEdit,) = unpack("<I", blob.read(4))
(offsetPersistDirectory,) = unpack("<I", blob.read(4))
(docPersistIdRef,) = unpack("<I", blob.read(4))
(persistIdSeed,) = unpack("<I", blob.read(4))
(lastView,) = unpack("<H", blob.read(2))
unused = blob.read(2)
# encryptSessionPersistIdRef (4 bytes): An optional PersistIdRef
# that specifies the value to look up in the persist object directory
# to find the offset of the CryptSession10Container record (section 2.3.7).
buf = blob.read(4)
if len(buf) == 4:
(encryptSessionPersistIdRef,) = unpack("<I", buf)
else:
encryptSessionPersistIdRef = None
return UserEditAtom(
rh=rh,
lastSlideIdRef=lastSlideIdRef,
version=version,
minorVersion=minorVersion,
majorVersion=majorVersion,
offsetLastEdit=offsetLastEdit,
offsetPersistDirectory=offsetPersistDirectory,
docPersistIdRef=docPersistIdRef,
persistIdSeed=persistIdSeed,
lastView=lastView,
unused=unused,
encryptSessionPersistIdRef=encryptSessionPersistIdRef,
)
def _packUserEditAtom(usereditatom):
blob = io.BytesIO()
buf = _packRecordHeader(usereditatom.rh).read()
blob.write(buf)
buf = pack("<I", usereditatom.lastSlideIdRef)
blob.write(buf)
buf = pack("<H", usereditatom.version)
blob.write(buf)
buf = pack("<BB", usereditatom.minorVersion, usereditatom.majorVersion)
blob.write(buf)
buf = pack("<I", usereditatom.offsetLastEdit)
blob.write(buf)
buf = pack("<I", usereditatom.offsetPersistDirectory)
blob.write(buf)
buf = pack("<I", usereditatom.docPersistIdRef)
blob.write(buf)
buf = pack("<I", usereditatom.persistIdSeed)
blob.write(buf)
buf = pack("<H", usereditatom.lastView)
blob.write(buf)
buf = usereditatom.unused
blob.write(buf)
# Optional value
if usereditatom.encryptSessionPersistIdRef is not None:
buf = pack("<I", usereditatom.encryptSessionPersistIdRef)
blob.write(buf)
blob.seek(0)
return blob
PersistDirectoryEntry = namedtuple(
"PersistDirectoryEntry",
[
"persistId",
"cPersist",
"rgPersistOffset",
],
)
def _parsePersistDirectoryEntry(blob):
# PersistDirectoryEntry: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/6214b5a6-7ca2-4a86-8a0e-5fd3d3eff1c9
getBitSlice = lambda bits, i, w: (bits & (2**w - 1 << i)) >> i
(buf,) = unpack("<I", blob.read(4))
persistId = getBitSlice(buf, 0, 20)
cPersist = getBitSlice(buf, 20, 12)
# cf. PersistOffsetEntry: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/a056484a-2132-4e1e-aa54-6e387f9695cf
size_rgPersistOffset = 4 * cPersist
_rgPersistOffset = blob.read(size_rgPersistOffset)
_rgPersistOffset = io.BytesIO(_rgPersistOffset)
rgPersistOffset = []
pos = 0
while pos < size_rgPersistOffset:
(persistoffsetentry,) = unpack("<I", _rgPersistOffset.read(4))
rgPersistOffset.append(persistoffsetentry)
pos += 4
return PersistDirectoryEntry(
persistId=persistId,
cPersist=cPersist,
rgPersistOffset=rgPersistOffset,
)
def _packPersistDirectoryEntry(directoryentry):
setBitSlice = lambda bits, i, w, v: (bits & ~((2**w - 1) << i)) | ((v & (2**w - 1)) << i)
blob = io.BytesIO()
_buf = 0xFFFFFFFF
_buf = setBitSlice(_buf, 0, 20, directoryentry.persistId)
_buf = setBitSlice(_buf, 20, 12, directoryentry.cPersist)
buf = pack("<I", _buf)
blob.write(buf)
for v in directoryentry.rgPersistOffset:
buf = pack("<I", v)
blob.write(buf)
blob.seek(0)
return blob
PersistDirectoryAtom = namedtuple(
"PersistDirectoryAtom",
[
"rh",
"rgPersistDirEntry",
],
)
def _parsePersistDirectoryAtom(blob):
# PersistDirectoryAtom: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/d10a093d-860f-409c-b065-aeb24b830505
# rh (8 bytes): A RecordHeader structure...
buf = io.BytesIO(blob.read(8))
rh = _parseRecordHeader(buf)
# logger.debug(rh)
# ...Sub-fields are further specified in the following table.
assert rh.recVer == 0x0
assert rh.recInstance == 0x000
assert rh.recType == 0x1772
_rgPersistDirEntry = blob.read(rh.recLen)
_rgPersistDirEntry = io.BytesIO(_rgPersistDirEntry)
rgPersistDirEntry = []
pos = 0
while pos < rh.recLen:
persistdirectoryentry = _parsePersistDirectoryEntry(_rgPersistDirEntry)
size_persistdirectoryentry = 4 + 4 * len(persistdirectoryentry.rgPersistOffset)
# logger.debug((persistdirectoryentry, size_persistdirectoryentry))
rgPersistDirEntry.append(persistdirectoryentry)
pos += size_persistdirectoryentry
return PersistDirectoryAtom(
rh=rh,
rgPersistDirEntry=rgPersistDirEntry,
)
def _packPersistDirectoryAtom(directoryatom):
blob = io.BytesIO()
buf = _packRecordHeader(directoryatom.rh).read()
blob.write(buf)
for v in directoryatom.rgPersistDirEntry:
buf = _packPersistDirectoryEntry(v)
blob.write(buf.read())
blob.seek(0)
return blob
def _parseCryptSession10Container(blob):
# CryptSession10Container: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/b0963334-4408-4621-879a-ef9c54551fd8
CryptSession10Container = namedtuple(
"CryptSession10Container",
[
"rh",
"data",
],
)
# rh (8 bytes): A RecordHeader structure...
buf = io.BytesIO(blob.read(8))
rh = _parseRecordHeader(buf)
# logger.debug(rh)
# ...Sub-fields are further specified in the following table.
assert rh.recVer == 0xF
# The specified value fails
# assert rh.recInstance == 0x000
assert rh.recType == 0x2F14
data = blob.read(rh.recLen)
return CryptSession10Container(
rh=rh,
data=data,
)
[docs]def construct_persistobjectdirectory(data):
# PowerPoint Document Stream: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/1fc22d56-28f9-4818-bd45-67c2bf721ccf
# 1. Read the CurrentUserAtom record (section 2.3.2) from the Current User Stream (section 2.1.1). ...
data.currentuser.seek(0)
currentuser = _parseCurrentUser(data.currentuser)
# logger.debug(currentuser)
# 2. Seek, in the PowerPoint Document Stream, to the offset specified by the offsetToCurrentEdit field of
# the CurrentUserAtom record identified in step 1.
data.powerpointdocument.seek(currentuser.currentuseratom.offsetToCurrentEdit)
persistdirectoryatom_stack = []
# The stream MUST contain exactly one UserEditAtom record.
# https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/b0963334-4408-4621-879a-ef9c54551fd8
for i in range(1):
# 3. Read the UserEditAtom record at the current offset. ...
usereditatom = _parseUserEditAtom(data.powerpointdocument)
# logger.debug(usereditatom)
# 4. Seek to the offset specified by the offsetPersistDirectory field of the UserEditAtom record identified in step 3.
data.powerpointdocument.seek(usereditatom.offsetPersistDirectory)
# 5. Read the PersistDirectoryAtom record at the current offset. ...
persistdirectoryatom = _parsePersistDirectoryAtom(data.powerpointdocument)
# logger.debug(persistdirectoryatom)
persistdirectoryatom_stack.append(persistdirectoryatom)
# 6. Seek to the offset specified by the offsetLastEdit field in the UserEditAtom record identified in step 3.
# 7. Repeat steps 3 through 6 until offsetLastEdit is 0x00000000.
if usereditatom.offsetLastEdit == 0x00000000:
break
else:
data.powerpointdocument.seek(usereditatom.offsetLastEdit)
# 8. Construct the complete persist object directory for this file as follows:
persistobjectdirectory = {}
# 8a. For each PersistDirectoryAtom record previously identified in step 5,
# add the persist object identifier and persist object stream offset pairs to
# the persist object directory starting with the PersistDirectoryAtom record
# last identified, that is, the one closest to the beginning of the stream.
# 8b. Continue adding these pairs to the persist object directory for each PersistDirectoryAtom record
# in the reverse order that they were identified in step 5; that is, the pairs from the PersistDirectoryAtom record
# closest to the end of the stream are added last.
# 8c. When adding a new pair to the persist object directory, if the persist object identifier
# already exists in the persist object directory, the persist object stream offset from
# the new pair replaces the existing persist object stream offset for that persist object identifier.
while len(persistdirectoryatom_stack) > 0:
persistdirectoryatom = persistdirectoryatom_stack.pop()
for entry in persistdirectoryatom.rgPersistDirEntry:
# logger.debug("persistId: %d" % entry.persistId)
for i, offset in enumerate(entry.rgPersistOffset):
persistobjectdirectory[entry.persistId + i] = offset
return persistobjectdirectory
def _parse_header_RC4CryptoAPI(encryptionInfo):
flags = encryptionInfo.read(4)
(headerSize,) = unpack("<I", encryptionInfo.read(4))
logger.debug(headerSize)
blob = io.BytesIO(encryptionInfo.read(headerSize))
header = _parse_encryptionheader(blob)
logger.debug(header)
blob = io.BytesIO(encryptionInfo.read())
verifier = _parse_encryptionverifier(blob, "RC4") # TODO: Fix (cf. ooxml.py)
logger.debug(verifier)
info = {
"salt": verifier["salt"],
"keySize": header["keySize"],
"encryptedVerifier": verifier["encryptedVerifier"],
"encryptedVerifierHash": verifier["encryptedVerifierHash"],
}
return info
[docs]class Ppt97File(base.BaseOfficeFile):
"""Return a MS-PPT file object.
Examples:
>>> with open("tests/inputs/rc4cryptoapi_password.ppt", "rb") as f:
... officefile = Ppt97File(f)
... officefile.load_key(password="Password1234_")
>>> with open("tests/inputs/rc4cryptoapi_password.ppt", "rb") as f:
... officefile = Ppt97File(f)
... officefile.load_key(password="0000")
Traceback (most recent call last):
...
msoffcrypto.exceptions.InvalidKeyError: ...
"""
def __init__(self, file):
self.file = file
ole = olefile.OleFileIO(file) # do not close this, would close file
self.ole = ole
self.format = "ppt97"
self.keyTypes = ["password"]
self.key = None
self.salt = None
# streams closed in destructor:
currentuser = ole.openstream("Current User")
powerpointdocument = ole.openstream("PowerPoint Document")
Data = namedtuple("Data", ["currentuser", "powerpointdocument"])
self.data = Data(
currentuser=currentuser,
powerpointdocument=powerpointdocument,
)
def __del__(self):
"""Destructor, closes opened streams."""
if hasattr(self, "data") and self.data:
if self.data.currentuser:
self.data.currentuser.close()
if self.data.powerpointdocument:
self.data.powerpointdocument.close()
[docs] def load_key(self, password=None):
persistobjectdirectory = construct_persistobjectdirectory(self.data)
logger.debug("[*] persistobjectdirectory: {}".format(persistobjectdirectory))
self.data.currentuser.seek(0)
currentuser = _parseCurrentUser(self.data.currentuser)
logger.debug("[*] currentuser: {}".format(currentuser))
self.data.powerpointdocument.seek(currentuser.currentuseratom.offsetToCurrentEdit)
usereditatom = _parseUserEditAtom(self.data.powerpointdocument)
logger.debug("[*] usereditatom: {}".format(usereditatom))
# cf. Part 2 in https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/1fc22d56-28f9-4818-bd45-67c2bf721ccf
cryptsession10container_offset = persistobjectdirectory[usereditatom.encryptSessionPersistIdRef]
logger.debug("[*] cryptsession10container_offset: {}".format(cryptsession10container_offset))
self.data.powerpointdocument.seek(cryptsession10container_offset)
cryptsession10container = _parseCryptSession10Container(self.data.powerpointdocument)
logger.debug("[*] cryptsession10container: {}".format(cryptsession10container))
encryptionInfo = io.BytesIO(cryptsession10container.data)
encryptionVersionInfo = encryptionInfo.read(4)
vMajor, vMinor = unpack("<HH", encryptionVersionInfo)
logger.debug("[*] encryption version: {} {}".format(vMajor, vMinor))
assert vMajor in [0x0002, 0x0003, 0x0004] and vMinor == 0x0002 # RC4 CryptoAPI
info = _parse_header_RC4CryptoAPI(encryptionInfo)
if DocumentRC4CryptoAPI.verifypw(password, info["salt"], info["keySize"], info["encryptedVerifier"], info["encryptedVerifierHash"]):
self.type = "rc4_cryptoapi"
self.key = password
self.salt = info["salt"]
self.keySize = info["keySize"]
else:
raise exceptions.InvalidKeyError("Failed to verify password")
[docs] def decrypt(self, ofile):
# Current User Stream
self.data.currentuser.seek(0)
currentuser = _parseCurrentUser(self.data.currentuser)
# logger.debug(currentuser)
cuatom = currentuser.currentuseratom
currentuser_new = CurrentUser(
currentuseratom=CurrentUserAtom(
rh=cuatom.rh,
size=cuatom.size,
# https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/940d5700-e4d7-4fc0-ab48-fed5dbc48bc1
# 0xE391C05F: The file SHOULD NOT<6> be an encrypted document.
headerToken=0xE391C05F,
offsetToCurrentEdit=cuatom.offsetToCurrentEdit,
lenUserName=cuatom.lenUserName,
docFileVersion=cuatom.docFileVersion,
majorVersion=cuatom.majorVersion,
minorVersion=cuatom.minorVersion,
unused=cuatom.unused,
ansiUserName=cuatom.ansiUserName,
relVersion=cuatom.relVersion,
unicodeUserName=cuatom.unicodeUserName,
)
)
buf = _packCurrentUser(currentuser_new)
buf.seek(0)
currentuser_buf = buf
# List of encrypted parts: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/b0963334-4408-4621-879a-ef9c54551fd8
# PowerPoint Document Stream
self.data.powerpointdocument.seek(0)
powerpointdocument_size = len(self.data.powerpointdocument.read())
logger.debug("[*] powerpointdocument_size: {}".format(powerpointdocument_size))
self.data.powerpointdocument.seek(0)
dec_bytearray = bytearray(self.data.powerpointdocument.read())
# UserEditAtom
self.data.powerpointdocument.seek(currentuser.currentuseratom.offsetToCurrentEdit)
# currentuseratom_raw = self.data.powerpointdocument.read(40)
self.data.powerpointdocument.seek(currentuser.currentuseratom.offsetToCurrentEdit)
usereditatom = _parseUserEditAtom(self.data.powerpointdocument)
# logger.debug(usereditatom)
# logger.debug(["offsetToCurrentEdit", currentuser.currentuseratom.offsetToCurrentEdit])
rh_new = RecordHeader(
recVer=usereditatom.rh.recVer,
recInstance=usereditatom.rh.recInstance,
recType=usereditatom.rh.recType,
recLen=usereditatom.rh.recLen - 4, # Omit encryptSessionPersistIdRef field
)
# logger.debug([_packRecordHeader(usereditatom.rh).read(), _packRecordHeader(rh_new).read()])
usereditatom_new = UserEditAtom(
rh=rh_new,
lastSlideIdRef=usereditatom.lastSlideIdRef,
version=usereditatom.version,
minorVersion=usereditatom.minorVersion,
majorVersion=usereditatom.majorVersion,
offsetLastEdit=usereditatom.offsetLastEdit,
offsetPersistDirectory=usereditatom.offsetPersistDirectory,
docPersistIdRef=usereditatom.docPersistIdRef,
persistIdSeed=usereditatom.persistIdSeed,
lastView=usereditatom.lastView,
unused=usereditatom.unused,
encryptSessionPersistIdRef=0x00000000, # Clear
)
# logger.debug(currentuseratom_raw)
# logger.debug(_packUserEditAtom(usereditatom).read())
# logger.debug(_packUserEditAtom(usereditatom_new).read())
buf = _packUserEditAtom(usereditatom_new)
buf.seek(0)
buf_bytes = bytearray(buf.read())
offset = currentuser.currentuseratom.offsetToCurrentEdit
dec_bytearray[offset : offset + len(buf_bytes)] = buf_bytes
# PersistDirectoryAtom
self.data.powerpointdocument.seek(currentuser.currentuseratom.offsetToCurrentEdit)
usereditatom = _parseUserEditAtom(self.data.powerpointdocument)
# logger.debug(usereditatom)
self.data.powerpointdocument.seek(usereditatom.offsetPersistDirectory)
persistdirectoryatom = _parsePersistDirectoryAtom(self.data.powerpointdocument)
# logger.debug(persistdirectoryatom)
persistdirectoryatom_new = PersistDirectoryAtom(
rh=persistdirectoryatom.rh,
rgPersistDirEntry=[
PersistDirectoryEntry(
persistId=persistdirectoryatom.rgPersistDirEntry[0].persistId,
# Omit CryptSession10Container
cPersist=persistdirectoryatom.rgPersistDirEntry[0].cPersist - 1,
rgPersistOffset=persistdirectoryatom.rgPersistDirEntry[0].rgPersistOffset,
),
],
)
self.data.powerpointdocument.seek(usereditatom.offsetPersistDirectory)
buf = _packPersistDirectoryAtom(persistdirectoryatom_new)
buf_bytes = bytearray(buf.read())
offset = usereditatom.offsetPersistDirectory
dec_bytearray[offset : offset + len(buf_bytes)] = buf_bytes
# Persist Objects
self.data.powerpointdocument.seek(0)
persistobjectdirectory = construct_persistobjectdirectory(self.data)
directory_items = list(persistobjectdirectory.items())
for i, (persistId, offset) in enumerate(directory_items):
self.data.powerpointdocument.seek(offset)
buf = self.data.powerpointdocument.read(8)
rh = _parseRecordHeader(io.BytesIO(buf))
logger.debug("[*] rh: {}".format(rh))
# CryptSession10Container
if rh.recType == 0x2F14:
logger.debug("[*] CryptSession10Container found")
# Remove encryption, pad by zero to preserve stream size
dec_bytearray[offset : offset + (8 + rh.recLen)] = b"\x00" * (8 + rh.recLen)
continue
# The UserEditAtom record (section 2.3.3) and the PersistDirectoryAtom record (section 2.3.4) MUST NOT be encrypted.
if rh.recType in [0x0FF5, 0x1772]:
logger.debug("[*] UserEditAtom/PersistDirectoryAtom found")
continue
# TODO: Fix here
recLen = directory_items[i + 1][1] - offset - 8
logger.debug("[*] recLen: {}".format(recLen))
self.data.powerpointdocument.seek(offset)
enc_buf = io.BytesIO(self.data.powerpointdocument.read(8 + recLen))
blocksize = self.keySize * ((8 + recLen) // self.keySize + 1) # Undocumented
dec = DocumentRC4CryptoAPI.decrypt(self.key, self.salt, self.keySize, enc_buf, blocksize=blocksize, block=persistId)
dec_bytes = bytearray(dec.read())
dec_bytearray[offset : offset + len(dec_bytes)] = dec_bytes
# To BytesIO
dec_buf = io.BytesIO(dec_bytearray)
dec_buf.seek(0)
for i, (persistId, offset) in enumerate(directory_items):
dec_buf.seek(offset)
buf = dec_buf.read(8)
rh = _parseRecordHeader(io.BytesIO(buf))
logger.debug("[*] rh: {}".format(rh))
dec_buf.seek(0)
logger.debug("[*] powerpointdocument_size={}, len(dec_buf.read())={}".format(powerpointdocument_size, len(dec_buf.read())))
dec_buf.seek(0)
powerpointdocument_dec_buf = dec_buf
# TODO: Pictures Stream
# TODO: Encrypted Summary Info Stream
with tempfile.TemporaryFile() as _ofile:
self.file.seek(0)
shutil.copyfileobj(self.file, _ofile)
outole = olefile.OleFileIO(_ofile, write_mode=True)
outole.write_stream("Current User", currentuser_buf.read())
outole.write_stream("PowerPoint Document", powerpointdocument_dec_buf.read())
# Finalize
_ofile.seek(0)
shutil.copyfileobj(_ofile, ofile)
return
[docs] def is_encrypted(self):
r"""
Test if the file is encrypted.
>>> f = open("tests/inputs/plain.ppt", "rb")
>>> file = Ppt97File(f)
>>> file.is_encrypted()
False
>>> f = open("tests/inputs/rc4cryptoapi_password.ppt", "rb")
>>> file = Ppt97File(f)
>>> file.is_encrypted()
True
"""
self.data.currentuser.seek(0)
currentuser = _parseCurrentUser(self.data.currentuser)
logger.debug("[*] currentuser: {}".format(currentuser))
self.data.powerpointdocument.seek(currentuser.currentuseratom.offsetToCurrentEdit)
usereditatom = _parseUserEditAtom(self.data.powerpointdocument)
logger.debug("[*] usereditatom: {}".format(usereditatom))
if usereditatom.rh.recLen == 0x00000020: # Cf. _parseUserEditAtom
return True
else:
return False