class PEdump::Resource

Public Instance Methods

bitmap_hdr() click to toggle source
# File lib/pedump/resources.rb, line 29
def bitmap_hdr
  bmp_info_hdr = data.find{ |x| x.is_a?(BITMAPINFOHEADER) }
  raise "no BITMAPINFOHEADER for #{self.type} #{self.name}" unless bmp_info_hdr

  bmp_info_hdr.biHeight/=2 if %w'ICON CURSOR'.include?(type)

  colors_used = bmp_info_hdr.biClrUsed
  colors_used = 2**bmp_info_hdr.biBitCount if colors_used == 0 && bmp_info_hdr.biBitCount < 16

  # XXX: one byte in each color is unused!
  @palette_size = colors_used * 4 # each color takes 4 bytes

  # scanlines are DWORD-aligned and padded to DWORD-align with zeroes
  # XXX: some data may be hidden in padding bytes!
  scanline_size = bmp_info_hdr.biWidth * bmp_info_hdr.biBitCount / 8
  scanline_size += (4-scanline_size%4) if scanline_size % 4 > 0

  @imgdata_size = scanline_size * bmp_info_hdr.biHeight
  "BM" + [
    BITMAPINFOHEADER::SIZE + 14 + @palette_size + @imgdata_size,
    0,
    BITMAPINFOHEADER::SIZE + 14 + @palette_size
  ].pack("V3") + bmp_info_hdr.pack
ensure
  bmp_info_hdr.biHeight*=2 if %w'ICON CURSOR'.include?(type)
end
bitmap_mask(src_fname) click to toggle source

only valid for types BITMAP, ICON & CURSOR

# File lib/pedump/resources.rb, line 98
def bitmap_mask src_fname
  File.open(src_fname, "rb") do |f|
    parse f
    bmp_info_hdr = bitmap_hdr
    bitmap_size = BITMAPINFOHEADER::SIZE + @palette_size + @imgdata_size
    return nil if bitmap_size >= self.size

    mask_size = self.size - bitmap_size
    f.seek file_offset + bitmap_size

    bmp_info_hdr = BITMAPINFOHEADER.new(*bmp_info_hdr[14..-1].unpack(BITMAPINFOHEADER::FORMAT))
    bmp_info_hdr.biBitCount = 1
    bmp_info_hdr.biCompression = bmp_info_hdr.biSizeImage = 0
    bmp_info_hdr.biClrUsed = bmp_info_hdr.biClrImportant = 2

    palette = [0,0xffffff].pack('V2')
    @palette_size = palette.size

    "BM" + [
      BITMAPINFOHEADER::SIZE + 14 + @palette_size + mask_size,
      0,
      BITMAPINFOHEADER::SIZE + 14 + @palette_size
    ].pack("V3") + bmp_info_hdr.pack + palette + f.read(mask_size)
  end
end
parse(f) click to toggle source

also sets the file position for restore_bitmap next call

# File lib/pedump/resources.rb, line 125
def parse f
  raise "called parse with type not set" unless self.type
  #return if self.data

  self.data = []
  return nil unless file_offset

  case type
  when 'BITMAP','ICON'
    f.seek file_offset
    if f.read(4) == "\x89PNG"
      data << 'PNG'
    else
      f.seek file_offset
      data << BITMAPINFOHEADER.read(f)
    end
  when 'CURSOR'
    f.seek file_offset
    data << CURSOR_HOTSPOT.read(f)
    data << BITMAPINFOHEADER.read(f)
  when 'GROUP_CURSOR'
    f.seek file_offset
    data << CUR_ICO_HEADER.read(f)
    nRead = CUR_ICO_HEADER::SIZE
    data.last.wNumImages.to_i.times do
      if nRead >= self.size
        PEdump.logger.error "[!] refusing to read CURDIRENTRY beyond resource size"
        break
      end
      data  << CURDIRENTRY.read(f)
      nRead += CURDIRENTRY::SIZE
    end
  when 'GROUP_ICON'
    f.seek file_offset
    data << CUR_ICO_HEADER.read(f)
    nRead = CUR_ICO_HEADER::SIZE
    data.last.wNumImages.to_i.times do
      if nRead >= self.size
        PEdump.logger.error "[!] refusing to read ICODIRENTRY beyond resource size"
        break
      end
      data  << ICODIRENTRY.read(f)
      nRead += ICODIRENTRY::SIZE
    end
  when 'STRING'
    f.seek file_offset
    16.times do
      break if f.tell >= file_offset+self.size
      nChars = f.read(2).to_s.unpack('v').first.to_i
      t =
        if nChars*2 + 1 > self.size
          # TODO: if it's not 1st string in table then truncated size must be less
          PEdump.logger.error "[!] string size(#{nChars*2}) > stringtable size(#{self.size}). truncated to #{self.size-2}"
          f.read(self.size-2)
        else
          f.read(nChars*2)
        end
      data <<
        begin
          t.force_encoding('UTF-16LE').encode!('UTF-8')
        rescue
          t.force_encoding('ASCII')
          tt = t.size > 0x10 ? t[0,0x10].inspect+'...' : t.inspect
          PEdump.logger.error "[!] cannot convert #{tt} to UTF-16"
          [nChars,t].pack('va*')
        end
    end
    # XXX: check if readed strings summary length is less than resource data length
  when 'VERSION'
    f.seek file_offset
    data << PEdump::VS_VERSIONINFO.read(f)
  end

  data.delete_if do |x|
    valid = !x.respond_to?(:valid?) || x.valid?
    PEdump.logger.warn "[?] ignoring invalid #{x.class}" unless valid
    !valid
  end
ensure
  validate
end
restore_bitmap(src_fname) click to toggle source

only valid for types BITMAP, ICON & CURSOR

# File lib/pedump/resources.rb, line 57
def restore_bitmap src_fname
  File.open(src_fname, "rb") do |f|
    parse f
    if data.first == "PNG"
      "\x89PNG" +f.read(self.size-4)
    else
      bitmap_hdr + f.read(@palette_size + @imgdata_size)
    end
  end
end
restore_icon(src_fname) click to toggle source

only valid for types BITMAP, ICON & CURSOR

# File lib/pedump/resources.rb, line 69
def restore_icon src_fname
  File.open(src_fname, "rb") do |f|
    parse f
    if data.first == "PNG"
      "\x89PNG" +f.read(self.size-4)
    else
      icondir = [
        0,        # Reserved. Must always be 0.
        1,        # image type: 1 for icon (.ICO), 2 for cursor (.CUR). Other values are invalid
        1,        # number of images in the file
      ].pack("v3")
      bitmap_hdr = data.first # BITMAPINFOHEADER
      icondirentry = ICODIRENTRY.new(
        bitmap_hdr.biWidth,
        bitmap_hdr.biHeight / (%w'ICON CURSOR'.include?(type) ? 2 : 1),
        0,   # XXX: bColors: may be wrong here
        0,
        1,
        bitmap_hdr.biBitCount,
        bitmap_hdr.biSizeImage,
        icondir.size + 2 + ICODIRENTRY::SIZE # offset of BMP data from the beginning of ICO file
      )
      # ICONDIRENTRY is 2 bytes larger than ICODIRENTRY
      icondir + icondirentry.pack + "\x00\x00" + bitmap_hdr.pack + f.read(self.size)
    end
  end
end
valid?() click to toggle source
# File lib/pedump/resources.rb, line 220
def valid?
  valid
end
validate() click to toggle source
# File lib/pedump/resources.rb, line 207
def validate
  self.valid = self.file_offset &&
    case type
    when 'BITMAP','ICON','CURSOR'
      data.any?{ |x| x.is_a?(BITMAPINFOHEADER) && x.valid? } || data.first == 'PNG'
    when 'GROUP_ICON'
      # rough validation
      data.first.is_a?(CUR_ICO_HEADER) && data.size == data.first.wNumImages.to_i+1
    else
      true
    end
end