pedump 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|