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
data/data/sig.bin
CHANGED
Binary file
|
data/lib/pedump.rb
CHANGED
@@ -1,115 +1,47 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require '
|
3
|
-
require '
|
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
|
+
|
10
|
+
require 'pedump/core'
|
11
|
+
require 'pedump/pe'
|
12
|
+
require 'pedump/resources'
|
13
|
+
require 'pedump/version_info'
|
14
|
+
require 'pedump/tls'
|
15
|
+
require 'pedump/security'
|
16
|
+
require 'pedump/packer'
|
17
|
+
require 'pedump/ne'
|
18
|
+
require 'pedump/ne/version_info'
|
4
19
|
|
5
20
|
# pedump.rb by zed_0xff
|
6
21
|
#
|
7
22
|
# http://zed.0xff.me
|
8
23
|
# http://github.com/zed-0xff
|
9
24
|
|
10
|
-
class String
|
11
|
-
def xor x
|
12
|
-
if x.is_a?(String)
|
13
|
-
r = ''
|
14
|
-
j = 0
|
15
|
-
0.upto(self.size-1) do |i|
|
16
|
-
r << (self[i].ord^x[j].ord).chr
|
17
|
-
j+=1
|
18
|
-
j=0 if j>= x.size
|
19
|
-
end
|
20
|
-
r
|
21
|
-
else
|
22
|
-
r = ''
|
23
|
-
0.upto(self.size-1) do |i|
|
24
|
-
r << (self[i].ord^x).chr
|
25
|
-
end
|
26
|
-
r
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class File
|
32
|
-
def checked_seek newpos
|
33
|
-
@file_range ||= (0..size)
|
34
|
-
@file_range.include?(newpos) && (seek(newpos) || true)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
25
|
class PEdump
|
39
|
-
attr_accessor :fname, :logger, :force
|
26
|
+
attr_accessor :fname, :logger, :force, :io
|
40
27
|
|
41
|
-
VERSION
|
28
|
+
VERSION = Version::STRING
|
29
|
+
MAX_ERRORS = 100
|
42
30
|
|
43
31
|
@@logger = nil
|
44
32
|
|
45
|
-
def initialize
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
class Logger < ::Logger
|
52
|
-
def initialize *args
|
53
|
-
super
|
54
|
-
@formatter = proc do |severity,_,_,msg|
|
55
|
-
# quick and dirty way to remove duplicate messages
|
56
|
-
if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
|
57
|
-
''
|
58
|
-
else
|
59
|
-
@prevmsg = msg
|
60
|
-
"#{msg}\n"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
@level = Logger::WARN
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
module Readable
|
68
|
-
def read file, size = nil
|
69
|
-
size ||= const_get 'SIZE'
|
70
|
-
data = file.read(size).to_s
|
71
|
-
if data.size < size && PEdump.logger
|
72
|
-
PEdump.logger.error "[!] #{self.to_s} want #{size} bytes, got #{data.size}"
|
73
|
-
end
|
74
|
-
new(*data.unpack(const_get('FORMAT')))
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class << self
|
79
|
-
def logger; @@logger; end
|
80
|
-
def logger= l; @@logger=l; end
|
81
|
-
|
82
|
-
def create_struct fmt, *args
|
83
|
-
size = fmt.scan(/([a-z])(\d*)/i).map do |f,len|
|
84
|
-
[len.to_i, 1].max *
|
85
|
-
case f
|
86
|
-
when /[aAC]/ then 1
|
87
|
-
when 'v' then 2
|
88
|
-
when 'V' then 4
|
89
|
-
when 'Q' then 8
|
90
|
-
else raise "unknown fmt #{f.inspect}"
|
91
|
-
end
|
92
|
-
end.inject(&:+)
|
93
|
-
|
94
|
-
Struct.new( *args ).tap do |x|
|
95
|
-
x.const_set 'FORMAT', fmt
|
96
|
-
x.const_set 'SIZE', size
|
97
|
-
x.class_eval do
|
98
|
-
def pack
|
99
|
-
to_a.pack self.class.const_get('FORMAT')
|
100
|
-
end
|
101
|
-
def empty?
|
102
|
-
to_a.all?{ |t| t == 0 || t.nil? || t.to_s.tr("\x00","").empty? }
|
103
|
-
end
|
104
|
-
end
|
105
|
-
x.extend Readable
|
106
|
-
end
|
33
|
+
def initialize io = nil, params = {}
|
34
|
+
if io.is_a?(Hash)
|
35
|
+
@io, params = nil, io
|
36
|
+
else
|
37
|
+
@io = io
|
107
38
|
end
|
39
|
+
@force = params[:force]
|
40
|
+
@logger = @@logger = Logger.create(params)
|
108
41
|
end
|
109
42
|
|
110
|
-
|
111
43
|
# http://www.delorie.com/djgpp/doc/exe/
|
112
|
-
MZ =
|
44
|
+
MZ = IOStruct.new( "a2v13Qv2V6",
|
113
45
|
:signature,
|
114
46
|
:bytes_in_last_block,
|
115
47
|
:blocks_in_file,
|
@@ -135,24 +67,8 @@ class PEdump
|
|
135
67
|
:lfanew
|
136
68
|
)
|
137
69
|
|
138
|
-
class PE < Struct.new(
|
139
|
-
:signature, # "PE\x00\x00"
|
140
|
-
:image_file_header,
|
141
|
-
:image_optional_header,
|
142
|
-
:section_table
|
143
|
-
)
|
144
|
-
alias :ifh :image_file_header
|
145
|
-
alias :ioh :image_optional_header
|
146
|
-
def x64?
|
147
|
-
ifh && ifh.Machine == 0x8664
|
148
|
-
end
|
149
|
-
def dll?
|
150
|
-
ifh && ifh.flags.include?('DLL')
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
70
|
# http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
155
|
-
class IMAGE_FILE_HEADER <
|
71
|
+
class IMAGE_FILE_HEADER < IOStruct.new( 'v2V3v2',
|
156
72
|
:Machine, # w
|
157
73
|
:NumberOfSections, # w
|
158
74
|
:TimeDateStamp, # dw
|
@@ -183,10 +99,10 @@ class PEdump
|
|
183
99
|
0x8000 => 'BYTES_REVERSED_HI' # The bytes of the word are reversed. This flag is obsolete.
|
184
100
|
}
|
185
101
|
|
186
|
-
def initialize *args
|
187
|
-
super
|
188
|
-
self.TimeDateStamp = Time.at(self.TimeDateStamp)
|
189
|
-
end
|
102
|
+
# def initialize *args
|
103
|
+
# super
|
104
|
+
# self.TimeDateStamp = Time.at(self.TimeDateStamp).utc
|
105
|
+
# end
|
190
106
|
def flags
|
191
107
|
FLAGS.find_all{ |k,v| (self.Characteristics & k) != 0 }.map(&:last)
|
192
108
|
end
|
@@ -226,6 +142,7 @@ class PEdump
|
|
226
142
|
cFORMAT = self.const_get 'FORMAT'
|
227
143
|
size ||= cSIZE
|
228
144
|
PEdump.logger.warn "[?] unusual size of IMAGE_OPTIONAL_HEADER = #{size} (must be #{usual_size})" if size != usual_size
|
145
|
+
PEdump.logger.warn "[?] #{size-usual_size} spare bytes after IMAGE_OPTIONAL_HEADER" if size > usual_size
|
229
146
|
new(*file.read([size,cSIZE].min).to_s.unpack(cFORMAT)).tap do |ioh|
|
230
147
|
ioh.DataDirectory = []
|
231
148
|
|
@@ -240,6 +157,9 @@ class PEdump
|
|
240
157
|
ioh.DataDirectory.last.type = IMAGE_DATA_DIRECTORY::TYPES[idx]
|
241
158
|
end
|
242
159
|
#ioh.DataDirectory.pop while ioh.DataDirectory.last.empty?
|
160
|
+
|
161
|
+
# skip spare bytes, if any. XXX may contain suspicious data
|
162
|
+
file.seek(size-usual_size, IO::SEEK_CUR) if size > usual_size
|
243
163
|
end
|
244
164
|
end
|
245
165
|
end
|
@@ -251,7 +171,7 @@ class PEdump
|
|
251
171
|
end
|
252
172
|
|
253
173
|
# http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
254
|
-
class IMAGE_OPTIONAL_HEADER32 <
|
174
|
+
class IMAGE_OPTIONAL_HEADER32 < IOStruct.new( 'vC2V9v6V4v2V6',
|
255
175
|
:Magic, # w
|
256
176
|
:MajorLinkerVersion, :MinorLinkerVersion, # 2b
|
257
177
|
:SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, # 9dw
|
@@ -269,7 +189,7 @@ class PEdump
|
|
269
189
|
end
|
270
190
|
|
271
191
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=VS.85).aspx)
|
272
|
-
class IMAGE_OPTIONAL_HEADER64 <
|
192
|
+
class IMAGE_OPTIONAL_HEADER64 < IOStruct.new( 'vC2V5QV2v6V4v2Q4V2',
|
273
193
|
:Magic, # w
|
274
194
|
:MajorLinkerVersion, :MinorLinkerVersion, # 2b
|
275
195
|
:SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, :BaseOfCode, # 5dw
|
@@ -287,7 +207,7 @@ class PEdump
|
|
287
207
|
include IMAGE_OPTIONAL_HEADER
|
288
208
|
end
|
289
209
|
|
290
|
-
IMAGE_DATA_DIRECTORY =
|
210
|
+
IMAGE_DATA_DIRECTORY = IOStruct.new( "VV", :va, :size, :type )
|
291
211
|
IMAGE_DATA_DIRECTORY::TYPES =
|
292
212
|
%w'EXPORT IMPORT RESOURCE EXCEPTION SECURITY BASERELOC DEBUG ARCHITECTURE GLOBALPTR TLS LOAD_CONFIG
|
293
213
|
Bound_IAT IAT Delay_IAT CLR_Header'
|
@@ -295,7 +215,7 @@ class PEdump
|
|
295
215
|
IMAGE_DATA_DIRECTORY.const_set(type,idx)
|
296
216
|
end
|
297
217
|
|
298
|
-
IMAGE_SECTION_HEADER =
|
218
|
+
IMAGE_SECTION_HEADER = IOStruct.new( 'A8V6v2V',
|
299
219
|
:Name, # A8 6dw
|
300
220
|
:VirtualSize, :VirtualAddress, :SizeOfRawData, :PointerToRawData, :PointerToRelocations, :PointerToLinenumbers,
|
301
221
|
:NumberOfRelocations, :NumberOfLinenumbers, # 2w
|
@@ -303,6 +223,7 @@ class PEdump
|
|
303
223
|
)
|
304
224
|
class IMAGE_SECTION_HEADER
|
305
225
|
alias :flags :Characteristics
|
226
|
+
alias :va :VirtualAddress
|
306
227
|
def flags_desc
|
307
228
|
r = ''
|
308
229
|
f = self.flags.to_i
|
@@ -321,6 +242,10 @@ class PEdump
|
|
321
242
|
r << ' SHARED' if f & 0x10000000 > 0
|
322
243
|
r
|
323
244
|
end
|
245
|
+
|
246
|
+
def pack
|
247
|
+
to_a.pack FORMAT.tr('A','a') # pad names with NULL bytes on pack()
|
248
|
+
end
|
324
249
|
end
|
325
250
|
|
326
251
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=VS.85).aspx
|
@@ -377,11 +302,19 @@ class PEdump
|
|
377
302
|
@logger = @@logger = l
|
378
303
|
end
|
379
304
|
|
380
|
-
def self.dump fname
|
381
|
-
new(fname).dump
|
305
|
+
def self.dump fname, params = {}
|
306
|
+
new(fname, params).dump
|
307
|
+
end
|
308
|
+
|
309
|
+
def self.quiet
|
310
|
+
oldlevel = @@logger.level
|
311
|
+
@@logger.level = ::Logger::FATAL
|
312
|
+
yield
|
313
|
+
ensure
|
314
|
+
@@logger.level = oldlevel
|
382
315
|
end
|
383
316
|
|
384
|
-
def mz f
|
317
|
+
def mz f=@io
|
385
318
|
@mz ||= f && MZ.read(f).tap do |mz|
|
386
319
|
if mz.signature != 'MZ' && mz.signature != 'ZM'
|
387
320
|
if @force
|
@@ -394,13 +327,13 @@ class PEdump
|
|
394
327
|
end
|
395
328
|
end
|
396
329
|
|
397
|
-
def dos_stub f
|
330
|
+
def dos_stub f=@io
|
398
331
|
@dos_stub ||=
|
399
332
|
begin
|
400
333
|
return nil unless mz = mz(f)
|
401
334
|
dos_stub_offset = mz.header_paragraphs.to_i * 0x10
|
402
335
|
dos_stub_size = mz.lfanew.to_i - dos_stub_offset
|
403
|
-
if dos_stub_offset
|
336
|
+
if dos_stub_offset < 0
|
404
337
|
logger.warn "[?] invalid DOS stub offset #{dos_stub_offset}"
|
405
338
|
nil
|
406
339
|
elsif f && dos_stub_offset > f.size
|
@@ -416,6 +349,7 @@ class PEdump
|
|
416
349
|
# no open file, it's ok
|
417
350
|
nil
|
418
351
|
else
|
352
|
+
return nil if dos_stub_size == MZ::SIZE && dos_stub_offset == 0
|
419
353
|
if dos_stub_size > 0x1000
|
420
354
|
logger.warn "[?] DOS stub size too big (#{dos_stub_size}), limiting to 0x1000"
|
421
355
|
dos_stub_size = 0x1000
|
@@ -433,94 +367,102 @@ class PEdump
|
|
433
367
|
end
|
434
368
|
end
|
435
369
|
|
436
|
-
def rich_hdr f
|
370
|
+
def rich_hdr f=@io
|
437
371
|
dos_stub(f) && @rich_hdr
|
438
372
|
end
|
439
373
|
alias :rich_header :rich_hdr
|
440
374
|
alias :rich :rich_hdr
|
441
375
|
|
442
|
-
def
|
443
|
-
|
444
|
-
begin
|
445
|
-
pe_offset = mz(f) && mz(f).lfanew
|
446
|
-
if pe_offset.nil?
|
447
|
-
logger.fatal "[!] NULL PE offset (e_lfanew). cannot continue."
|
448
|
-
nil
|
449
|
-
elsif pe_offset > f.size
|
450
|
-
logger.fatal "[!] PE offset beyond EOF. cannot continue."
|
451
|
-
nil
|
452
|
-
else
|
453
|
-
f.seek pe_offset
|
454
|
-
pe_sig = f.read 4
|
455
|
-
logger.error "[!] 'NE' format is not supported!" if pe_sig == "NE\x00\x00"
|
456
|
-
if pe_sig != "PE\x00\x00"
|
457
|
-
if @force
|
458
|
-
logger.warn "[?] no PE signature (want: 'PE\\x00\\x00', got: #{pe_sig.inspect})"
|
459
|
-
else
|
460
|
-
logger.error "[?] no PE signature (want: 'PE\\x00\\x00', got: #{pe_sig.inspect}). (not forced)"
|
461
|
-
return nil
|
462
|
-
end
|
463
|
-
end
|
464
|
-
PE.new(pe_sig).tap do |pe|
|
465
|
-
pe.image_file_header = IMAGE_FILE_HEADER.read(f)
|
466
|
-
if pe.ifh.SizeOfOptionalHeader > 0
|
467
|
-
if pe.x64?
|
468
|
-
pe.image_optional_header = IMAGE_OPTIONAL_HEADER64.read(f, pe.ifh.SizeOfOptionalHeader)
|
469
|
-
else
|
470
|
-
pe.image_optional_header = IMAGE_OPTIONAL_HEADER32.read(f, pe.ifh.SizeOfOptionalHeader)
|
471
|
-
end
|
472
|
-
end
|
376
|
+
def va2file va, h={}
|
377
|
+
return nil if va.nil?
|
473
378
|
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
logger.warn "[!] too many sections (#{pe.ifh.NumberOfSections}). not forced, reading first 32"
|
479
|
-
nToRead = 32
|
480
|
-
end
|
481
|
-
end
|
482
|
-
pe.section_table = nToRead.times.map do
|
483
|
-
IMAGE_SECTION_HEADER.read(f)
|
484
|
-
end
|
485
|
-
end
|
486
|
-
end
|
379
|
+
sections.each do |s|
|
380
|
+
if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
|
381
|
+
offset = va - s.VirtualAddress
|
382
|
+
return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
|
487
383
|
end
|
488
|
-
|
384
|
+
end
|
385
|
+
|
386
|
+
# not found with regular search. assume any of VirtualSize was 0, and try with RawSize
|
387
|
+
sections.each do |s|
|
388
|
+
if (s.VirtualAddress...(s.VirtualAddress+s.SizeOfRawData)).include?(va)
|
389
|
+
offset = va - s.VirtualAddress
|
390
|
+
return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# still not found, bad/zero VirtualSizes & RawSizes ?
|
489
395
|
|
490
|
-
|
491
|
-
|
396
|
+
# a special case - PE without sections
|
397
|
+
return va if sections.empty?
|
398
|
+
|
399
|
+
# check if only one section
|
400
|
+
if sections.size == 1 || sections.all?{ |s| s.VirtualAddress.to_i == 0 }
|
401
|
+
s = sections.first
|
402
|
+
offset = va - s.VirtualAddress
|
403
|
+
return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
|
404
|
+
#return va - s.VirtualAddress + s.PointerToRawData
|
405
|
+
end
|
406
|
+
|
407
|
+
# TODO: not all VirtualAdresses == 0 case
|
408
|
+
|
409
|
+
if h[:quiet]
|
410
|
+
logger.debug "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)} (quiet=true)"
|
411
|
+
else
|
412
|
+
logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
|
413
|
+
end
|
414
|
+
nil
|
492
415
|
end
|
493
416
|
|
494
417
|
# OPTIONAL: assigns @mz, @rich_hdr, @pe, etc
|
495
|
-
def dump f
|
496
|
-
|
418
|
+
def dump f=@io
|
419
|
+
if f.is_a?(String)
|
420
|
+
File.open(f,'rb'){ |f| _dump_handle(f) }
|
421
|
+
elsif f.is_a?(::IO)
|
422
|
+
_dump_handle f
|
423
|
+
elsif @io
|
424
|
+
_dump_handle @io
|
425
|
+
end
|
497
426
|
self
|
498
427
|
end
|
499
428
|
|
500
429
|
def _dump_handle h
|
501
|
-
|
502
|
-
|
503
|
-
|
430
|
+
return unless pe(h) # also calls mz(h)
|
431
|
+
rich_hdr h
|
432
|
+
resources h
|
433
|
+
imports h # also calls tls(h)
|
504
434
|
exports h
|
505
|
-
packer
|
435
|
+
packer h
|
506
436
|
end
|
507
437
|
|
508
|
-
def data_directory f
|
438
|
+
def data_directory f=@io
|
509
439
|
pe(f) && pe.ioh && pe.ioh.DataDirectory
|
510
440
|
end
|
511
441
|
|
512
|
-
def sections f
|
513
|
-
pe(f)
|
442
|
+
def sections f=@io
|
443
|
+
if pe(f)
|
444
|
+
pe.section_table
|
445
|
+
elsif ne(f)
|
446
|
+
ne.segments
|
447
|
+
end
|
514
448
|
end
|
515
449
|
alias :section_table :sections
|
516
450
|
|
451
|
+
def ne?
|
452
|
+
@pe ? false : (@ne ? true : (pe ? false : (ne ? true : false)))
|
453
|
+
end
|
454
|
+
|
455
|
+
def pe?
|
456
|
+
@pe ? true : (@ne ? false : (pe ? true : false ))
|
457
|
+
end
|
458
|
+
|
517
459
|
##############################################################################
|
518
460
|
# imports
|
519
461
|
##############################################################################
|
520
462
|
|
521
463
|
# http://sandsprite.com/CodeStuff/Understanding_imports.html
|
522
464
|
# http://stackoverflow.com/questions/5631317/import-table-it-vs-import-address-table-iat
|
523
|
-
IMAGE_IMPORT_DESCRIPTOR =
|
465
|
+
IMAGE_IMPORT_DESCRIPTOR = IOStruct.new 'V5',
|
524
466
|
:OriginalFirstThunk,
|
525
467
|
:TimeDateStamp,
|
526
468
|
:ForwarderChain,
|
@@ -531,58 +473,134 @@ class PEdump
|
|
531
473
|
:original_first_thunk,
|
532
474
|
:first_thunk
|
533
475
|
|
534
|
-
ImportedFunction
|
476
|
+
class ImportedFunction < Struct.new(:hint, :name, :ordinal, :va, :module_name)
|
477
|
+
# def == x
|
478
|
+
# self.hint == x.hint && self.name == x.name && self.ordinal == x.ordinal
|
479
|
+
# end
|
480
|
+
# def <=> x
|
481
|
+
# self.to_a[0..-2] <=> x.to_a[0..-2]
|
482
|
+
# end
|
483
|
+
|
484
|
+
# magic to be able to easy merge :first_thunk & :original_first_thunk arrays
|
485
|
+
# (keeping va different)
|
486
|
+
def hash
|
487
|
+
[hint,name,ordinal,module_name].hash
|
488
|
+
end
|
489
|
+
def eql? x
|
490
|
+
self.hint == x.hint && self.name == x.name && self.ordinal == x.ordinal &&
|
491
|
+
self.module_name == x.module_name
|
492
|
+
end
|
493
|
+
end
|
535
494
|
|
536
|
-
def imports f
|
495
|
+
def imports f=@io
|
496
|
+
if pe(f)
|
497
|
+
pe_imports(f)
|
498
|
+
elsif ne(f)
|
499
|
+
ne(f).imports
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def pe_imports f=@io
|
537
504
|
return @imports if @imports
|
538
505
|
return nil unless pe(f) && pe(f).ioh && f
|
539
506
|
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT]
|
540
507
|
return [] if !dir || (dir.va == 0 && dir.size == 0)
|
541
|
-
|
542
|
-
file_offset = va2file(va)
|
508
|
+
file_offset = va2file(dir.va)
|
543
509
|
return nil unless file_offset
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
510
|
+
|
511
|
+
# scan TLS first, to catch many fake imports trick from
|
512
|
+
# http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
|
513
|
+
tls_aoi = nil
|
514
|
+
if (tls = tls(f)) && tls.any?
|
515
|
+
tls_aoi = tls.first.AddressOfIndex.to_i - @pe.ioh.ImageBase.to_i
|
516
|
+
tls_aoi = tls_aoi > 0 ? va2file(tls_aoi) : nil
|
517
|
+
end
|
518
|
+
|
519
|
+
r = []; t = nil
|
520
|
+
if f.checked_seek(file_offset)
|
521
|
+
while true
|
522
|
+
if tls_aoi && tls_aoi == file_offset+16
|
523
|
+
# catched the neat trick! :)
|
524
|
+
# f.tell + 12 = offset of 'FirstThunk' field from start of IMAGE_IMPORT_DESCRIPTOR structure
|
525
|
+
logger.warn "[!] catched the 'imports terminator in TLS trick'"
|
526
|
+
# http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
|
527
|
+
break
|
528
|
+
end
|
529
|
+
t=IMAGE_IMPORT_DESCRIPTOR.read(f)
|
530
|
+
break if t.Name.to_i == 0 # also catches EOF
|
531
|
+
r << t
|
532
|
+
file_offset += IMAGE_IMPORT_DESCRIPTOR::SIZE
|
533
|
+
end
|
534
|
+
else
|
535
|
+
logger.warn "[?] imports info beyond EOF"
|
548
536
|
end
|
537
|
+
|
538
|
+
logger.warn "[?] non-empty last IMAGE_IMPORT_DESCRIPTOR: #{t.inspect}" if t && !t.empty?
|
549
539
|
@imports = r.each do |x|
|
550
|
-
if x.Name.to_i != 0 && (
|
551
|
-
|
552
|
-
|
540
|
+
if x.Name.to_i != 0 && (ofs = va2file(x.Name))
|
541
|
+
begin
|
542
|
+
f.seek ofs
|
543
|
+
rescue
|
544
|
+
logger.warn "[?] cannot seek to #{ofs} (VA=0x#{x.Name.to_i.to_s(16)} for reading imports, skipped"
|
545
|
+
next
|
546
|
+
end
|
547
|
+
x.module_name = f.gets("\x00").to_s.chomp("\x00")
|
553
548
|
end
|
554
549
|
[:original_first_thunk, :first_thunk].each do |tbl|
|
555
550
|
camel = tbl.capitalize.to_s.gsub(/_./){ |char| char[1..-1].upcase}
|
556
|
-
if x[camel].to_i != 0 && (
|
557
|
-
f.seek va
|
551
|
+
if x[camel].to_i != 0 && (ofs = va2file(x[camel])) && f.checked_seek(ofs)
|
558
552
|
x[tbl] ||= []
|
559
553
|
if pe.x64?
|
560
|
-
x[tbl] << t while (t = f.read(8).unpack('Q').first) != 0
|
554
|
+
x[tbl] << t while (t = f.read(8).to_s.unpack('Q').first).to_i != 0
|
561
555
|
else
|
562
|
-
x[tbl] << t while (t = f.read(4).unpack('V').first) != 0
|
556
|
+
x[tbl] << t while (t = f.read(4).to_s.unpack('V').first).to_i != 0
|
563
557
|
end
|
564
558
|
end
|
565
559
|
cache = {}
|
566
560
|
bits = pe.x64? ? 64 : 32
|
561
|
+
mask = 2**(bits-1)
|
562
|
+
idx = -1
|
567
563
|
x[tbl] && x[tbl].map! do |t|
|
564
|
+
idx += 1
|
565
|
+
va = x[camel].to_i + idx*4
|
568
566
|
cache[t] ||=
|
569
|
-
if t &
|
570
|
-
ImportedFunction.new(nil,nil,t & (
|
571
|
-
elsif
|
572
|
-
f.
|
573
|
-
|
574
|
-
|
567
|
+
if t & mask > 0 # 0x8000_0000(_0000_0000)
|
568
|
+
ImportedFunction.new(nil,nil,t & (mask-1),va) # 0x7fff_ffff(_ffff_ffff)
|
569
|
+
elsif ofs=va2file(t, :quiet => true)
|
570
|
+
if !f.checked_seek(ofs) || f.eof?
|
571
|
+
logger.warn "[?] import ofs 0x#{ofs.to_s(16)} VA=0x#{t.to_s(16)} beyond EOF"
|
572
|
+
nil
|
573
|
+
else
|
574
|
+
ImportedFunction.new(
|
575
|
+
f.read(2).unpack('v').first,
|
576
|
+
f.gets("\x00").chomp("\x00"),
|
577
|
+
nil,
|
578
|
+
va
|
579
|
+
)
|
580
|
+
end
|
581
|
+
elsif tbl == :original_first_thunk
|
582
|
+
# OriginalFirstThunk entries can not be invalid, show a warning msg
|
583
|
+
logger.warn "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
|
584
|
+
nil
|
585
|
+
elsif tbl == :first_thunk
|
586
|
+
# FirstThunk entries can be invalid, so `info` msg only
|
587
|
+
logger.info "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
|
575
588
|
nil
|
589
|
+
else
|
590
|
+
raise "You are not supposed to be here! O_o"
|
576
591
|
end
|
577
592
|
end
|
578
593
|
x[tbl] && x[tbl].compact!
|
579
594
|
end
|
580
595
|
if x.original_first_thunk && !x.first_thunk
|
581
|
-
logger.warn "[?] import table: empty FirstThunk
|
596
|
+
logger.warn "[?] import table: empty FirstThunk for #{x.module_name}"
|
582
597
|
elsif !x.original_first_thunk && x.first_thunk
|
583
|
-
logger.
|
584
|
-
elsif
|
585
|
-
|
598
|
+
logger.info "[?] import table: empty OriginalFirstThunk for #{x.module_name}"
|
599
|
+
elsif logger.debug?
|
600
|
+
# compare all but VAs
|
601
|
+
if x.original_first_thunk != x.first_thunk
|
602
|
+
logger.debug "[?] import table: OriginalFirstThunk != FirstThunk for #{x.module_name}"
|
603
|
+
end
|
586
604
|
end
|
587
605
|
end
|
588
606
|
end
|
@@ -592,464 +610,187 @@ class PEdump
|
|
592
610
|
##############################################################################
|
593
611
|
|
594
612
|
#http://msdn.microsoft.com/en-us/library/ms809762.aspx
|
595
|
-
IMAGE_EXPORT_DIRECTORY =
|
613
|
+
IMAGE_EXPORT_DIRECTORY = IOStruct.new 'V2v2V7',
|
596
614
|
:Characteristics,
|
597
615
|
:TimeDateStamp,
|
598
616
|
:MajorVersion, # These fields appear to be unused and are set to 0.
|
599
617
|
:MinorVersion, # These fields appear to be unused and are set to 0.
|
600
618
|
:Name,
|
601
619
|
:Base, # The starting ordinal number for exported functions
|
602
|
-
:NumberOfFunctions,
|
620
|
+
:NumberOfFunctions, # UNSIGNED!, perfectly valid when = 0xffff_ffff, see corkami/dllord.dll
|
603
621
|
:NumberOfNames,
|
604
622
|
:AddressOfFunctions,
|
605
623
|
:AddressOfNames,
|
606
624
|
:AddressOfNameOrdinals,
|
607
625
|
# manual:
|
608
|
-
:name, :entry_points, :names, :name_ordinals
|
626
|
+
:name, :entry_points, :names, :name_ordinals, :functions,
|
627
|
+
:description # NE only
|
609
628
|
|
610
|
-
|
629
|
+
ExportedFunction = Struct.new :name, :ord, :va, :file_offset
|
630
|
+
|
631
|
+
def exports f=@io
|
632
|
+
if pe(f)
|
633
|
+
pe_exports(f)
|
634
|
+
elsif ne(f)
|
635
|
+
ne(f).exports
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def pe_exports f=@io
|
611
640
|
return @exports if @exports
|
612
641
|
return nil unless pe(f) && pe(f).ioh && f
|
613
642
|
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT]
|
614
|
-
return
|
643
|
+
return nil if !dir || (dir.va == 0 && dir.size == 0)
|
615
644
|
va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT].va
|
616
645
|
file_offset = va2file(va)
|
617
646
|
return nil unless file_offset
|
618
|
-
f.
|
647
|
+
if !f.checked_seek(file_offset) || f.eof?
|
648
|
+
logger.warn "[?] exports info beyond EOF"
|
649
|
+
return nil
|
650
|
+
end
|
619
651
|
@exports = IMAGE_EXPORT_DIRECTORY.read(f).tap do |x|
|
620
652
|
x.entry_points = []
|
621
653
|
x.name_ordinals = []
|
622
654
|
x.names = []
|
623
|
-
if x.Name.to_i != 0 && (
|
624
|
-
f.seek
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
f.
|
630
|
-
x.entry_points = f.read(x.NumberOfFunctions*4).unpack('V*')
|
631
|
-
end
|
632
|
-
if x.AddressOfNameOrdinals.to_i !=0 && (va = va2file(x.AddressOfNameOrdinals))
|
633
|
-
f.seek va
|
634
|
-
x.name_ordinals = f.read(x.NumberOfNames*2).unpack('v*').map{ |o| o+x.Base }
|
635
|
-
end
|
636
|
-
end
|
637
|
-
if x.NumberOfNames.to_i != 0 && x.AddressOfNames.to_i !=0 && (va = va2file(x.AddressOfNames))
|
638
|
-
f.seek va
|
639
|
-
x.names = f.read(x.NumberOfNames*4).unpack('V*').map do |va|
|
640
|
-
f.seek va2file(va)
|
641
|
-
f.gets("\x00").chop
|
655
|
+
if x.Name.to_i != 0 && (ofs = va2file(x.Name))
|
656
|
+
f.seek ofs
|
657
|
+
if f.eof?
|
658
|
+
logger.warn "[?] export ofs 0x#{ofs.to_s(16)} beyond EOF"
|
659
|
+
nil
|
660
|
+
else
|
661
|
+
x.name = f.gets("\x00").chomp("\x00")
|
642
662
|
end
|
643
663
|
end
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
##############################################################################
|
650
|
-
|
651
|
-
IMAGE_RESOURCE_DIRECTORY = create_struct 'V2v4',
|
652
|
-
:Characteristics, :TimeDateStamp, # 2dw
|
653
|
-
:MajorVersion, :MinorVersion, :NumberOfNamedEntries, :NumberOfIdEntries, # 4w
|
654
|
-
:entries # manual
|
655
|
-
class IMAGE_RESOURCE_DIRECTORY
|
656
|
-
class << self
|
657
|
-
attr_accessor :base
|
658
|
-
alias :read_without_children :read
|
659
|
-
def read f, root=true
|
660
|
-
if root
|
661
|
-
@@loopchk1 = Hash.new(0)
|
662
|
-
@@loopchk2 = Hash.new(0)
|
663
|
-
@@loopchk3 = Hash.new(0)
|
664
|
-
elsif (@@loopchk1[f.tell] += 1) > 1
|
665
|
-
PEdump.logger.error "[!] #{self}: loop1 detected at file pos #{f.tell}" if @@loopchk1[f.tell] < 2
|
666
|
-
return nil
|
667
|
-
end
|
668
|
-
read_without_children(f).tap do |r|
|
669
|
-
nToRead = r.NumberOfNamedEntries.to_i + r.NumberOfIdEntries.to_i
|
670
|
-
r.entries = []
|
671
|
-
nToRead.times do |i|
|
664
|
+
if x.NumberOfFunctions.to_i > 0
|
665
|
+
if x.AddressOfFunctions.to_i !=0 && (ofs = va2file(x.AddressOfFunctions))
|
666
|
+
f.seek ofs
|
667
|
+
x.entry_points = []
|
668
|
+
x.NumberOfFunctions.times do
|
672
669
|
if f.eof?
|
673
|
-
|
670
|
+
logger.warn "[?] got EOF while reading exports entry_points"
|
674
671
|
break
|
675
672
|
end
|
676
|
-
|
677
|
-
PEdump.logger.error "[!] #{self}: loop2 detected at file pos #{f.tell}" if @@loopchk2[f.tell] < 2
|
678
|
-
next
|
679
|
-
end
|
680
|
-
r.entries << IMAGE_RESOURCE_DIRECTORY_ENTRY.read(f)
|
673
|
+
x.entry_points << f.read(4).unpack('V').first
|
681
674
|
end
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
f.read(nChars*2).force_encoding('UTF-16LE').encode!('UTF-8')
|
691
|
-
rescue
|
692
|
-
PEdump.logger.error "[!] #{self} failed to read entry name: #{$!}"
|
693
|
-
"???"
|
694
|
-
end
|
695
|
-
else
|
696
|
-
# Name is a numeric id
|
697
|
-
"##{entry.Name}"
|
698
|
-
end
|
699
|
-
if entry.OffsetToData && f.checked_seek(base + entry.OffsetToData & 0x7fff_ffff)
|
700
|
-
if (@@loopchk3[f.tell] += 1) > 1
|
701
|
-
PEdump.logger.error "[!] #{self}: loop3 detected at file pos #{f.tell}" if @@loopchk3[f.tell] < 2
|
702
|
-
next
|
703
|
-
end
|
704
|
-
entry.data =
|
705
|
-
if entry.OffsetToData & 0x8000_0000 > 0
|
706
|
-
# child is a directory
|
707
|
-
IMAGE_RESOURCE_DIRECTORY.read(f,false)
|
708
|
-
else
|
709
|
-
# child is a resource
|
710
|
-
IMAGE_RESOURCE_DATA_ENTRY.read(f)
|
711
|
-
end
|
675
|
+
end
|
676
|
+
if x.AddressOfNameOrdinals.to_i !=0 && (ofs = va2file(x.AddressOfNameOrdinals))
|
677
|
+
f.seek ofs
|
678
|
+
x.name_ordinals = []
|
679
|
+
x.NumberOfNames.times do
|
680
|
+
if f.eof?
|
681
|
+
logger.warn "[?] got EOF while reading exports name_ordinals"
|
682
|
+
break
|
712
683
|
end
|
684
|
+
x.name_ordinals << f.read(2).unpack('v').first + x.Base
|
713
685
|
end
|
714
|
-
@@loopchk1 = @@loopchk2 = @@loopchk3 = nil if root # save some memory
|
715
686
|
end
|
716
687
|
end
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
IMAGE_RESOURCE_DATA_ENTRY = create_struct 'V4',
|
725
|
-
:OffsetToData, :Size, :CodePage, :Reserved
|
726
|
-
|
727
|
-
def va2file va
|
728
|
-
sections.each do |s|
|
729
|
-
if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
|
730
|
-
return va - s.VirtualAddress + s.PointerToRawData
|
731
|
-
end
|
732
|
-
end
|
733
|
-
# not found with regular search. assume any of VirtualSize was 0, and try with RawSize
|
734
|
-
sections.each do |s|
|
735
|
-
if (s.VirtualAddress...(s.VirtualAddress+s.SizeOfRawData)).include?(va)
|
736
|
-
return va - s.VirtualAddress + s.PointerToRawData
|
737
|
-
end
|
738
|
-
end
|
739
|
-
logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
|
740
|
-
nil
|
741
|
-
end
|
742
|
-
|
743
|
-
def _read_resource_directory_tree f
|
744
|
-
return nil unless pe(f) && pe(f).ioh && f
|
745
|
-
res_dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE]
|
746
|
-
return [] if !res_dir || (res_dir.va == 0 && res_dir.size == 0)
|
747
|
-
res_va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE].va
|
748
|
-
res_section = @pe.section_table.find{ |t| t.VirtualAddress == res_va }
|
749
|
-
unless res_section
|
750
|
-
logger.warn "[?] can't find resource section for va=0x#{res_va.to_s(16)}"
|
751
|
-
return []
|
752
|
-
end
|
753
|
-
f.seek res_section.PointerToRawData
|
754
|
-
IMAGE_RESOURCE_DIRECTORY.base = res_section.PointerToRawData
|
755
|
-
#@resource_data_base = res_section.PointerToRawData - res_section.VirtualAddress
|
756
|
-
IMAGE_RESOURCE_DIRECTORY.read(f)
|
757
|
-
end
|
758
|
-
|
759
|
-
class Resource < Struct.new(:type, :name, :id, :lang, :file_offset, :size, :cp, :reserved, :data, :valid)
|
760
|
-
def bitmap_hdr
|
761
|
-
bmp_info_hdr = data.find{ |x| x.is_a?(BITMAPINFOHEADER) }
|
762
|
-
raise "no BITMAPINFOHEADER for #{self.type} #{self.name}" unless bmp_info_hdr
|
763
|
-
|
764
|
-
bmp_info_hdr.biHeight/=2 if %w'ICON CURSOR'.include?(type)
|
765
|
-
|
766
|
-
colors_used = bmp_info_hdr.biClrUsed
|
767
|
-
colors_used = 2**bmp_info_hdr.biBitCount if colors_used == 0 && bmp_info_hdr.biBitCount < 16
|
768
|
-
|
769
|
-
# XXX: one byte in each color is unused!
|
770
|
-
@palette_size = colors_used * 4 # each color takes 4 bytes
|
771
|
-
|
772
|
-
# scanlines are DWORD-aligned and padded to DWORD-align with zeroes
|
773
|
-
# XXX: some data may be hidden in padding bytes!
|
774
|
-
scanline_size = bmp_info_hdr.biWidth * bmp_info_hdr.biBitCount / 8
|
775
|
-
scanline_size += (4-scanline_size%4) if scanline_size % 4 > 0
|
776
|
-
|
777
|
-
@imgdata_size = scanline_size * bmp_info_hdr.biHeight
|
778
|
-
"BM" + [
|
779
|
-
BITMAPINFOHEADER::SIZE + 14 + @palette_size + @imgdata_size,
|
780
|
-
0,
|
781
|
-
BITMAPINFOHEADER::SIZE + 14 + @palette_size
|
782
|
-
].pack("V3") + bmp_info_hdr.pack
|
783
|
-
ensure
|
784
|
-
bmp_info_hdr.biHeight*=2 if %w'ICON CURSOR'.include?(type)
|
785
|
-
end
|
786
|
-
|
787
|
-
# only valid for types BITMAP, ICON & CURSOR
|
788
|
-
def restore_bitmap src_fname
|
789
|
-
File.open(src_fname, "rb") do |f|
|
790
|
-
parse f
|
791
|
-
if data.first == "PNG"
|
792
|
-
"\x89PNG" +f.read(self.size-4)
|
793
|
-
else
|
794
|
-
bitmap_hdr + f.read(@palette_size + @imgdata_size)
|
795
|
-
end
|
796
|
-
end
|
797
|
-
end
|
798
|
-
|
799
|
-
def bitmap_mask src_fname
|
800
|
-
File.open(src_fname, "rb") do |f|
|
801
|
-
parse f
|
802
|
-
bmp_info_hdr = bitmap_hdr
|
803
|
-
bitmap_size = BITMAPINFOHEADER::SIZE + @palette_size + @imgdata_size
|
804
|
-
return nil if bitmap_size >= self.size
|
805
|
-
|
806
|
-
mask_size = self.size - bitmap_size
|
807
|
-
f.seek file_offset + bitmap_size
|
808
|
-
|
809
|
-
bmp_info_hdr = BITMAPINFOHEADER.new(*bmp_info_hdr[14..-1].unpack(BITMAPINFOHEADER::FORMAT))
|
810
|
-
bmp_info_hdr.biBitCount = 1
|
811
|
-
bmp_info_hdr.biCompression = bmp_info_hdr.biSizeImage = 0
|
812
|
-
bmp_info_hdr.biClrUsed = bmp_info_hdr.biClrImportant = 2
|
813
|
-
|
814
|
-
palette = [0,0xffffff].pack('V2')
|
815
|
-
@palette_size = palette.size
|
816
|
-
|
817
|
-
"BM" + [
|
818
|
-
BITMAPINFOHEADER::SIZE + 14 + @palette_size + mask_size,
|
819
|
-
0,
|
820
|
-
BITMAPINFOHEADER::SIZE + 14 + @palette_size
|
821
|
-
].pack("V3") + bmp_info_hdr.pack + palette + f.read(mask_size)
|
822
|
-
end
|
823
|
-
end
|
824
|
-
|
825
|
-
# also sets the file position for restore_bitmap next call
|
826
|
-
def parse f
|
827
|
-
raise "called parse with type not set" unless self.type
|
828
|
-
#return if self.data
|
829
|
-
|
830
|
-
self.data = []
|
831
|
-
case type
|
832
|
-
when 'BITMAP','ICON'
|
833
|
-
f.seek file_offset
|
834
|
-
if f.read(4) == "\x89PNG"
|
835
|
-
data << 'PNG'
|
836
|
-
else
|
837
|
-
f.seek file_offset
|
838
|
-
data << BITMAPINFOHEADER.read(f)
|
839
|
-
end
|
840
|
-
when 'CURSOR'
|
841
|
-
f.seek file_offset
|
842
|
-
data << CURSOR_HOTSPOT.read(f)
|
843
|
-
data << BITMAPINFOHEADER.read(f)
|
844
|
-
when 'GROUP_CURSOR'
|
845
|
-
f.seek file_offset
|
846
|
-
data << CUR_ICO_HEADER.read(f)
|
847
|
-
nRead = CUR_ICO_HEADER::SIZE
|
848
|
-
data.last.wNumImages.to_i.times do
|
849
|
-
if nRead >= self.size
|
850
|
-
PEdump.logger.error "[!] refusing to read CURDIRENTRY beyond resource size"
|
688
|
+
if x.NumberOfNames.to_i > 0 && x.AddressOfNames.to_i !=0 && (ofs = va2file(x.AddressOfNames))
|
689
|
+
f.seek ofs
|
690
|
+
x.names = []
|
691
|
+
x.NumberOfNames.times do
|
692
|
+
if f.eof?
|
693
|
+
logger.warn "[?] got EOF while reading exports names"
|
851
694
|
break
|
852
695
|
end
|
853
|
-
|
854
|
-
nRead += CURDIRENTRY::SIZE
|
696
|
+
x.names << f.read(4).unpack('V').first
|
855
697
|
end
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
end
|
868
|
-
when 'STRING'
|
869
|
-
f.seek file_offset
|
870
|
-
16.times do
|
871
|
-
break if f.tell >= file_offset+self.size
|
872
|
-
nChars = f.read(2).to_s.unpack('v').first.to_i
|
873
|
-
t =
|
874
|
-
if nChars*2 + 1 > self.size
|
875
|
-
# TODO: if it's not 1st string in table then truncated size must be less
|
876
|
-
PEdump.logger.error "[!] string size(#{nChars*2}) > stringtable size(#{self.size}). truncated to #{self.size-2}"
|
877
|
-
f.read(self.size-2)
|
878
|
-
else
|
879
|
-
f.read(nChars*2)
|
880
|
-
end
|
881
|
-
data <<
|
882
|
-
begin
|
883
|
-
t.force_encoding('UTF-16LE').encode!('UTF-8')
|
884
|
-
rescue
|
885
|
-
t.force_encoding('ASCII')
|
886
|
-
tt = t.size > 0x10 ? t[0,0x10].inspect+'...' : t.inspect
|
887
|
-
PEdump.logger.error "[!] cannot convert #{tt} to UTF-16"
|
888
|
-
[nChars,t].pack('va*')
|
698
|
+
nErrors = 0
|
699
|
+
x.names.size.times do |i|
|
700
|
+
begin
|
701
|
+
f.seek va2file(x.names[i])
|
702
|
+
x.names[i] = f.gets("\x00").to_s.chomp("\x00")
|
703
|
+
rescue
|
704
|
+
nErrors += 1
|
705
|
+
if nErrors > MAX_ERRORS
|
706
|
+
logger.warn "[?] too many errors getting export names, stopped on #{i} of #{x.names.size}"
|
707
|
+
x.names = x.names[0,i]
|
708
|
+
break
|
889
709
|
end
|
710
|
+
nil
|
711
|
+
end
|
890
712
|
end
|
891
|
-
# XXX: check if readed strings summary length is less than resource data length
|
892
|
-
when 'VERSION'
|
893
|
-
require 'pedump/version_info'
|
894
|
-
f.seek file_offset
|
895
|
-
data << PEdump::VS_VERSIONINFO.read(f)
|
896
713
|
end
|
897
714
|
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
715
|
+
ord2name = {}
|
716
|
+
if x.names && x.names.any?
|
717
|
+
n = x.NumberOfNames
|
718
|
+
if n > 2048
|
719
|
+
logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to 2048"
|
720
|
+
n = 2048
|
721
|
+
end
|
722
|
+
n.times do |i|
|
723
|
+
ord2name[x.name_ordinals[i]] ||= []
|
724
|
+
ord2name[x.name_ordinals[i]] << x.names[i]
|
725
|
+
end
|
902
726
|
end
|
903
|
-
ensure
|
904
|
-
validate
|
905
|
-
end
|
906
727
|
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
end
|
728
|
+
x.functions = []
|
729
|
+
x.entry_points.each_with_index do |ep,i|
|
730
|
+
names = ord2name[i+x.Base]
|
731
|
+
names = names.join(', ') if names
|
732
|
+
next if ep.to_i == 0 && names.nil?
|
733
|
+
x.functions << ExportedFunction.new(names, i+x.Base, ep)
|
734
|
+
end
|
915
735
|
end
|
916
736
|
end
|
917
737
|
|
918
|
-
|
738
|
+
##############################################################################
|
739
|
+
# TLS
|
740
|
+
##############################################################################
|
919
741
|
|
920
|
-
def
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
742
|
+
def tls f=@io
|
743
|
+
@tls ||= pe(f) && pe(f).ioh && f &&
|
744
|
+
begin
|
745
|
+
dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::TLS]
|
746
|
+
return nil if !dir || dir.va == 0
|
747
|
+
return nil unless file_offset = va2file(dir.va)
|
748
|
+
f.seek file_offset
|
749
|
+
if f.eof?
|
750
|
+
logger.info "[?] TLS info beyond EOF"
|
751
|
+
return nil
|
752
|
+
end
|
929
753
|
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
:biSizeImage,
|
940
|
-
:biXPelsPerMeter,
|
941
|
-
:biYPelsPerMeter,
|
942
|
-
:biClrUsed,
|
943
|
-
:biClrImportant
|
944
|
-
|
945
|
-
def valid?
|
946
|
-
self.biSize == 40
|
947
|
-
end
|
754
|
+
klass = @pe.x64? ? IMAGE_TLS_DIRECTORY64 : IMAGE_TLS_DIRECTORY32
|
755
|
+
nEntries = [1,dir.size / klass.const_get('SIZE')].max
|
756
|
+
r = []
|
757
|
+
nEntries.times do
|
758
|
+
break if f.eof? || !(entry = klass.read(f))
|
759
|
+
r << entry
|
760
|
+
end
|
761
|
+
r
|
762
|
+
end
|
948
763
|
end
|
949
764
|
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
:wResID, # always 2
|
954
|
-
:wNumImages # Number of cursor images/directory entries
|
955
|
-
)
|
765
|
+
##############################################################################
|
766
|
+
# resources
|
767
|
+
##############################################################################
|
956
768
|
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
CURSOR_HOTSPOT = create_struct 'v2', :x, :y
|
966
|
-
|
967
|
-
ICODIRENTRY = create_struct 'C4v2Vv',
|
968
|
-
:bWidth,
|
969
|
-
:bHeight,
|
970
|
-
:bColors,
|
971
|
-
:bReserved,
|
972
|
-
:wPlanes,
|
973
|
-
:wBitCount,
|
974
|
-
:dwBytesInImage,
|
975
|
-
:wID
|
976
|
-
|
977
|
-
ROOT_RES_NAMES = [nil] + # numeration is started from 1
|
978
|
-
%w'CURSOR BITMAP ICON MENU DIALOG STRING FONTDIR FONT ACCELERATORS RCDATA' +
|
979
|
-
%w'MESSAGETABLE GROUP_CURSOR' + [nil] + %w'GROUP_ICON' + [nil] +
|
980
|
-
%w'VERSION DLGINCLUDE' + [nil] + %w'PLUGPLAY VXD ANICURSOR ANIICON HTML MANIFEST'
|
981
|
-
|
982
|
-
def resources f=nil
|
983
|
-
@resources ||= _scan_resources(f)
|
769
|
+
def resources f=@io
|
770
|
+
@resources ||=
|
771
|
+
if pe(f)
|
772
|
+
_scan_pe_resources(f)
|
773
|
+
elsif ne(f)
|
774
|
+
ne(f).resources(f)
|
775
|
+
end
|
984
776
|
end
|
985
777
|
|
986
|
-
def version_info f
|
778
|
+
def version_info f=@io
|
987
779
|
resources(f) && resources(f).find_all{ |res| res.type == 'VERSION' }.map(&:data).flatten
|
988
780
|
end
|
989
781
|
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
dir.entries.map do |entry|
|
994
|
-
case entry.data
|
995
|
-
when IMAGE_RESOURCE_DIRECTORY
|
996
|
-
if dir == @resource_directory # root resource directory
|
997
|
-
entry_type =
|
998
|
-
if entry.Name & 0x8000_0000 == 0
|
999
|
-
# root resource directory & entry name is a number
|
1000
|
-
ROOT_RES_NAMES[entry.Name] || entry.name
|
1001
|
-
else
|
1002
|
-
entry.name
|
1003
|
-
end
|
1004
|
-
_scan_resources(f,entry.data).each do |res|
|
1005
|
-
res.type = entry_type
|
1006
|
-
res.parse f
|
1007
|
-
end
|
1008
|
-
else
|
1009
|
-
_scan_resources(f,entry.data).each do |res|
|
1010
|
-
res.name = res.name == "##{res.lang}" ? entry.name : "#{entry.name} / #{res.name}"
|
1011
|
-
res.id ||= entry.Name if entry.Name.is_a?(Numeric) && entry.Name < 0x8000_0000
|
1012
|
-
end
|
1013
|
-
end
|
1014
|
-
when IMAGE_RESOURCE_DATA_ENTRY
|
1015
|
-
Resource.new(
|
1016
|
-
nil, # type
|
1017
|
-
entry.name,
|
1018
|
-
nil, # id
|
1019
|
-
entry.Name, # lang
|
1020
|
-
#entry.data.OffsetToData + @resource_data_base,
|
1021
|
-
va2file(entry.data.OffsetToData),
|
1022
|
-
entry.data.Size,
|
1023
|
-
entry.data.CodePage,
|
1024
|
-
entry.data.Reserved
|
1025
|
-
)
|
1026
|
-
else
|
1027
|
-
logger.error "[!] invalid resource entry: #{entry.data.inspect}"
|
1028
|
-
nil
|
1029
|
-
end
|
1030
|
-
end.flatten.compact
|
1031
|
-
end
|
782
|
+
##############################################################################
|
783
|
+
# packer / compiler detection
|
784
|
+
##############################################################################
|
1032
785
|
|
1033
|
-
def packer f
|
786
|
+
def packer f=@io
|
1034
787
|
@packer ||= pe(f) && @pe.ioh &&
|
1035
788
|
begin
|
1036
|
-
if
|
1037
|
-
logger.error "[?]
|
1038
|
-
nil
|
1039
|
-
elsif va == 0 && @pe.dll?
|
1040
|
-
logger.debug "[.] it's a DLL with no EntryPoint"
|
1041
|
-
nil
|
1042
|
-
elsif !(ofs = va2file(va))
|
1043
|
-
logger.error "[?] can't find EntryPoint RVA (0x#{va.to_s(16)}) file offset"
|
789
|
+
if PEdump::Packer.all.size == 0
|
790
|
+
logger.error "[?] no packer definitions found"
|
1044
791
|
nil
|
1045
792
|
else
|
1046
|
-
|
1047
|
-
if PEdump::Packer.all.size == 0
|
1048
|
-
logger.error "[?] no packer definitions found"
|
1049
|
-
nil
|
1050
|
-
else
|
1051
|
-
Packer.of f, :ep_offset => ofs
|
1052
|
-
end
|
793
|
+
Packer.of f, :pedump => self
|
1053
794
|
end
|
1054
795
|
end
|
1055
796
|
end
|
@@ -1088,17 +829,13 @@ if $0 == __FILE__
|
|
1088
829
|
exit
|
1089
830
|
end
|
1090
831
|
p dump.mz
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
832
|
+
dump.dos_stub.hexdump if dump.dos_stub
|
833
|
+
puts
|
834
|
+
if dump.rich_hdr
|
835
|
+
dump.rich_hdr.hexdump
|
1095
836
|
puts
|
1096
|
-
|
1097
|
-
|
1098
|
-
puts
|
1099
|
-
p(dump.rich_hdr.decode)
|
1100
|
-
puts hexdump(dump.rich_hdr.dexor)
|
1101
|
-
end
|
837
|
+
p(dump.rich_hdr.decode)
|
838
|
+
dump.rich_hdr.dexor.hexdump
|
1102
839
|
end
|
1103
840
|
pp dump.pe
|
1104
841
|
pp dump.resources
|