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:
parent
1016cb675d
commit
1b6bd927d0
@ -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
|
||||
|
@ -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'
|
||||
|
202
lib/rex/ole.rb
202
lib/rex/ole.rb
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -1 +0,0 @@
|
||||
[MS-CFB].pdf
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue
Block a user