pedump 0.5.3
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.
- checksums.yaml +7 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +20 -0
- data/README.md +410 -0
- data/Rakefile +179 -0
- data/VERSION +1 -0
- data/bin/pedump +7 -0
- data/data/fs.txt +224 -0
- data/data/jc-userdb.txt +14371 -0
- data/data/sig.bin +0 -0
- data/data/signatures.txt +678 -0
- data/data/userdb.txt +14083 -0
- data/lib/pedump.rb +868 -0
- data/lib/pedump/cli.rb +804 -0
- 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 +351 -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 +173 -0
- 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 +507 -0
- 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 +10 -0
- data/lib/pedump/version_info.rb +171 -0
- 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 +109 -0
- metadata +227 -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 = "__imp_" + (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
|