1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-08-28 23:26:18 +02:00

Rex::OLE is now rex-ole gem, fixes MS-1712

This commit is contained in:
Pearce Barry 2016-07-21 18:07:23 -05:00
parent 1016cb675d
commit 1b6bd927d0
28 changed files with 6 additions and 3746 deletions

View File

@ -33,6 +33,7 @@ PATH
redcarpet
rex-arch
rex-java
rex-ole
rex-powershell
rex-random_identifier
rex-registry
@ -224,6 +225,7 @@ GEM
rex-arch (0.1.0)
rex-text
rex-java (0.1.2)
rex-ole (0.1.2)
rex-powershell (0.1.1)
rex-random_identifier
rex-text

View File

@ -55,6 +55,8 @@ require 'rex/registry'
require 'rex/java'
# Library for creating C-style Structs
require 'rex/struct2'
# Library for working with OLE
require 'rex/ole'
# Generic classes
require 'rex/constants'

View File

@ -1,202 +0,0 @@
# -*- coding: binary -*-
##
# 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)
# 11. PropertySet streams (except .to_s)
#
#
# 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 non-committal editing (open, change, close w/o save)
# 7. support timestamps
# 8. 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'
# constants for property sets
# PropertyIds
PID_DICTIONARY = 0x00000000
PID_CODEPAGE = 0x00000001
PID_LOCALE = 0x80000000
PID_BEHAVIOR = 0x80000003
# Well-known PropertyIds
PIDSI_TITLE = 0x02
PIDSI_SUBJECT = 0x03
PIDSI_AUTHOR = 0x04
PIDSI_KEYWORDS = 0x05
PIDSI_COMMENTS = 0x06
PIDSI_TEMPLATE = 0x07
PIDSI_LASTAUTHOR = 0x08
PIDSI_REVNUMBER = 0x09
PIDSI_EDITTIME = 0x0a
PIDSI_LASTPRINTED = 0x0b
PIDSI_CREATE_DTM = 0x0c
PIDSI_LASTSAVE_DTM = 0x0d
PIDSI_PAGECOUNT = 0x0e
PIDSI_WORDCOUNT = 0x0f
PIDSI_CHARCOUNT = 0x10
PIDSI_THUMBNAIL = 0x11
PIDSI_APPNAME = 0x12
PIDSI_DOC_SECURITY = 0x13
# PropertyTypes
VT_EMPTY = 0x00
VT_NULL = 0x01
VT_I2 = 0x02
VT_I4 = 0x03
VT_R4 = 0x04
VT_R8 = 0x05
VT_CY = 0x06
VT_DATE = 0x07
VT_BSTR = 0x08
VT_ERROR = 0x0a
VT_BOOL = 0x0b
VT_VARIANT = 0x0c # used with VT_VECTOR
# 0xd
VT_DECIMAL = 0x0e
# 0xf
VT_I1 = 0x10
VT_UI1 = 0x11
VT_UI2 = 0x12
VT_UI4 = 0x13
VT_I8 = 0x14
VT_UI8 = 0x15
VT_INT = 0x16
VT_UINT = 0x17
VT_LPSTR = 0x1e
VT_LPWSTR = 0x1f
# 0x20-0x3f
VT_FILETIME = 0x40
VT_BLOB = 0x41
VT_STREAM = 0x42
VT_STORAGE = 0x43
VT_STREAMED_OBJ = 0x44
VT_STORED_OBJ = 0x45
VT_BLOB_OBJ = 0x46
VT_CF = 0x47 # Clipboard Format
VT_CLSID = 0x48
VT_VERSIONED_STREAM = 0x49
# Flags
VT_VECTOR = 0x1000
VT_ARRAY = 0x2000 # Requires OLE version >= 1
# Format IDs
FMTID_SummaryInformation = "\xe0\x85\x9f\xf2\xf9\x4f\x68\x10\xab\x91\x08\x00\x2b\x27\xb3\xd9"
FMTID_DocSummaryInformation = "\x02\xd5\xcd\xd5\x9c\x2e\x1b\x10\x93\x97\x08\x00\x2b\x2c\xf9\xae"
FMTID_UserDefinedProperties = "\x05\xd5\xcd\xd5\x9c\x2e\x1b\x10\x93\x97\x08\x00\x2b\x2c\xf9\xae"
FMTID_GlobalInfo = "\x00\x6f\x61\x56\x54\xc1\xce\x11\x85\x53\x00\xaa\x00\xa1\xf9\x5b"
FMTID_ImageContents = "\x00\x64\x61\x56\x54\xc1\xce\x11\x85\x53\x00\xaa\x00\xa1\xf9\x5b"
FMTID_ImageInfo = "\x00\x65\x61\x56\x54\xc1\xce\x11\x85\x53\x00\xaa\x00\xa1\xf9\x5b"
FMTID_PropertyBag = "\x01\x18\x00\x20\xe6\x5d\xd1\x11\x8e\x38\x00\xc0\x4f\xb9\x38\x6d"
# defines PropertySet class
require 'rex/ole/propset'
end
end

View File

@ -1,44 +0,0 @@
# -*- coding: binary -*-
##
# 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

View File

@ -1,138 +0,0 @@
# -*- coding: binary -*-
##
# 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

View File

@ -1,228 +0,0 @@
# -*- coding: binary -*-
##
# 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
}
if (not sib)
raise RuntimeError, 'Unable to find a sibling to link to in the directory'
end
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

View File

@ -1,237 +0,0 @@
# -*- coding: binary -*-
##
# 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(sid, cde)
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

View File

@ -1,8 +0,0 @@
Object Dependencies
------------------ ---------------------
User Data None
Header Fat, DIFat, Directory, MiniStream
DIFat Fat
Fat Directory, *Fat, User Data, MiniStream
Directory User Data, MiniStream
MiniFat MiniStreams

View File

@ -1 +0,0 @@
[MS-CFB].pdf

View File

@ -1,96 +0,0 @@
# -*- coding: binary -*-
##
# 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

View File

@ -1,201 +0,0 @@
# -*- coding: binary -*-
##
# 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

View File

@ -1,74 +0,0 @@
# -*- coding: binary -*-
##
# 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

@ -1,141 +0,0 @@
# -*- coding: binary -*-
##
# Rex::OLE - an OLE implementation
# written in 2010 by Joshua J. Drake <jduck [at] metasploit.com>
##
module Rex
module OLE
class Property
def initialize(id, type, data)
@id = id
@type = type
@data = data
end
def pack_pio(off = 0)
[ @id, off ].pack('V*')
end
def pack_data
buf = [ @type ].pack('V')
case @type
when VT_BLOB
buf << [ @data.length ].pack('V')
when VT_CF
buf << [ 4 + @data.length, -1 ].pack('V*')
end
buf << @data
buf
end
def to_s
"Rex::OLE::Property - to_s unimplemented"
end
end
class PropertySet
def initialize(fmtid = nil)
@fmtid = CLSID.new(fmtid)
@properties = []
end
def <<(val)
@properties << val
end
def pack_fno(off = 0)
@fmtid.pack + [ off ].pack('V')
end
def pack_data
# Pack all the property data
data = []
dlen = 0
@properties.each { |p|
dat = p.pack_data
dlen += dat.length
data << dat
}
buf = ''
# First the header
off = 8 + (@properties.length * 8)
buf << [ off + dlen, @properties.length ].pack('V*')
# Now, the Property Id and Offset for each
@properties.each_with_index { |p,x|
buf << p.pack_pio(off)
off += data[x].length
}
# Finally, all the data
buf << data.join
buf
end
def to_s
"Rex::OLE::PropertySet - to_s unimplemented"
end
end
class PropertySetStream
def initialize
@byte_order = 0xfffe
@ole_version = 0
@os_version = 1
@os_platform = 2
@clsid = CLSID.new
@propsets = []
end
def <<(ps)
@propsets << ps
end
def pack
buf = ''
# First, add the header
buf << [
@byte_order,
@ole_version,
@os_version,
@os_platform
].pack('vvvv')
buf << @clsid.pack
buf << [@propsets.length].pack('V')
# Pack all the PropertySet children
data = []
@propsets.each { |p|
data << p.pack_data
}
# Next, add all the FMTID and Offset headers
off = buf.length + (20 * @propsets.length)
@propsets.each_with_index { |ps,x|
buf << ps.pack_fno(off)
off += data[x].length
}
# Finally, add all the data
buf << data.join
buf
end
def to_s
"Rex::OLE::PropertySetStream - to_s unimplemented"
end
end
end
end

View File

@ -1,27 +0,0 @@
# -*- coding: binary -*-
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

View File

@ -1,35 +0,0 @@
# -*- coding: binary -*-
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

@ -1,34 +0,0 @@
# -*- coding: binary -*-
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.print data
stm.close
else
$stderr.puts "Unable to open stream: #{stream}"
end
stg.close
else
$stderr.puts "Unable to open storage: #{document}"
end

View File

@ -1,23 +0,0 @@
# -*- coding: binary -*-
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

View File

@ -1,392 +0,0 @@
# -*- 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

View File

@ -1,50 +0,0 @@
# -*- coding: binary -*-
##
# 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

View File

@ -1,46 +0,0 @@
# -*- coding: binary -*-
##
# 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

View File

@ -1,154 +0,0 @@
# -*- coding: binary -*-
##
# 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('v*').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('v*')
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

View File

@ -128,6 +128,8 @@ Gem::Specification.new do |spec|
# Library which contains architecture specific information such as registers, opcodes,
# and stack manipulation routines.
spec.add_runtime_dependency 'rex-arch'
# Library for working with OLE.
spec.add_runtime_dependency 'rex-ole'
# rb-readline doesn't work with Ruby Installer due to error with Fiddle:
# NoMethodError undefined method `dlopen' for Fiddle:Module

View File

@ -1,58 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/ole'
RSpec.describe Rex::OLE::CLSID do
before(:example) do
Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN)
end
let(:sample_clsid) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff" }
subject(:clsid) do
described_class.new(sample_clsid)
end
describe "#initialize" do
subject(:clsid_class) do
described_class.allocate
end
it "returns the buf value" do
expect(clsid_class.send(:initialize, sample_clsid)).to eq(sample_clsid)
end
context "when buf is nil" do
it "returns padding" do
expect(clsid_class.send(:initialize)).to eq("\x00" * 16)
end
end
end
describe "#pack" do
it "returns the buf field" do
expect(clsid.pack).to eq(sample_clsid)
end
end
describe "#to_s" do
it "returns printable clsid" do
expect(clsid.to_s).to eq('33221100-5544-7766-8899-aabbccddeeff')
end
context "when buf is nil" do
it "raises NoMethodError" do
clsid.instance_variable_set(:@buf, nil)
expect { clsid.to_s }.to raise_error(NoMethodError)
end
end
context "when buf is shorter than 16 bytes" do
it "raises TypeError" do
clsid.instance_variable_set(:@buf, '')
expect { clsid.to_s }.to raise_error(TypeError)
end
end
end
end

View File

@ -1,294 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/ole'
RSpec.describe Rex::OLE::DIFAT do
before(:example) do
Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN)
end
let(:storage) do
Rex::OLE::Storage.new
end
subject(:difat) do
described_class.new(storage)
end
describe ".new" do
it "returns a Rex::OLE::DIFAT instance" do
expect(described_class.new(storage)).to be_a(Rex::OLE::DIFAT)
end
it "initializes @stg" do
expect(difat.instance_variable_get(:@stg)).to eq(storage)
end
it "initializes @entries" do
expect(difat.instance_variable_get(:@entries)).to be_an(Array)
end
it "initializes @entries as empty array" do
expect(difat.instance_variable_get(:@entries)).to be_empty
end
end
describe "#[]=" do
context "when the entry doesn't exist" do
it "sets an element in the @entries array" do
difat[0] = 1
expect(difat.instance_variable_get(:@entries)[0]).to eq(1)
end
end
context "when the entry exists" do
it "replaces the element in the @entries array" do
difat[0] = 1
difat[0] = 2
expect(difat.instance_variable_get(:@entries)[0]).to eq(2)
end
end
end
describe "#[]" do
context "when the entry doesn't exist" do
it "returns nil" do
expect(difat[3]).to eq(nil)
end
end
context "when the entry exists" do
it "returns the entry value" do
difat[3] = 31
expect(difat[3]).to eq(31)
end
end
end
describe "#+" do
context "when @entries is empty" do
it "sets the @entries values" do
difat + [1, 2]
expect(difat.instance_variable_get(:@entries)).to eq([1, 2])
end
end
context "when @entries isn't empty" do
it "concatenates the array to @entries" do
difat[2] = 0
difat + [1, 2]
expect(difat.instance_variable_get(:@entries)).to eq([nil, nil, 0, 1, 2])
end
end
end
describe "#<<" do
it "concatenates the element to the @entries array" do
difat[0] = 1
difat << 3
expect(difat.instance_variable_get(:@entries)).to eq([1, 3])
end
end
describe "#length" do
subject(:difat_length) do
difat.length
end
context "when @entries is empty" do
it "returns 0" do
is_expected.to eq(0)
end
end
context "when @entries isn't empty" do
it "returns the @entries length" do
difat[0] = 1
difat[1] = 2
is_expected.to eq(2)
end
end
end
describe "#slice!" do
context "when @entries is empty" do
it "returns empty array" do
expect(difat.slice!(0, 1)).to eq([])
end
end
context "when start is out of range" do
it "returns nil" do
difat[0] = 1
expect(difat.slice!(10, 1)).to eq(nil)
end
end
context "when stop is 0" do
it "returns empty array" do
difat[0] = 1
expect(difat.slice!(0, 0)).to eq([])
end
it "doesn't delete nothing" do
difat[0] = 1
difat.slice!(0, 0)
expect(difat[0]).to eq(1)
end
end
context "when @entries is long enough" do
it "returns the deleted elements" do
difat + [1, 2]
expect(difat.slice!(0, 1)).to eq([1])
end
it "deletes the elements in the range" do
difat + [1, 2]
difat.slice!(0, 1)
expect(difat.instance_variable_get(:@entries)).to eq([2])
end
end
end
describe "#reset" do
it "resets the @entries array" do
difat[0] = 1
difat.reset
expect(difat.length).to eq(0)
end
end
describe "#each" do
it "calls the block for every @entries element" do
difat + [1, 2, 3]
res = 0
difat.each { |elem| res += elem}
expect(res).to eq(1 + 2 + 3)
end
end
describe "#to_s" do
subject(:difat_string) do
difat.to_s
end
it "returns an String" do
is_expected.to be_an(String)
end
it "starts with {" do
is_expected.to start_with('{')
end
it "ends with }" do
is_expected.to end_with('}')
end
it "contains @entries values" do
difat + [Rex::OLE::SECT_FAT, 1, 2, 3, Rex::OLE::SECT_DIF, Rex::OLE::SECT_FREE, Rex::OLE::SECT_END]
is_expected.to match(/FAT, 0x1, 0x2, 0x3, DIF, FREE, END/)
end
end
describe "#read" do
context "when difat is empty" do
it "returns nil" do
expect(difat.read).to be_nil
end
end
end
describe "#write" do
context "when entries is empty" do
it "returns 0" do
expect(difat.write).to eq(0)
end
it "fills the first 109 FAT sectors in the storage header" do
difat.write
storage = difat.instance_variable_get(:@stg)
expect(storage.header._sectFat.length).to eq(109)
end
it "fills the first 109 FAT sectors in the storage header with SECT_FREE" do
difat.write
storage = difat.instance_variable_get(:@stg)
storage.header._sectFat.each { |s|
expect(s).to eq(Rex::OLE::SECT_FREE)
}
end
end
context "when entries length is less than 109" do
let(:entries) { [1] * 20 }
it "returns the number of entries" do
difat + entries
expect(difat.write).to eq(20)
end
it "fills the first 109 FAT sectors in the storage header" do
difat + entries
difat.write
storage = difat.instance_variable_get(:@stg)
expect(storage.header._sectFat.length).to eq(109)
end
it "fills the first FAT sectors with the entries" do
difat + entries
difat.write
storage = difat.instance_variable_get(:@stg)
(0..entries.length - 1).each { |i|
expect(storage.header._sectFat[i]).to eq(1)
}
end
it "fills the remaining FAT sectors with FREE sectors" do
difat + entries
difat.write
storage = difat.instance_variable_get(:@stg)
(entries.length..109 - 1).each { |i|
expect(storage.header._sectFat[i]).to eq(Rex::OLE::SECT_FREE)
}
end
end
context "when entries length is 109" do
let(:entries) { [1] * 109 }
it "returns the number of entries" do
difat + entries
expect(difat.write).to eq(109)
end
it "fills the first 109 FAT sectors in the storage header" do
difat + entries
difat.write
storage = difat.instance_variable_get(:@stg)
expect(storage.header._sectFat.length).to eq(109)
end
it "fills the first 109 FAT sectors with the entries" do
difat + entries
difat.write
storage = difat.instance_variable_get(:@stg)
(0..storage.header._sectFat.length - 1).each { |i|
expect(storage.header._sectFat[i]).to eq(1)
}
end
end
context "when entries length is greater than 109" do
let(:entries) { [1] * 110 }
it "raises a RuntimeError" do
difat + entries
expect { difat.write }.to raise_error(RuntimeError)
end
end
end
end

View File

@ -1,401 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/ole'
RSpec.describe Rex::OLE::DirEntry do
before(:example) do
Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN)
end
let(:storage) do
Rex::OLE::Storage.new
end
subject(:dir_entry) do
described_class.new(storage)
end
describe ".new" do
it "returns a Rex::OLE::DirEntry instance" do
expect(described_class.new(storage)).to be_a(Rex::OLE::DirEntry)
end
it { expect(dir_entry.instance_variable_get(:@stg)).to eq(storage) }
it { expect(dir_entry.sid).to eq(0) }
it { expect(dir_entry.instance_variable_get(:@_ab)).to eq('Root Entry') }
it { expect(dir_entry.instance_variable_get(:@_cb)).to be_nil }
it { expect(dir_entry.instance_variable_get(:@_mse)).to eq(Rex::OLE::STGTY_ROOT) }
it { expect(dir_entry.instance_variable_get(:@_bflags)).to eq(0) }
it { expect(dir_entry._sidLeftSib).to eq(Rex::OLE::SECT_FREE) }
it { expect(dir_entry._sidRightSib).to eq(Rex::OLE::SECT_FREE) }
it { expect(dir_entry._sidChild).to eq(Rex::OLE::SECT_FREE) }
it { expect(dir_entry.instance_variable_get(:@_clsId)).to be_a(Rex::OLE::CLSID) }
it { expect(dir_entry.instance_variable_get(:@_dwUserFlags)).to eq(0) }
it { expect(dir_entry.instance_variable_get(:@_ctime)).to eq("\x00" * 8) }
it { expect(dir_entry.instance_variable_get(:@_mtime)).to eq("\x00" * 8) }
it { expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END) }
it { expect(dir_entry.instance_variable_get(:@_ulSize)).to eq(0) }
it { expect(dir_entry.instance_variable_get(:@children)).to be_an(Array) }
it { expect(dir_entry.instance_variable_get(:@children)).to be_empty }
end
describe "#length" do
it "returns _ulSize" do
dir_entry.instance_variable_set(:@_ulSize, 28)
expect(dir_entry.length).to eq(28)
end
end
describe "#<<" do
it "increments the children array" do
dir_entry << 1
children = dir_entry.instance_variable_get(:@children)
expect(children.length).to eq(1)
end
it "appends to the children array" do
dir_entry << 1
children = dir_entry.instance_variable_get(:@children)
expect(children).to eq([1])
end
end
describe "#each" do
it "calls the block for every children element" do
dir_entry << 1
dir_entry << 2
dir_entry << 3
res = 0
dir_entry.each { |elem| res += elem}
expect(res).to eq(1 + 2 + 3)
end
end
describe "#type" do
it "returns the _mse field" do
expect(dir_entry.type).to eq(Rex::OLE::STGTY_ROOT)
end
end
describe "#type=" do
it "modifies the _mse field" do
dir_entry.type = 3838
expect(dir_entry.instance_variable_get(:@_mse)).to eq(3838)
end
end
describe "#name" do
it "returns the _ab field" do
expect(dir_entry.name).to eq('Root Entry')
end
end
describe "#name=" do
it "modifies the _ab field" do
dir_entry.name = 'test'
expect(dir_entry.instance_variable_get(:@_ab)).to eq('test')
end
end
describe "#start_sector" do
it "returns the _sectStart field" do
expect(dir_entry.start_sector).to eq(Rex::OLE::SECT_END)
end
end
describe "#start_sector=" do
it "modifies the _sectStart field" do
dir_entry.start_sector = Rex::OLE::SECT_FREE
expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_FREE)
end
end
describe "#find_stream_by_name_and_type" do
context "when any children matches the search criteria" do
it "returns nil" do
expect(dir_entry.find_stream_by_name_and_type('name', Rex::OLE::STGTY_ROOT)).to be_nil
end
end
context "when one children matches the search criteria" do
let(:stream) { Rex::OLE::Stream.new(storage) }
let(:name) { 'name' }
let(:type) { Rex::OLE::STGTY_ROOT }
it "returns the matching stream" do
stream.name = name
stream.type = type
dir_entry << stream
expect(dir_entry.find_stream_by_name_and_type(name, type)).to eq(stream)
end
end
context "when several children matches the search criteria" do
let(:stream) { Rex::OLE::Stream.new(storage) }
let(:stream_two) { Rex::OLE::Stream.new(storage) }
let(:name) { 'name' }
let(:type) { Rex::OLE::STGTY_ROOT }
let(:sid) { 2 }
it "returns the first matching stream" do
stream.name = name
stream.type = type
dir_entry << stream
stream_two.name = name
stream_two.type = type
stream_two.sid = sid
dir_entry << stream_two
expect(dir_entry.find_stream_by_name_and_type(name, type)).to eq(stream)
end
end
end
describe "#find_by_sid" do
let(:stream) { Rex::OLE::Stream.new(storage) }
let(:another_stream) { Rex::OLE::Stream.new(storage) }
context "when self match the criteria" do
it "returns self" do
expect(dir_entry.find_by_sid(0, dir_entry)).to eq(dir_entry)
end
end
context "when self and a children stream match the criteria" do
it "returns self" do
stream.sid = 0
dir_entry << stream
expect(dir_entry.find_by_sid(0, dir_entry)).to eq(dir_entry)
end
end
context "when only one children stream match the criteria" do
it "returns the child stream" do
stream.sid = 20
dir_entry << stream
expect(dir_entry.find_by_sid(20, dir_entry)).to eq(stream)
end
end
context "when several children stream match the criteria" do
it "returns the first child" do
stream.sid = 20
stream.name = 'stream'
dir_entry << stream
another_stream.sid = 20
another_stream.name = 'another'
dir_entry << another_stream
expect(dir_entry.find_by_sid(20, dir_entry)).to eq(stream)
end
end
end
describe "#from_s" do
let(:valid_direntry) do
"\x52\x00\x6f\x00\x6f\x00\x74\x00\x20\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x79\x00\x00\x00" + # name (_ab)
("\x00" * 42) + # padding
"\x16\x00" + # _cb
"\x05" + # _mse
"\x00" + #_bflags
"\xff\xff\xff\xff" + # _sidLeftSib
"\xff\xff\xff\xff" + # _sidRightSib
"\xff\xff\xff\xff" + # _sidChild
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # clsid
"\x00\x00\x00\x00" + # _dwUserFlags
"\x00\x00\x00\x00\x00\x00\x00\x00" + # _ctime
"\x00\x00\x00\x00\x00\x00\x00\x00" + # _metime
"\xfe\xff\xff\xff" + # _sectStart
"\x00\x00\x00\x00\x00\x00\x00\x00" # _ulSize
end
let(:invalid_name_length)do
"\x52\x00\x6f\x00\x6f\x00\x74\x00\x20\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x79\x00\x00\x00" + # name (_ab)
("\x00" * 42) + # padding
"\x41\x00" + # _cb (invalid, major than 0x40)
"\x05" + # _mse
"\x00" + #_bflags
"\xff\xff\xff\xff" + # _sidLeftSib
"\xff\xff\xff\xff" + # _sidRightSib
"\xff\xff\xff\xff" + # _sidChild
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # clsid
"\x00\x00\x00\x00" + # _dwUserFlags
"\x00\x00\x00\x00\x00\x00\x00\x00" + # _ctime
"\x00\x00\x00\x00\x00\x00\x00\x00" + # _metime
"\xfe\xff\xff\xff" + # _sectStart
"\x00\x00\x00\x00\x00\x00\x00\x00" # _ulSize
end
let(:mismatch_length) do
"\x52\x00\x6f\x00\x6f\x00\x74\x00\x20\x00\x45\x00\x6e\x00\x74\x00\x72\x00\x79\x00\x00\x00" + # name (_ab)
("\x00" * 42) + # padding
"\x13\x00" + # _cb (invalid length, shorter than real name length)
"\x05" + # _mse
"\x00" + #_bflags
"\xff\xff\xff\xff" + # _sidLeftSib
"\xff\xff\xff\xff" + # _sidRightSib
"\xff\xff\xff\xff" + # _sidChild
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # clsid
"\x00\x00\x00\x00" + # _dwUserFlags
"\x00\x00\x00\x00\x00\x00\x00\x00" + # _ctime
"\x00\x00\x00\x00\x00\x00\x00\x00" + # _metime
"\xfe\xff\xff\xff" + # _sectStart
"\x00\x00\x00\x00\x00\x00\x00\x00" # _ulSize
end
let(:sid) { 0 }
context "when name length major than 64" do
it "raises RuntimeError" do
expect { dir_entry.from_s(sid, invalid_name_length) }.to raise_error(RuntimeError)
end
end
context "when name length doesn't match real length" do
it "raises RuntimeError" do
expect { dir_entry.from_s(sid, mismatch_length) }.to raise_error(RuntimeError)
end
end
context "when valid buf" do
it "uses argument sid" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.sid).to eq(sid)
end
it "parses _ab from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_ab)).to eq('Root Entry')
end
it "parses _cb from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_cb)).to eq(22)
end
it "parses _mse from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_mse)).to eq(Rex::OLE::STGTY_ROOT)
end
it "parses _bflags from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_bflags)).to eq(0)
end
it "parses _sidLeftSib from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry._sidLeftSib).to eq(Rex::OLE::SECT_FREE)
end
it "parses _sidRightSib from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry._sidRightSib).to eq(Rex::OLE::SECT_FREE)
end
it "parses _sidChild from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry._sidChild).to eq(Rex::OLE::SECT_FREE)
end
it "parses _clsId from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_clsId)).to be_a(Rex::OLE::CLSID)
end
it "parses _dwUserFlags from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_dwUserFlags)).to eq(0)
end
it "parses _ctime from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_ctime)).to eq("\x00" * 8)
end
it "parses _mtime from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_mtime)).to eq("\x00" * 8)
end
it "parses _sectStart from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END)
end
it "parses _ulSize from buf" do
dir_entry.from_s(sid, valid_direntry)
expect(dir_entry.instance_variable_get(:@_ulSize)).to eq(0)
end
end
end
describe "#pack" do
it "returns an string" do
expect(dir_entry.pack).to be_an(String)
end
it "includes the unicode dir entry name" do
expect(dir_entry.pack).to match(/R\x00o\x00o\x00t\x00 \x00E\x00n\x00t\x00r\x00y\x00/)
end
context "when _sectStart is undefined" do
it "sets _sectStart to SECT_END" do
dir_entry.instance_variable_set(:@_sectStart, nil)
dir_entry.pack
expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END)
end
end
context "when _sectStart is defined" do
it "doesn't modify _sectStart value" do
dir_entry.instance_variable_set(:@_sectStart, Rex::OLE::SECT_FREE)
dir_entry.pack
expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_FREE)
end
end
it "sets _cb as the unicode length of the name" do
dir_entry.pack
expect(dir_entry.instance_variable_get(:@_cb)).to eq("Root Entry\x00".length * 2)
end
end
describe "#to_s" do
it "returns an string" do
expect(dir_entry.to_s).to be_an(String)
end
it "starts with {" do
expect(dir_entry.to_s).to start_with('{')
end
it "ends with }" do
expect(dir_entry.to_s).to end_with('}')
end
it "contains the entry name" do
expect(dir_entry.to_s).to match(/Root Entry/)
end
context "when _sectStart is undefined" do
it "sets _sectStart to SECT_END" do
dir_entry.instance_variable_set(:@_sectStart, nil)
dir_entry.to_s
expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_END)
end
end
context "when _sectStart is defined" do
it "doesn't modify _sectStart value" do
dir_entry.instance_variable_set(:@_sectStart, Rex::OLE::SECT_FREE)
dir_entry.to_s
expect(dir_entry.instance_variable_get(:@_sectStart)).to eq(Rex::OLE::SECT_FREE)
end
end
it "sets _cb as the unicode length of the name" do
dir_entry.to_s
expect(dir_entry.instance_variable_get(:@_cb)).to eq("Root Entry\x00".length * 2)
end
end
end

View File

@ -1,355 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/ole'
RSpec.describe Rex::OLE::Header do
before(:example) do
Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN)
end
subject(:header) do
described_class.new
end
describe ".new" do
it "returns a Rex::OLE::Header instance" do
expect(described_class.new).to be_a(Rex::OLE::Header)
end
it { expect(header.instance_variable_get(:@_abSig)).to eq(Rex::OLE::SIG) }
it { expect(header.instance_variable_get(:@_clid)).to be_a(Rex::OLE::CLSID) }
it { expect(header.instance_variable_get(:@_uByteOrder)).to eq(Rex::OLE::LITTLE_ENDIAN) }
it { expect(header.instance_variable_get(:@_uMinorVersion)).to eq(0x3e) }
it { expect(header._uMajorVersion).to eq(0x03) }
it { expect(header.instance_variable_get(:@_uSectorShift)).to eq(9) }
it { expect(header._uMiniSectorShift).to eq(6) }
it { expect(header.instance_variable_get(:@_csectDir)).to be_nil }
it { expect(header._csectFat).to be_nil }
it { expect(header._sectDirStart).to be_nil }
it { expect(header.instance_variable_get(:@_signature)).to eq(0) }
it { expect(header._ulMiniSectorCutoff).to eq(0x1000) }
it { expect(header._sectMiniFatStart).to eq(Rex::OLE::SECT_END) }
it { expect(header._csectMiniFat).to eq(0) }
it { expect(header._sectDifStart).to eq(Rex::OLE::SECT_END) }
it { expect(header._csectDif).to eq(0) }
it { expect(header._sectFat).to be_an(Array) }
it { expect(header.instance_variable_get(:@_sectFat)).to be_empty }
it { expect(header.sector_size).to eq(1 << 9) }
it { expect(header.mini_sector_size).to eq(1 << 6) }
it { expect(header.idx_per_sect).to eq((1 << 9) / 4) }
end
describe "#set_defaults" do
it "sets OLECF signature" do
header.set_defaults
expect(header.instance_variable_get(:@_abSig)).to eq(Rex::OLE::SIG)
end
it "setup a class identifier (guid)" do
header.set_defaults
expect(header.instance_variable_get(:@_clid)).to be_a(Rex::OLE::CLSID)
end
it "sets byte order identifier as little endian" do
header.set_defaults
expect(header.instance_variable_get(:@_uByteOrder)).to eq(Rex::OLE::LITTLE_ENDIAN)
end
it "sets the minor version to 0x3e" do
header.set_defaults
expect(header.instance_variable_get(:@_uMinorVersion)).to eq(0x3e)
end
it "sets the major version to 0x3" do
header.set_defaults
expect(header._uMajorVersion).to eq(0x03)
end
it "sets the size of sectors to 9" do
header.set_defaults
expect(header.instance_variable_get(:@_uSectorShift)).to eq(9)
end
it "sets the size of mini-sectors to 6" do
header.set_defaults
expect(header._uMiniSectorShift).to eq(6)
end
it "sets the number of sectors in the directory chain to nil" do
header.set_defaults
expect(header.instance_variable_get(:@_csectDir)).to be_nil
end
it "sets the number of sectors in the FAT chain to nil" do
header.set_defaults
expect(header._csectFat).to be_nil
end
it "sets first sector in the directory chain to nil" do
header.set_defaults
expect(header._sectDirStart).to be_nil
end
it "sets the signature used for transactioning to zero" do
header.set_defaults
expect(header.instance_variable_get(:@_signature)).to eq(0)
end
it "sets the maximum size of mini-streams to 4096" do
header.set_defaults
expect(header._ulMiniSectorCutoff).to eq(0x1000)
end
it "sets the first sector in the mini-FAT chain to end of chain" do
header.set_defaults
expect(header._sectMiniFatStart).to eq(Rex::OLE::SECT_END)
end
it "sets the number of sectors in the mini-FAT chain to 0" do
header.set_defaults
expect(header._csectMiniFat).to eq(0)
end
it "sets the first sector in the DIF chain to end of chain" do
header.set_defaults
expect(header._sectDifStart).to eq(Rex::OLE::SECT_END)
end
it "sets the number of sectors in the DIF chain to 0" do
header.set_defaults
expect(header._csectDif).to eq(0)
end
it "creates an array for the sectors of the first 109 FAT sectors" do
header.set_defaults
expect(header._sectFat).to be_an(Array)
end
it "creates an empty array for the FAT sectors" do
header.set_defaults
expect(header.instance_variable_get(:@_sectFat)).to be_empty
end
end
describe "#to_s" do
subject(:header_string) { header.to_s }
it "returns an String" do
expect(header_string).to be_an(String)
end
it "starts with {" do
expect(header_string).to start_with('{')
end
it "ends with {" do
expect(header_string).to end_with('}')
end
it "includes the OLECF signature" do
expect(header_string).to match(/_abSig => "\\xd0\\xcf\\x11\\xe0\\xa1\\xb1\\x1a\\xe1"/)
end
it "includes the class identifier value" do
expect(header_string).to match(/_clid => 00000000-0000-0000-0000-000000000000/)
end
it "includes the minor version value" do
expect(header_string).to match(/_uMinorVersion => 0x003e/)
end
it "includes the major version value" do
expect(header_string).to match(/_uMajorVersion => 0x0003/)
end
it "includes the byte order identifier value" do
expect(header_string).to match(/_uByteOrder => 0xfffe/)
end
it "includes the size of sectors value" do
expect(header_string).to match(/_uSectorShift => 0x0009/)
end
it "includes the size of mini-sectors value" do
expect(header_string).to match(/_uMiniSectorShift => 0x0006/)
end
it "includes the number of sectors in the directory chain" do
expect(header_string).to match(/_csectDir => UNALLOCATED/)
end
it "includes the number of sectors in the FAT chain" do
expect(header_string).to match(/_csectFat => UNALLOCATED/)
end
it "includes the first sector in the directory chain" do
expect(header_string).to match(/_sectDirStart => UNALLOCATED/)
end
it "includes the signature used for transactioning" do
expect(header_string).to match(/_signature => 0x00000000/)
end
it "includes the maximum size of mini-streams" do
expect(header_string).to match(/_uMiniSectorCutoff => 0x00001000/)
end
it "includes the first sector in the mini-FAT chain value" do
expect(header_string).to match(/_sectMiniFatStart => 0xfffffffe/)
end
it "includes the number of sectors in the mini-FAT chain" do
expect(header_string).to match(/_csectMiniFat => 0x00000000/)
end
it "includes the first sector in the DIF chain value" do
expect(header_string).to match(/_sectDifStart => 0xfffffffe/)
end
it "includes the number of sectors in the DIF chain" do
expect(header_string).to match(/_csectDif => 0x00000000/)
end
end
describe "#read" do
context "when reading empty header" do
let(:empty_fd) do
s = ''
StringIO.new(s, 'rb')
end
it "raises NoMethodError" do
expect { header.read(empty_fd) }.to raise_error(NoMethodError)
end
end
context "when reading header with invalid signature" do
let(:incorrect_fd) do
s = 'A' * Rex::OLE::HDR_SZ
StringIO.new(s, 'rb')
end
it "raises RuntimeError" do
expect { header.read(incorrect_fd) }.to raise_error(RuntimeError)
end
end
context "when reading header with valid signature" do
let(:correct_fd) do
hdr = ""
hdr << Rex::OLE::SIG
hdr << 'A' * 16 # @_clid
hdr << 'BB' # @_uMinorVersion
hdr << 'CC' # @_uMajorVersion
hdr << "\xfe\xff" # @_uByteOrder
hdr << 'EE' # @_uSectorShift
hdr << 'FF' # @_uMiniSectorShift
hdr << '123456' # padding
hdr << 'GGGG' # @_csectDir
hdr << 'HHHH' # @_csectFat
hdr << 'IIII' # @_sectDirStart
hdr << 'JJJJ' # @_signature
hdr << 'KKKK' # @_ulMiniSectorCutoff
hdr << 'LLLL' # @_sectMiniFatStart
hdr << 'MMMM' # @_csectMiniFat
hdr << 'NNNN' # @_sectDifStart
hdr << 'OOOO' # @_csectDif
hdr << 'P' * 109 * 4 # @_sectFat
StringIO.new(hdr, 'rb')
end
it "sets clsid from input" do
header.read(correct_fd)
expect(header.instance_variable_get(:@_clid).to_s).to eq("41414141-4141-4141-4141-414141414141")
end
it "sets minor version from input" do
header.read(correct_fd)
expect(header.instance_variable_get(:@_uMinorVersion)).to eq(0x4242)
end
it "sets major version from input" do
header.read(correct_fd)
expect(header._uMajorVersion).to eq(0x4343)
end
it "sets byte order from input" do
header.read(correct_fd)
expect(header.instance_variable_get(:@_uByteOrder)).to eq(Rex::OLE::LITTLE_ENDIAN)
end
it "sets the size of sectors from input" do
header.read(correct_fd)
expect(header.instance_variable_get(:@_uSectorShift)).to eq(0x4545)
end
it "sets the size of mini-sectors from input" do
header.read(correct_fd)
expect(header._uMiniSectorShift).to eq(0x4646)
end
it "sets the number of sectors in the directory chain from input" do
header.read(correct_fd)
expect(header.instance_variable_get(:@_csectDir)).to eq(0x47474747)
end
it "sets the number of sectors in the FAT chain from input" do
header.read(correct_fd)
expect(header._csectFat).to eq(0x48484848)
end
it "sets the first sector in the directory chain from input" do
header.read(correct_fd)
expect(header._sectDirStart).to eq(0x49494949)
end
it "sets the signature used for transactioning from input" do
header.read(correct_fd)
expect(header.instance_variable_get(:@_signature)).to eq(0x4a4a4a4a)
end
it "sets the maximum size of mini-streams from input" do
header.read(correct_fd)
expect(header._ulMiniSectorCutoff).to eq(0x4b4b4b4b)
end
it "sets the first sector in the mini-FAT chain from input" do
header.read(correct_fd)
expect(header._sectMiniFatStart).to eq(0x4c4c4c4c)
end
it "sets the number of sectors in the mini-FAT chain from input" do
header.read(correct_fd)
expect(header._csectMiniFat).to eq(0x4d4d4d4d)
end
it "sets the first sector in the DIF chain from input" do
header.read(correct_fd)
expect(header._sectDifStart).to eq(0x4e4e4e4e)
end
it "sets the number of sectors in the DIF chain from input" do
header.read(correct_fd)
expect(header._csectDif).to eq(0x4f4f4f4f)
end
it "creates an array for the FAT sectors from input" do
header.read(correct_fd)
expect(header._sectFat.length).to eq(109)
end
end
end
describe "#write" do
context "when default header" do
it "writes 76 bytes" do
fd = StringIO.new('', 'wb')
header.write(fd)
expect(fd.string.length).to eq(76)
end
end
end
end

View File

@ -1,98 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/ole'
RSpec.describe Rex::OLE::MiniFAT do
before(:example) do
Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN)
end
let(:storage) do
Rex::OLE::Storage.new
end
subject(:minifat) do
described_class.new(storage)
end
describe "#allocate_sector" do
context "when entries is empty" do
it "returns index 0" do
expect(minifat.allocate_sector).to eq(0)
end
it "allocates idx_per_sect entries" do
minifat.allocate_sector
storage = minifat.instance_variable_get(:@stg)
expect(minifat.length).to eq(storage.header.idx_per_sect)
end
it "marks the first entry as SECT_END" do
minifat.allocate_sector
expect(minifat[0]).to eq(Rex::OLE::SECT_END)
end
it "marks the remaining entries as SECT_FREE" do
minifat.allocate_sector
storage = minifat.instance_variable_get(:@stg)
(1..storage.header.idx_per_sect - 1).each do |i|
expect(minifat[i]).to eq(Rex::OLE::SECT_FREE)
end
end
end
context "when entries include a free sector" do
it "returns the free sector index entry" do
minifat + [1, 2, Rex::OLE::SECT_FREE]
expect(minifat.allocate_sector).to eq(2)
end
end
context "when entries don't include a free sector" do
it "returns index of a new entry" do
minifat + [1, 2, 3]
expect(minifat.allocate_sector).to eq(3)
end
it "allocates idx_per_sect entries" do
minifat + [1, 2, 3]
minifat.allocate_sector
storage = minifat.instance_variable_get(:@stg)
expect(minifat.length).to eq(storage.header.idx_per_sect + 3)
end
it "marks the first entry as SECT_END" do
minifat + [1, 2, 3]
minifat.allocate_sector
expect(minifat[3]).to eq(Rex::OLE::SECT_END)
end
it "marks the remaining entries as SECT_FREE" do
minifat + [1, 2, 3]
minifat.allocate_sector
storage = minifat.instance_variable_get(:@stg)
(4..3 + storage.header.idx_per_sect - 1).each do |i|
expect(minifat[i]).to eq(Rex::OLE::SECT_FREE)
end
end
end
end
describe "#read" do
context "when the MiniFAT in the storage is empty" do
it "returns zero" do
expect(minifat.read).to eq(0)
end
end
end
describe "#write" do
context "when entries is empty" do
it "returns nil" do
expect(minifat.write).to be_nil
end
end
end
end

View File

@ -1,409 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/ole'
RSpec.describe Rex::OLE::Util do
before(:example) do
Rex::OLE::Util.set_endian(Rex::OLE::LITTLE_ENDIAN)
end
describe ".Hexify32array" do
subject(:hex_array) { described_class.Hexify32array(arr) }
context "when arr is empty" do
let(:arr) { [] }
it "returns empty string" do
is_expected.to be_empty
end
end
context "when arr is filled" do
let(:arr) { [0, 1, 0x20, 0x40, 0x100, 0x200, 0x12345678] }
it "returns an string with the hexify array" do
is_expected.to eq('0x00000000 0x00000001 0x00000020 0x00000040 0x00000100 0x00000200 0x12345678')
end
end
end
describe ".Printable" do
subject(:printable_buf) { described_class.Printable(buf) }
context "when buf is empty" do
let(:buf) { '' }
it "returns empty string" do
is_expected.to be_empty
end
end
context "when buf only contains printable chars" do
let(:buf) { 'abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()' }
it "returns the same string" do
is_expected.to eq(buf)
end
end
context "when buf contains no printable chars" do
let(:buf) { "abcde\x88" }
it "returns hex representation for non printable chars" do
is_expected.to eq('abcde\\x88')
end
end
end
describe ".set_endian" do
subject(:set_endian) { described_class.set_endian(endian) }
let(:endian) { Rex::OLE::LITTLE_ENDIAN }
it "sets the endian field" do
set_endian
expect(described_class.instance_variable_get(:@endian)).to eq(0xfffe)
end
it "returns the set endianness" do
is_expected.to eq(0xfffe)
end
end
describe ".get64" do
subject(:quad_word) { described_class.get64(buf, offset) }
context "when buf is empty" do
let(:buf) { '' }
let(:offset) { 0 }
it "raises a null dereference exception" do
expect { quad_word }.to raise_error(NoMethodError)
end
end
context "when buf is shorter than offset" do
let(:buf) { "\x12\x34\x56\x78\x12\x34\x56\x78" }
let(:offset) { 8 }
it "raises a null dereference exceptioon" do
expect { quad_word }.to raise_error(NoMethodError)
end
end
context "when @endian is little endian" do
let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" }
let(:offset) { 1 }
it "returns the little endian quad word at offset" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq(0x8877665544332211)
end
end
context "when @endian is big endian" do
let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" }
let(:offset) { 1 }
it "returns the big endian quad word at offset" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq(0x1122334455667788)
end
end
end
describe ".pack64" do
subject(:packed_quad_word) { described_class.pack64(value) }
let(:value) { 0x1122334455667788 }
context "when @endian is little endian" do
it "returns the packed little endian quad word" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq("\x88\x77\x66\x55\x44\x33\x22\x11")
end
end
context "when @endian is big endian" do
it "returns the packed big endian quad word" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq("\x11\x22\x33\x44\x55\x66\x77\x88")
end
end
end
describe ".get32" do
subject(:word) { described_class.get32(buf, offset) }
context "when buf is empty" do
let(:buf) { '' }
let(:offset) { 0 }
it "returns nil" do
is_expected.to be_nil
end
end
context "when buf is shorter than offset" do
let(:buf) { "\x12\x34\x56" }
let(:offset) { 4 }
it "raises a null dereference exceptioon" do
expect { word }.to raise_error(NoMethodError)
end
end
context "when @endian is little endian" do
let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" }
let(:offset) { 1 }
it "returns the little endian word at offset" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq(0x44332211)
end
end
context "when @endian is big endian" do
let(:buf) { "\x00\x11\x22\x33\x44\x55\x66\x77\x88" }
let(:offset) { 1 }
it "returns the big endian word at offset" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq(0x11223344)
end
end
end
describe ".pack32" do
subject(:packed_word) { described_class.pack32(value) }
let(:value) { 0x11223344 }
context "when @endian is little endian" do
it "returns the packed little endian word" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq("\x44\x33\x22\x11")
end
end
context "when @endian is big endian" do
it "returns the packed big endian word at offset" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq("\x11\x22\x33\x44")
end
end
end
describe ".get32array" do
subject(:word_array) { described_class.get32array(buf) }
context "when buf is empty" do
let(:buf) { '' }
it "returns an empty array" do
is_expected.to eq([])
end
end
context "when buf isn't empty" do
let(:buf) { "\x11\x22\x33\x44\x55\x66\x77\x88" }
context "when @endian is little endian" do
it "unpacks an array of little endian words" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq([0x44332211, 0x88776655])
end
end
context "when @endian is big endian" do
it "unpacks an array of big endian words" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq([0x11223344, 0x55667788])
end
end
end
end
describe ".pack32array" do
subject(:packed_word) { described_class.pack32array(arr) }
context "when arr is empty" do
let(:arr) { [] }
it "returns an empty string" do
is_expected.to eq('')
end
end
context "when arr isn't empty" do
let(:arr) { [0x11223344, 0x55667788] }
context "when @endian is little endian" do
it "returns the little endian words array packed" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq("\x44\x33\x22\x11\x88\x77\x66\x55")
end
end
context "when @endian is big endian" do
it "returns the big endian words array packed" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq("\x11\x22\x33\x44\x55\x66\x77\x88")
end
end
end
end
describe ".get16" do
subject(:half_word) { described_class.get16(buf, offset) }
context "when buf is empty" do
let(:buf) { '' }
let(:offset) { 0 }
it "returns nil" do
is_expected.to be_nil
end
end
context "when buf is shorter than offset" do
let(:buf) { "\x12\x34" }
let(:offset) { 4 }
it "raises a null dereference exceptioon" do
expect { half_word }.to raise_error(NoMethodError)
end
end
context "when @endian is little endian" do
let(:buf) { "\x00\x11\x22\x33\x44" }
let(:offset) { 1 }
it "returns the little endian half word at offset" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq(0x2211)
end
end
context "when @endian is big endian" do
let(:buf) { "\x00\x11\x22\x33\x44" }
let(:offset) { 1 }
it "returns the big endian word at offset" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq(0x1122)
end
end
end
describe ".pack16" do
subject(:packed_word) { described_class.pack16(value) }
let(:value) { 0x1122 }
context "when @endian is little endian" do
it "returns the packed little endian word" do
described_class.set_endian(Rex::OLE::LITTLE_ENDIAN)
is_expected.to eq("\x22\x11")
end
end
context "when @endian is big endian" do
it "returns the packed big endian word at offset" do
described_class.set_endian(Rex::OLE::BIG_ENDIAN)
is_expected.to eq("\x11\x22")
end
end
end
describe ".get8" do
subject(:byte) { described_class.get8(buf, offset) }
context "when buf is empty" do
let(:buf) { '' }
let(:offset) { 0 }
it "returns nil" do
is_expected.to be_nil
end
end
context "when buf is shorter than offset" do
let(:buf) { "\x12\x34" }
let(:offset) { 4 }
it "raises a null dereference exceptioon" do
expect { byte }.to raise_error(NoMethodError)
end
end
let(:buf) { "\x00\x11\x22" }
let(:offset) { 1 }
it "returns the byte at offset" do
is_expected.to eq(0x11)
end
end
describe ".pack8" do
subject(:packed_byte) { described_class.pack8(value) }
let(:value) { 0x11 }
it "returns the packed byte" do
is_expected.to eq("\x11")
end
end
describe ".getUnicodeString" do
subject(:unicode_string) { described_class.getUnicodeString(buf) }
let(:buf) { "T\x00h\x00i\x00s\x00 \x00i\x00s\x00 \x00a\x00n\x00 \x00u\x00n\x00i\x00c\x00o\x00d\x00e\x00 \x00s\x00t\x00r\x00i\x00n\x00g\x00" }
it "unpacks unicode string" do
is_expected.to eq('This is an unicode string')
end
context "when buf contains unicode nulls" do
let(:buf) { "T\x00h\x00\x00i\x00s\x00" }
it "unpacks unicode string until null" do
is_expected.to eq('Th')
end
end
end
describe ".putUnicodeString" do
subject(:packed_byte) { described_class.putUnicodeString(buf) }
let(:buf) { 'A' * 32 }
it "returns the unicode version of the string" do
is_expected.to eq("A\x00" * 32)
end
context "when buf is shorter than 32" do
let(:buf) { 'A' * 30 }
it "adds null byte padding" do
is_expected.to eq(("A\x00" * 30) + "\x00\x00\x00\x00")
end
end
end
describe ".name_is_valid" do
subject(:valid_name) { described_class.name_is_valid(name) }
context "when name length is greater than 31" do
let(:name) { 'A' * 32 }
it "returns nil" do
is_expected.to be_nil
end
end
context "when name contains [0x00..0x1f] chars" do
let(:name) { "ABCDE\x1f" }
it "returns nil" do
is_expected.to be_nil
end
end
context "when name doesn't contain [0x00..0x1f] chars" do
let(:name) { "ABCDE\x88" }
it "returns true" do
is_expected.to be_truthy
end
end
end
end