pedump 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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