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 +2 -0
- data/Gemfile.lock +5 -1
- data/VERSION +1 -1
- data/lib/pedump.rb +21 -18
- data/lib/pedump/cli.rb +40 -46
- data/lib/pedump/core.rb +0 -47
- data/lib/pedump/core_ext/try.rb +57 -0
- data/lib/pedump/loader.rb +284 -22
- data/lib/pedump/loader/minidump.rb +187 -0
- data/lib/pedump/loader/section.rb +9 -3
- data/lib/pedump/ne.rb +8 -8
- data/lib/pedump/ne/version_info.rb +7 -7
- data/lib/pedump/pe.rb +2 -0
- data/lib/pedump/resources.rb +8 -8
- data/lib/pedump/security.rb +1 -1
- data/lib/pedump/tls.rb +2 -2
- data/lib/pedump/unpacker/aspack.rb +7 -2
- data/lib/pedump/version.rb +2 -2
- data/lib/pedump/version_info.rb +7 -7
- data/pedump.gemspec +12 -2
- data/spec/loader/names_spec.rb +24 -0
- data/spec/loader/va_spec.rb +44 -0
- metadata +67 -31
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.
|
data/Gemfile.lock
CHANGED
@@ -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.
|
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.
|
1
|
+
0.5.0
|
data/lib/pedump.rb
CHANGED
@@ -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 =
|
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 <
|
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 <
|
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 <
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
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
|
-
|
831
|
-
|
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
|
data/lib/pedump/cli.rb
CHANGED
@@ -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
|
-
|
144
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
797
|
+
data.hexdump
|
776
798
|
puts
|
777
799
|
puts "# dexored:"
|
778
|
-
|
800
|
+
data.dexor.hexdump
|
779
801
|
end
|
780
802
|
end
|
781
803
|
|
782
|
-
|
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
|
data/lib/pedump/core.rb
CHANGED
@@ -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
|
data/lib/pedump/loader.rb
CHANGED
@@ -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,
|
18
|
-
@pedump = PEdump.new(io,
|
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)
|
29
|
-
@sections = section_hdrs.map
|
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
|
-
|
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.
|
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
|
-
|
103
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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 =
|
115
|
-
|
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
|
-
|
119
|
-
section.hdr.PointerToRawData =
|
120
|
-
|
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 =
|
344
|
+
eof = io.tell
|
124
345
|
|
125
346
|
# 2nd write of section table with correct raw_ptr's
|
126
|
-
|
127
|
-
|
347
|
+
io.seek section_tbl_offset
|
348
|
+
io.write section_table
|
128
349
|
|
129
|
-
|
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
|