Commit 7fa91aa42f9e9b2e8b80d1c56efcf0fd7e062d80

Authored by Philippe Lagadec
1 parent d8969fe1

added new tools: oledir and olemap

oletools/oledir.py 0 → 100755
  1 +#!/usr/bin/env python
  2 +"""
  3 +oledir.py
  4 +
  5 +oledir parses OLE files to display technical information about its directory
  6 +entries, including deleted/orphan streams/storages and unused entries.
  7 +
  8 +Author: Philippe Lagadec - http://www.decalage.info
  9 +License: BSD, see source code or documentation
  10 +
  11 +oledir is part of the python-oletools package:
  12 +http://www.decalage.info/python/oletools
  13 +"""
  14 +
  15 +#=== LICENSE ==================================================================
  16 +
  17 +# oledir is copyright (c) 2015-2016 Philippe Lagadec (http://www.decalage.info)
  18 +# All rights reserved.
  19 +#
  20 +# Redistribution and use in source and binary forms, with or without modification,
  21 +# are permitted provided that the following conditions are met:
  22 +#
  23 +# * Redistributions of source code must retain the above copyright notice, this
  24 +# list of conditions and the following disclaimer.
  25 +# * Redistributions in binary form must reproduce the above copyright notice,
  26 +# this list of conditions and the following disclaimer in the documentation
  27 +# and/or other materials provided with the distribution.
  28 +#
  29 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  30 +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  31 +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  32 +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  33 +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  34 +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  35 +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  36 +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  37 +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  38 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  39 +
  40 +
  41 +#------------------------------------------------------------------------------
  42 +# CHANGELOG:
  43 +# 2015-04-17 v0.01 PL: - first version
  44 +# 2015-04-21 v0.02 PL: - improved display with prettytable
  45 +# 2016-01-13 v0.03 PL: - replaced prettytable by tablestream, added colors
  46 +
  47 +__version__ = '0.03'
  48 +
  49 +#------------------------------------------------------------------------------
  50 +# TODO:
  51 +# TODO: show FAT/MiniFAT
  52 +# TODO: show errors when reading streams
  53 +
  54 +# === IMPORTS ================================================================
  55 +
  56 +import sys, os
  57 +from thirdparty.olefile import olefile
  58 +# from thirdparty.prettytable import prettytable
  59 +from thirdparty.tablestream import tablestream
  60 +from thirdparty.colorclass import colorclass
  61 +
  62 +
  63 +def sid_display(sid):
  64 + if sid == olefile.NOSTREAM:
  65 + return '-' #None
  66 + else:
  67 + return sid
  68 +
  69 +STORAGE_NAMES = {
  70 + olefile.STGTY_EMPTY: 'Empty',
  71 + olefile.STGTY_STORAGE: 'Storage',
  72 + olefile.STGTY_STREAM: 'Stream',
  73 + olefile.STGTY_LOCKBYTES: 'ILockBytes',
  74 + olefile.STGTY_PROPERTY: 'IPropertyStorage',
  75 + olefile.STGTY_ROOT: 'Root',
  76 +}
  77 +
  78 +STORAGE_COLORS = {
  79 + olefile.STGTY_EMPTY: 'green',
  80 + olefile.STGTY_STORAGE: 'blue',
  81 + olefile.STGTY_STREAM: 'yellow',
  82 + olefile.STGTY_LOCKBYTES: 'magenta',
  83 + olefile.STGTY_PROPERTY: 'magenta',
  84 + olefile.STGTY_ROOT: 'cyan',
  85 +}
  86 +
  87 +STATUS_COLORS = {
  88 + 'unused': 'green',
  89 + '<Used>': 'yellow',
  90 + 'ORPHAN': 'red',
  91 +}
  92 +
  93 +# === MAIN ===================================================================
  94 +
  95 +if __name__ == '__main__':
  96 + # print banner with version
  97 + print 'oledir %s - http://decalage.info/python/oletools' % __version__
  98 +
  99 + if os.name == 'nt':
  100 + colorclass.Windows.enable(auto_colors=True, reset_atexit=True)
  101 +
  102 + fname = sys.argv[1]
  103 + print('OLE directory entries in file %s:' % fname)
  104 + ole = olefile.OleFileIO(fname)
  105 + # ole.dumpdirectory()
  106 +
  107 + # t = prettytable.PrettyTable(('id', 'Status', 'Type', 'Name', 'Left', 'Right', 'Child', '1st Sect', 'Size'))
  108 + # t.align = 'l'
  109 + # t.max_width['id'] = 4
  110 + # t.max_width['Status'] = 6
  111 + # t.max_width['Type'] = 10
  112 + # t.max_width['Name'] = 10
  113 + # t.max_width['Left'] = 5
  114 + # t.max_width['Right'] = 5
  115 + # t.max_width['Child'] = 5
  116 + # t.max_width['1st Sect'] = 8
  117 + # t.max_width['Size'] = 6
  118 +
  119 + table = tablestream.TableStream(column_width=[4, 6, 7, 22, 5, 5, 5, 8, 6],
  120 + header_row=('id', 'Status', 'Type', 'Name', 'Left', 'Right', 'Child', '1st Sect', 'Size'),
  121 + style=tablestream.TableStyleSlim)
  122 +
  123 + # TODO: read ALL the actual directory entries from the directory stream, because olefile does not!
  124 + # TODO: OR fix olefile!
  125 + # TODO: olefile should store or give access to the raw direntry data on demand
  126 + # TODO: oledir option to hexdump the raw direntries
  127 + # TODO: olefile should be less picky about incorrect directory structures
  128 +
  129 + for id in xrange(len(ole.direntries)):
  130 + d = ole.direntries[id]
  131 + if d is None:
  132 + # this direntry is not part of the tree: either unused or an orphan
  133 + d = ole._load_direntry(id) #ole.direntries[id]
  134 + # print('%03d: %s *** ORPHAN ***' % (id, d.name))
  135 + if d.entry_type == olefile.STGTY_EMPTY:
  136 + status = 'unused'
  137 + else:
  138 + status = 'ORPHAN'
  139 + else:
  140 + # print('%03d: %s' % (id, d.name))
  141 + status = '<Used>'
  142 + if d.name.startswith('\x00'):
  143 + # this may happen with unused entries, the name may be filled with zeroes
  144 + name = ''
  145 + else:
  146 + # handle non-printable chars using repr(), remove quotes:
  147 + name = repr(d.name)[1:-1]
  148 + left = sid_display(d.sid_left)
  149 + right = sid_display(d.sid_right)
  150 + child = sid_display(d.sid_child)
  151 + entry_type = STORAGE_NAMES.get(d.entry_type, 'Unknown')
  152 + etype_color = STORAGE_COLORS.get(d.entry_type, 'red')
  153 + status_color = STATUS_COLORS.get(status, 'red')
  154 +
  155 + # print(' type=%7s sid_left=%s sid_right=%s sid_child=%s'
  156 + # %(entry_type, left, right, child))
  157 + # t.add_row((id, status, entry_type, name, left, right, child, hex(d.isectStart), d.size))
  158 + table.write_row((id, status, entry_type, name, left, right, child, '%X' % d.isectStart, d.size),
  159 + colors=(None, status_color, etype_color, None, None, None, None, None, None))
  160 + ole.close()
  161 + # print t
  162 +
  163 +
oletools/olemap.py 0 → 100755
  1 +#!/usr/bin/env python
  2 +"""
  3 +olemap
  4 +
  5 +olemap parses OLE files to display technical information about its structure.
  6 +
  7 +Author: Philippe Lagadec - http://www.decalage.info
  8 +License: BSD, see source code or documentation
  9 +
  10 +olemap is part of the python-oletools package:
  11 +http://www.decalage.info/python/oletools
  12 +"""
  13 +
  14 +#=== LICENSE ==================================================================
  15 +
  16 +# olemap is copyright (c) 2015 Philippe Lagadec (http://www.decalage.info)
  17 +# All rights reserved.
  18 +#
  19 +# Redistribution and use in source and binary forms, with or without modification,
  20 +# are permitted provided that the following conditions are met:
  21 +#
  22 +# * Redistributions of source code must retain the above copyright notice, this
  23 +# list of conditions and the following disclaimer.
  24 +# * Redistributions in binary form must reproduce the above copyright notice,
  25 +# this list of conditions and the following disclaimer in the documentation
  26 +# and/or other materials provided with the distribution.
  27 +#
  28 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  29 +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  30 +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  31 +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  32 +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  33 +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  34 +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  35 +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  36 +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  37 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  38 +
  39 +
  40 +#------------------------------------------------------------------------------
  41 +# CHANGELOG:
  42 +# 2015-11-01 v0.01 PL: - first version
  43 +# 2016-01-13 v0.02 PL: - improved display with tablestream, added colors
  44 +
  45 +__version__ = '0.02'
  46 +
  47 +#------------------------------------------------------------------------------
  48 +# TODO:
  49 +
  50 +# === IMPORTS ================================================================
  51 +
  52 +import sys
  53 +from thirdparty.olefile import olefile
  54 +from thirdparty.tablestream import tablestream
  55 +
  56 +
  57 +
  58 +def sid_display(sid):
  59 + if sid == olefile.NOSTREAM:
  60 + return None
  61 + else:
  62 + return sid
  63 +
  64 +STORAGE_NAMES = {
  65 + olefile.STGTY_EMPTY: 'Empty',
  66 + olefile.STGTY_STORAGE: 'Storage',
  67 + olefile.STGTY_STREAM: 'Stream',
  68 + olefile.STGTY_LOCKBYTES: 'ILockBytes',
  69 + olefile.STGTY_PROPERTY: 'IPropertyStorage',
  70 + olefile.STGTY_ROOT: 'Root',
  71 +}
  72 +
  73 +FAT_TYPES = {
  74 + olefile.FREESECT: "Free",
  75 + olefile.ENDOFCHAIN: "End of Chain",
  76 + olefile.FATSECT: "FAT Sector",
  77 + olefile.DIFSECT: "DIFAT Sector"
  78 + }
  79 +
  80 +FAT_COLORS = {
  81 + olefile.FREESECT: "green",
  82 + olefile.ENDOFCHAIN: "yellow",
  83 + olefile.FATSECT: "cyan",
  84 + olefile.DIFSECT: "blue",
  85 + 'default': None,
  86 + }
  87 +
  88 +
  89 +# === MAIN ===================================================================
  90 +
  91 +if __name__ == '__main__':
  92 + # print banner with version
  93 + print 'olemap %s - http://decalage.info/python/oletools' % __version__
  94 +
  95 + fname = sys.argv[1]
  96 + ole = olefile.OleFileIO(fname)
  97 +
  98 + print 'FAT:'
  99 + t = tablestream.TableStream([8, 12, 8, 8], header_row=['Sector #', 'Type', 'Offset', 'Next #'])
  100 + for i in xrange(ole.nb_sect):
  101 + fat_value = ole.fat[i]
  102 + fat_type = FAT_TYPES.get(fat_value, '<Data>')
  103 + color_type = FAT_COLORS.get(fat_value, FAT_COLORS['default'])
  104 + # compute offset based on sector size:
  105 + offset = ole.sectorsize * (i+1)
  106 + # print '%8X: %-12s offset=%08X next=%8X' % (i, fat_type, 0, fat_value)
  107 + t.write_row(['%8X' % i, fat_type, '%08X' % offset, '%8X' % fat_value],
  108 + colors=[None, color_type, None, None])
  109 + print ''
  110 +
  111 + print 'MiniFAT:'
  112 + # load MiniFAT if it wasn't already done:
  113 + ole.loadminifat()
  114 + for i in xrange(len(ole.minifat)):
  115 + fat_value = ole.minifat[i]
  116 + fat_type = FAT_TYPES.get(fat_value, 'Data')
  117 + print '%8X: %-12s offset=%08X next=%8X' % (i, fat_type, 0, fat_value)
  118 +
  119 + ole.close()
  120 +
  121 +