add OLE library as Rex::OLE
git-svn-id: file:///home/svn/framework3/trunk@8457 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
8a44f4b9d8
commit
d4d271eef3
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue