metasploit-framework/lib/rex/ole/storage.rb

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