diff --git a/oletools/ppt_record_parser.py b/oletools/ppt_record_parser.py new file mode 100644 index 0000000..35dacca --- /dev/null +++ b/oletools/ppt_record_parser.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python + +""" +ppt_record_parser.py + +Alternative to ppt_parser.py that works on records +""" + +# === LICENSE ================================================================= +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from __future__ import print_function + +#------------------------------------------------------------------------------ +# CHANGELOG: +# 2017-11-30 v0.01 CH: - first version based on xls_parser + +#------------------------------------------------------------------------------ +# TODO: + +# ----------------------------------------------------------------------------- +# REFERENCES: +# - [MS-PPT] + + +import sys +from struct import unpack +import logging +import record_base +import io + + +class PptFile(record_base.OleRecordFile): + """ Record-based view on a PowerPoint ppt file """ + + @classmethod + def stream_class_for_name(self, stream_name): + return PptStream + +class PptStream(record_base.OleRecordStream): + """ a stream of records in a ppt file """ + + def read_record_head(self): + """ read first few bytes of record to determine size and type + + returns (type, size, other) where other is (instance, version) + """ + ver_inst, rec_type, rec_size = unpack(' use PptRecordCurrentUser instead + (0x1772, 'PersistDirectoryAtom'), + (0x2f14, 'CryptSession10Container'), + # document types + (0x03e8, 'DocumentContainer'), + (0x0fc9, 'HandoutContainer'), + (0x03f0, 'NotesContainer'), + (0x03ff, 'VbaInfoContainer'), + (0x03e9, 'DocumentAtom'), + (0x03ea, 'EndDocumentAtom'), + # slide types + (0x03ee, 'SlideContainer'), + (0x03f8, 'MainMasterContainer'), + # external object ty + (0x0409, 'ExObjListContainer'), + (0x1011, 'ExOleVbaActiveXAtom'), # ExOleObj|VbaProject|ExControl]Stg[Unc|C]ompressedAtom + (0x1006, 'ExAviMovieContainer'), + (0x100e, 'ExCDAudioContainer'), + (0x0fee, 'ExControlContainer'), + (0x0fd7, 'ExHyperlinkContainer'), + (0x1007, 'ExMCIMovieContainer'), + (0x100d, 'ExMIDIAudioContainer'), + (0x0fcc, 'ExOleEmbedContainer'), + (0x0fce, 'ExOleLinkContainer'), + (0x100f, 'ExWAVAudioEmbeddedContainer'), + (0x1010, 'ExWAVAudioLinkContainer'), + (0x1004, 'ExMediaAtom'), + # other types + (0x0fc1, 'MetafileBlob'), + (0x0fb8, 'FontEmbedDataBlob'), + (0x07e7, 'SoundDataBlob'), + (0x138b, 'BinaryTagDataBlob'), +]) + +# record types where version is not 0x0 or 0xf +VERSION_EXCEPTIONS = dict([ + (0x0400, 2), # rt_vbainfoatom + (0x03ef, 2), # rt_slideatom +]) + +# record types where instance is not 0x0 or 0x1 +INSTANCE_EXCEPTIONS = dict([ + (0x0fba, (2, 0x14)), # rt_cstring, + (0x0ff0, (2, 2)), # rt_slidelistwithtext, + (0x0fd9, (3, 4)), # rt_headersfooters, + (0x07e4, (5, 5)), # rt_soundcollection, + (0x03fb, (7, 7)), # rt_guideatom, + (0x07e9, (2, 2)), # rt_bookmarkseeatom, + (0x07f0, (6, 6)), # rt_colorschemeatom, + (0xf125, (0, 5)), # rt_timeconditioncontainer, + (0xf13d, (0, 0xa)), # rt_timepropertylist, + (0x0fc8, (2, 2)), # rt_kinsoku, + (0x0fd2, (3, 3)), # rt_kinsokuatom, + (0x0f9f, (0, 5)), # rt_textheaderatom, + (0x0fb7, (0, 128)), # rt_fontentityatom, + (0x0fa3, (0, 8)), # rt_textmasterstyleatom, + (0x0fad, (0, 8)), # rt_textmasterstyle9atom, + (0x0fb2, (0, 8)), # rt_textmasterstyle10atom, + (0x07f9, (0, 0x80)), # rt_blibentitiy9atom, + (0x0faf, (0, 5)), # rt_outlinetextpropsheader9atom, + (0x0fb8, (0, 3)), # rt_fontembeddatablob, +]) + + +############################################################################### +# TESTING +############################################################################### + + +if __name__ == '__main__': + def print_subrecords(record): + if isinstance(record, PptContainerRecord): + for subrec in record.records: + logging.info(' {0}'.format(subrec)) + elif isinstance(record, PptRecordCurrentUser): + logging.info(' crypt: {0}, offset {1}, user {2}/{3}' + .format(record.is_document_encrypted(), + record.offset_to_current_edit, + repr(record.ansi_user_name), + repr(record.unicode_user_name))) + sys.exit(record_base.test(sys.argv[1:], PptFile, + do_per_record=print_subrecords))