add OLE library as Rex::OLE

git-svn-id: file:///home/svn/framework3/trunk@8457 4d416f70-5f16-0410-b530-b9f4589650da
unstable
Joshua Drake 2010-02-11 18:36:38 +00:00
parent 8a44f4b9d8
commit d4d271eef3
16 changed files with 1939 additions and 0 deletions

128
lib/rex/ole.rb Normal file
View File

@ -0,0 +1,128 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
#
# License: MSF_LICENSE
#
#
# This module implements Object-Linking-and-Embedding otherwise known as
# Compound File Binary File Format or Windows Compound Binary File Format.
# OLE is the container format for modern Excel, Word, PowerPoint, and many
# other file formats.
#
# NOTE: This implementation is almost fully compliant with [MS-CFB] v1.1
#
#
# SUPPORTS:
#
# 1. R/W v3 OLE files (v4 may work, but wasn't tested)
# 2. RO double-indirect fat sectors
# 3. RO fat sectors (including those in double-indirect parts)
# 4. WO support for less than 109 fat sectors :)
# 5. R/W minifat sectors
# 6. R/W ministream
# 7. R/W normal streams
# 8. R/W substorages (including nesting)
# 9. full directory support (hierarchal and flattened access)
# 10. big and little endian files (although only little endian was tested)
#
#
# TODO (in order of priority):
#
# 1. support deleting storages/streams
# 2. create copyto and other typical interface functions
# 3. support writing DIF sectors > 109
# - may lead to allocating more fat sectors :-/
# 4. properly support mode params for open_stream/open_storage/etc
# 5. optimize to prevent unecessary loading/writing
# 6. support for auxillary streams (DocumentSummaryInformation and SummaryInformation)
# 7. support non-committal editing (open, change, close w/o save)
# 8. support timestamps
# 9. provide interface to change paramters (endian, etc)
#
#
# TO INVESTIGATE:
#
# 1. moving storage interface functions into something used by both
# the main storage and substorages (unifying the code) (mixin?)
# 2. eliminating flattening the directory prior to writing it out
#
##
require 'rex'
module Rex
module OLE
# misc util
# NOTE: the v1.1 spec says that everything "MUST be stored in little-endian byte order"
BIG_ENDIAN = 0xfeff
LITTLE_ENDIAN = 0xfffe
# defines Util class
require 'rex/ole/util'
require 'rex/ole/clsid'
# constants for dealing with the header
HDR_SZ = 512
# signatures
SIG = "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1"
SIG_BETA = "\x0e\x11\xfc\x0d\xd0\xcf\x11\xe0"
# defines Header class
require 'rex/ole/header'
# sector types
SECT_MAX = 0xfffffffa
SECT_DIF = 0xfffffffc
SECT_FAT = 0xfffffffd
SECT_END = 0xfffffffe
SECT_FREE = 0xffffffff
# defines DIFAT class
require 'rex/ole/difat'
# defines FAT class
require 'rex/ole/fat'
# defines MiniFAT class
require 'rex/ole/minifat'
# directory entries
DIRENTRY_SZ = 128
DIR_NOSTREAM = 0xffffffff
DIR_MAXREGSID = 0xfffffffa
# defines Directory class
require 'rex/ole/directory'
# types
STGTY_INVALID = 0
STGTY_STORAGE = 1
STGTY_STREAM = 2
STGTY_LOCKBYTES = 3
STGTY_PROPERTY = 4
STGTY_ROOT = 5
# for red/black tree
COLOR_RED = 0
COLOR_BLACK = 1
# defines DirEntry base class
require 'rex/ole/direntry'
# constants for storages
STGM_READ = 0
STGM_WRITE = 1
STGM_READWRITE = 2
# defines Storage class
require 'rex/ole/storage'
# defines SubStorage class
require 'rex/ole/substorage'
# defines Stream class
require 'rex/ole/stream'
end
end

47
lib/rex/ole/clsid.rb Normal file
View File

@ -0,0 +1,47 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class CLSID
def initialize(buf=nil)
@buf = buf
@buf ||= "\x00" * 16
end
def pack
@buf
end
def to_s
ret = ""
ret << "%08x" % Util.get32(@buf, 0)
ret << "-"
ret << "%04x" % Util.get16(@buf, 4)
ret << "-"
ret << "%04x" % Util.get16(@buf, 6)
ret << "-"
idx = 0
last8 = @buf[8,8]
last8.unpack('C*').each { |byte|
ret << [byte].pack('C').unpack('H*')[0]
ret << "-" if (idx == 1)
idx += 1
}
ret
end
end
end
end

141
lib/rex/ole/difat.rb Normal file
View File

@ -0,0 +1,141 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class DIFAT
def initialize stg
@stg = stg
@entries = []
end
#
# convenience access to entries
#
def []=(idx,expr)
@entries[idx] = expr
end
def [](idx)
@entries[idx]
end
def +(expr)
@entries += expr
self
end
def <<(expr)
@entries << expr
end
def length
@entries.length
end
def slice!(start,stop)
@entries.slice!(start,stop)
end
def reset
@entries = []
end
def each
@entries.each { |el|
yield el
}
end
#
# woop
#
def to_s
ret = "{ "
@entries.each { |el|
ret << ", " if (ret.length > 2)
case el
when SECT_END
ret << "END"
when SECT_DIF
ret << "DIF"
when SECT_FAT
ret << "FAT"
when SECT_FREE
ret << "FREE"
else
ret << "0x%x" % el
end
}
ret << " }"
ret
end
#
# low-level functions
#
def read
@entries = []
# start with the header part
@entries += @stg.header._sectFat
# double indirect fat
sect = @stg.header._sectDifStart
while (sect != SECT_END)
if (@entries.include?(sect))
raise RuntimeError, 'Sector chain loop detected (0x%08x)' % sect
end
@entries << sect
buf = @stg.read_sector(sect, @stg.header.sector_size)
# the last sect ptr in the block becomes the next entry
sect = Util.get32(buf, ((@stg.header.idx_per_sect)-1) * 4)
end
# don't need these free ones, but it doesn't hurt to keep them.
#@difat.delete(SECT_FREE)
end
def write
len = @entries.length
first109 = @entries.dup
rest = nil
if (len > 109)
rest = first109.slice!(109,len)
end
@stg.header._sectFat = []
@stg.header._sectFat += first109
if (len < 109)
need = 109 - len
need.times {
@stg.header._sectFat << SECT_FREE
}
end
if (rest and rest.length > 0)
raise RuntimeError, 'TODO: support writing DIF properly!'
# may require adding more fat sectors :-/
#@stg.header._csectDif = rest.length
#@stg.header._sectDifStart = idx
end
@stg.header._csectFat = len
end
end
end
end

230
lib/rex/ole/directory.rb Normal file
View File

@ -0,0 +1,230 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
require 'rex/ole/direntry'
#
# This class serves as the root directory entry in addition to
# an abstraction around the concept of a directory as a whole.
#
class Directory < DirEntry
# XXX: num_entries is not maintained once a stream/storage is added!
attr_accessor :num_entries
def initialize(stg)
super
@num_entries = 1
end
# woop, recursive each
def yield_entries(de, &block)
block.call(de)
de.each { |el|
yield_entries(el, &block)
}
end
def each_entry(&block)
yield_entries(self, &block)
end
def set_ministream_params(start, size)
@_sectStart = start
@_ulSize = size
end
def link_item(parent, child)
# set sid, advance count
child.sid = @num_entries
@num_entries += 1
# link item to siblings and/or parent
if (parent._sidChild == DIR_NOSTREAM)
parent._sidChild = child.sid
dlog("Linking #{child.name} as THE child of #{parent.name} as sid #{child.sid}", 'rex', LEV_3)
else
sib = nil
parent.each { |el|
if (el._sidLeftSib == DIR_NOSTREAM)
sib = el
el._sidLeftSib = child.sid
dlog("Linking #{child.name} as the LEFT sibling of #{sib.name} as sid #{child.sid}", 'rex', LEV_3)
break
end
if (el._sidRightSib == DIR_NOSTREAM)
sib = el
el._sidRightSib = child.sid
dlog("Linking #{child.name} as the RIGHT sibling of #{sib.name} as sid #{child.sid}", 'rex', LEV_3)
break
end
}
raise RuntimeError, 'Unable to find a sibling to link to in the directory'
end
parent << child
end
#
# low-level functions
#
def from_s(sid, buf)
super
if (@_sidRightSib != DIR_NOSTREAM)
raise RuntimeError, 'Root Entry is invalid! (has right sibling)'
end
if (@_sidLeftSib != DIR_NOSTREAM)
raise RuntimeError, 'Root Entry is invalid! (has left sibling)'
end
end
def read
@children = []
visited = []
entries = []
root_node = nil
sect = @stg.header._sectDirStart
while (sect != SECT_END)
if (visited.include?(sect))
raise RuntimeError, 'Sector chain loop detected (0x%08x)' % sect
end
visited << sect
sbuf = @stg.read_sector(sect, @stg.header.sector_size)
while (sbuf.length >= DIRENTRY_SZ)
debuf = sbuf.slice!(0, DIRENTRY_SZ)
type = Util.get8(debuf, 0x42)
case type
when STGTY_ROOT
if (entries.length != 0)
raise RuntimeError, 'Root Entry found, but not first encountered!'
end
if (root_node)
raise RuntimeError, 'Multiple root directory sectors detected (0x%08x)' % sect
end
de = self
root_node = de
when STGTY_STORAGE
de = SubStorage.new @stg
when STGTY_STREAM
de = Stream.new @stg
when STGTY_INVALID
# skip invalid entries
next
else
raise RuntimeError, 'Unsupported directory entry type (0x%02x)' % type
end
# read content
de.from_s(entries.length, debuf)
entries << de
end
sect = @stg.next_sector(sect)
end
@num_entries = entries.length
# sort out the tree structure, starting with the root
if (@_sidChild != DIR_NOSTREAM)
populate_children(entries, root_node, @_sidChild)
end
end
# recursively add entries to their proper parents :)
def populate_children(entries, parent, sid)
node = entries[sid]
dlog("populate_children(entries, \"#{parent.name}\", #{sid}) - node: #{node.name}", 'rex', LEV_3)
parent << node
if (node.type == STGTY_STORAGE) and (node._sidChild != DIR_NOSTREAM)
populate_children(entries, node, node._sidChild)
end
if (node._sidLeftSib != DIR_NOSTREAM)
populate_children(entries, parent, node._sidLeftSib)
end
if (node._sidRightSib != DIR_NOSTREAM)
populate_children(entries, parent, node._sidRightSib)
end
end
# NOTE: this may not be necessary if we were to use each_entry
def flatten_tree(entries, parent)
entries << parent
parent.each { |el|
flatten_tree(entries, el)
}
end
def write
# flatten the directory again
entries = []
flatten_tree(entries, self)
dlog("flattened tree has #{entries.length} entries...", 'rex', LEV_3)
# count directory sectors
ds_count = entries.length / 4
if ((entries.length % 4) > 0)
# one more sector to hold the rest
ds_count += 1
end
# put the root entry first
sbuf = self.pack
# add the rest
prev_sect = nil
dir_start = nil
entries.each { |de|
# we already got the root entry, no more!
next if (de.type == STGTY_ROOT)
dir = de.pack
dlog("writing dir entry #{de.name}", 'rex', LEV_3)
sbuf << dir
if (sbuf.length == @stg.header.sector_size)
# we have a full sector, add it!
sect = @stg.write_sector(sbuf, nil, prev_sect)
prev_sect = sect
dir_start ||= sect
# reset..
sbuf = ""
end
}
# still a partial sector left?
if (sbuf.length > 0)
# add it! (NOTE: it will get padded with nul bytes if its not sector sized)
sect = @stg.write_sector(sbuf, nil, prev_sect)
prev_sect = sect
dir_start ||= sect
end
@stg.header._sectDirStart = dir_start
end
end
end
end

240
lib/rex/ole/direntry.rb Normal file
View File

@ -0,0 +1,240 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
#
# This class serves as the base class for SubStorage, Stream, and Directory head
#
class DirEntry
attr_accessor :sid
attr_accessor :_sidChild, :_sidLeftSib, :_sidRightSib
def initialize(stg)
@stg = stg
# default to a root entry :)
@sid = 0
@_ab = "Root Entry"
@_cb = nil # NOTE: this is not used until pack
@_mse = STGTY_ROOT
@_bflags = 0
@_sidLeftSib = SECT_FREE
@_sidRightSib = SECT_FREE
@_sidChild = SECT_FREE
@_clsId = CLSID.new
@_dwUserFlags = 0
@_ctime = "\x00" * 8
@_mtime = "\x00" * 8
@_sectStart = SECT_END
@_ulSize = 0
# keep track of logical children (in a tree)
@children = []
end
def length
@_ulSize
end
def <<(expr)
@children << expr
end
def each
@children.each { |de|
yield de
}
end
def type
@_mse
end
def type=(arg)
@_mse = arg
end
def name
@_ab
end
def name=(arg)
# XXX: validate?
@_ab = arg
end
def start_sector
@_sectStart
end
def start_sector=(expr)
@_sectStart = expr
end
# NOTE: this will not look at children
def find_stream_by_name_and_type(name, type)
@children.each { |de|
next if (de.type != type)
if (de.name == name)
return de
end
}
nil
end
def find_by_sid(sid, de=self)
if (de.sid == sid)
return de
end
@children.each { |cde|
ret = find_by_sid(cde, sid)
if (ret)
return ret
end
}
nil
end
#
# low-level functions
#
def from_s(sid, buf)
@sid = sid
@_ab = Util.getUnicodeString(buf[0x00,64])
@_cb = Util.get16(buf, 0x40)
# too big?
if (@_cb > 0x40)
raise RuntimeError, 'Invalid directory entry name length %#x' % @_cb
end
# mismatch?
if (@_ab.length > 0)
declen = ((@_cb) / 2) - 1
if (declen != @_ab.length)
raise RuntimeError, 'Directory entry name and length mismatch (%d != %d)' % [declen, @_ab.length]
end
end
@_mse = Util.get8(buf, 0x42)
@_bflags = Util.get8(buf, 0x43)
@_sidLeftSib = Util.get32(buf, 0x44)
@_sidRightSib = Util.get32(buf, 0x48)
@_sidChild = Util.get32(buf, 0x4c)
# only used for storages..
@_clsId = CLSID.new(buf[0x50,16])
@_dwUserFlags = Util.get32(buf, 0x60)
@_ctime = buf[0x64,8]
@_mtime = buf[0x6c,8]
# only used for streams...
@_sectStart = Util.get32(buf, 0x74)
if (@stg.header._uMajorVersion == 4)
@_ulSize = Util.get64(buf, 0x78)
else
@_ulSize = Util.get32(buf, 0x78)
end
# ignore _dptPropType and pad
end
def pack
@_sectStart ||= SECT_END
@_cb = (@_ab.length + 1) * 2
data = ""
data << Util.putUnicodeString(@_ab) # gets padded/truncated to 0x40 bytes
data << Util.pack16(@_cb)
data << Util.pack8(@_mse)
data << Util.pack8(@_bflags)
data << Util.pack32(@_sidLeftSib)
data << Util.pack32(@_sidRightSib)
data << Util.pack32(@_sidChild)
data << @_clsId.pack
data << Util.pack32(@_dwUserFlags)
data << @_ctime
data << @_mtime
data << Util.pack32(@_sectStart)
data << Util.pack64(@_ulSize)
data
end
def to_s(extra_spaces=0)
@_sectStart ||= SECT_END
@_cb = (@_ab.length + 1) * 2
spstr = " " * extra_spaces
ret = "%s{\n" % spstr
ret << "%s sid => 0x%x" % [spstr, @sid]
ret << ",\n"
ret << "%s _ab => \"%s\"" % [spstr, Util.Printable(@_ab)]
ret << ",\n"
ret << "%s _cb => 0x%04x" % [spstr, @_cb]
ret << ",\n"
ret << "%s _mse => 0x%02x" % [spstr, @_mse]
ret << ",\n"
ret << "%s _bflags => 0x%02x" % [spstr, @_bflags]
ret << ",\n"
ret << "%s _sidLeftSib => 0x%08x" % [spstr, @_sidLeftSib]
ret << ",\n"
ret << "%s _sidRightSib => 0x%08x" % [spstr, @_sidRightSib]
ret << ",\n"
ret << "%s _sidChild => 0x%08x" % [spstr, @_sidChild]
ret << ",\n"
ret << "%s _clsId => %s" % [spstr, @_clsId.to_s]
ret << ",\n"
ret << "%s _dwUserFlags => 0x%08x" % [spstr, @_dwUserFlags]
ret << ",\n"
ret << "%s _ctime => %s" % [spstr, Rex::Text.to_hex_dump(@_ctime).strip]
ret << "\n"
ret << "%s _mtime => %s" % [spstr, Rex::Text.to_hex_dump(@_mtime).strip]
ret << "\n"
ret << "%s _sectStart => 0x%08x" % [spstr, @_sectStart]
ret << ",\n"
ret << "%s _ulSize => 0x%016x" % [spstr, @_ulSize]
if (@_mse == STGTY_STREAM)
ret << ",\n"
ret << "%s data =>\n" % spstr
if (@data)
#ret << Util.Printable(@data)
ret << Rex::Text.to_hex_dump(@data).strip
else
if (@_ulSize > 0)
ret << "--NOT OPENED YET--"
end
end
elsif (@_mse == STGTY_STORAGE) or (@_mse == STGTY_ROOT)
if (@children.length > 0)
ret << ",\n"
ret << "%s *children* =>\n" % spstr
@children.each { |de|
ret << de.to_s(extra_spaces+2)
ret << "\n"
}
end
end
ret << "\n"
ret << "%s}" % spstr
end
end
end
end

99
lib/rex/ole/fat.rb Normal file
View File

@ -0,0 +1,99 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class FAT < DIFAT
#
# low-level functions
#
def read(difat)
@entries = []
cnt = left = @stg.header._csectFat
difat.each { |fs|
break if (left == 0)
if (fs != SECT_FREE)
buf = @stg.read_sector(fs, @stg.header.sector_size)
arr = Util.get32array(buf)
# hax!
if (@entries[fs] == SECT_DIF)
# chop the next ptr
@entries += arr.slice!(0, arr.length - 1)
else
@entries += arr
end
left -= 1
end
}
if (left != 0)
raise RuntimeError, 'Only found %u of %u sectors' % [(cnt - left), cnt]
end
end
def allocate_sector(type=nil)
idx = @entries.index(SECT_FREE)
if (not idx)
# add a sector worth
idx = @entries.length
@stg.header.idx_per_sect.times {
@entries << SECT_FREE
}
end
# mark the sector as in use
if (type)
@entries[idx] = type
else
# default normal sectors to end of chain
@entries[idx] = SECT_END
end
idx
end
def write(difat)
# we build the difat as we write these..
difat.reset
# allocate the sectors
fat_sects = []
left = @entries.length
while (left > 0)
if (left > @stg.header.idx_per_sect)
left -= @stg.header.idx_per_sect
else
left = 0
end
fat_sects << allocate_sector(SECT_FAT)
end
# write the fat into the difat/allocated sectors
copy = @entries.dup
fat_sects.each { |fs|
part = copy.slice!(0, @stg.header.idx_per_sect)
sbuf = Util.pack32array(part)
if (sbuf.length != @stg.header.sector_size)
raise RuntimeError, 'Unsupported number of fat sectors (not multiple of idx per sect)'
end
@stg.write_sector_raw(fs, sbuf)
difat << fs
}
end
end
end
end

204
lib/rex/ole/header.rb Normal file
View File

@ -0,0 +1,204 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
#
# Should we support major == 4 && sectorshift == 0xc ?
#
module Rex
module OLE
require 'rex/ole/util'
class Header
attr_accessor :_csectFat, :_sectFat
attr_accessor :_csectMiniFat, :_sectMiniFatStart
attr_accessor :_ulMiniSectorCutoff, :_uMiniSectorShift
attr_accessor :_csectDif, :_sectDifStart
attr_accessor :_sectDirStart
attr_accessor :_uMajorVersion
attr_accessor :sector_size, :idx_per_sect
attr_accessor :mini_sector_size
def initialize
set_defaults
# calculate some numbers (save a little math)
@sector_size = 1 << @_uSectorShift
@mini_sector_size = 1 << @_uMiniSectorShift
@idx_per_sect = @sector_size / 4
end
def set_defaults
@_abSig = SIG
@_clid = CLSID.new
@_uByteOrder = LITTLE_ENDIAN
@_uMinorVersion = 0x3e
@_uMajorVersion = 0x03
@_uSectorShift = 9 # 512 byte sectors
@_uMiniSectorShift = 6 # 64 byte mini-sectors
@_csectDir = nil # TBD (v4 only, 1 required)
@_csectFat = nil # TBD (one required)
@_sectDirStart = nil # TBD (one required)
@_signature = 0 # no transactions support
@_ulMiniSectorCutoff = 0x1000 # 4k
@_sectMiniFatStart = SECT_END # TBD
@_csectMiniFat = 0 # TBD
@_sectDifStart = SECT_END # TBD (default to none)
@_csectDif = 0 # TBD (default to none)
@_sectFat = [] # TBD
end
def to_s
ret = "{\n"
ret << " _abSig => \"%s\"" % Util.Printable(@_abSig)
ret << ",\n"
ret << " _clid => %s" % @_clid.to_s
ret << ",\n"
ret << " _uMinorVersion => 0x%04x" % @_uMinorVersion
ret << ",\n"
ret << " _uMajorVersion => 0x%04x" % @_uMajorVersion
ret << ",\n"
ret << " _uByteOrder => 0x%04x" % @_uByteOrder
ret << ",\n"
ret << " _uSectorShift => 0x%04x" % @_uSectorShift
ret << ",\n"
ret << " _uMiniSectorShift => 0x%04x" % @_uMiniSectorShift
ret << ",\n"
if (@_csectDir)
ret << " _csectDir => 0x%08x" % @_csectDir
else
ret << " _csectDir => UNALLOCATED" % @_csectDir
end
ret << ",\n"
if (@_csectFat)
ret << " _csectFat => 0x%08x" % @_csectFat
else
ret << " _csectFat => UNALLOCATED"
end
ret << ",\n"
if (@_sectDirStart)
ret << " _sectDirStart => 0x%08x" % @_sectDirStart
else
ret << " _sectDirStart => UNALLOCATED"
end
ret << ",\n"
ret << " _signature => 0x%08x" % @_signature
ret << ",\n"
ret << " _uMiniSectorCutoff => 0x%08x" % @_ulMiniSectorCutoff
ret << ",\n"
ret << " _sectMiniFatStart => 0x%08x" % @_sectMiniFatStart
ret << ",\n"
ret << " _csectMiniFat => 0x%08x" % @_csectMiniFat
ret << ",\n"
ret << " _sectDifStart => 0x%08x" % @_sectDifStart
ret << ",\n"
ret << " _csectDif => 0x%08x" % @_csectDif
#ret << ",\n"
#ret << " _sectFat => "
#ret << Rex::Text.to_hex_dump32array(@_sectFat)
ret << "\n}"
ret
end
#
# low-level functions
#
def read(fd)
buf = fd.read(HDR_SZ)
@_abSig = buf[0x00,8]
if (@_abSig != SIG) and (@_abSig != SIG_BETA)
raise RuntimeError, 'Invalid signature for OLE file'
end
@_clid = CLSID.new(buf[0x08,16])
@_uByteOrder = Util.get16(buf, 0x1c)
Util.set_endian(@_uByteOrder)
@_uMinorVersion = Util.get16(buf, 0x18)
@_uMajorVersion = Util.get16(buf, 0x1a)
@_uSectorShift = Util.get16(buf, 0x1e)
@_uMiniSectorShift = Util.get16(buf, 0x20)
# ignore reserved bytes
@_csectDir = Util.get32(buf, 0x28) # NOTE: only for v4 files
@_csectFat = Util.get32(buf, 0x2c)
@_sectDirStart = Util.get32(buf, 0x30)
@_signature = Util.get32(buf, 0x34)
@_ulMiniSectorCutoff = Util.get32(buf, 0x38)
@_sectMiniFatStart = Util.get32(buf, 0x3c)
@_csectMiniFat = Util.get32(buf, 0x40)
@_sectDifStart = Util.get32(buf, 0x44)
@_csectDif = Util.get32(buf, 0x48)
@_sectFat = Util.get32array(buf[0x4c, (109 * 4)])
end
def write(fd)
hdr = ""
hdr << @_abSig
hdr << @_clid.pack
hdr << Util.pack16(@_uMinorVersion)
hdr << Util.pack16(@_uMajorVersion)
hdr << Util.pack16(@_uByteOrder)
hdr << Util.pack16(@_uSectorShift)
hdr << Util.pack16(@_uMiniSectorShift)
if (@_uMajorVersion == 0x04)
hdr << "\x00" * 6 # reserved bytes
hdr << Util.pack32(@_csectDir)
else
hdr << "\x00" * 10 # reserved bytes
end
fs_count = @_csectFat
fs_count ||= 0
hdr << Util.pack32(fs_count)
dir_start = @_sectDirStart
dir_start ||= SECT_END
hdr << Util.pack32(dir_start)
hdr << Util.pack32(@_signature)
hdr << Util.pack32(@_ulMiniSectorCutoff)
hdr << Util.pack32(@_sectMiniFatStart)
hdr << Util.pack32(@_csectMiniFat)
hdr << Util.pack32(@_sectDifStart)
hdr << Util.pack32(@_csectDif)
hdr << Util.pack32array(@_sectFat)
fd.seek(0, ::IO::SEEK_SET)
fd.write(hdr)
end
end
end
end

77
lib/rex/ole/minifat.rb Normal file
View File

@ -0,0 +1,77 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class MiniFAT < DIFAT
#
# low-level functions
#
def read
@entries = []
visited = []
sect = @stg.header._sectMiniFatStart
@stg.header._csectMiniFat.times { |idx|
break if sect == SECT_END
if (visited.include?(sect))
raise RuntimeError, 'Sector chain loop detected (0x%08x)' % sect
end
visited << sect
buf = @stg.read_sector(sect, @stg.header.sector_size)
@stg.header.idx_per_sect.times { |idx|
@entries << Util.get32(buf, (idx*4))
}
sect = @stg.next_sector(sect)
}
end
def allocate_sector
idx = @entries.index(SECT_FREE)
if (not idx)
# add a sector worth
idx = @entries.length
@stg.header.idx_per_sect.times {
@entries << SECT_FREE
}
end
# default mini-sectors to end of chain
@entries[idx] = SECT_END
idx
end
def write
return if (@entries.length < 1)
mf_start = nil
mfs_count = 0
prev_sect = nil
copy = @entries.dup
while (copy.length > 0)
part = copy.slice!(0, @stg.header.idx_per_sect)
sbuf = Util.pack32array(part)
idx = @stg.write_sector(sbuf, nil, prev_sect)
mfs_count += 1
mf_start ||= idx
end
@stg.header._sectMiniFatStart = mf_start
@stg.header._csectMiniFat = mfs_count
end
end
end
end

View File

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
path = File.expand_path(File.dirname(msfbase))
path += "/../../../"
$:.unshift(path)
require 'rex/ole'
if (ARGV.length < 1)
$stderr.puts "usage: make_ole <file>"
exit(1)
end
document = ARGV.shift
if (stg = Rex::OLE::Storage.new(document, Rex::OLE::STGM_WRITE))
if (stm = stg.create_stream("testing"))
stm << "A" * 1024
stm.close
end
stg.close
end

35
lib/rex/ole/samples/dir.rb Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env ruby
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
path = File.expand_path(File.dirname(msfbase))
path += "/../../../"
$:.unshift(path)
require 'rex/ole'
if (ARGV.length < 1)
$stderr.puts "usage: dir <file>"
exit(1)
end
document = ARGV.shift
# recursive printer :)
def show_entries(ent, spaces=0)
spstr = " " * spaces
puts "%s + #{ent.name}" % spstr
ent.each { |el|
show_entries(el, spaces+2)
}
end
if (stg = Rex::OLE::Storage.new(document))
show_entries(stg)
stg.close
end

View File

@ -0,0 +1,34 @@
#!/usr/bin/env ruby
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
path = File.expand_path(File.dirname(msfbase))
path += "/../../../"
$:.unshift(path)
require 'rex/ole'
if (ARGV.length < 2)
$stderr.puts "usage: dump_stream <file> <stream>"
exit(1)
end
document = ARGV.shift
stream = ARGV.shift
if (stg = Rex::OLE::Storage.new(document))
if (stm = stg.open_stream(stream))
data = stm.read(stm.length)
data ||= ""
$stderr.puts "Successfully opened the \"%s\" stream (%u bytes)" % [stream, data.length]
$stdout.puts data
stm.close
else
$stderr.puts "Unable to open stream: #{stream}"
end
stg.close
else
$stderr.puts "Unable to open storage: #{document}"
end

23
lib/rex/ole/samples/ole_info.rb Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env ruby
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
path = File.expand_path(File.dirname(msfbase))
path += "/../../../"
$:.unshift(path)
require 'rex/ole'
if (ARGV.length < 1)
$stderr.puts "usage: ole_info <file>"
exit(1)
end
document = ARGV.shift
if (stg = Rex::OLE::Storage.new(document))
puts stg.inspect
stg.close
end

395
lib/rex/ole/storage.rb Normal file
View File

@ -0,0 +1,395 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class Storage
attr_accessor :header
def initialize(filename=nil, mode=STGM_READ)
@mode = mode
@modified = nil
@fd = nil
@filename = nil
@header = Header.new
@difat = DIFAT.new self
@fat = FAT.new self
@minifat = MiniFAT.new self
@directory = Directory.new self
@ministream = Stream.new self
if (filename)
@filename = filename
open(filename, mode)
return
end
end
def each
@directory.each { |el|
yield el
}
end
def name
@filename
end
def open(filename, mode)
if (mode == STGM_READWRITE)
fmode = 'r+b'
elsif (mode == STGM_WRITE)
fmode = 'w+b'
else
fmode = 'rb'
end
@fd = File.new(filename, fmode)
# don't read for new files
if (mode == STGM_WRITE)
# ensure there is a root
write_to_disk
return
end
# parse the header
@header.read @fd
@difat.read
@fat.read @difat
@minifat.read
@directory.read
# NOTE: we can't use read_stream_data here (must read using regular FAT, regardless of size)
# read data using the root node's start/length
@ministream << read_data(@directory)
end
def close
if (@modified) and (@mode != STGM_READ)
write_to_disk
end
@fd.close
end
def inspect
ret = ""
ret << "header = %s\n" % @header.to_s
ret << "*** %u DIFAT sectors\n" % @difat.length
ret << @difat.to_s << "\n"
ret << "*** %u FAT sectors\n" % @fat.length
ret << @fat.to_s << "\n"
ret << "*** %u MiniFAT sectors:\n" % @minifat.length
if (@minifat.length > 0)
ret << @minifat.to_s << "\n"
end
ret << "*** ministream (%u bytes):\n" % @ministream.length
if (@ministream.length > 0)
ret << @ministream.to_s << "\n"
end
ret << "*** %u directory entries\n" % @directory.num_entries
ret << @directory.to_s << "\n"
end
#
# stream manipulation functions
#
def create_stream(name, mode=STGM_WRITE, parent_stg=nil)
if (stm = open_stream(name, mode, parent_stg))
stm.close
return nil
end
# eek, don't check the name for now
# if we do, we cant create alot of streams (summary info for example)
=begin
if (not Util.name_is_valid(name))
return nil
end
=end
stm = Stream.new self
stm.name = name
parent_stg ||= @directory
dlog("Adding stream #{name} to storage #{parent_stg.name}", 'rex', LEV_3)
@directory.link_item(parent_stg, stm)
@modified = true
stm
end
def open_stream(name, mode=STGM_READ, parent_stg=nil)
parent_stg ||= @directory
stm = parent_stg.find_stream_by_name_and_type(name, STGTY_STREAM)
if (stm)
# TODO: optimize out the need to read all of the data up-front
stm << read_stream_data(stm)
end
stm
end
#
# storage manipulation functions
#
def create_storage(name, mode=STGM_READ, parent_stg=nil)
stg = SubStorage.new self
stg.name = name
parent_stg ||= @directory
dlog("Adding storage #{name} to storage #{parent_stg.name}", 'rex', LEV_3)
@directory.link_item(parent_stg, stg)
stg
end
def open_storage(name, mode=STGM_READ, parent_stg=nil)
@directory.find_stream_by_name_and_type(name, STGTY_STORAGE)
end
#
# low-level functions
#
def write_to_disk
# reset FAT/DIFAT
@difat = DIFAT.new self
@fat = FAT.new self
@header.write @fd
write_user_data
# NOTE: we call write_stream here since we MUST write this to
# the regular stream (regardless of size)
ms_start = write_stream(@ministream)
@directory.set_ministream_params(ms_start, @ministream.length)
@minifat.write
@directory.write
@fat.write(@difat)
@difat.write
# write it again, now that its complete
@header.write @fd
@fd.flush
end
def write_sector(sbuf, type=nil, prev_sect=nil)
len = sbuf.length
if (len != @header.sector_size)
# pad it if less
if (len < @header.sector_size)
sbuf = sbuf.dup
sbuf << "\x00" * (@header.sector_size - len)
else
raise RuntimeError, 'not sector sized!'
end
end
# write the data
idx = @fat.allocate_sector(type)
# point previous sector to here
if (prev_sect)
@fat[prev_sect] = idx
end
write_sector_raw(idx, sbuf)
return idx
end
def write_sector_raw(sect, sbuf)
dlog("Writing sector 0x%02x" % sect, 'rex', LEV_3)
@fd.seek((sect + 1) * @header.sector_size, ::IO::SEEK_SET)
@fd.write(sbuf)
end
def write_mini_sector(sbuf, prev_sect=nil)
len = sbuf.length
if (len != @header.mini_sector_size)
if (len < @header.mini_sector_size)
sbuf = sbuf.dup
sbuf << "\x00" * (@header.mini_sector_size - len)
else
raise RuntimeError, 'not mini sector sized!'
end
end
idx = @minifat.allocate_sector
# point the previous mini sector to here
if (prev_sect)
@minifat[prev_sect] = idx
end
write_mini_sector_raw(idx, sbuf)
idx
end
def write_mini_sector_raw(sect, sbuf)
dlog("Writing mini sector 0x%02x" % sect, 'rex', LEV_3)
@ministream << sbuf
end
def write_user_data
@directory.each_entry { |stm|
# only regular streams this pass
next if (stm.type != STGTY_STREAM)
if (stm.length >= @header._ulMiniSectorCutoff)
stm.start_sector = write_stream(stm)
else
# NOTE: stm_start is a minifat value
stm.start_sector = write_mini_stream(stm)
end
}
end
def write_stream(stm)
dlog("Writing \"%s\" to regular stream" % stm.name, 'rex', LEV_3)
stm_start = nil
prev_sect = nil
stm.seek(0)
while (sbuf = stm.read(@header.sector_size))
sect = write_sector(sbuf, nil, prev_sect)
stm_start ||= sect
prev_sect = sect
end
stm_start
end
def write_mini_stream(stm)
dlog("Writing \"%s\" to mini stream" % stm.name, 'rex', LEV_3)
prev_sect = nil
stm.seek(0)
while (sbuf = stm.read(@header.mini_sector_size))
sect = write_mini_sector(sbuf, prev_sect)
stm_start ||= sect
prev_sect = sect
end
stm_start
end
def read_stream_data(direntry)
if (direntry.length < @header._ulMiniSectorCutoff)
return read_data_mini(direntry)
end
read_data(direntry)
end
def read_data(direntry)
ret = ""
visited = []
left = direntry.length
sect = direntry.start_sector
while (sect != SECT_END)
if (visited.include?(sect))
raise RuntimeError, 'Sector chain loop detected (0x%08x)' % sect
end
visited << sect
# how much to read?
block = @header.sector_size
block = left if (block > left)
# read it.
dlog("read_data - reading 0x%x bytes" % block, 'rex', LEV_3)
buf = read_sector(sect, block)
ret << buf
left -= buf.length
# done?
break if (left == 0)
sect = next_sector(sect)
end
ret
end
def read_data_mini(direntry)
ret = ""
visited = []
left = direntry.length
sect = direntry.start_sector
while (sect != SECT_END)
if (visited.include?(sect))
raise RuntimeError, 'Sector chain loop detected (0x%08x mini)' % sect
end
visited << sect
# how much to read?
block = @header.mini_sector_size
block = left if (block > left)
# read it.
dlog("read_data_mini - reading 0x%x bytes" % block, 'rex', LEV_3)
buf = read_mini_sector(sect, block)
ret << buf
left -= buf.length
# done?
break if (left == 0)
sect = next_mini_sector(sect)
end
ret
end
def read_sector(sect, len)
off = ((sect + 1) * @header.sector_size)
@fd.seek(off, ::IO::SEEK_SET)
buf = @fd.read(len)
if (not buf)
if (@fd.eof?)
raise RuntimeError, 'EOF while reading sector data (0x%08x)' % sect
else
raise RuntimeError, 'Unknown error while reading sector data (0x%08x)' % sect
end
end
if (buf.length != len)
raise RuntimeError, 'Insufficient data for sector (0x%08x): got %u of %u' % [sect, buf.length, len]
end
buf
end
def next_sector(sect)
return SECT_END if (sect >= @fat.length)
@fat[sect]
end
def read_mini_sector(sect, len)
dlog("Reading mini sector 0x%x" % sect, 'rex', LEV_3)
off = (@header.mini_sector_size * sect)
dlog("Reading from offset 0x%x of ministream" % off, 'rex', LEV_3)
@ministream.seek(off)
data = @ministream.read(len)
data
end
def next_mini_sector(sect)
return SECT_END if (sect >= @minifat.length)
@minifat[sect]
end
end
end
end

53
lib/rex/ole/stream.rb Normal file
View File

@ -0,0 +1,53 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class Stream < DirEntry
def initialize(stg)
super
# for reading/writing from this
@offset = 0
@_mse = STGTY_STREAM
end
def close
@mode = nil
@offset = nil
end
def seek(offset)
@offset = offset
end
def read(len)
return nil if (not @data)
ret = @data[@offset, len]
@offset += len
ret
end
def <<(expr)
if (not @data)
@data = expr.dup
else
@data << expr
end
@_ulSize = @data.length
end
end
end
end

49
lib/rex/ole/substorage.rb Normal file
View File

@ -0,0 +1,49 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class SubStorage < DirEntry
def initialize(stg)
super
@_mse = STGTY_STORAGE
end
def close
end
# stream handling stuff
def create_stream(name, mode=STGM_WRITE)
@stg.create_stream(name, mode, self)
end
def open_stream(name, mode=STGM_READ)
@stg.open_stream(name, mode, self)
end
# storage handling stuff
def create_storage(name, mode=STGM_WRITE)
@stg.create_storage(name, mode, self)
end
def open_storage(name, mode=STGM_WRITE)
@stg.open_storage(name, mode, self)
end
end
end
end

157
lib/rex/ole/util.rb Normal file
View File

@ -0,0 +1,157 @@
##
# $Id$
# Version: $Revision$
##
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class Util
def self.Hexify32array(arr)
ret = ""
arr.each { |dw|
ret << " " if ret.length > 0
ret << "0x%08x" % dw
}
ret
end
def self.Printable(buf)
ret = ""
buf.unpack('C*').each { |byte|
ch = byte.chr
if (byte < 0x20 || byte > 0x7e)
ret << "\\x" + ch.unpack('H*')[0]
else
ret << ch
end
}
ret
end
def self.set_endian(endian)
@endian = endian
end
def self.get64(buf, offset)
@endian = LITTLE_ENDIAN if not @endian
if (@endian == LITTLE_ENDIAN)
arr = buf[offset,8].unpack('VV')
return (arr[0] + (arr[1] << 32))
else
arr = buf[offset,8].unpack('NN')
return ((arr[0] << 32) + arr[1])
end
end
def self.pack64(value)
@endian = LITTLE_ENDIAN if not @endian
arr = []
arr << (value & 0xffffffff)
arr << (value >> 32)
if (@endian == LITTLE_ENDIAN)
arr.pack('VV')
else
arr.reverse.pack('NN')
end
end
def self.get32(buf, offset)
@endian = LITTLE_ENDIAN if not @endian
if (@endian == LITTLE_ENDIAN)
buf[offset,4].unpack('V')[0]
else
buf[offset,4].unpack('N')[0]
end
end
def self.pack32(value)
@endian = LITTLE_ENDIAN if not @endian
if (@endian == LITTLE_ENDIAN)
[value].pack('V')
else
[value].pack('N')
end
end
def self.get32array(buf)
@endian = LITTLE_ENDIAN if not @endian
if (@endian == LITTLE_ENDIAN)
buf.unpack('V*')
else
buf.unpack('N*')
end
end
def self.pack32array(arr)
@endian = LITTLE_ENDIAN if not @endian
if (@endian == LITTLE_ENDIAN)
arr.pack('V*')
else
arr.pack('N*')
end
end
def self.get16(buf, offset)
@endian = LITTLE_ENDIAN if not @endian
if (@endian == LITTLE_ENDIAN)
buf[offset,2].unpack('v')[0]
else
buf[offset,2].unpack('n')[0]
end
end
def self.pack16(value)
@endian = LITTLE_ENDIAN if not @endian
if (@endian == LITTLE_ENDIAN)
[value].pack('v')
else
[value].pack('n')
end
end
def self.get8(buf, offset)
buf[offset,1].unpack('C')[0]
end
def self.pack8(value)
[value].pack('C')
end
def self.getUnicodeString(buf)
buf = buf.unpack('S*').pack('C*')
if (idx = buf.index(0x00.chr))
buf.slice!(idx, buf.length)
end
buf
end
def self.putUnicodeString(buf)
buf = buf.unpack('C*').pack('S*')
if (buf.length < 0x40)
buf << "\x00" * (0x40 - buf.length)
end
buf
end
def self.name_is_valid(name)
return nil if (name.length > 31)
(0..0x1f).to_a.each { |x|
return nil if (name.include?(x.chr))
}
return true
end
end
end
end