pedump 0.5.1 → 0.6.1

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