Source code for msoffcrypto.format.ppt97

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