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.
Files changed (70) hide show
  1. data/.travis.yml +4 -0
  2. data/Gemfile +10 -6
  3. data/Gemfile.lock +27 -19
  4. data/README.md +37 -25
  5. data/Rakefile +45 -6
  6. data/VERSION +1 -1
  7. data/data/fs.txt +37 -1408
  8. data/data/jc-userdb.txt +14371 -0
  9. data/data/sig.bin +0 -0
  10. data/lib/pedump.rb +355 -618
  11. data/lib/pedump/cli.rb +214 -113
  12. data/lib/pedump/comparer.rb +147 -0
  13. data/lib/pedump/composite_io.rb +56 -0
  14. data/lib/pedump/core.rb +38 -0
  15. data/lib/pedump/core_ext/try.rb +57 -0
  16. data/lib/pedump/loader.rb +393 -0
  17. data/lib/pedump/loader/minidump.rb +187 -0
  18. data/lib/pedump/loader/section.rb +57 -0
  19. data/lib/pedump/logger.rb +67 -0
  20. data/lib/pedump/ne.rb +425 -0
  21. data/lib/pedump/ne/version_info.rb +171 -0
  22. data/lib/pedump/packer.rb +50 -2
  23. data/lib/pedump/pe.rb +121 -0
  24. data/lib/pedump/resources.rb +436 -0
  25. data/lib/pedump/security.rb +58 -0
  26. data/lib/pedump/sig_parser.rb +145 -24
  27. data/lib/pedump/tls.rb +17 -0
  28. data/lib/pedump/unpacker.rb +26 -0
  29. data/lib/pedump/unpacker/aspack.rb +858 -0
  30. data/lib/pedump/unpacker/upx.rb +13 -0
  31. data/lib/pedump/version.rb +1 -1
  32. data/lib/pedump/version_info.rb +15 -10
  33. data/misc/aspack/Makefile +3 -0
  34. data/misc/aspack/aspack_unlzx.c +92 -0
  35. data/misc/aspack/lzxdec.c +479 -0
  36. data/misc/aspack/lzxdec.h +56 -0
  37. data/misc/nedump.c +751 -0
  38. data/pedump.gemspec +75 -25
  39. data/samples/bad/68.exe +0 -0
  40. data/samples/bad/data_dir_15_entries.exe +0 -0
  41. data/spec/65535sects_spec.rb +8 -0
  42. data/spec/bad_imports_spec.rb +20 -0
  43. data/spec/bad_samples_spec.rb +13 -0
  44. data/spec/composite_io_spec.rb +122 -0
  45. data/spec/data/calc.exe_sections.yml +49 -0
  46. data/spec/data/data_dir_15_entries.exe_sections.yml +95 -0
  47. data/spec/dllord_spec.rb +21 -0
  48. data/spec/foldedhdr_spec.rb +28 -0
  49. data/spec/imports_badterm_spec.rb +52 -0
  50. data/spec/imports_vterm_spec.rb +52 -0
  51. data/spec/loader/names_spec.rb +24 -0
  52. data/spec/loader/va_spec.rb +44 -0
  53. data/spec/manyimportsW7_spec.rb +22 -0
  54. data/spec/ne_spec.rb +125 -0
  55. data/spec/packer_spec.rb +17 -0
  56. data/spec/pe_spec.rb +67 -0
  57. data/spec/pedump_spec.rb +16 -4
  58. data/spec/sections_spec.rb +11 -0
  59. data/spec/sig_all_packers_spec.rb +15 -5
  60. data/spec/sig_spec.rb +6 -1
  61. data/spec/spec_helper.rb +15 -3
  62. data/spec/support/samples.rb +24 -0
  63. data/spec/unpackers/aspack_spec.rb +69 -0
  64. data/spec/unpackers/find_spec.rb +21 -0
  65. data/spec/virtsectblXP_spec.rb +12 -0
  66. data/tmp/.keep +0 -0
  67. metadata +146 -35
  68. data/README.md.tpl +0 -90
  69. data/samples/calc.7z +0 -0
  70. data/samples/zlib.dll +0 -0
Binary file
@@ -1,115 +1,47 @@
1
1
  #!/usr/bin/env ruby
2
- require 'logger'
3
- require 'pedump/version'
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 = Version::STRING
28
+ VERSION = Version::STRING
29
+ MAX_ERRORS = 100
42
30
 
43
31
  @@logger = nil
44
32
 
45
- def initialize fname, params = {}
46
- @fname = fname
47
- @force = params[:force]
48
- @logger = @@logger = params[:logger] || PEdump::Logger.new(STDERR)
49
- end
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 = create_struct( "a2v13Qv2V6",
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 < create_struct( 'v2V3v2',
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 < create_struct( 'vC2V9v6V4v2V6',
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 < create_struct( 'vC2V5QV2v6V4v2Q4V2',
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 = create_struct( "VV", :va, :size, :type )
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 = create_struct( 'A8V6v2V',
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=nil
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=nil
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 <= 0
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=nil
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 pe f=nil
443
- @pe ||=
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
- if (nToRead=pe.ifh.NumberOfSections) > 32
475
- if @force.is_a?(Numeric) && @force > 1
476
- logger.warn "[!] too many sections (#{pe.ifh.NumberOfSections}). forced. reading all"
477
- else
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
- end
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
- def resource_directory f=nil
491
- @resource_directory ||= _read_resource_directory_tree(f)
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=nil
496
- f ? _dump_handle(f) : File.open(@fname,'rb'){ |f| _dump_handle(f) }
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
- rich_hdr(h) # includes mz(h)
502
- resources(h) # includes pe(h)
503
- imports h
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 h
435
+ packer h
506
436
  end
507
437
 
508
- def data_directory f=nil
438
+ def data_directory f=@io
509
439
  pe(f) && pe.ioh && pe.ioh.DataDirectory
510
440
  end
511
441
 
512
- def sections f=nil
513
- pe(f) && pe.section_table
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 = create_struct 'V5',
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 = Struct.new(:hint, :name, :ordinal)
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=nil
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
- va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT].va
542
- file_offset = va2file(va)
508
+ file_offset = va2file(dir.va)
543
509
  return nil unless file_offset
544
- f.seek file_offset
545
- r = []
546
- until (t=IMAGE_IMPORT_DESCRIPTOR.read(f)).empty?
547
- r << t
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 && (va = va2file(x.Name))
551
- f.seek va
552
- x.module_name = f.gets("\x00").chop
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 && (va = va2file(x[camel]))
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 & (2**(bits-1)) > 0 # 0x8000_0000(_0000_0000)
570
- ImportedFunction.new(nil,nil,t & (2**(bits-1)-1)) # 0x7fff_ffff(_ffff_ffff)
571
- elsif va=va2file(t)
572
- f.seek va
573
- ImportedFunction.new(f.read(2).unpack('v').first, f.gets("\x00").chop)
574
- else
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 of #{x.module_name}"
596
+ logger.warn "[?] import table: empty FirstThunk for #{x.module_name}"
582
597
  elsif !x.original_first_thunk && x.first_thunk
583
- logger.warn "[?] import table: empty OriginalFirstThunk of #{x.module_name}"
584
- elsif x.original_first_thunk != x.first_thunk
585
- logger.warn "[?] import table: OriginalFirstThunk != FirstThunk of #{x.module_name}"
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 = create_struct 'V2v2V7',
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
- def exports f=nil
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 [] if !dir || (dir.va == 0 && dir.size == 0)
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.seek file_offset
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 && (va = va2file(x.Name))
624
- f.seek va
625
- x.name = f.gets("\x00").chop
626
- end
627
- if x.NumberOfFunctions.to_i != 0
628
- if x.AddressOfFunctions.to_i !=0 && (va = va2file(x.AddressOfFunctions))
629
- f.seek va
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
- end
645
- end
646
-
647
- ##############################################################################
648
- # resources
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
- PEdump.logger.error "[!] #{self}: #{nToRead} entries in directory, but got EOF on #{i}-th."
670
+ logger.warn "[?] got EOF while reading exports entry_points"
674
671
  break
675
672
  end
676
- if (@@loopchk2[f.tell] += 1) > 1
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
- #r.entries.uniq!
683
- r.entries.each do |entry|
684
- entry.name =
685
- if entry.Name.to_i & 0x8000_0000 > 0
686
- # Name is an address of unicode string
687
- f.seek base + entry.Name & 0x7fff_ffff
688
- nChars = f.read(2).to_s.unpack("v").first.to_i
689
- begin
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
- end
718
- end
719
-
720
- IMAGE_RESOURCE_DIRECTORY_ENTRY = create_struct 'V2',
721
- :Name, :OffsetToData,
722
- :name, :data
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
- data << CURDIRENTRY.read(f)
854
- nRead += CURDIRENTRY::SIZE
696
+ x.names << f.read(4).unpack('V').first
855
697
  end
856
- when 'GROUP_ICON'
857
- f.seek file_offset
858
- data << CUR_ICO_HEADER.read(f)
859
- nRead = CUR_ICO_HEADER::SIZE
860
- data.last.wNumImages.to_i.times do
861
- if nRead >= self.size
862
- PEdump.logger.error "[!] refusing to read ICODIRENTRY beyond resource size"
863
- break
864
- end
865
- data << ICODIRENTRY.read(f)
866
- nRead += ICODIRENTRY::SIZE
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
- data.delete_if do |x|
899
- valid = !x.respond_to?(:valid?) || x.valid?
900
- PEdump.logger.warn "[?] ignoring invalid #{x.class}" unless valid
901
- !valid
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
- def validate
908
- self.valid =
909
- case type
910
- when 'BITMAP','ICON','CURSOR'
911
- data.any?{ |x| x.is_a?(BITMAPINFOHEADER) && x.valid? } || data.first == 'PNG'
912
- else
913
- true
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
- STRING = Struct.new(:id, :lang, :value)
738
+ ##############################################################################
739
+ # TLS
740
+ ##############################################################################
919
741
 
920
- def strings f=nil
921
- r = []
922
- Array(resources(f)).find_all{ |x| x.type == 'STRING'}.each do |res|
923
- res.data.each_with_index do |string,idx|
924
- r << STRING.new( ((res.id-1)<<4) + idx, res.lang, string ) unless string.empty?
925
- end
926
- end
927
- r
928
- end
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
- # see also http://www.informit.com/articles/article.aspx?p=1186882 about icons format
931
-
932
- class BITMAPINFOHEADER < create_struct 'V3v2V6',
933
- :biSize, # BITMAPINFOHEADER::SIZE
934
- :biWidth,
935
- :biHeight,
936
- :biPlanes,
937
- :biBitCount,
938
- :biCompression,
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
- # http://www.devsource.com/c/a/Architecture/Resources-From-PE-I/2/
951
- CUR_ICO_HEADER = create_struct('v3',
952
- :wReserved, # always 0
953
- :wResID, # always 2
954
- :wNumImages # Number of cursor images/directory entries
955
- )
765
+ ##############################################################################
766
+ # resources
767
+ ##############################################################################
956
768
 
957
- CURDIRENTRY = create_struct 'v4Vv',
958
- :wWidth,
959
- :wHeight, # Divide by 2 to get the actual height.
960
- :wPlanes,
961
- :wBitCount,
962
- :dwBytesInImage,
963
- :wID
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=nil
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
- def _scan_resources f=nil, dir=nil
991
- dir ||= resource_directory(f)
992
- return nil unless dir
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 = nil
786
+ def packer f=@io
1034
787
  @packer ||= pe(f) && @pe.ioh &&
1035
788
  begin
1036
- if !(va=@pe.ioh.AddressOfEntryPoint)
1037
- logger.error "[?] can't find EntryPoint RVA"
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
- require 'pedump/packer'
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
- require './lib/hexdump_helper' if File.exist?("lib/hexdump_helper.rb")
1092
- if defined?(HexdumpHelper)
1093
- include HexdumpHelper
1094
- puts hexdump(dump.dos_stub) if dump.dos_stub
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
- if dump.rich_hdr
1097
- puts hexdump(dump.rich_hdr)
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