pedump 0.4.16 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -5,6 +5,8 @@ source "http://rubygems.org"
5
5
  gem "multipart-post", "~> 1.1.4"
6
6
  gem "progressbar"
7
7
  gem "awesome_print"
8
+ gem "iostruct", ">= 0.0.4"
9
+ gem "zhexdump", ">= 0.0.2"
8
10
 
9
11
  # Add dependencies to develop your gem here.
10
12
  # Include everything needed to run rake, tests, features, etc.
@@ -4,6 +4,7 @@ GEM
4
4
  awesome_print (1.1.0)
5
5
  diff-lcs (1.1.3)
6
6
  git (1.2.5)
7
+ iostruct (0.0.4)
7
8
  jeweler (1.8.4)
8
9
  bundler (~> 1.0)
9
10
  git (>= 1.2.5)
@@ -12,7 +13,7 @@ GEM
12
13
  json (1.7.5)
13
14
  multipart-post (1.1.5)
14
15
  progressbar (0.12.0)
15
- rake (10.0.2)
16
+ rake (10.0.4)
16
17
  rdoc (3.12)
17
18
  json (~> 1.4)
18
19
  rspec (2.12.0)
@@ -24,6 +25,7 @@ GEM
24
25
  diff-lcs (~> 1.1.3)
25
26
  rspec-mocks (2.12.0)
26
27
  what_methods (1.0.1)
28
+ zhexdump (0.0.2)
27
29
 
28
30
  PLATFORMS
29
31
  ruby
@@ -31,8 +33,10 @@ PLATFORMS
31
33
  DEPENDENCIES
32
34
  awesome_print
33
35
  bundler
36
+ iostruct (>= 0.0.4)
34
37
  jeweler
35
38
  multipart-post (~> 1.1.4)
36
39
  progressbar
37
40
  rspec
38
41
  what_methods
42
+ zhexdump (>= 0.0.2)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.16
1
+ 0.5.0
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'stringio'
3
+ require 'iostruct'
4
+ require 'zhexdump'
5
+
6
+ unless Object.new.respond_to?(:try) && nil.respond_to?(:try)
7
+ require 'pedump/core_ext/try'
8
+ end
9
+
3
10
  require 'pedump/core'
4
11
  require 'pedump/pe'
5
12
  require 'pedump/resources'
@@ -34,7 +41,7 @@ class PEdump
34
41
  end
35
42
 
36
43
  # http://www.delorie.com/djgpp/doc/exe/
37
- MZ = create_struct( "a2v13Qv2V6",
44
+ MZ = IOStruct.new( "a2v13Qv2V6",
38
45
  :signature,
39
46
  :bytes_in_last_block,
40
47
  :blocks_in_file,
@@ -61,7 +68,7 @@ class PEdump
61
68
  )
62
69
 
63
70
  # http://msdn.microsoft.com/en-us/library/ms809762.aspx
64
- class IMAGE_FILE_HEADER < create_struct( 'v2V3v2',
71
+ class IMAGE_FILE_HEADER < IOStruct.new( 'v2V3v2',
65
72
  :Machine, # w
66
73
  :NumberOfSections, # w
67
74
  :TimeDateStamp, # dw
@@ -164,7 +171,7 @@ class PEdump
164
171
  end
165
172
 
166
173
  # http://msdn.microsoft.com/en-us/library/ms809762.aspx
167
- class IMAGE_OPTIONAL_HEADER32 < create_struct( 'vC2V9v6V4v2V6',
174
+ class IMAGE_OPTIONAL_HEADER32 < IOStruct.new( 'vC2V9v6V4v2V6',
168
175
  :Magic, # w
169
176
  :MajorLinkerVersion, :MinorLinkerVersion, # 2b
170
177
  :SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, # 9dw
@@ -182,7 +189,7 @@ class PEdump
182
189
  end
183
190
 
184
191
  # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=VS.85).aspx)
185
- class IMAGE_OPTIONAL_HEADER64 < create_struct( 'vC2V5QV2v6V4v2Q4V2',
192
+ class IMAGE_OPTIONAL_HEADER64 < IOStruct.new( 'vC2V5QV2v6V4v2Q4V2',
186
193
  :Magic, # w
187
194
  :MajorLinkerVersion, :MinorLinkerVersion, # 2b
188
195
  :SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, :BaseOfCode, # 5dw
@@ -200,7 +207,7 @@ class PEdump
200
207
  include IMAGE_OPTIONAL_HEADER
201
208
  end
202
209
 
203
- IMAGE_DATA_DIRECTORY = create_struct( "VV", :va, :size, :type )
210
+ IMAGE_DATA_DIRECTORY = IOStruct.new( "VV", :va, :size, :type )
204
211
  IMAGE_DATA_DIRECTORY::TYPES =
205
212
  %w'EXPORT IMPORT RESOURCE EXCEPTION SECURITY BASERELOC DEBUG ARCHITECTURE GLOBALPTR TLS LOAD_CONFIG
206
213
  Bound_IAT IAT Delay_IAT CLR_Header'
@@ -208,7 +215,7 @@ class PEdump
208
215
  IMAGE_DATA_DIRECTORY.const_set(type,idx)
209
216
  end
210
217
 
211
- IMAGE_SECTION_HEADER = create_struct( 'A8V6v2V',
218
+ IMAGE_SECTION_HEADER = IOStruct.new( 'A8V6v2V',
212
219
  :Name, # A8 6dw
213
220
  :VirtualSize, :VirtualAddress, :SizeOfRawData, :PointerToRawData, :PointerToRelocations, :PointerToLinenumbers,
214
221
  :NumberOfRelocations, :NumberOfLinenumbers, # 2w
@@ -455,7 +462,7 @@ class PEdump
455
462
 
456
463
  # http://sandsprite.com/CodeStuff/Understanding_imports.html
457
464
  # http://stackoverflow.com/questions/5631317/import-table-it-vs-import-address-table-iat
458
- IMAGE_IMPORT_DESCRIPTOR = create_struct 'V5',
465
+ IMAGE_IMPORT_DESCRIPTOR = IOStruct.new 'V5',
459
466
  :OriginalFirstThunk,
460
467
  :TimeDateStamp,
461
468
  :ForwarderChain,
@@ -603,7 +610,7 @@ class PEdump
603
610
  ##############################################################################
604
611
 
605
612
  #http://msdn.microsoft.com/en-us/library/ms809762.aspx
606
- IMAGE_EXPORT_DIRECTORY = create_struct 'V2v2V7',
613
+ IMAGE_EXPORT_DIRECTORY = IOStruct.new 'V2v2V7',
607
614
  :Characteristics,
608
615
  :TimeDateStamp,
609
616
  :MajorVersion, # These fields appear to be unused and are set to 0.
@@ -822,17 +829,13 @@ if $0 == __FILE__
822
829
  exit
823
830
  end
824
831
  p dump.mz
825
- require './lib/hexdump_helper' if File.exist?("lib/hexdump_helper.rb")
826
- if defined?(HexdumpHelper)
827
- include HexdumpHelper
828
- puts hexdump(dump.dos_stub) if dump.dos_stub
832
+ dump.dos_stub.hexdump if dump.dos_stub
833
+ puts
834
+ if dump.rich_hdr
835
+ dump.rich_hdr.hexdump
829
836
  puts
830
- if dump.rich_hdr
831
- puts hexdump(dump.rich_hdr)
832
- puts
833
- p(dump.rich_hdr.decode)
834
- puts hexdump(dump.rich_hdr.dexor)
835
- end
837
+ p(dump.rich_hdr.decode)
838
+ dump.rich_hdr.dexor.hexdump
836
839
  end
837
840
  pp dump.pe
838
841
  pp dump.resources
@@ -29,23 +29,15 @@ rescue LoadError
29
29
  end
30
30
  end
31
31
 
32
- unless Object.instance_methods.include?(:try)
33
- class Object
34
- def try(*x)
35
- send(*x) if respond_to?(x.first)
36
- end
37
- end
38
- end
39
-
40
32
  class PEdump::CLI
41
33
  attr_accessor :data, :argv
42
34
 
43
35
  KNOWN_ACTIONS = (
44
36
  %w'mz dos_stub rich pe ne data_directory sections tls security' +
45
- %w'strings resources resource_directory imports exports version_info packer web packer_only'
37
+ %w'strings resources resource_directory imports exports version_info packer web console packer_only'
46
38
  ).map(&:to_sym)
47
39
 
48
- DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only'.map(&:to_sym)
40
+ DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only console'.map(&:to_sym)
49
41
 
50
42
  URL_BASE = "http://pedump.me"
51
43
 
@@ -107,9 +99,15 @@ class PEdump::CLI
107
99
  opts.on "--va2file VA", "Convert RVA to file offset" do |va|
108
100
  @actions << [:va2file,va]
109
101
  end
102
+
103
+ opts.separator ''
104
+
110
105
  opts.on "-W", "--web", "Uploads files to a #{URL_BASE}","for a nice HTML tables with image previews,","candies & stuff" do
111
106
  @actions << :web
112
107
  end
108
+ opts.on "-C", "--console", "opens IRB console with specified file loaded" do
109
+ @actions << :console
110
+ end
113
111
  end
114
112
 
115
113
  if (@argv = optparser.parse(@argv)).empty?
@@ -140,8 +138,9 @@ class PEdump::CLI
140
138
  next if !@options[:force] && !@pedump.mz(f)
141
139
 
142
140
  @actions.each do |action|
143
- if action == :web
144
- upload f
141
+ case action
142
+ when :web; upload f
143
+ when :console; console f
145
144
  else
146
145
  dump_action action,f
147
146
  end
@@ -271,6 +270,29 @@ class PEdump::CLI
271
270
  STDOUT.sync = stdout_sync
272
271
  end
273
272
 
273
+ def console f
274
+ require 'pedump/loader'
275
+ require 'pp'
276
+
277
+ ARGV.clear # clear ARGV so IRB is not confused
278
+ require 'irb'
279
+ f.rewind
280
+ ldr = @ldr = PEdump::Loader.new(f)
281
+
282
+ # override IRB.setup, called from IRB.start
283
+ m0 = IRB.method(:setup)
284
+ IRB.define_singleton_method :setup do |*args|
285
+ m0.call *args
286
+ conf[:IRB_RC] = Proc.new do |context|
287
+ context.main.instance_variable_set '@ldr', ldr
288
+ context.main.define_singleton_method(:ldr){ @ldr }
289
+ end
290
+ end
291
+
292
+ puts "[.] ldr = PEdump::Loader.new(open(#{f.path.inspect}))".gray
293
+ IRB.start
294
+ end
295
+
274
296
  def action_title action
275
297
  if @need_fname_header
276
298
  @need_fname_header = false
@@ -302,7 +324,7 @@ class PEdump::CLI
302
324
  data = @pedump.send(action, f)
303
325
  return if !data || (data.respond_to?(:empty?) && data.empty?)
304
326
 
305
- puts action_title(action)
327
+ puts action_title(action) unless @options[:format] == :binary
306
328
 
307
329
  return dump(data) if [:inspect, :table, :yaml].include?(@options[:format])
308
330
 
@@ -333,7 +355,7 @@ class PEdump::CLI
333
355
  def dump data, opts = {}
334
356
  case opts[:format] || @options[:format] || :dump
335
357
  when :dump, :hexdump
336
- puts hexdump(data)
358
+ data.hexdump
337
359
  when :hex
338
360
  puts data.each_byte.map{ |x| "%02x" % x }.join(' ')
339
361
  when :binary
@@ -454,7 +476,7 @@ class PEdump::CLI
454
476
  puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
455
477
  end
456
478
  elsif data.is_a?(PEdump::DOSStub)
457
- puts hexdump(data)
479
+ data.hexdump
458
480
  elsif data.is_a?(PEdump::RichHdr)
459
481
  dump_rich_hdr data
460
482
  else
@@ -772,39 +794,11 @@ class PEdump::CLI
772
794
  end
773
795
  else
774
796
  puts "# raw:"
775
- puts hexdump(data)
797
+ data.hexdump
776
798
  puts
777
799
  puts "# dexored:"
778
- puts hexdump(data.dexor)
800
+ data.dexor.hexdump
779
801
  end
780
802
  end
781
803
 
782
- def hexdump *args
783
- self.class.hexdump(*args)
784
- end
785
-
786
- def self.hexdump data, h = {}
787
- offset = h[:offset] || 0
788
- add = h[:add] || 0
789
- size = h[:size] || (data.size-offset)
790
- tail = h[:tail] || "\n"
791
- width = h[:width] || 0x10 # row width, in bytes
792
-
793
- size = data.size-offset if size+offset > data.size
794
-
795
- r = ''; s = ''
796
- r << "%08x: " % (offset + add)
797
- ascii = ''
798
- size.times do |i|
799
- if i%width==0 && i>0
800
- r << "%s |%s|\n%08x: " % [s, ascii, offset + add + i]
801
- ascii = ''; s = ''
802
- end
803
- s << " " if i%width%8==0
804
- c = data[offset+i].ord
805
- s << "%02x " % c
806
- ascii << ((32..126).include?(c) ? c.chr : '.')
807
- end
808
- r << "%-*s |%-*s|%s" % [width*3+width/8+(width%8==0?0:1), s, width, ascii, tail]
809
- end
810
- end
804
+ end # class PEdump::CLI
@@ -31,55 +31,8 @@ class File
31
31
  end
32
32
 
33
33
  class PEdump
34
-
35
- module Readable
36
- # src can be IO or String, or anything that responds to :read or :unpack
37
- def read src, size = nil
38
- size ||= const_get 'SIZE'
39
- data =
40
- if src.respond_to?(:read)
41
- src.read(size).to_s
42
- elsif src.respond_to?(:unpack)
43
- src
44
- else
45
- raise "[?] don't know how to read from #{src.inspect}"
46
- end
47
- if data.size < size && PEdump.logger
48
- PEdump.logger.error "[!] #{self.to_s} want #{size} bytes, got #{data.size}"
49
- end
50
- new(*data.unpack(const_get('FORMAT')))
51
- end
52
- end
53
-
54
34
  class << self
55
35
  def logger; @@logger; end
56
36
  def logger= l; @@logger=l; end
57
-
58
- def create_struct fmt, *args
59
- size = fmt.scan(/([a-z])(\d*)/i).map do |f,len|
60
- [len.to_i, 1].max *
61
- case f
62
- when /[aAC]/ then 1
63
- when 'v' then 2
64
- when 'V','l' then 4
65
- when 'Q' then 8
66
- else raise "unknown fmt #{f.inspect}"
67
- end
68
- end.inject(&:+)
69
-
70
- Struct.new( *args ).tap do |x|
71
- x.const_set 'FORMAT', fmt
72
- x.const_set 'SIZE', size
73
- x.class_eval do
74
- def pack
75
- to_a.pack self.class.const_get('FORMAT')
76
- end
77
- def empty?
78
- to_a.all?{ |t| t == 0 || t.nil? || t.to_s.tr("\x00","").empty? }
79
- end
80
- end
81
- x.extend Readable
82
- end
83
- end
84
37
  end
85
38
  end
@@ -0,0 +1,57 @@
1
+ class Object
2
+ # Invokes the method identified by the symbol +method+, passing it any arguments
3
+ # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
4
+ #
5
+ # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
6
+ # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
7
+ #
8
+ # If try is called without a method to call, it will yield any given block with the object.
9
+ #
10
+ # Please also note that +try+ is defined on +Object+, therefore it won't work with
11
+ # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will
12
+ # delegate +try+ to target instead of calling it on delegator itself.
13
+ #
14
+ # ==== Examples
15
+ #
16
+ # Without +try+
17
+ # @person && @person.name
18
+ # or
19
+ # @person ? @person.name : nil
20
+ #
21
+ # With +try+
22
+ # @person.try(:name)
23
+ #
24
+ # +try+ also accepts arguments and/or a block, for the method it is trying
25
+ # Person.try(:find, 1)
26
+ # @people.try(:collect) {|p| p.name}
27
+ #
28
+ # Without a method argument try will yield to the block unless the receiver is nil.
29
+ # @person.try { |p| "#{p.first_name} #{p.last_name}" }
30
+ #--
31
+ # +try+ behaves like +Object#send+, unless called on +NilClass+.
32
+ def try(*a, &b)
33
+ if a.empty? && block_given?
34
+ yield self
35
+ else
36
+ __send__(*a, &b)
37
+ end
38
+ end
39
+ end
40
+
41
+ class NilClass
42
+ # Calling +try+ on +nil+ always returns +nil+.
43
+ # It becomes specially helpful when navigating through associations that may return +nil+.
44
+ #
45
+ # === Examples
46
+ #
47
+ # nil.try(:name) # => nil
48
+ #
49
+ # Without +try+
50
+ # @person && !@person.children.blank? && @person.children.first.name
51
+ #
52
+ # With +try+
53
+ # @person.try(:children).try(:first).try(:name)
54
+ def try(*args)
55
+ nil
56
+ end
57
+ end
@@ -1,9 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
1
3
  require 'pedump'
2
4
  require 'stringio'
3
5
  require 'pedump/loader/section'
6
+ require 'pedump/loader/minidump'
4
7
 
8
+ # This class is kinda Virtual Machine that mimics executable loading as real OS does.
9
+ # Can be used for unpacking, emulating, reversing, ...
5
10
  class PEdump::Loader
6
- attr_accessor :mz_hdr, :dos_stub, :pe_hdr, :sections, :pedump
11
+ attr_accessor :mz_hdr, :dos_stub, :pe_hdr, :sections, :pedump, :image_base
12
+ attr_accessor :find_limit
13
+
14
+ DEFAULT_FIND_LIMIT = 2**64
7
15
 
8
16
  # shortcuts
9
17
  alias :pe :pe_hdr
@@ -14,19 +22,24 @@ class PEdump::Loader
14
22
  # constructors
15
23
  ########################################################################
16
24
 
17
- def initialize io = nil, pedump_params = {}
18
- @pedump = PEdump.new(io, pedump_params)
25
+ def initialize io = nil, params = {}
26
+ @pedump = PEdump.new(io, params)
19
27
  if io
20
28
  @mz_hdr = @pedump.mz
21
29
  @dos_stub = @pedump.dos_stub
22
30
  @pe_hdr = @pedump.pe
31
+ @image_base = params[:image_base] || @pe_hdr.try(:ioh).try(:ImageBase) || 0
23
32
  load_sections @pedump.sections, io
24
33
  end
34
+ @find_limit = params[:find_limit] || DEFAULT_FIND_LIMIT
25
35
  end
26
36
 
27
37
  def load_sections section_hdrs, f = nil
28
- if section_hdrs.is_a?(Array) && section_hdrs.map(&:class).uniq == [PEdump::IMAGE_SECTION_HEADER]
29
- @sections = section_hdrs.map{ |x| Section.new(x, :deferred_load_io => f) }
38
+ if section_hdrs.is_a?(Array)
39
+ @sections = section_hdrs.map do |x|
40
+ raise "unknown section hdr: #{x.inspect}" unless x.is_a?(PEdump::IMAGE_SECTION_HEADER)
41
+ Section.new(x, :deferred_load_io => f, :image_base => @image_base )
42
+ end
30
43
  if f.respond_to?(:seek) && f.respond_to?(:read)
31
44
  #
32
45
  # converted to deferred loading
@@ -43,14 +56,41 @@ class PEdump::Loader
43
56
  end
44
57
  end
45
58
 
59
+ # load MS Minidump (*.dmp) file, that can be created in Task Manager via
60
+ # right click on process -> save memory dump
61
+ def load_minidump io, options = {}
62
+ @sections ||= []
63
+ md = Minidump.new io
64
+ options[:merge] = true unless options.key?(:merge)
65
+ md.memory_ranges(options).each do |mr|
66
+ hdr = PEdump::IMAGE_SECTION_HEADER.new(
67
+ :VirtualAddress => mr.va,
68
+ :PointerToRawData => mr.file_offset,
69
+ :SizeOfRawData => mr.size,
70
+ :VirtualSize => mr.size # XXX may be larger than SizeOfRawData
71
+ )
72
+ @sections << Section.new( hdr, :deferred_load_io => io )
73
+ end
74
+ end
75
+
76
+ def self.load_minidump io
77
+ new.tap{ |ldr| ldr.load_minidump io }
78
+ end
79
+
46
80
  ########################################################################
47
81
  # VA conversion
48
82
  ########################################################################
49
83
 
84
+ # VA to section
50
85
  def va2section va
51
86
  @sections.find{ |x| x.range.include?(va) }
52
87
  end
53
88
 
89
+ # RVA (Relative VA) to section
90
+ def rva2section rva
91
+ va2section( rva + @image_base )
92
+ end
93
+
54
94
  def va2stream va
55
95
  return nil unless section = va2section(va)
56
96
  StringIO.new(section.data).tap do |io|
@@ -58,23 +98,30 @@ class PEdump::Loader
58
98
  end
59
99
  end
60
100
 
101
+ def rva2stream rva
102
+ va2stream( rva + @image_base )
103
+ end
104
+
61
105
  ########################################################################
62
106
  # virtual memory read/write
63
107
  ########################################################################
64
108
 
65
- def [] va, size
109
+ # read arbitrary string
110
+ def [] va, size, params = {}
66
111
  section = va2section(va)
67
112
  raise "no section for va=0x#{va.to_s 16}" unless section
68
113
  offset = va - section.va
69
114
  raise "negative offset #{offset}" if offset < 0
70
115
  r = section.data[offset,size]
71
- if r.size < size
116
+ return nil if r.nil?
117
+ if r.size < size && params.fetch(:zerofill, true)
72
118
  # append some empty data
73
119
  r << ("\x00".force_encoding('binary')) * (size - r.size)
74
120
  end
75
121
  r
76
122
  end
77
123
 
124
+ # write arbitrary string
78
125
  def []= va, size, data
79
126
  raise "data.size != size" if data.size != size
80
127
  section = va2section(va)
@@ -88,6 +135,148 @@ class PEdump::Loader
88
135
  section.data[offset, data.size] = data
89
136
  end
90
137
 
138
+ # returns StringIO with section data, pre-seeked to specified VA
139
+ # TODO: make io cross sections
140
+ def io va
141
+ section = va2section(va)
142
+ raise "no section for va=0x#{va.to_s 16}" unless section
143
+ offset = va - section.va
144
+ raise "negative offset #{offset}" if offset < 0
145
+ StringIO.new(section.data).tap{ |io| io.seek offset }
146
+ end
147
+
148
+ # read single DWord (4 bytes) if no 'n' specified
149
+ # delegate to #dwords otherwise
150
+ def dw va, n=nil
151
+ n ? dwords(va,n) : self[va,4].unpack('L')[0]
152
+ end
153
+ alias :dword :dw
154
+
155
+ # read N DWords, returns array
156
+ def dwords va, n
157
+ self[va,4*n].unpack('L*')
158
+ end
159
+
160
+ # check if any section has specified VA in its range
161
+ def valid_va? va
162
+ @ranges ||= _merge_ranges
163
+ @ranges.any?{ |range| range.include?(va) }
164
+ end
165
+
166
+ # increasing max_diff speed ups the :valid_va? method, but may cause false positives
167
+ def _merge_ranges max_diff = nil
168
+ max_diff ||=
169
+ if sections.size > 100
170
+ 1024*1024
171
+ else
172
+ 0
173
+ end
174
+
175
+ ranges0 = sections.map(&:range).sort_by(&:begin)
176
+ #puts "[.] #{ranges0.size} ranges"
177
+ ranges1 = []
178
+ range = ranges0.shift
179
+ while ranges0.any?
180
+ while (ranges0.first.begin-range.end).abs <= max_diff
181
+ range = range.begin...ranges0.shift.end
182
+ break if ranges0.empty?
183
+ end
184
+ #puts "[.] diff #{ranges0.first.begin-range.end}"
185
+ ranges1 << range
186
+ range = ranges0.shift
187
+ end
188
+ ranges1 << range
189
+ #puts "[=] #{ranges1.size} ranges"
190
+ ranges1.uniq.compact
191
+ end
192
+
193
+ # find first occurence of string
194
+ # returns VA
195
+ def find needle, options = {}
196
+ options[:align] ||= 1
197
+ options[:limit] ||= @find_limit
198
+
199
+ if needle.is_a?(Fixnum)
200
+ # silently convert to DWORD
201
+ needle = [needle].pack('L')
202
+ end
203
+
204
+ if options[:align] == 1
205
+ # fastest find?
206
+ processed_bytes = 0
207
+ sections.each do |section|
208
+ next unless section.data # skip empty sections
209
+ pos = section.data.index(needle)
210
+ return section.va+pos if pos
211
+ processed_bytes += section.vsize
212
+ return nil if processed_bytes >= options[:limit]
213
+ end
214
+ end
215
+ nil
216
+ end
217
+
218
+ # find all occurences of string
219
+ # returns array of VAs or empty array
220
+ def find_all needle, options = {}
221
+ options[:align] ||= 1
222
+ options[:limit] ||= @find_limit
223
+
224
+ if needle.is_a?(Fixnum)
225
+ # silently convert to DWORD
226
+ needle = [needle].pack('L')
227
+ end
228
+
229
+ r = []
230
+ if options[:align] == 1
231
+ # fastest find?
232
+ processed_bytes = 0
233
+ sections.each do |section|
234
+ next unless section.data # skip empty sections
235
+ section.data.scan(needle) do
236
+ r << $~.begin(0) + section.va
237
+ end
238
+ processed_bytes += section.vsize
239
+ return r if processed_bytes >= options[:limit]
240
+ end
241
+ end
242
+ r
243
+ end
244
+
245
+ ########################################################################
246
+ # parsing names
247
+ ########################################################################
248
+
249
+ def names
250
+ return @names if @names
251
+ @names = {}
252
+ if oep = @pe_hdr.try(:ioh).try(:AddressOfEntryPoint)
253
+ oep += @image_base
254
+ @names[oep] = 'start'
255
+ end
256
+ _parse_imports
257
+ _parse_exports
258
+ #TODO: debug info
259
+ @names
260
+ end
261
+
262
+ def _parse_imports
263
+ @pedump.imports.each do |iid| # Image Import Descriptor
264
+ va = iid.FirstThunk + @image_base
265
+ (Array(iid.original_first_thunk) + Array(iid.first_thunk)).uniq.each do |func|
266
+ name = func.name || "##{func.ordinal}"
267
+ @names[va] = name
268
+ va += 4
269
+ end
270
+ end
271
+ end
272
+
273
+ def _parse_exports
274
+ return {} unless @pedump.exports
275
+ @pedump.exports.functions.each do |func|
276
+ @names[@image_base + func.va] = func.name || "##{func.ordinal}"
277
+ end
278
+ end
279
+
91
280
  ########################################################################
92
281
  # generating PE binary
93
282
  ########################################################################
@@ -95,37 +284,110 @@ class PEdump::Loader
95
284
  def section_table
96
285
  @sections.map do |section|
97
286
  section.hdr.SizeOfRawData = section.data.size
287
+ section.hdr.PointerToRelocations ||= 0
288
+ section.hdr.PointerToLinenumbers ||= 0
289
+ section.hdr.NumberOfRelocations ||= 0
290
+ section.hdr.NumberOfLinenumbers ||= 0
291
+ section.hdr.Characteristics ||= 0
98
292
  section.hdr.pack
99
293
  end.join
100
294
  end
101
295
 
102
- def dump f
103
- align = @pe_hdr.ioh.FileAlignment
296
+ # save a new PE file to specified IO
297
+ def export io
298
+ @mz_hdr ||= PEdump::MZ.new("MZ", *[0]*22)
299
+ @dos_stub ||= ''
300
+ @pe_hdr ||= PEdump::PE.new("PE\x00\x00")
301
+ @pe_hdr.ioh ||=
302
+ PEdump::IMAGE_OPTIONAL_HEADER32.read( StringIO.new("\x00" * 224) ).tap do |ioh|
303
+ ioh.Magic = 0x10b # 32-bit executable
304
+ #ioh.NumberOfRvaAndSizes = 0x10
305
+ end
306
+ @pe_hdr.ifh ||= PEdump::IMAGE_FILE_HEADER.new(
307
+ :Machine => 0x14c, # x86
308
+ :NumberOfSections => @sections.size,
309
+ :TimeDateStamp => 0,
310
+ :PointerToSymbolTable => 0,
311
+ :NumberOfSymbols => 0,
312
+ :SizeOfOptionalHeader => @pe_hdr.ioh.pack.size,
313
+ :Characteristics => 0x102 # EXECUTABLE_IMAGE | 32BIT_MACHINE
314
+ )
315
+
316
+ if @pe_hdr.ioh.FileAlignment.to_i == 0
317
+ # default file align = 512 bytes
318
+ @pe_hdr.ioh.FileAlignment = 0x200
319
+ end
320
+ if @pe_hdr.ioh.SectionAlignment.to_i == 0
321
+ # default section align = 4k
322
+ @pe_hdr.ioh.SectionAlignment = 0x1000
323
+ end
104
324
 
105
325
  mz_size = @mz_hdr.pack.size
106
326
  raise "odd mz_size #{mz_size}" if mz_size % 0x10 != 0
107
327
  @mz_hdr.header_paragraphs = mz_size / 0x10 # offset of dos_stub
108
328
  @mz_hdr.lfanew = mz_size + @dos_stub.size # offset of PE hdr
109
- f.write @mz_hdr.pack
110
- f.write @dos_stub
111
- f.write @pe_hdr.pack
112
- f.write @pe_hdr.ioh.DataDirectory.map(&:pack).join
329
+ io.write @mz_hdr.pack
330
+ io.write @dos_stub
331
+ io.write @pe_hdr.pack
332
+ io.write @pe_hdr.ioh.DataDirectory.map(&:pack).join
113
333
 
114
- section_tbl_offset = f.tell # store offset for 2nd write of section table
115
- f.write section_table
334
+ section_tbl_offset = io.tell # store offset for 2nd write of section table
335
+ io.write section_table
116
336
 
337
+ align = @pe_hdr.ioh.FileAlignment
117
338
  @sections.each do |section|
118
- f.seek(align - (f.tell % align), IO::SEEK_CUR) if f.tell % align != 0
119
- section.hdr.PointerToRawData = f.tell # fix raw_ptr
120
- f.write(section.data)
339
+ io.seek(align - (io.tell % align), IO::SEEK_CUR) if io.tell % align != 0
340
+ section.hdr.PointerToRawData = io.tell # fix raw_ptr
341
+ io.write(section.data)
121
342
  end
122
343
 
123
- eof = f.tell
344
+ eof = io.tell
124
345
 
125
346
  # 2nd write of section table with correct raw_ptr's
126
- f.seek section_tbl_offset
127
- f.write section_table
347
+ io.seek section_tbl_offset
348
+ io.write section_table
128
349
 
129
- f.seek eof
350
+ io.seek eof
130
351
  end
352
+
353
+ alias :dump :export
354
+ end
355
+
356
+ ###################################################################
357
+
358
+ if $0 == __FILE__
359
+ require 'pp'
360
+ require 'zhexdump'
361
+
362
+ io = open ARGV.first
363
+ ldr = PEdump::Loader.load_minidump io
364
+
365
+ File.open(ARGV.first + ".exe", "wb") do |f|
366
+ ldr.sections[100..-1] = []
367
+ ldr.export f
368
+ end
369
+ exit
370
+
371
+ va = 0x3a10000+0xceb00-0x300+0x18c
372
+ ZHexdump.dump ldr[va, 0x200], :add => va
373
+ exit
374
+
375
+ #puts
376
+ #ZHexdump.dump ldr[x,0x100]
377
+
378
+ ldr.find_all(va, :limit => 100_000_000).each do |va0|
379
+ printf "[.] found at VA=%x\n", va0
380
+ 5.times do |i|
381
+ puts
382
+ va = ldr.dw(va0+i*4)
383
+ ZHexdump.dump ldr[va,0x30], :add => va if va != 0
384
+ end
385
+ end
386
+
387
+ puts "---"
388
+ ldr.find_all(0x3dff970, :limit => 100_000_000).each do |va|
389
+ ldr[va,0x20].hexdump
390
+ puts
391
+ end
392
+
131
393
  end