class PEdump::Loader
This class is kinda Virtual Machine that mimics executable loading as real OS does. Can be used for unpacking, emulating, reversing, ā¦
Constants
- AddressOfEntryPoint
- DEFAULT_FIND_LIMIT
- FileAlignment
- Magic
- PointerToRawData
- SectionAlignment
- SizeOfRawData
Attributes
Public Class Methods
# File lib/pedump/loader.rb, line 96 def self.load_minidump io new.tap{ |ldr| ldr.load_minidump io } end
constructors
# File lib/pedump/loader.rb, line 33 def initialize io = nil, params = {} # @delta is for EFI TE loading, based on https://github.com/gdbinit/TELoader/blob/master/teloader.cpp @delta = 0 @pedump = PEdump.new(io, params) if io @mz_hdr = @pedump.mz @dos_stub = @pedump.dos_stub @pe_hdr = @pedump.pe @te_hdr = @pedump.te @image_base = params[:image_base] if @pe_hdr @image_base ||= @pe_hdr.try(:ioh).try(:ImageBase) elsif @te_hdr @image_base ||= @te_hdr.ImageBase @delta = @pedump.te_shift end @image_base ||= 0 load_sections @pedump.sections, io end @find_limit = params[:find_limit] || DEFAULT_FIND_LIMIT end
Public Instance Methods
read arbitrary string
# File lib/pedump/loader.rb, line 130 def [] va, size, params = {} section = va2section(va) raise "no section for va=0x#{va.to_s 16}" unless section offset = va - section.va raise "negative offset #{offset}" if offset < 0 r = section.data[offset,size] return nil if r.nil? if r.size < size && params.fetch(:zerofill, true) # append some empty data r << ("\x00".force_encoding('binary')) * (size - r.size) end r end
write arbitrary string
# File lib/pedump/loader.rb, line 145 def []= va, size, data raise "data.size != size" if data.size != size section = va2section(va) raise "no section for va=0x#{va.to_s 16}" unless section offset = va - section.va raise "negative offset #{offset}" if offset < 0 if section.data.size < offset # append some empty data section.data << ("\x00".force_encoding('binary') * (offset-section.data.size)) end section.data[offset, data.size] = data end
increasing max_diff speed ups the :valid_va? method, but may cause false positives
# File lib/pedump/loader.rb, line 187 def _merge_ranges max_diff = nil max_diff ||= if sections.size > 100 1024*1024 else 0 end ranges0 = sections.map(&:range).sort_by(&:begin) #puts "[.] #{ranges0.size} ranges" ranges1 = [] range = ranges0.shift while ranges0.any? while (ranges0.first.begin-range.end).abs <= max_diff range = range.begin...ranges0.shift.end break if ranges0.empty? end #puts "[.] diff #{ranges0.first.begin-range.end}" ranges1 << range range = ranges0.shift end ranges1 << range #puts "[=] #{ranges1.size} ranges" ranges1.uniq.compact end
# File lib/pedump/loader.rb, line 295 def _parse_exports return {} unless @pedump.exports @pedump.exports.functions.each do |func| @names[@image_base + func.va] = func.name || "##{func.ordinal}" end end
# File lib/pedump/loader.rb, line 284 def _parse_imports @pedump.imports.each do |iid| # Image Import Descriptor va = iid.FirstThunk + @image_base (Array(iid.original_first_thunk) + Array(iid.first_thunk)).uniq.each do |func| name = "__imp_" + (func.name || "#{func.ordinal}") @names[va] = name va += 4 end end end
read single DWord (4 bytes) if no ānā specified delegate to dwords
otherwise
# File lib/pedump/loader.rb, line 170 def dw va, n=nil n ? dwords(va,n) : self[va,4].unpack('L')[0] end
read N DWords, returns array
# File lib/pedump/loader.rb, line 176 def dwords va, n self[va,4*n].unpack('L*') end
# File lib/pedump/loader.rb, line 19 def ep if @pe_hdr @pe_hdr.try(:ioh).try(:AddressOfEntryPoint) elsif @te_hdr @te_hdr.AddressOfEntryPoint - @delta end end
# File lib/pedump/loader.rb, line 27 def ep= v; @pe_hdr.ioh.AddressOfEntryPoint=v; end
save a new PE file to specified IO
# File lib/pedump/loader.rb, line 319 def export io @mz_hdr ||= PEdump::MZ.new("MZ", *[0]*22) @dos_stub ||= '' @pe_hdr ||= PEdump::PE.new("PE\x00\x00") @pe_hdr.ioh ||= PEdump::IMAGE_OPTIONAL_HEADER32.read( StringIO.new("\x00" * 224) ).tap do |ioh| ioh.Magic = 0x10b # 32-bit executable #ioh.NumberOfRvaAndSizes = 0x10 end @pe_hdr.ifh ||= PEdump::IMAGE_FILE_HEADER.new( :Machine => 0x14c, # x86 :NumberOfSections => @sections.size, :TimeDateStamp => 0, :PointerToSymbolTable => 0, :NumberOfSymbols => 0, :SizeOfOptionalHeader => @pe_hdr.ioh.pack.size, :Characteristics => 0x102 # EXECUTABLE_IMAGE | 32BIT_MACHINE ) if @pe_hdr.ioh.FileAlignment.to_i == 0 # default file align = 512 bytes @pe_hdr.ioh.FileAlignment = 0x200 end if @pe_hdr.ioh.SectionAlignment.to_i == 0 # default section align = 4k @pe_hdr.ioh.SectionAlignment = 0x1000 end mz_size = @mz_hdr.pack.size raise "odd mz_size #{mz_size}" if mz_size % 0x10 != 0 @mz_hdr.header_paragraphs = mz_size / 0x10 # offset of dos_stub @mz_hdr.lfanew = mz_size + @dos_stub.size # offset of PE hdr io.write @mz_hdr.pack io.write @dos_stub io.write @pe_hdr.pack io.write @pe_hdr.ioh.DataDirectory.map(&:pack).join section_tbl_offset = io.tell # store offset for 2nd write of section table io.write section_table align = @pe_hdr.ioh.FileAlignment @sections.each do |section| io.seek(align - (io.tell % align), IO::SEEK_CUR) if io.tell % align != 0 section.hdr.PointerToRawData = io.tell # fix raw_ptr io.write(section.data) end eof = io.tell # 2nd write of section table with correct raw_ptr's io.seek section_tbl_offset io.write section_table io.seek eof end
find first occurence of string returns VA
# File lib/pedump/loader.rb, line 215 def find needle, options = {} options[:align] ||= 1 options[:limit] ||= @find_limit if needle.is_a?(Fixnum) # silently convert to DWORD needle = [needle].pack('L') end if options[:align] == 1 # fastest find? processed_bytes = 0 sections.each do |section| next unless section.data # skip empty sections pos = section.data.index(needle) return section.va+pos if pos processed_bytes += section.vsize return nil if processed_bytes >= options[:limit] end end nil end
find all occurences of string returns array of VAs or empty array
# File lib/pedump/loader.rb, line 240 def find_all needle, options = {} options[:align] ||= 1 options[:limit] ||= @find_limit if needle.is_a?(Fixnum) # silently convert to DWORD needle = [needle].pack('L') end r = [] if options[:align] == 1 # fastest find? processed_bytes = 0 sections.each do |section| next unless section.data # skip empty sections section.data.scan(needle) do r << $~.begin(0) + section.va end processed_bytes += section.vsize return r if processed_bytes >= options[:limit] end end r end
returns StringIO with section data, pre-seeked to specified VA TODO: make io cross sections
# File lib/pedump/loader.rb, line 160 def io va section = va2section(va) raise "no section for va=0x#{va.to_s 16}" unless section offset = va - section.va raise "negative offset #{offset}" if offset < 0 StringIO.new(section.data).tap{ |io| io.seek offset } end
load MS Minidump
(*.dmp) file, that can be created in Task Manager via right click on process -> save memory dump
# File lib/pedump/loader.rb, line 81 def load_minidump io, options = {} @sections ||= [] md = Minidump.new io options[:merge] = true unless options.key?(:merge) md.memory_ranges(options).each do |mr| hdr = PEdump::IMAGE_SECTION_HEADER.new( :VirtualAddress => mr.va, :PointerToRawData => mr.file_offset, :SizeOfRawData => mr.size, :VirtualSize => mr.size # XXX may be larger than SizeOfRawData ) @sections << Section.new( hdr, :deferred_load_io => io ) end end
# File lib/pedump/loader.rb, line 57 def load_sections section_hdrs, f = nil if section_hdrs.is_a?(Array) @sections = section_hdrs.map do |x| raise "unknown section hdr: #{x.inspect}" unless x.is_a?(PEdump::IMAGE_SECTION_HEADER) Section.new(x, :deferred_load_io => f, :image_base => @image_base, :delta => @delta ) end if f.respond_to?(:seek) && f.respond_to?(:read) # # converted to deferred loading # # section_hdrs.each_with_index do |sect_hdr, idx| # f.seek sect_hdr.PointerToRawData # @sections[idx].data = f.read(sect_hdr.SizeOfRawData) # end elsif f raise "invalid 2nd arg: #{f.inspect}" end else raise "invalid arg: #{section_hdrs.inspect}" end end
parsing names
# File lib/pedump/loader.rb, line 269 def names return @names if @names @names = {} oep = ep() if oep @names[oep + @image_base] = 'start' end _parse_imports _parse_exports #TODO: debug info @names end
RVA (Relative VA) to section
# File lib/pedump/loader.rb, line 110 def rva2section rva va2section( rva + @image_base ) end
# File lib/pedump/loader.rb, line 121 def rva2stream rva va2stream( rva + @image_base ) end
generating PE binary
# File lib/pedump/loader.rb, line 306 def section_table @sections.map do |section| section.hdr.SizeOfRawData = section.data.size section.hdr.PointerToRelocations ||= 0 section.hdr.PointerToLinenumbers ||= 0 section.hdr.NumberOfRelocations ||= 0 section.hdr.NumberOfLinenumbers ||= 0 section.hdr.Characteristics ||= 0 section.hdr.pack end.join end
VA to section
# File lib/pedump/loader.rb, line 105 def va2section va @sections.find{ |x| x.range.include?(va) } end
# File lib/pedump/loader.rb, line 114 def va2stream va return nil unless section = va2section(va) StringIO.new(section.data).tap do |io| io.seek va-section.va end end
check if any section has specified VA in its range
# File lib/pedump/loader.rb, line 181 def valid_va? va @ranges ||= _merge_ranges @ranges.any?{ |range| range.include?(va) } end