pedump 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,868 @@
1
+ #!/usr/bin/env ruby
2
+ require 'stringio'
3
+ require 'iostruct'
4
+ require 'zhexdump'
5
+ require 'set'
6
+
7
+ unless Object.new.respond_to?(:try) && nil.respond_to?(:try)
8
+ require 'pedump/core_ext/try'
9
+ end
10
+
11
+ require 'pedump/core'
12
+ require 'pedump/pe'
13
+ require 'pedump/resources'
14
+ require 'pedump/version_info'
15
+ require 'pedump/tls'
16
+ require 'pedump/security'
17
+ require 'pedump/packer'
18
+ require 'pedump/ne'
19
+ require 'pedump/ne/version_info'
20
+
21
+ # pedump.rb by zed_0xff
22
+ #
23
+ # http://zed.0xff.me
24
+ # http://github.com/zed-0xff
25
+
26
+ class PEdump
27
+ attr_accessor :fname, :logger, :force, :io
28
+
29
+ VERSION = Version::STRING
30
+ MAX_ERRORS = 100
31
+ MAX_IMAGE_IMPORT_DESCRIPTORS = 1000
32
+
33
+ @@logger = nil
34
+
35
+ def initialize io = nil, params = {}
36
+ if io.is_a?(Hash)
37
+ @io, params = nil, io
38
+ else
39
+ @io = io
40
+ end
41
+ @force = params[:force]
42
+ @logger = @@logger = Logger.create(params)
43
+ end
44
+
45
+ # http://www.delorie.com/djgpp/doc/exe/
46
+ MZ = IOStruct.new( "a2v13Qv2V6",
47
+ :signature,
48
+ :bytes_in_last_block,
49
+ :blocks_in_file,
50
+ :num_relocs,
51
+ :header_paragraphs,
52
+ :min_extra_paragraphs,
53
+ :max_extra_paragraphs,
54
+ :ss,
55
+ :sp,
56
+ :checksum,
57
+ :ip,
58
+ :cs,
59
+ :reloc_table_offset,
60
+ :overlay_number,
61
+ :reserved0, # 8 reserved bytes
62
+ :oem_id,
63
+ :oem_info,
64
+ :reserved2, # 20 reserved bytes
65
+ :reserved3,
66
+ :reserved4,
67
+ :reserved5,
68
+ :reserved6,
69
+ :lfanew
70
+ )
71
+
72
+ # http://msdn.microsoft.com/en-us/library/ms809762.aspx
73
+ class IMAGE_FILE_HEADER < IOStruct.new( 'v2V3v2',
74
+ :Machine, # w
75
+ :NumberOfSections, # w
76
+ :TimeDateStamp, # dw
77
+ :PointerToSymbolTable, # dw
78
+ :NumberOfSymbols, # dw
79
+ :SizeOfOptionalHeader, # w
80
+ :Characteristics # w
81
+ )
82
+ # Characteristics, http://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=VS.85).aspx)
83
+ FLAGS = {
84
+ 0x0001 => 'RELOCS_STRIPPED', # Relocation information was stripped from the file.
85
+ # The file must be loaded at its preferred base address.
86
+ # If the base address is not available, the loader reports an error.
87
+ 0x0002 => 'EXECUTABLE_IMAGE',
88
+ 0x0004 => 'LINE_NUMS_STRIPPED',
89
+ 0x0008 => 'LOCAL_SYMS_STRIPPED',
90
+ 0x0010 => 'AGGRESIVE_WS_TRIM', # Aggressively trim the working set. This value is obsolete as of Windows 2000.
91
+ 0x0020 => 'LARGE_ADDRESS_AWARE', # The application can handle addresses larger than 2 GB.
92
+ 0x0040 => '16BIT_MACHINE',
93
+ 0x0080 => 'BYTES_REVERSED_LO', # The bytes of the word are reversed. This flag is obsolete.
94
+ 0x0100 => '32BIT_MACHINE',
95
+ 0x0200 => 'DEBUG_STRIPPED',
96
+ 0x0400 => 'REMOVABLE_RUN_FROM_SWAP',
97
+ 0x0800 => 'NET_RUN_FROM_SWAP',
98
+ 0x1000 => 'SYSTEM',
99
+ 0x2000 => 'DLL',
100
+ 0x4000 => 'UP_SYSTEM_ONLY', # The file should be run only on a uniprocessor computer.
101
+ 0x8000 => 'BYTES_REVERSED_HI' # The bytes of the word are reversed. This flag is obsolete.
102
+ }
103
+
104
+ # def initialize *args
105
+ # super
106
+ # self.TimeDateStamp = Time.at(self.TimeDateStamp).utc
107
+ # end
108
+ def flags
109
+ FLAGS.find_all{ |k,v| (self.Characteristics & k) != 0 }.map(&:last)
110
+ end
111
+ end
112
+
113
+ module IMAGE_OPTIONAL_HEADER
114
+ # DllCharacteristics, http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx)
115
+ FLAGS = {
116
+ 0x0001 => '0x01', # reserved
117
+ 0x0002 => '0x02', # reserved
118
+ 0x0004 => '0x04', # reserved
119
+ 0x0008 => '0x08', # reserved
120
+ 0x0010 => '0x10', # ?
121
+ 0x0020 => '0x20', # ?
122
+ 0x0040 => 'DYNAMIC_BASE',
123
+ 0x0080 => 'FORCE_INTEGRITY',
124
+ 0x0100 => 'NX_COMPAT',
125
+ 0x0200 => 'NO_ISOLATION',
126
+ 0x0400 => 'NO_SEH',
127
+ 0x0800 => 'NO_BIND',
128
+ 0x1000 => '0x1000', # ?
129
+ 0x2000 => 'WDM_DRIVER',
130
+ 0x4000 => '0x4000', # ?
131
+ 0x8000 => 'TERMINAL_SERVER_AWARE'
132
+ }
133
+ def initialize *args
134
+ super
135
+ self.extend InstanceMethods
136
+ end
137
+ def self.included base
138
+ base.extend ClassMethods
139
+ end
140
+ module ClassMethods
141
+ def read file, size = nil
142
+ usual_size = self.const_get('USUAL_SIZE')
143
+ cSIZE = self.const_get 'SIZE'
144
+ cFORMAT = self.const_get 'FORMAT'
145
+ size ||= cSIZE
146
+ PEdump.logger.warn "[?] unusual size of IMAGE_OPTIONAL_HEADER = #{size} (must be #{usual_size})" if size != usual_size
147
+ PEdump.logger.warn "[?] #{size-usual_size} spare bytes after IMAGE_OPTIONAL_HEADER" if size > usual_size
148
+ new(*file.read([size,cSIZE].min).to_s.unpack(cFORMAT)).tap do |ioh|
149
+ ioh.DataDirectory = []
150
+
151
+ # check if "...this address is outside the memory mapped file and is zeroed by the OS"
152
+ # see http://www.phreedom.org/solar/code/tinype/, section "Removing the data directories"
153
+ ioh.each_pair{ |k,v| ioh[k] = 0 if v.nil? }
154
+
155
+ # http://opcode0x90.wordpress.com/2007/04/22/windows-loader-does-it-differently/
156
+ # maximum of 0x10 entries, even if bigger
157
+ [0x10,ioh.NumberOfRvaAndSizes].min.times do |idx|
158
+ ioh.DataDirectory << IMAGE_DATA_DIRECTORY.read(file)
159
+ ioh.DataDirectory.last.type = IMAGE_DATA_DIRECTORY::TYPES[idx]
160
+ end
161
+ #ioh.DataDirectory.pop while ioh.DataDirectory.last.empty?
162
+
163
+ # skip spare bytes, if any. XXX may contain suspicious data
164
+ file.seek(size-usual_size, IO::SEEK_CUR) if size > usual_size
165
+ end
166
+ end
167
+ end
168
+ module InstanceMethods
169
+ def flags
170
+ FLAGS.find_all{ |k,v| (self.DllCharacteristics & k) != 0 }.map(&:last)
171
+ end
172
+ end
173
+ end
174
+
175
+ # http://msdn.microsoft.com/en-us/library/ms809762.aspx
176
+ class IMAGE_OPTIONAL_HEADER32 < IOStruct.new( 'vC2V9v6V4v2V6',
177
+ :Magic, # w
178
+ :MajorLinkerVersion, :MinorLinkerVersion, # 2b
179
+ :SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, # 9dw
180
+ :BaseOfCode, :BaseOfData, :ImageBase, :SectionAlignment, :FileAlignment,
181
+ :MajorOperatingSystemVersion, :MinorOperatingSystemVersion, # 6w
182
+ :MajorImageVersion, :MinorImageVersion, :MajorSubsystemVersion, :MinorSubsystemVersion,
183
+ :Reserved1, :SizeOfImage, :SizeOfHeaders, :CheckSum, # 4dw
184
+ :Subsystem, :DllCharacteristics, # 2w
185
+ :SizeOfStackReserve, :SizeOfStackCommit, :SizeOfHeapReserve, :SizeOfHeapCommit, # 6dw
186
+ :LoaderFlags, :NumberOfRvaAndSizes,
187
+ :DataDirectory # readed manually
188
+ )
189
+ USUAL_SIZE = 224
190
+ include IMAGE_OPTIONAL_HEADER
191
+ end
192
+
193
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=VS.85).aspx)
194
+ class IMAGE_OPTIONAL_HEADER64 < IOStruct.new( 'vC2V5QV2v6V4v2Q4V2',
195
+ :Magic, # w
196
+ :MajorLinkerVersion, :MinorLinkerVersion, # 2b
197
+ :SizeOfCode, :SizeOfInitializedData, :SizeOfUninitializedData, :AddressOfEntryPoint, :BaseOfCode, # 5dw
198
+ :ImageBase, # qw
199
+ :SectionAlignment, :FileAlignment, # 2dw
200
+ :MajorOperatingSystemVersion, :MinorOperatingSystemVersion, # 6w
201
+ :MajorImageVersion, :MinorImageVersion, :MajorSubsystemVersion, :MinorSubsystemVersion,
202
+ :Reserved1, :SizeOfImage, :SizeOfHeaders, :CheckSum, # 4dw
203
+ :Subsystem, :DllCharacteristics, # 2w
204
+ :SizeOfStackReserve, :SizeOfStackCommit, :SizeOfHeapReserve, :SizeOfHeapCommit, # 4qw
205
+ :LoaderFlags, :NumberOfRvaAndSizes, #2dw
206
+ :DataDirectory # readed manually
207
+ )
208
+ USUAL_SIZE = 240
209
+ include IMAGE_OPTIONAL_HEADER
210
+ end
211
+
212
+ IMAGE_DATA_DIRECTORY = IOStruct.new( "VV", :va, :size, :type )
213
+ IMAGE_DATA_DIRECTORY::TYPES =
214
+ %w'EXPORT IMPORT RESOURCE EXCEPTION SECURITY BASERELOC DEBUG ARCHITECTURE GLOBALPTR TLS LOAD_CONFIG
215
+ Bound_IAT IAT Delay_IAT CLR_Header'
216
+ IMAGE_DATA_DIRECTORY::TYPES.each_with_index do |type,idx|
217
+ IMAGE_DATA_DIRECTORY.const_set(type,idx)
218
+ end
219
+
220
+ IMAGE_SECTION_HEADER = IOStruct.new( 'A8V6v2V',
221
+ :Name, # A8 6dw
222
+ :VirtualSize, :VirtualAddress, :SizeOfRawData, :PointerToRawData, :PointerToRelocations, :PointerToLinenumbers,
223
+ :NumberOfRelocations, :NumberOfLinenumbers, # 2w
224
+ :Characteristics # dw
225
+ )
226
+ class IMAGE_SECTION_HEADER
227
+ alias :flags :Characteristics
228
+ alias :va :VirtualAddress
229
+ def flags_desc
230
+ r = ''
231
+ f = self.flags.to_i
232
+ r << (f & 0x4000_0000 > 0 ? 'R' : '-')
233
+ r << (f & 0x8000_0000 > 0 ? 'W' : '-')
234
+ r << (f & 0x2000_0000 > 0 ? 'X' : '-')
235
+ r << ' CODE' if f & 0x20 > 0
236
+
237
+ # section contains initialized data. Almost all sections except executable and the .bss section have this flag set
238
+ r << ' IDATA' if f & 0x40 > 0
239
+
240
+ # section contains uninitialized data (for example, the .bss section)
241
+ r << ' UDATA' if f & 0x80 > 0
242
+
243
+ r << ' DISCARDABLE' if f & 0x02000000 > 0
244
+ r << ' SHARED' if f & 0x10000000 > 0
245
+ r
246
+ end
247
+
248
+ def pack
249
+ to_a.pack FORMAT.tr('A','a') # pad names with NULL bytes on pack()
250
+ end
251
+ end
252
+
253
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=VS.85).aspx
254
+ IMAGE_SUBSYSTEMS = %w'UNKNOWN NATIVE WINDOWS_GUI WINDOWS_CUI' + [nil,'OS2_CUI',nil,'POSIX_CUI',nil] +
255
+ %w'WINDOWS_CE_GUI EFI_APPLICATION EFI_BOOT_SERVICE_DRIVER EFI_RUNTIME_DRIVER EFI_ROM XBOX' +
256
+ [nil, 'WINDOWS_BOOT_APPLICATION']
257
+
258
+ # http://ntcore.com/files/richsign.htm
259
+ class RichHdr < String
260
+ attr_accessor :offset, :key # xor key
261
+
262
+ class Entry < Struct.new(:version,:id,:times)
263
+ def inspect
264
+ "<id=#{id}, version=#{version}, times=#{times}>"
265
+ end
266
+ end
267
+
268
+ def self.from_dos_stub stub
269
+ key = stub[stub.index('Rich')+4,4]
270
+ start_idx = stub.index(key.xor('DanS'))
271
+ end_idx = stub.index('Rich')+8
272
+ if stub[end_idx..-1].tr("\x00",'') != ''
273
+ t = stub[end_idx..-1]
274
+ t = "#{t[0,0x100]}..." if t.size > 0x100
275
+ PEdump.logger.error "[!] non-zero dos stub after rich_hdr: #{t.inspect}"
276
+ return nil
277
+ end
278
+ RichHdr.new(stub[start_idx, end_idx-start_idx]).tap do |x|
279
+ x.key = key
280
+ x.offset = stub.offset + start_idx
281
+ end
282
+ end
283
+
284
+ def dexor
285
+ self[4..-9].sub(/\A(#{Regexp::escape(key)}){3}/,'').xor(key)
286
+ end
287
+
288
+ def decode
289
+ x = dexor
290
+ if x.size%8 == 0
291
+ x.unpack('vvV'*(x.size/8)).each_slice(3).map{ |slice| Entry.new(*slice)}
292
+ else
293
+ PEdump.logger.error "[?] #{self.class}: dexored size(#{x.size}) must be a multiple of 8"
294
+ nil
295
+ end
296
+ end
297
+ end
298
+
299
+ class DOSStub < String
300
+ attr_accessor :offset
301
+ end
302
+
303
+ def logger= l
304
+ @logger = @@logger = l
305
+ end
306
+
307
+ def self.dump fname, params = {}
308
+ new(fname, params).dump
309
+ end
310
+
311
+ def self.quiet
312
+ oldlevel = @@logger.level
313
+ @@logger.level = ::Logger::FATAL
314
+ yield
315
+ ensure
316
+ @@logger.level = oldlevel
317
+ end
318
+
319
+ def mz f=@io
320
+ @mz ||= f && MZ.read(f).tap do |mz|
321
+ if mz.signature != 'MZ' && mz.signature != 'ZM'
322
+ if @force
323
+ logger.warn "[?] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}"
324
+ else
325
+ logger.error "[!] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}. (not forced)"
326
+ return nil
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ def dos_stub f=@io
333
+ @dos_stub ||=
334
+ begin
335
+ return nil unless mz = mz(f)
336
+ dos_stub_offset = mz.header_paragraphs.to_i * 0x10
337
+ dos_stub_size = mz.lfanew.to_i - dos_stub_offset
338
+ if dos_stub_offset < 0
339
+ logger.warn "[?] invalid DOS stub offset #{dos_stub_offset}"
340
+ nil
341
+ elsif f && dos_stub_offset > f.size
342
+ logger.warn "[?] DOS stub offset beyond EOF: #{dos_stub_offset}"
343
+ nil
344
+ elsif dos_stub_size < 0
345
+ logger.warn "[?] invalid DOS stub size #{dos_stub_size}"
346
+ nil
347
+ elsif dos_stub_size == 0
348
+ # no DOS stub, it's ok
349
+ nil
350
+ elsif !f
351
+ # no open file, it's ok
352
+ nil
353
+ else
354
+ return nil if dos_stub_size == MZ::SIZE && dos_stub_offset == 0
355
+ if dos_stub_size > 0x1000
356
+ logger.warn "[?] DOS stub size too big (#{dos_stub_size}), limiting to 0x1000"
357
+ dos_stub_size = 0x1000
358
+ end
359
+ f.seek dos_stub_offset
360
+ DOSStub.new(f.read(dos_stub_size)).tap do |dos_stub|
361
+ dos_stub.offset = dos_stub_offset
362
+ if dos_stub['Rich']
363
+ if @rich_hdr = RichHdr.from_dos_stub(dos_stub)
364
+ dos_stub[dos_stub.index(@rich_hdr)..-1] = ''
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end
370
+ end
371
+
372
+ def rich_hdr f=@io
373
+ dos_stub(f) && @rich_hdr
374
+ end
375
+ alias :rich_header :rich_hdr
376
+ alias :rich :rich_hdr
377
+
378
+ def va2file va, h={}
379
+ return nil if va.nil?
380
+
381
+ sections.each do |s|
382
+ if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
383
+ offset = va - s.VirtualAddress
384
+ return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
385
+ end
386
+ end
387
+
388
+ # not found with regular search. assume any of VirtualSize was 0, and try with RawSize
389
+ sections.each do |s|
390
+ if (s.VirtualAddress...(s.VirtualAddress+s.SizeOfRawData)).include?(va)
391
+ offset = va - s.VirtualAddress
392
+ return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
393
+ end
394
+ end
395
+
396
+ # still not found, bad/zero VirtualSizes & RawSizes ?
397
+
398
+ # a special case - PE without sections
399
+ return va if sections.empty?
400
+
401
+ # check if only one section
402
+ if sections.size == 1 || sections.all?{ |s| s.VirtualAddress.to_i == 0 }
403
+ s = sections.first
404
+ offset = va - s.VirtualAddress
405
+ return (s.PointerToRawData + offset) if offset < s.SizeOfRawData
406
+ #return va - s.VirtualAddress + s.PointerToRawData
407
+ end
408
+
409
+ # TODO: not all VirtualAdresses == 0 case
410
+
411
+ if h[:quiet]
412
+ logger.debug "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)} (quiet=true)"
413
+ else
414
+ logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
415
+ end
416
+ nil
417
+ end
418
+
419
+ # OPTIONAL: assigns @mz, @rich_hdr, @pe, etc
420
+ def dump f=@io
421
+ if f.is_a?(String)
422
+ File.open(f,'rb'){ |f| _dump_handle(f) }
423
+ elsif f.is_a?(::IO)
424
+ _dump_handle f
425
+ elsif @io
426
+ _dump_handle @io
427
+ end
428
+ self
429
+ end
430
+
431
+ def _dump_handle h
432
+ return unless pe(h) # also calls mz(h)
433
+ rich_hdr h
434
+ resources h
435
+ imports h # also calls tls(h)
436
+ exports h
437
+ packer h
438
+ end
439
+
440
+ def data_directory f=@io
441
+ pe(f) && pe.ioh && pe.ioh.DataDirectory
442
+ end
443
+
444
+ def sections f=@io
445
+ if pe(f)
446
+ pe.section_table
447
+ elsif ne(f)
448
+ ne.segments
449
+ end
450
+ end
451
+ alias :section_table :sections
452
+
453
+ def ne?
454
+ @pe ? false : (@ne ? true : (pe ? false : (ne ? true : false)))
455
+ end
456
+
457
+ def pe?
458
+ @pe ? true : (@ne ? false : (pe ? true : false ))
459
+ end
460
+
461
+ ##############################################################################
462
+ # imports
463
+ ##############################################################################
464
+
465
+ # http://sandsprite.com/CodeStuff/Understanding_imports.html
466
+ # http://stackoverflow.com/questions/5631317/import-table-it-vs-import-address-table-iat
467
+ IMAGE_IMPORT_DESCRIPTOR = IOStruct.new 'V5',
468
+ :OriginalFirstThunk,
469
+ :TimeDateStamp,
470
+ :ForwarderChain,
471
+ :Name,
472
+ :FirstThunk,
473
+ # manual:
474
+ :module_name,
475
+ :original_first_thunk,
476
+ :first_thunk
477
+
478
+ class ImportedFunction < Struct.new(:hint, :name, :ordinal, :va, :module_name)
479
+ def == x
480
+ self.hint == x.hint && self.name == x.name && self.ordinal == x.ordinal &&
481
+ self.module_name == x.module_name
482
+ end
483
+ # def <=> x
484
+ # self.to_a[0..-2] <=> x.to_a[0..-2]
485
+ # end
486
+
487
+ # magic to be able to easy merge :first_thunk & :original_first_thunk arrays
488
+ # (keeping va different)
489
+ def hash
490
+ [hint,name,ordinal,module_name].hash
491
+ end
492
+ def eql? x
493
+ self.hint == x.hint && self.name == x.name && self.ordinal == x.ordinal &&
494
+ self.module_name == x.module_name
495
+ end
496
+ end
497
+
498
+ def imports f=@io
499
+ if pe(f)
500
+ pe_imports(f)
501
+ elsif ne(f)
502
+ ne(f).imports
503
+ end
504
+ end
505
+
506
+ def pe_imports f=@io
507
+ return @imports if @imports
508
+ return nil unless pe(f) && pe(f).ioh && f
509
+ dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT]
510
+ return [] if !dir || (dir.va == 0 && dir.size == 0)
511
+ file_offset = va2file(dir.va)
512
+ return nil unless file_offset
513
+
514
+ # scan TLS first, to catch many fake imports trick from
515
+ # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
516
+ tls_aoi = nil
517
+ if (tls = tls(f)) && tls.any?
518
+ tls_aoi = tls.first.AddressOfIndex.to_i - @pe.ioh.ImageBase.to_i
519
+ tls_aoi = tls_aoi > 0 ? va2file(tls_aoi) : nil
520
+ end
521
+
522
+ r = []; t = nil
523
+ if f.checked_seek(file_offset)
524
+ while true
525
+ if tls_aoi && tls_aoi == file_offset+16
526
+ # catched the neat trick! :)
527
+ # f.tell + 12 = offset of 'FirstThunk' field from start of IMAGE_IMPORT_DESCRIPTOR structure
528
+ logger.warn "[!] catched the 'imports terminator in TLS trick'"
529
+ # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
530
+ break
531
+ end
532
+ if r.size >= MAX_IMAGE_IMPORT_DESCRIPTORS
533
+ logger.warn "[!] too many IMAGE_IMPORT_DESCRIPTORs, not reading more than #{r.size}"
534
+ break
535
+ end
536
+ t = IMAGE_IMPORT_DESCRIPTOR.read(f)
537
+ break if t.Name.to_i == 0 # also catches EOF
538
+ r << t
539
+ file_offset += IMAGE_IMPORT_DESCRIPTOR::SIZE
540
+ end
541
+ else
542
+ logger.warn "[?] imports info beyond EOF"
543
+ end
544
+
545
+ n_bad_names = 0
546
+ logger.warn "[?] non-empty last IMAGE_IMPORT_DESCRIPTOR: #{t.inspect}" if t && !t.empty?
547
+ @imports = r
548
+ r = nil
549
+ @imports.each_with_index do |x, iidx|
550
+ if n_bad_names > MAX_ERRORS
551
+ logger.warn "[!] too many bad imported function names. skipping further imports parsing"
552
+ @imports = @imports[0,iidx]
553
+ break
554
+ end
555
+ if x.Name.to_i != 0 && (ofs = va2file(x.Name))
556
+ begin
557
+ f.seek ofs
558
+ rescue
559
+ logger.warn "[?] cannot seek to #{ofs} (VA=0x#{x.Name.to_i.to_s(16)} for reading imports, skipped"
560
+ next
561
+ end
562
+ x.module_name = f.gets("\x00").to_s.chomp("\x00")
563
+ end
564
+ [:original_first_thunk, :first_thunk].each do |tbl|
565
+ camel = tbl.capitalize.to_s.gsub(/_./){ |char| char[1..-1].upcase}
566
+ if x[camel].to_i != 0 && (ofs = va2file(x[camel])) && f.checked_seek(ofs)
567
+ x[tbl] ||= []
568
+ if pe.x64?
569
+ x[tbl] << t while (t = f.read(8).to_s.unpack('Q').first).to_i != 0
570
+ else
571
+ x[tbl] << t while (t = f.read(4).to_s.unpack('V').first).to_i != 0
572
+ end
573
+ end
574
+ cache = {}
575
+ bits = pe.x64? ? 64 : 32
576
+ mask = 2**(bits-1)
577
+ idx = -1
578
+ x[tbl] && x[tbl].map! do |t|
579
+ idx += 1
580
+ va = x[camel].to_i + idx*4
581
+ cache[t] ||=
582
+ if t & mask > 0 # 0x8000_0000(_0000_0000)
583
+ ImportedFunction.new(nil,nil,t & (mask-1),va) # 0x7fff_ffff(_ffff_ffff)
584
+ elsif ofs=va2file(t, :quiet => true)
585
+ if !f.checked_seek(ofs) || f.eof?
586
+ logger.warn "[?] import ofs 0x#{ofs.to_s(16)} VA=0x#{t.to_s(16)} beyond EOF"
587
+ nil
588
+ else
589
+ hint = f.read(2).unpack('v').first
590
+ name = f.gets("\x00").chomp("\x00")
591
+ if !name.empty? && name !~ /\A[\x33-\x7f]+\Z/
592
+ n_bad_names += 1
593
+ if n_bad_names > MAX_ERRORS
594
+ nil
595
+ else
596
+ ImportedFunction.new(hint, name, nil, va)
597
+ end
598
+ else
599
+ ImportedFunction.new(hint, name, nil, va)
600
+ end
601
+ end
602
+ elsif tbl == :original_first_thunk
603
+ # OriginalFirstThunk entries can not be invalid, show a warning msg
604
+ logger.warn "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
605
+ nil
606
+ elsif tbl == :first_thunk
607
+ # FirstThunk entries can be invalid, so `info` msg only
608
+ logger.info "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
609
+ nil
610
+ else
611
+ raise "You are not supposed to be here! O_o"
612
+ end
613
+ end
614
+ x[tbl] && x[tbl].compact!
615
+ end # [:original_first_thunk, :first_thunk].each
616
+ if x.original_first_thunk && !x.first_thunk
617
+ logger.warn "[?] import table: empty FirstThunk for #{x.module_name}"
618
+ elsif !x.original_first_thunk && x.first_thunk
619
+ logger.info "[?] import table: empty OriginalFirstThunk for #{x.module_name}"
620
+ elsif logger.debug?
621
+ # compare all but VAs
622
+ if x.original_first_thunk != x.first_thunk
623
+ logger.debug "[?] import table: OriginalFirstThunk != FirstThunk for #{x.module_name}"
624
+ end
625
+ end
626
+ end # r.each
627
+ @imports
628
+ end
629
+
630
+ ##############################################################################
631
+ # exports
632
+ ##############################################################################
633
+
634
+ #http://msdn.microsoft.com/en-us/library/ms809762.aspx
635
+ IMAGE_EXPORT_DIRECTORY = IOStruct.new 'V2v2V7',
636
+ :Characteristics,
637
+ :TimeDateStamp,
638
+ :MajorVersion, # These fields appear to be unused and are set to 0.
639
+ :MinorVersion, # These fields appear to be unused and are set to 0.
640
+ :Name,
641
+ :Base, # The starting ordinal number for exported functions
642
+ :NumberOfFunctions, # UNSIGNED!, perfectly valid when = 0xffff_ffff, see corkami/dllord.dll
643
+ :NumberOfNames,
644
+ :AddressOfFunctions,
645
+ :AddressOfNames,
646
+ :AddressOfNameOrdinals,
647
+ # manual:
648
+ :name, :entry_points, :names, :name_ordinals, :functions,
649
+ :description # NE only
650
+
651
+ class ExportedFunction < Struct.new :name, :ord, :va, :file_offset
652
+ def ordinal
653
+ self.ord
654
+ end
655
+ end
656
+
657
+ def exports f=@io
658
+ if pe(f)
659
+ pe_exports(f)
660
+ elsif ne(f)
661
+ ne(f).exports
662
+ end
663
+ end
664
+
665
+ def pe_exports f=@io
666
+ return @exports if @exports
667
+ return nil unless pe(f) && pe(f).ioh && f
668
+ dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT]
669
+ return nil if !dir || (dir.va == 0 && dir.size == 0)
670
+ va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT].va
671
+ file_offset = va2file(va)
672
+ return nil unless file_offset
673
+ if !f.checked_seek(file_offset) || f.eof?
674
+ logger.warn "[?] exports info beyond EOF"
675
+ return nil
676
+ end
677
+ @exports = IMAGE_EXPORT_DIRECTORY.read(f).tap do |x|
678
+ x.entry_points = []
679
+ x.name_ordinals = []
680
+ x.names = []
681
+ if x.Name.to_i != 0 && (ofs = va2file(x.Name))
682
+ f.seek ofs
683
+ if f.eof?
684
+ logger.warn "[?] export ofs 0x#{ofs.to_s(16)} beyond EOF"
685
+ nil
686
+ else
687
+ x.name = f.gets("\x00").chomp("\x00")
688
+ end
689
+ end
690
+ if x.NumberOfFunctions.to_i > 0
691
+ if x.AddressOfFunctions.to_i !=0 && (ofs = va2file(x.AddressOfFunctions))
692
+ f.seek ofs
693
+ x.entry_points = []
694
+ x.NumberOfFunctions.times do
695
+ if f.eof?
696
+ logger.warn "[?] got EOF while reading exports entry_points"
697
+ break
698
+ end
699
+ x.entry_points << f.read(4).unpack('V').first
700
+ end
701
+ end
702
+ if x.AddressOfNameOrdinals.to_i !=0 && (ofs = va2file(x.AddressOfNameOrdinals))
703
+ f.seek ofs
704
+ x.name_ordinals = []
705
+ x.NumberOfNames.times do
706
+ if f.eof?
707
+ logger.warn "[?] got EOF while reading exports name_ordinals"
708
+ break
709
+ end
710
+ x.name_ordinals << f.read(2).unpack('v').first + x.Base
711
+ end
712
+ end
713
+ end
714
+ if x.NumberOfNames.to_i > 0 && x.AddressOfNames.to_i !=0 && (ofs = va2file(x.AddressOfNames))
715
+ f.seek ofs
716
+ x.names = []
717
+ x.NumberOfNames.times do
718
+ if f.eof?
719
+ logger.warn "[?] got EOF while reading exports names"
720
+ break
721
+ end
722
+ x.names << f.read(4).unpack('V').first
723
+ end
724
+ nErrors = 0
725
+ x.names.size.times do |i|
726
+ begin
727
+ f.seek va2file(x.names[i])
728
+ x.names[i] = f.gets("\x00").to_s.chomp("\x00")
729
+ rescue
730
+ nErrors += 1
731
+ if nErrors > MAX_ERRORS
732
+ logger.warn "[?] too many errors getting export names, stopped on #{i} of #{x.names.size}"
733
+ x.names = x.names[0,i]
734
+ break
735
+ end
736
+ nil
737
+ end
738
+ end
739
+ end
740
+
741
+ ord2name = {}
742
+ if x.names && x.names.any?
743
+ n = x.NumberOfNames
744
+ if n > 2048
745
+ logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to 2048"
746
+ n = 2048
747
+ end
748
+ n.times do |i|
749
+ ord2name[x.name_ordinals[i]] ||= []
750
+ ord2name[x.name_ordinals[i]] << x.names[i]
751
+ end
752
+ end
753
+
754
+ x.functions = []
755
+ x.entry_points.each_with_index do |ep,i|
756
+ names = ord2name[i+x.Base]
757
+ names = names.join(', ') if names
758
+ next if ep.to_i == 0 && names.nil?
759
+ x.functions << ExportedFunction.new(names, i+x.Base, ep)
760
+ end
761
+ end
762
+ end
763
+
764
+ ##############################################################################
765
+ # TLS
766
+ ##############################################################################
767
+
768
+ def tls f=@io
769
+ @tls ||= pe(f) && pe(f).ioh && f &&
770
+ begin
771
+ dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::TLS]
772
+ return nil if !dir || dir.va == 0
773
+ return nil unless file_offset = va2file(dir.va)
774
+ f.seek file_offset
775
+ if f.eof?
776
+ logger.info "[?] TLS info beyond EOF"
777
+ return nil
778
+ end
779
+
780
+ klass = @pe.x64? ? IMAGE_TLS_DIRECTORY64 : IMAGE_TLS_DIRECTORY32
781
+ nEntries = [1,dir.size / klass.const_get('SIZE')].max
782
+ r = []
783
+ nEntries.times do
784
+ break if f.eof? || !(entry = klass.read(f))
785
+ r << entry
786
+ end
787
+ r
788
+ end
789
+ end
790
+
791
+ ##############################################################################
792
+ # resources
793
+ ##############################################################################
794
+
795
+ def resources f=@io
796
+ @resources ||=
797
+ if pe(f)
798
+ _scan_pe_resources(f)
799
+ elsif ne(f)
800
+ ne(f).resources(f)
801
+ end
802
+ end
803
+
804
+ def version_info f=@io
805
+ resources(f) && resources(f).find_all{ |res| res.type == 'VERSION' }.map(&:data).flatten
806
+ end
807
+
808
+ ##############################################################################
809
+ # packer / compiler detection
810
+ ##############################################################################
811
+
812
+ def packer f=@io
813
+ @packer ||= pe(f) && @pe.ioh &&
814
+ begin
815
+ if PEdump::Packer.all.size == 0
816
+ logger.error "[?] no packer definitions found"
817
+ nil
818
+ else
819
+ Packer.of f, :pedump => self
820
+ end
821
+ end
822
+ end
823
+ alias :packers :packer
824
+ end
825
+
826
+ ####################################################################################
827
+
828
+ if $0 == __FILE__
829
+ require 'pp'
830
+ dump = PEdump.new(ARGV.shift).dump
831
+ if ARGV.any?
832
+ ARGV.each do |arg|
833
+ if dump.respond_to?(arg)
834
+ pp dump.send(arg)
835
+ elsif arg == 'restore_bitmaps'
836
+ File.open(dump.fname,"rb") do |fi|
837
+ r = dump.resources.
838
+ find_all{ |r| %w'ICON BITMAP CURSOR'.include?(r.type) }.
839
+ each do |r|
840
+ fname = r.name.tr("/# ",'_')+".bmp"
841
+ puts "[.] #{fname}"
842
+ File.open(fname,"wb"){ |fo| fo << r.restore_bitmap(fi) }
843
+ if mask = r.bitmap_mask(fi)
844
+ fname.sub! '.bmp', '.mask.bmp'
845
+ puts "[.] #{fname}"
846
+ File.open(fname,"wb"){ |fo| fo << r.bitmap_mask(fi) }
847
+ end
848
+ end
849
+ end
850
+ exit
851
+ else
852
+ puts "[?] invalid arg #{arg.inspect}"
853
+ end
854
+ end
855
+ exit
856
+ end
857
+ p dump.mz
858
+ dump.dos_stub.hexdump if dump.dos_stub
859
+ puts
860
+ if dump.rich_hdr
861
+ dump.rich_hdr.hexdump
862
+ puts
863
+ p(dump.rich_hdr.decode)
864
+ dump.rich_hdr.dexor.hexdump
865
+ end
866
+ pp dump.pe
867
+ pp dump.resources
868
+ end