pedump 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/Gemfile +10 -6
- data/Gemfile.lock +27 -19
- data/README.md +37 -25
- data/Rakefile +45 -6
- data/VERSION +1 -1
- data/data/fs.txt +37 -1408
- data/data/jc-userdb.txt +14371 -0
- data/data/sig.bin +0 -0
- data/lib/pedump.rb +355 -618
- data/lib/pedump/cli.rb +214 -113
- data/lib/pedump/comparer.rb +147 -0
- data/lib/pedump/composite_io.rb +56 -0
- data/lib/pedump/core.rb +38 -0
- data/lib/pedump/core_ext/try.rb +57 -0
- data/lib/pedump/loader.rb +393 -0
- data/lib/pedump/loader/minidump.rb +187 -0
- data/lib/pedump/loader/section.rb +57 -0
- data/lib/pedump/logger.rb +67 -0
- data/lib/pedump/ne.rb +425 -0
- data/lib/pedump/ne/version_info.rb +171 -0
- data/lib/pedump/packer.rb +50 -2
- data/lib/pedump/pe.rb +121 -0
- data/lib/pedump/resources.rb +436 -0
- data/lib/pedump/security.rb +58 -0
- data/lib/pedump/sig_parser.rb +145 -24
- data/lib/pedump/tls.rb +17 -0
- data/lib/pedump/unpacker.rb +26 -0
- data/lib/pedump/unpacker/aspack.rb +858 -0
- data/lib/pedump/unpacker/upx.rb +13 -0
- data/lib/pedump/version.rb +1 -1
- data/lib/pedump/version_info.rb +15 -10
- data/misc/aspack/Makefile +3 -0
- data/misc/aspack/aspack_unlzx.c +92 -0
- data/misc/aspack/lzxdec.c +479 -0
- data/misc/aspack/lzxdec.h +56 -0
- data/misc/nedump.c +751 -0
- data/pedump.gemspec +75 -25
- data/samples/bad/68.exe +0 -0
- data/samples/bad/data_dir_15_entries.exe +0 -0
- data/spec/65535sects_spec.rb +8 -0
- data/spec/bad_imports_spec.rb +20 -0
- data/spec/bad_samples_spec.rb +13 -0
- data/spec/composite_io_spec.rb +122 -0
- data/spec/data/calc.exe_sections.yml +49 -0
- data/spec/data/data_dir_15_entries.exe_sections.yml +95 -0
- data/spec/dllord_spec.rb +21 -0
- data/spec/foldedhdr_spec.rb +28 -0
- data/spec/imports_badterm_spec.rb +52 -0
- data/spec/imports_vterm_spec.rb +52 -0
- data/spec/loader/names_spec.rb +24 -0
- data/spec/loader/va_spec.rb +44 -0
- data/spec/manyimportsW7_spec.rb +22 -0
- data/spec/ne_spec.rb +125 -0
- data/spec/packer_spec.rb +17 -0
- data/spec/pe_spec.rb +67 -0
- data/spec/pedump_spec.rb +16 -4
- data/spec/sections_spec.rb +11 -0
- data/spec/sig_all_packers_spec.rb +15 -5
- data/spec/sig_spec.rb +6 -1
- data/spec/spec_helper.rb +15 -3
- data/spec/support/samples.rb +24 -0
- data/spec/unpackers/aspack_spec.rb +69 -0
- data/spec/unpackers/find_spec.rb +21 -0
- data/spec/virtsectblXP_spec.rb +12 -0
- data/tmp/.keep +0 -0
- metadata +146 -35
- data/README.md.tpl +0 -90
- data/samples/calc.7z +0 -0
- data/samples/zlib.dll +0 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'pedump'
|
2
|
+
require 'pedump/loader'
|
3
|
+
|
4
|
+
########################################################################
|
5
|
+
# comparing 2 binaries
|
6
|
+
########################################################################
|
7
|
+
|
8
|
+
class PEdump::Comparer
|
9
|
+
attr_accessor :verbose
|
10
|
+
attr_accessor :ignored_data_dirs, :ignored_sections
|
11
|
+
|
12
|
+
METHODS = [:sections, :data_dirs, :imports, :resources, :pe_hdr]
|
13
|
+
|
14
|
+
def initialize ldr1, ldr2
|
15
|
+
@ldr1,@ldr2 = ldr1,ldr2
|
16
|
+
@ignored_data_dirs = []
|
17
|
+
@ignored_sections = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def equal?
|
21
|
+
METHODS.map{ |m| send("cmp_#{m}") }.uniq == [true]
|
22
|
+
end
|
23
|
+
|
24
|
+
def diff
|
25
|
+
METHODS.map{ |m| send("cmp_#{m}") ? nil : m }.compact
|
26
|
+
end
|
27
|
+
|
28
|
+
def cmp_pe_hdr
|
29
|
+
@ldr1.pe.ioh.AddressOfEntryPoint == @ldr2.pe.ioh.AddressOfEntryPoint &&
|
30
|
+
@ldr1.pe.ioh.ImageBase == @ldr2.pe.ioh.ImageBase
|
31
|
+
end
|
32
|
+
|
33
|
+
def cmp_resources
|
34
|
+
PEdump.quiet do
|
35
|
+
#@ldr1.pedump.resources == @ldr2.pedump.resources
|
36
|
+
@ldr1.pedump.resources.each_with_index do |r1,idx|
|
37
|
+
r2 = @ldr2.pedump.resources[idx]
|
38
|
+
if (r1.to_a - [r1.file_offset]) != (r2.to_a - [r2.file_offset])
|
39
|
+
p r1
|
40
|
+
p r2
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def cmp_sections
|
49
|
+
r = true
|
50
|
+
@ldr1.sections.each_with_index do |s1,idx|
|
51
|
+
next if @ignored_sections.include?(s1.name)
|
52
|
+
s2 = @ldr2.sections[idx]
|
53
|
+
|
54
|
+
if !s2
|
55
|
+
r = false
|
56
|
+
printf "[!] extra section %-12s in %s\n".red, s1.name.inspect, f1
|
57
|
+
elsif s1.data == s2.data
|
58
|
+
printf "[.] section: %s == %s\n".green, s1.name, s2.name if @verbose
|
59
|
+
else
|
60
|
+
r = false
|
61
|
+
printf "[!] section: %s != %s\n".red, s1.name, s2.name
|
62
|
+
self.class.cmp_ios *[s1,s2].map{ |section| StringIO.new(section.data) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
r
|
66
|
+
end
|
67
|
+
|
68
|
+
def cmp_data_dirs
|
69
|
+
r = true
|
70
|
+
@ldr1.pe.ioh.DataDirectory.each_with_index do |d1,idx|
|
71
|
+
break if idx == 15
|
72
|
+
d2 = @ldr2.pe.ioh.DataDirectory[idx]
|
73
|
+
|
74
|
+
case idx
|
75
|
+
when PEdump::IMAGE_DATA_DIRECTORY::BASERELOC
|
76
|
+
# total 8-byte size relocs == no relocs at all
|
77
|
+
next if [d1.va, d2.va].min == 0 && [d1.size, d2.size].max == 8
|
78
|
+
end
|
79
|
+
|
80
|
+
next if @ignored_data_dirs.include?(idx)
|
81
|
+
|
82
|
+
if d1.va != d2.va && d1.size != d2.size
|
83
|
+
r = false
|
84
|
+
printf "[!] data_dir: %-12s: SIZE & VA: %6x %6x | %6x %6x\n".red, d1.type,
|
85
|
+
d1.va, d1.size, d2.va, d2.size
|
86
|
+
elsif d1.va != d2.va
|
87
|
+
r = false
|
88
|
+
printf "[!] data_dir: %-12s: VA : %x != %x\n".red, d1.type, d1.va, d2.va
|
89
|
+
elsif d1.size != d2.size
|
90
|
+
r = false
|
91
|
+
printf "[!] data_dir: %-12s: SIZE : %x != %x\n".red, d1.type, d1.size, d2.size
|
92
|
+
end
|
93
|
+
end
|
94
|
+
r
|
95
|
+
end
|
96
|
+
|
97
|
+
def cmp_imports
|
98
|
+
@ldr1.pedump.imports.each_with_index do |iid1,idx|
|
99
|
+
iid2 = @ldr2.pedump.imports[idx]
|
100
|
+
if iid1 != iid2
|
101
|
+
puts "[!] diff imports".red
|
102
|
+
return false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
class << self
|
109
|
+
# arguments can be:
|
110
|
+
# a) filenames
|
111
|
+
# b) IO instances
|
112
|
+
# c) PEdump::Loader instances
|
113
|
+
def cmp *args
|
114
|
+
handles = []
|
115
|
+
if args.all?{|x| x.is_a?(String)}
|
116
|
+
handles = args.map{|x| File.open(x,"rb")}
|
117
|
+
_cmp(*handles.map{|h| PEdump::Loader.new(h)})
|
118
|
+
else
|
119
|
+
_cmp(*args)
|
120
|
+
end
|
121
|
+
ensure
|
122
|
+
handles.each(&:close)
|
123
|
+
end
|
124
|
+
|
125
|
+
# each arg is a PEdump::Loader
|
126
|
+
def _cmp ldr1, ldr2
|
127
|
+
new(ldr1, ldr2).equal?
|
128
|
+
end
|
129
|
+
|
130
|
+
def cmp_ios *ios
|
131
|
+
ndiff = 0
|
132
|
+
while !ios.any?(&:eof)
|
133
|
+
bytes = ios.map(&:readbyte)
|
134
|
+
if bytes.uniq.size > 1
|
135
|
+
ndiff += 1
|
136
|
+
printf ("\t%08x:"+" %02x"*ios.size).yellow+"\n", ios[0].pos-1, *bytes
|
137
|
+
if ndiff >= 5
|
138
|
+
puts "\t...".yellow
|
139
|
+
break
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
puts if ndiff > 0
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class PEdump
|
2
|
+
class CompositeIO
|
3
|
+
def initialize(*ios)
|
4
|
+
@ios = ios.flatten
|
5
|
+
@starts = ios.map(&:tell) # respect current position of each IO
|
6
|
+
@pos = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def read(amount = nil, buf = nil)
|
10
|
+
buf ||= ''; buf1 = ''
|
11
|
+
|
12
|
+
# truncates buffer to zero length if nothing read
|
13
|
+
@ios.first.read(amount,buf)
|
14
|
+
|
15
|
+
@ios[1..-1].each do |io|
|
16
|
+
break if amount && buf.size >= amount
|
17
|
+
io.read(amount ? (amount-buf.size) : nil, buf1)
|
18
|
+
buf << buf1
|
19
|
+
end
|
20
|
+
|
21
|
+
@pos += buf.size
|
22
|
+
|
23
|
+
buf.size > 0 ? buf : (amount ? nil : buf )
|
24
|
+
end
|
25
|
+
|
26
|
+
def tell
|
27
|
+
@pos
|
28
|
+
end
|
29
|
+
|
30
|
+
def seek pos
|
31
|
+
@pos = pos
|
32
|
+
@ios.each_with_index do |io,idx|
|
33
|
+
if pos > 0
|
34
|
+
sz = io.size-@starts[idx]
|
35
|
+
io.seek( @starts[idx] + (pos < sz ? pos : sz) )
|
36
|
+
pos -= sz
|
37
|
+
else
|
38
|
+
# seek all remaining IOs to 0
|
39
|
+
io.seek @starts[idx]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def rewind
|
45
|
+
seek(0)
|
46
|
+
end
|
47
|
+
|
48
|
+
def size
|
49
|
+
@ios.map(&:size).inject(&:+)
|
50
|
+
end
|
51
|
+
|
52
|
+
def eof?
|
53
|
+
@ios.all?(&:eof?)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/pedump/core.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'pedump/version'
|
3
|
+
require 'pedump/logger'
|
4
|
+
|
5
|
+
class String
|
6
|
+
def xor x
|
7
|
+
if x.is_a?(String)
|
8
|
+
r = ''
|
9
|
+
j = 0
|
10
|
+
0.upto(self.size-1) do |i|
|
11
|
+
r << (self[i].ord^x[j].ord).chr
|
12
|
+
j+=1
|
13
|
+
j=0 if j>= x.size
|
14
|
+
end
|
15
|
+
r
|
16
|
+
else
|
17
|
+
r = ''
|
18
|
+
0.upto(self.size-1) do |i|
|
19
|
+
r << (self[i].ord^x).chr
|
20
|
+
end
|
21
|
+
r
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class File
|
27
|
+
def checked_seek newpos
|
28
|
+
@file_range ||= (0..size)
|
29
|
+
@file_range.include?(newpos) && (seek(newpos) || true)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class PEdump
|
34
|
+
class << self
|
35
|
+
def logger; @@logger; end
|
36
|
+
def logger= l; @@logger=l; end
|
37
|
+
end
|
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
|
@@ -0,0 +1,393 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pedump'
|
4
|
+
require 'stringio'
|
5
|
+
require 'pedump/loader/section'
|
6
|
+
require 'pedump/loader/minidump'
|
7
|
+
|
8
|
+
# This class is kinda Virtual Machine that mimics executable loading as real OS does.
|
9
|
+
# Can be used for unpacking, emulating, reversing, ...
|
10
|
+
class PEdump::Loader
|
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
|
15
|
+
|
16
|
+
# shortcuts
|
17
|
+
alias :pe :pe_hdr
|
18
|
+
def ep; @pe_hdr.ioh.AddressOfEntryPoint; end
|
19
|
+
def ep= v; @pe_hdr.ioh.AddressOfEntryPoint=v; end
|
20
|
+
|
21
|
+
########################################################################
|
22
|
+
# constructors
|
23
|
+
########################################################################
|
24
|
+
|
25
|
+
def initialize io = nil, params = {}
|
26
|
+
@pedump = PEdump.new(io, params)
|
27
|
+
if io
|
28
|
+
@mz_hdr = @pedump.mz
|
29
|
+
@dos_stub = @pedump.dos_stub
|
30
|
+
@pe_hdr = @pedump.pe
|
31
|
+
@image_base = params[:image_base] || @pe_hdr.try(:ioh).try(:ImageBase) || 0
|
32
|
+
load_sections @pedump.sections, io
|
33
|
+
end
|
34
|
+
@find_limit = params[:find_limit] || DEFAULT_FIND_LIMIT
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_sections section_hdrs, f = nil
|
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
|
43
|
+
if f.respond_to?(:seek) && f.respond_to?(:read)
|
44
|
+
#
|
45
|
+
# converted to deferred loading
|
46
|
+
#
|
47
|
+
# section_hdrs.each_with_index do |sect_hdr, idx|
|
48
|
+
# f.seek sect_hdr.PointerToRawData
|
49
|
+
# @sections[idx].data = f.read(sect_hdr.SizeOfRawData)
|
50
|
+
# end
|
51
|
+
elsif f
|
52
|
+
raise "invalid 2nd arg: #{f.inspect}"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
raise "invalid arg: #{section_hdrs.inspect}"
|
56
|
+
end
|
57
|
+
end
|
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
|
+
|
80
|
+
########################################################################
|
81
|
+
# VA conversion
|
82
|
+
########################################################################
|
83
|
+
|
84
|
+
# VA to section
|
85
|
+
def va2section va
|
86
|
+
@sections.find{ |x| x.range.include?(va) }
|
87
|
+
end
|
88
|
+
|
89
|
+
# RVA (Relative VA) to section
|
90
|
+
def rva2section rva
|
91
|
+
va2section( rva + @image_base )
|
92
|
+
end
|
93
|
+
|
94
|
+
def va2stream va
|
95
|
+
return nil unless section = va2section(va)
|
96
|
+
StringIO.new(section.data).tap do |io|
|
97
|
+
io.seek va-section.va
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def rva2stream rva
|
102
|
+
va2stream( rva + @image_base )
|
103
|
+
end
|
104
|
+
|
105
|
+
########################################################################
|
106
|
+
# virtual memory read/write
|
107
|
+
########################################################################
|
108
|
+
|
109
|
+
# read arbitrary string
|
110
|
+
def [] va, size, params = {}
|
111
|
+
section = va2section(va)
|
112
|
+
raise "no section for va=0x#{va.to_s 16}" unless section
|
113
|
+
offset = va - section.va
|
114
|
+
raise "negative offset #{offset}" if offset < 0
|
115
|
+
r = section.data[offset,size]
|
116
|
+
return nil if r.nil?
|
117
|
+
if r.size < size && params.fetch(:zerofill, true)
|
118
|
+
# append some empty data
|
119
|
+
r << ("\x00".force_encoding('binary')) * (size - r.size)
|
120
|
+
end
|
121
|
+
r
|
122
|
+
end
|
123
|
+
|
124
|
+
# write arbitrary string
|
125
|
+
def []= va, size, data
|
126
|
+
raise "data.size != size" if data.size != size
|
127
|
+
section = va2section(va)
|
128
|
+
raise "no section for va=0x#{va.to_s 16}" unless section
|
129
|
+
offset = va - section.va
|
130
|
+
raise "negative offset #{offset}" if offset < 0
|
131
|
+
if section.data.size < offset
|
132
|
+
# append some empty data
|
133
|
+
section.data << ("\x00".force_encoding('binary') * (offset-section.data.size))
|
134
|
+
end
|
135
|
+
section.data[offset, data.size] = data
|
136
|
+
end
|
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
|
+
|
280
|
+
########################################################################
|
281
|
+
# generating PE binary
|
282
|
+
########################################################################
|
283
|
+
|
284
|
+
def section_table
|
285
|
+
@sections.map do |section|
|
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
|
292
|
+
section.hdr.pack
|
293
|
+
end.join
|
294
|
+
end
|
295
|
+
|
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
|
324
|
+
|
325
|
+
mz_size = @mz_hdr.pack.size
|
326
|
+
raise "odd mz_size #{mz_size}" if mz_size % 0x10 != 0
|
327
|
+
@mz_hdr.header_paragraphs = mz_size / 0x10 # offset of dos_stub
|
328
|
+
@mz_hdr.lfanew = mz_size + @dos_stub.size # offset of PE hdr
|
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
|
333
|
+
|
334
|
+
section_tbl_offset = io.tell # store offset for 2nd write of section table
|
335
|
+
io.write section_table
|
336
|
+
|
337
|
+
align = @pe_hdr.ioh.FileAlignment
|
338
|
+
@sections.each do |section|
|
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)
|
342
|
+
end
|
343
|
+
|
344
|
+
eof = io.tell
|
345
|
+
|
346
|
+
# 2nd write of section table with correct raw_ptr's
|
347
|
+
io.seek section_tbl_offset
|
348
|
+
io.write section_table
|
349
|
+
|
350
|
+
io.seek eof
|
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
|
+
|
393
|
+
end
|