pedump 0.4.16 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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