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 +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
|