# -*- coding: binary -*- ## # Rex::OLE - an OLE implementation # written in 2010 by Joshua J. Drake ## 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