393 lines
8.6 KiB
Ruby
393 lines
8.6 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
##
|
|
# 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
|