pedump 0.5.1 → 0.6.1

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.
@@ -2,6 +2,7 @@
2
2
  require 'stringio'
3
3
  require 'iostruct'
4
4
  require 'zhexdump'
5
+ require 'set'
5
6
 
6
7
  unless Object.new.respond_to?(:try) && nil.respond_to?(:try)
7
8
  require 'pedump/core_ext/try'
@@ -16,6 +17,7 @@ require 'pedump/security'
16
17
  require 'pedump/packer'
17
18
  require 'pedump/ne'
18
19
  require 'pedump/ne/version_info'
20
+ require 'pedump/te'
19
21
 
20
22
  # pedump.rb by zed_0xff
21
23
  #
@@ -27,6 +29,10 @@ class PEdump
27
29
 
28
30
  VERSION = Version::STRING
29
31
  MAX_ERRORS = 100
32
+ MAX_IMAGE_IMPORT_DESCRIPTORS = 1000
33
+ MAX_EXPORT_NUMBER_OF_NAMES = 16384 # got 7977 in http://pedump.me/03ad7400080678c6b1984f995d36fd04
34
+ GOOD_FUNCTION_NAME_RE = /\A[\x21-\x7f]+\Z/
35
+ SUPPORTED_SIGNATURES = ['MZ', 'ZM', 'VZ']
30
36
 
31
37
  @@logger = nil
32
38
 
@@ -255,7 +261,7 @@ class PEdump
255
261
 
256
262
  # http://ntcore.com/files/richsign.htm
257
263
  class RichHdr < String
258
- attr_accessor :offset, :key # xor key
264
+ attr_accessor :offset, :skip, :key # xor key
259
265
 
260
266
  class Entry < Struct.new(:version,:id,:times)
261
267
  def inspect
@@ -264,8 +270,21 @@ class PEdump
264
270
  end
265
271
 
266
272
  def self.from_dos_stub stub
273
+ #stub.hexdump
267
274
  key = stub[stub.index('Rich')+4,4]
268
275
  start_idx = stub.index(key.xor('DanS'))
276
+ skip = 0
277
+ if start_idx
278
+ skip = 4
279
+ else
280
+ PEdump.logger.warn "[?] cannot find rich_hdr start_idx, using heuristics"
281
+ start_idx = stub.index("$\x00\x00\x00\x00\x00\x00\x00")
282
+ unless start_idx
283
+ PEdump.logger.warn "[?] heuristics failed :("
284
+ return nil
285
+ end
286
+ start_idx += 8
287
+ end
269
288
  end_idx = stub.index('Rich')+8
270
289
  if stub[end_idx..-1].tr("\x00",'') != ''
271
290
  t = stub[end_idx..-1]
@@ -273,14 +292,16 @@ class PEdump
273
292
  PEdump.logger.error "[!] non-zero dos stub after rich_hdr: #{t.inspect}"
274
293
  return nil
275
294
  end
295
+ #stub[start_idx, end_idx-start_idx].hexdump
276
296
  RichHdr.new(stub[start_idx, end_idx-start_idx]).tap do |x|
277
297
  x.key = key
278
298
  x.offset = stub.offset + start_idx
299
+ x.skip = skip
279
300
  end
280
301
  end
281
302
 
282
303
  def dexor
283
- self[4..-9].sub(/\A(#{Regexp::escape(key)}){3}/,'').xor(key)
304
+ self[skip..-9].sub(/\A(#{Regexp::escape(key)}){3}/,'').xor(key)
284
305
  end
285
306
 
286
307
  def decode
@@ -318,9 +339,9 @@ class PEdump
318
339
  @mz ||= f && MZ.read(f).tap do |mz|
319
340
  if mz.signature != 'MZ' && mz.signature != 'ZM'
320
341
  if @force
321
- logger.warn "[?] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}"
342
+ #logger.warn "[?] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}"
322
343
  else
323
- logger.error "[!] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}. (not forced)"
344
+ #logger.error "[!] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}. (not forced)"
324
345
  return nil
325
346
  end
326
347
  end
@@ -376,6 +397,13 @@ class PEdump
376
397
  def va2file va, h={}
377
398
  return nil if va.nil?
378
399
 
400
+ va0 = va # save for log output of original addr
401
+ if pe?
402
+ # most common case, do nothing
403
+ elsif te?
404
+ va = va - te_shift()
405
+ end
406
+
379
407
  sections.each do |s|
380
408
  if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
381
409
  offset = va - s.VirtualAddress
@@ -407,9 +435,9 @@ class PEdump
407
435
  # TODO: not all VirtualAdresses == 0 case
408
436
 
409
437
  if h[:quiet]
410
- logger.debug "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)} (quiet=true)"
438
+ logger.debug "[?] can't find file_offset of VA 0x#{va0.to_i.to_s(16)} (quiet=true)"
411
439
  else
412
- logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
440
+ logger.error "[?] can't find file_offset of VA 0x#{va0.to_i.to_s(16)}"
413
441
  end
414
442
  nil
415
443
  end
@@ -427,16 +455,22 @@ class PEdump
427
455
  end
428
456
 
429
457
  def _dump_handle h
430
- return unless pe(h) # also calls mz(h)
431
- rich_hdr h
432
- resources h
433
- imports h # also calls tls(h)
434
- exports h
435
- packer h
458
+ if pe(h) # also calls mz(h)
459
+ rich_hdr h
460
+ resources h
461
+ imports h # also calls tls(h)
462
+ exports h
463
+ packer h
464
+ elsif te(h)
465
+ end
436
466
  end
437
467
 
438
468
  def data_directory f=@io
439
- pe(f) && pe.ioh && pe.ioh.DataDirectory
469
+ if pe(f)
470
+ pe.ioh && pe.ioh.DataDirectory
471
+ elsif te(f)
472
+ te.DataDirectory
473
+ end
440
474
  end
441
475
 
442
476
  def sections f=@io
@@ -444,16 +478,52 @@ class PEdump
444
478
  pe.section_table
445
479
  elsif ne(f)
446
480
  ne.segments
481
+ elsif te(f)
482
+ te.sections
447
483
  end
448
484
  end
449
485
  alias :section_table :sections
450
486
 
451
- def ne?
452
- @pe ? false : (@ne ? true : (pe ? false : (ne ? true : false)))
487
+ def supported_file? f=@io
488
+ pos = f.tell
489
+ sig = f.read(2)
490
+ f.seek(pos)
491
+ if SUPPORTED_SIGNATURES.include?(sig)
492
+ true
493
+ else
494
+ unless @not_supported_sig_warned
495
+ msg = "no supported signature. want: #{SUPPORTED_SIGNATURES.join("/")}, got: #{sig.inspect}"
496
+ if @force
497
+ logger.warn "[?] #{msg}"
498
+ else
499
+ logger.error "[!] #{msg}. (not forced)"
500
+ end
501
+ @not_supported_sig_warned = true
502
+ end
503
+ false
504
+ end
505
+ end
506
+
507
+ def _detect_format
508
+ return :pe if @pe
509
+ return :ne if @ne
510
+ return :te if @te
511
+ return :pe if pe()
512
+ return :ne if ne()
513
+ return :te if te()
514
+ nil
453
515
  end
454
516
 
455
517
  def pe?
456
- @pe ? true : (@ne ? false : (pe ? true : false ))
518
+ _detect_format() == :pe
519
+ end
520
+
521
+ def ne?
522
+ _detect_format() == :ne
523
+ end
524
+
525
+ def te?
526
+ _detect_format() == :te
457
527
  end
458
528
 
459
529
  ##############################################################################
@@ -498,6 +568,8 @@ class PEdump
498
568
  pe_imports(f)
499
569
  elsif ne(f)
500
570
  ne(f).imports
571
+ else
572
+ []
501
573
  end
502
574
  end
503
575
 
@@ -527,7 +599,11 @@ class PEdump
527
599
  # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
528
600
  break
529
601
  end
530
- t=IMAGE_IMPORT_DESCRIPTOR.read(f)
602
+ if r.size >= MAX_IMAGE_IMPORT_DESCRIPTORS
603
+ logger.warn "[!] too many IMAGE_IMPORT_DESCRIPTORs, not reading more than #{r.size}"
604
+ break
605
+ end
606
+ t = IMAGE_IMPORT_DESCRIPTOR.read(f)
531
607
  break if t.Name.to_i == 0 # also catches EOF
532
608
  r << t
533
609
  file_offset += IMAGE_IMPORT_DESCRIPTOR::SIZE
@@ -536,8 +612,16 @@ class PEdump
536
612
  logger.warn "[?] imports info beyond EOF"
537
613
  end
538
614
 
615
+ n_bad_names = 0
539
616
  logger.warn "[?] non-empty last IMAGE_IMPORT_DESCRIPTOR: #{t.inspect}" if t && !t.empty?
540
- @imports = r.each do |x|
617
+ @imports = r
618
+ r = nil
619
+ @imports.each_with_index do |x, iidx|
620
+ if n_bad_names > MAX_ERRORS
621
+ logger.warn "[!] too many bad imported function names. skipping further imports parsing"
622
+ @imports = @imports[0,iidx]
623
+ break
624
+ end
541
625
  if x.Name.to_i != 0 && (ofs = va2file(x.Name))
542
626
  begin
543
627
  f.seek ofs
@@ -572,12 +656,18 @@ class PEdump
572
656
  logger.warn "[?] import ofs 0x#{ofs.to_s(16)} VA=0x#{t.to_s(16)} beyond EOF"
573
657
  nil
574
658
  else
575
- ImportedFunction.new(
576
- f.read(2).unpack('v').first,
577
- f.gets("\x00").chomp("\x00"),
578
- nil,
579
- va
580
- )
659
+ hint = f.read(2).unpack('v').first
660
+ name = f.gets("\x00").to_s.chomp("\x00")
661
+ if !name.empty? && name !~ GOOD_FUNCTION_NAME_RE
662
+ n_bad_names += 1
663
+ if n_bad_names > MAX_ERRORS
664
+ nil
665
+ else
666
+ ImportedFunction.new(hint, name, nil, va)
667
+ end
668
+ else
669
+ ImportedFunction.new(hint, name, nil, va)
670
+ end
581
671
  end
582
672
  elsif tbl == :original_first_thunk
583
673
  # OriginalFirstThunk entries can not be invalid, show a warning msg
@@ -592,7 +682,7 @@ class PEdump
592
682
  end
593
683
  end
594
684
  x[tbl] && x[tbl].compact!
595
- end
685
+ end # [:original_first_thunk, :first_thunk].each
596
686
  if x.original_first_thunk && !x.first_thunk
597
687
  logger.warn "[?] import table: empty FirstThunk for #{x.module_name}"
598
688
  elsif !x.original_first_thunk && x.first_thunk
@@ -603,7 +693,8 @@ class PEdump
603
693
  logger.debug "[?] import table: OriginalFirstThunk != FirstThunk for #{x.module_name}"
604
694
  end
605
695
  end
606
- end
696
+ end # r.each
697
+ @imports
607
698
  end
608
699
 
609
700
  ##############################################################################
@@ -720,9 +811,9 @@ class PEdump
720
811
  ord2name = {}
721
812
  if x.names && x.names.any?
722
813
  n = x.NumberOfNames
723
- if n > 2048
724
- logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to 2048"
725
- n = 2048
814
+ if n > MAX_EXPORT_NUMBER_OF_NAMES
815
+ logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to #{MAX_EXPORT_NUMBER_OF_NAMES}"
816
+ n = MAX_EXPORT_NUMBER_OF_NAMES
726
817
  end
727
818
  n.times do |i|
728
819
  ord2name[x.name_ordinals[i]] ||= []
@@ -1,5 +1,6 @@
1
1
  require 'pedump'
2
2
  require 'pedump/packer'
3
+ require 'pedump/rich'
3
4
  require 'pedump/version_info'
4
5
  require 'optparse'
5
6
 
@@ -33,7 +34,7 @@ class PEdump::CLI
33
34
  attr_accessor :data, :argv
34
35
 
35
36
  KNOWN_ACTIONS = (
36
- %w'mz dos_stub rich pe ne data_directory sections tls security' +
37
+ %w'mz dos_stub rich pe ne te data_directory sections tls security' +
37
38
  %w'strings resources resource_directory imports exports version_info packer web console packer_only'
38
39
  ).map(&:to_sym)
39
40
 
@@ -65,8 +66,8 @@ class PEdump::CLI
65
66
  @options[:force] ||= 0
66
67
  @options[:force] += 1
67
68
  end
68
- opts.on "-f", "--format FORMAT", [:binary, :c, :dump, :hex, :inspect, :table, :yaml],
69
- "Output format: bin,c,dump,hex,inspect,table,yaml","(default: table)" do |v|
69
+ opts.on "-f", "--format FORMAT", [:binary, :c, :dump, :hex, :inspect, :json, :table, :yaml],
70
+ "Output format: bin,c,dump,hex,inspect,json,table,yaml","(default: table)" do |v|
70
71
  @options[:format] = v
71
72
  end
72
73
  KNOWN_ACTIONS.each do |t|
@@ -135,7 +136,7 @@ class PEdump::CLI
135
136
  File.open(fname,'rb') do |f|
136
137
  @pedump = create_pedump fname
137
138
 
138
- next if !@options[:force] && !@pedump.mz(f)
139
+ next if !@options[:force] && !@pedump.supported_file?(f)
139
140
 
140
141
  @actions.each do |action|
141
142
  case action
@@ -194,16 +195,14 @@ class PEdump::CLI
194
195
  end
195
196
 
196
197
  class ProgressProxy
197
- attr_reader :pbar
198
-
199
- def initialize file
200
- @file = file
201
- @pbar = ProgressBar.new("[.] uploading", file.size, STDOUT)
202
- @pbar.try(:file_transfer_mode)
203
- @pbar.bar_mark = '='
198
+ def initialize file, prefix = "[.] uploading: ", io = STDOUT
199
+ @file = file
200
+ @io = io
201
+ @prefix = prefix
204
202
  end
205
203
  def read *args
206
- @pbar.inc args.first
204
+ @io.write("\r#{@prefix}#{@file.tell}/#{@file.size} ")
205
+ @io.flush
207
206
  @file.read *args
208
207
  end
209
208
  def method_missing *args
@@ -212,6 +211,10 @@ class PEdump::CLI
212
211
  def respond_to? *args
213
212
  @file.respond_to?(args.first) || super(*args)
214
213
  end
214
+
215
+ def finish!
216
+ @io.write("\r#{@prefix}#{@file.size}/#{@file.size} \n")
217
+ end
215
218
  end
216
219
 
217
220
  def upload f
@@ -224,7 +227,6 @@ class PEdump::CLI
224
227
  require 'open-uri'
225
228
  require 'net/http'
226
229
  require 'net/http/post/multipart'
227
- require 'progressbar'
228
230
 
229
231
  stdout_sync = STDOUT.sync
230
232
  STDOUT.sync = true
@@ -250,15 +252,15 @@ class PEdump::CLI
250
252
 
251
253
  f.rewind
252
254
 
253
- # upload with progressbar
255
+ # upload with progress
254
256
  post_url = URI.parse(URL_BASE+'/')
257
+ # UploadIO is from multipart-post
255
258
  uio = UploadIO.new(f, "application/octet-stream", File.basename(f.path))
256
259
  ppx = ProgressProxy.new(uio)
257
260
  req = Net::HTTP::Post::Multipart.new post_url.path, "file" => ppx
258
261
  res = Net::HTTP.start(post_url.host, post_url.port){ |http| http.request(req) }
259
- ppx.pbar.finish
262
+ ppx.finish!
260
263
 
261
- puts
262
264
  puts "[.] analyzing..."
263
265
 
264
266
  if (r=open(File.join(URL_BASE,md5,'analyze')).read) != "OK"
@@ -326,7 +328,7 @@ class PEdump::CLI
326
328
 
327
329
  puts action_title(action) unless @options[:format] == :binary
328
330
 
329
- return dump(data) if [:inspect, :table, :yaml].include?(@options[:format])
331
+ return dump(data) if [:inspect, :table, :json, :yaml].include?(@options[:format])
330
332
 
331
333
  dump_opts = {:name => action}
332
334
  case action
@@ -376,6 +378,9 @@ class PEdump::CLI
376
378
  when :yaml
377
379
  require 'yaml'
378
380
  puts data.to_yaml
381
+ when :json
382
+ require 'json'
383
+ puts data.to_json
379
384
  end
380
385
  end
381
386
 
@@ -454,6 +459,8 @@ class PEdump::CLI
454
459
  case data.first
455
460
  when PEdump::IMAGE_DATA_DIRECTORY
456
461
  dump_data_dir data
462
+ when PEdump::EFI_IMAGE_DATA_DIRECTORY
463
+ dump_efi_data_dir data
457
464
  when PEdump::IMAGE_SECTION_HEADER
458
465
  dump_sections data
459
466
  when PEdump::Resource
@@ -778,19 +785,26 @@ class PEdump::CLI
778
785
  end
779
786
  end
780
787
 
781
-
782
788
  def dump_data_dir data
783
789
  data.each do |row|
784
790
  printf " %-12s rva:0x%8x size:0x %8x\n", row.type, row.va.to_i, row.size.to_i
785
791
  end
786
792
  end
787
793
 
794
+ def dump_efi_data_dir data
795
+ data.each_with_index do |row, idx|
796
+ printf " %-12s rva:0x%8x size:0x %8x\n", PEdump::EFI_IMAGE_DATA_DIRECTORY::TYPES[idx], row.va.to_i, row.size.to_i
797
+ end
798
+ end
799
+
788
800
  def dump_rich_hdr data
789
801
  if decoded = data.decode
790
- puts " LIB_ID VERSION TIMES_USED "
802
+ puts " ID VER COUNT DESCRIPTION"
791
803
  decoded.each do |row|
792
- printf " %5d %2x %7d %4x %7d %3x\n",
793
- row.id, row.id, row.version, row.version, row.times, row.times
804
+ # printf " %5d %4x %7d %4x %12d %8x\n",
805
+ # row.id, row.id, row.version, row.version, row.times, row.times
806
+ printf " %4x %4x %12d %s\n",
807
+ row.id, row.version, row.times, ::PEdump::RICH_IDS[(row.id<<16) + row.version]
794
808
  end
795
809
  else
796
810
  puts "# raw:"
@@ -15,7 +15,15 @@ class PEdump::Loader
15
15
 
16
16
  # shortcuts
17
17
  alias :pe :pe_hdr
18
- def ep; @pe_hdr.ioh.AddressOfEntryPoint; end
18
+
19
+ def ep
20
+ if @pe_hdr
21
+ @pe_hdr.try(:ioh).try(:AddressOfEntryPoint)
22
+ elsif @te_hdr
23
+ @te_hdr.AddressOfEntryPoint - @delta
24
+ end
25
+ end
26
+
19
27
  def ep= v; @pe_hdr.ioh.AddressOfEntryPoint=v; end
20
28
 
21
29
  ########################################################################
@@ -23,12 +31,24 @@ class PEdump::Loader
23
31
  ########################################################################
24
32
 
25
33
  def initialize io = nil, params = {}
34
+ # @delta is for EFI TE loading, based on https://github.com/gdbinit/TELoader/blob/master/teloader.cpp
35
+ @delta = 0
26
36
  @pedump = PEdump.new(io, params)
27
37
  if io
28
38
  @mz_hdr = @pedump.mz
29
39
  @dos_stub = @pedump.dos_stub
30
40
  @pe_hdr = @pedump.pe
31
- @image_base = params[:image_base] || @pe_hdr.try(:ioh).try(:ImageBase) || 0
41
+ @te_hdr = @pedump.te
42
+
43
+ @image_base = params[:image_base]
44
+ if @pe_hdr
45
+ @image_base ||= @pe_hdr.try(:ioh).try(:ImageBase)
46
+ elsif @te_hdr
47
+ @image_base ||= @te_hdr.ImageBase
48
+ @delta = @pedump.te_shift
49
+ end
50
+ @image_base ||= 0
51
+
32
52
  load_sections @pedump.sections, io
33
53
  end
34
54
  @find_limit = params[:find_limit] || DEFAULT_FIND_LIMIT
@@ -38,7 +58,7 @@ class PEdump::Loader
38
58
  if section_hdrs.is_a?(Array)
39
59
  @sections = section_hdrs.map do |x|
40
60
  raise "unknown section hdr: #{x.inspect}" unless x.is_a?(PEdump::IMAGE_SECTION_HEADER)
41
- Section.new(x, :deferred_load_io => f, :image_base => @image_base )
61
+ Section.new(x, :deferred_load_io => f, :image_base => @image_base, :delta => @delta )
42
62
  end
43
63
  if f.respond_to?(:seek) && f.respond_to?(:read)
44
64
  #
@@ -249,10 +269,12 @@ class PEdump::Loader
249
269
  def names
250
270
  return @names if @names
251
271
  @names = {}
252
- if oep = @pe_hdr.try(:ioh).try(:AddressOfEntryPoint)
253
- oep += @image_base
254
- @names[oep] = 'start'
272
+
273
+ oep = ep()
274
+ if oep
275
+ @names[oep + @image_base] = 'start'
255
276
  end
277
+
256
278
  _parse_imports
257
279
  _parse_exports
258
280
  #TODO: debug info