pedump 0.6.10 → 0.7.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.
data/lib/pedump/cli.rb CHANGED
@@ -31,15 +31,28 @@ rescue LoadError
31
31
  end
32
32
 
33
33
  class PEdump::CLI
34
- attr_accessor :data, :argv
34
+ attr_accessor :data, :argv, :success
35
+
36
+ SHORTCUT_ACTIONS = {
37
+ clr: %i'clr_header clr_readytorun clr_metadata clr_streams clr_strings clr_tables',
38
+ }
39
+
40
+ ACTION_COMMENTS = {}
41
+ SHORTCUT_ACTIONS.each do |k,v|
42
+ ACTION_COMMENTS[k] = 'a shortcut for --' + v.join(', --')
43
+ end
44
+
45
+ ACTION_ARGS = {
46
+ clr_tables: "[TABLES]",
47
+ }
35
48
 
36
49
  KNOWN_ACTIONS = (
37
- %w'mz dos_stub rich pe ne te data_directory sections tls security' +
38
- %w'strings resources resource_directory imports exports version_info imphash packer web console packer_only' +
39
- %w'extract' # 'disasm'
40
- ).map(&:to_sym)
50
+ %i'mz dos_stub rich pe ne te data_directory clr_header clr_metadata clr_readytorun clr_streams clr_strings clr_tables sections tls security' +
51
+ %i'strings resources resource_directory imports exports version_info imphash packer web console packer_only' +
52
+ %i'extract tail'
53
+ ) + SHORTCUT_ACTIONS.keys
41
54
 
42
- DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %w'resource_directory web packer_only console extract disasm'.map(&:to_sym)
55
+ DEFAULT_ALL_ACTIONS = KNOWN_ACTIONS - %i'resource_directory web packer_only console extract disasm clr clr_tables' - SHORTCUT_ACTIONS.keys
43
56
 
44
57
  URL_BASE = "https://pedump.me"
45
58
 
@@ -48,6 +61,7 @@ class PEdump::CLI
48
61
  end
49
62
 
50
63
  def run
64
+ @success = true
51
65
  @actions = []
52
66
  @options = { :format => :table, :verbose => 0 }
53
67
  optparser = OptionParser.new do |opts|
@@ -71,16 +85,23 @@ class PEdump::CLI
71
85
  "Output format: bin,c,dump,hex,inspect,json,table,yaml","(default: table)" do |v|
72
86
  @options[:format] = v
73
87
  end
74
- KNOWN_ACTIONS.each do |t|
88
+
89
+ opts.separator ''
90
+ KNOWN_ACTIONS.sort.each do |t|
75
91
  a = [
76
- "--#{t.to_s.tr('_','-')}",
77
- eval("lambda{ |_| @actions << :#{t.to_s.tr('-','_')} }")
92
+ "--#{t.to_s.tr('_','-')}" + (ACTION_ARGS[t] ? " #{ACTION_ARGS[t]}" : ""),
93
+ ACTION_COMMENTS[t] || t.to_s,
94
+ lambda{ |arg| @actions << (arg && ACTION_ARGS[t] ? [t, arg] : t) },
78
95
  ]
79
96
  a.unshift(a[0][1,2].upcase) if a[0] =~ /--(((ex|im)port|section|resource)s|version-info)/
80
97
  a.unshift(a[0][1,2]) if a[0] =~ /--strings/
81
98
  opts.on *a
82
99
  end
83
100
 
101
+ opts.separator ''
102
+ opts.on "--tokens", "Show CLR tokens" do
103
+ @options[:tokens] = true
104
+ end
84
105
  opts.on "--deep", "packer deep scan, significantly slower" do
85
106
  @options[:deep] ||= 0
86
107
  @options[:deep] += 1
@@ -111,13 +132,21 @@ class PEdump::CLI
111
132
  "ID: section:.text - section by name",
112
133
  "ID: section:rva/0x1000 - section by RVA",
113
134
  "ID: section:raw/0x400 - section by RAW_PTR",
135
+ "ID: tail - file tail",
136
+ "ID: tail:c00 - file tail + 0xc00 offset",
114
137
  ) do |v|
115
138
  @actions << [:extract, v]
116
139
  end
117
- opts.on "--va2file VA", "Convert RVA to file offset" do |va|
140
+
141
+ opts.separator ''
142
+ opts.on "--va2file VA", "Convert VA to file offset" do |va|
118
143
  @actions << [:va2file, va]
119
144
  end
145
+ opts.on "--file2va OFFSET", "Convert file offset to VA" do |offset|
146
+ @actions << [:file2va, offset]
147
+ end
120
148
 
149
+ opts.separator ''
121
150
  opts.on "--set-os-version VER", "Patch OS version in PE header" do |ver|
122
151
  @actions << [:set_os_version, ver]
123
152
  end
@@ -152,6 +181,12 @@ class PEdump::CLI
152
181
  return
153
182
  end
154
183
 
184
+ SHORTCUT_ACTIONS.each do |k,v|
185
+ if idx = @actions.index(k)
186
+ @actions[idx,1] = *v
187
+ end
188
+ end
189
+
155
190
  argv.each_with_index do |fname,idx|
156
191
  @need_fname_header = (argv.size > 1)
157
192
  @file_idx = idx
@@ -172,9 +207,11 @@ class PEdump::CLI
172
207
  end
173
208
  end
174
209
  end
210
+ @success
175
211
  rescue Errno::EPIPE
176
212
  # output interrupt, f.ex. when piping output to a 'head' command
177
213
  # prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
214
+ @success
178
215
  end
179
216
 
180
217
  def create_pedump io
@@ -331,6 +368,8 @@ class PEdump::CLI
331
368
  puts "# -----------------------------------------------"
332
369
  end
333
370
 
371
+ action = action.first if action.is_a?(Array)
372
+
334
373
  s = action.to_s.upcase.tr('_',' ')
335
374
  s += " Header" if [:mz, :pe, :rich].include?(action)
336
375
  s = "Packer / Compiler" if action == :packer
@@ -339,27 +378,35 @@ class PEdump::CLI
339
378
  end
340
379
 
341
380
  def dump_action action, f
381
+ data = nil
382
+ got_data = false
383
+
342
384
  if action.is_a?(Array)
343
385
  case action[0]
344
386
  when :disasm
345
387
  return
346
- when :extract
347
- return extract action[1]
348
- when :set_os_version
349
- return set_os_version action[1]
350
- when :set_dll_char
351
- return set_dll_char action[1]
352
- when :va2file
388
+ when :va2file, :file2va
389
+ cmd = action[0]
353
390
  @pedump.sections(f)
354
391
  va = action[1] =~ /(^0x)|(h$)/i ? action[1].to_i(16) : action[1].to_i
355
- file_offset = @pedump.va2file(va)
356
- printf "va2file(0x%x) = 0x%x (%d)\n", va, file_offset, file_offset
392
+ file_offset = @pedump.send(cmd, va)
393
+ if file_offset
394
+ printf("%s(0x%x) = 0x%x (%d)\n", cmd, va, file_offset, file_offset)
395
+ else
396
+ @success = false
397
+ end
357
398
  return
358
- else raise "unknown action #{action.inspect}"
399
+ else
400
+ if respond_to?(action[0])
401
+ return send(*action)
402
+ else
403
+ data = @pedump.send(*action)
404
+ got_data = true
405
+ end
359
406
  end
360
407
  end
361
408
 
362
- data = @pedump.send(action, f)
409
+ data = @pedump.send(action, f) unless got_data
363
410
  return if !data || (data.respond_to?(:empty?) && data.empty?)
364
411
 
365
412
  puts action_title(action) unless @options[:format] == :binary || @actions == [:imphash]
@@ -421,26 +468,47 @@ class PEdump::CLI
421
468
  end
422
469
 
423
470
  COMMENTS = {
424
- :Machine => {
425
- 0x014c => 'x86',
426
- 0x0200 => 'Intel Itanium',
427
- 0x8664 => 'x64',
428
- 'default' => '???'
471
+ PEdump::IMAGE_FILE_HEADER => {
472
+ Characteristics: Proc.new{ |v| _flags2string(v.flags) },
473
+ Machine: {
474
+ 0x014c => 'x86',
475
+ 0x0200 => 'Intel Itanium',
476
+ 0x8664 => 'x64',
477
+ default: '???'
478
+ }
429
479
  },
430
- :Magic => {
431
- 0x010b => '32-bit executable',
432
- 0x020b => '64-bit executable',
433
- 0x0107 => 'ROM image',
434
- 'default' => '???'
480
+ PEdump::IMAGE_COR20_HEADER => {
481
+ Flags: Proc.new{ |v| _flags2string(v.flags) },
482
+ },
483
+ PEdump::CLR::MetadataTableStreamHeader => {
484
+ HeapSizes: Proc.new{ |v| _flags2string(v.heap_sizes) },
485
+ Valid: Proc.new{ |v| _flags2string(v.valid_flags) },
486
+ Sorted: Proc.new{ |v| _flags2string(v.sorted_flags) },
487
+ },
488
+ PEdump::CLR::READYTORUN_CORE_HEADER => {
489
+ Flags: Proc.new{ |v| _flags2string(v.flags) },
435
490
  },
436
- :Subsystem => PEdump::IMAGE_SUBSYSTEMS
437
491
  }
492
+ [PEdump::IMAGE_OPTIONAL_HEADER32, PEdump::IMAGE_OPTIONAL_HEADER64].each do |klass|
493
+ COMMENTS[klass] = {
494
+ Magic: {
495
+ 0x010b => '32-bit executable',
496
+ 0x020b => '64-bit executable',
497
+ 0x0107 => 'ROM image',
498
+ default: '???'
499
+ },
500
+ Subsystem: PEdump::IMAGE_SUBSYSTEMS,
501
+ DllCharacteristics: Proc.new{ |v| _flags2string(v.flags) },
502
+ }
503
+ end
438
504
 
439
- def _flags2string flags
505
+ def self._flags2string flags, max_width: 80
440
506
  return '' if !flags || flags.empty?
441
- a = [flags.shift.dup]
507
+ flags.map!{ |f| f.is_a?(String) ? f.dup : f.to_s }
508
+
509
+ a = [flags.shift]
442
510
  flags.each do |f|
443
- if (a.last.size + f.size) < 40
511
+ if (a.last.size + f.size) < max_width
444
512
  a.last << ", " << f
445
513
  else
446
514
  a << f.dup
@@ -452,6 +520,7 @@ class PEdump::CLI
452
520
  def dump_generic_table data
453
521
  data.each_pair do |k,v|
454
522
  next if [:DataDirectory, :section_table].include?(k)
523
+
455
524
  case v
456
525
  when Numeric
457
526
  case k
@@ -461,17 +530,35 @@ class PEdump::CLI
461
530
  when /TimeDateStamp/
462
531
  printf "%30s: %24s\n", k, Time.at(v).utc.strftime('"%Y-%m-%d %H:%M:%S"')
463
532
  else
464
- comment = ''
465
- if COMMENTS[k]
466
- comment = COMMENTS[k][v] || (COMMENTS[k].is_a?(Hash) ? COMMENTS[k]['default'] : '') || ''
467
- elsif data.is_a?(PEdump::IMAGE_FILE_HEADER) && k == :Characteristics
468
- comment = _flags2string(data.flags)
469
- elsif k == :DllCharacteristics
470
- comment = _flags2string(data.flags)
471
- end
533
+ comment =
534
+ if COMMENTS[data.class] && cmt=COMMENTS[data.class][k]
535
+ case cmt
536
+ when Hash
537
+ cmt[v] || cmt[:default]
538
+ when Array
539
+ cmt[v]
540
+ when Proc
541
+ cmt.call(data)
542
+ else
543
+ raise "invalid comment type: #{cmt.inspect}"
544
+ end
545
+ end
546
+ comment ||= ''
472
547
  comment.strip!
473
548
  comment = " #{comment}" unless comment.empty?
474
- printf "%30s: %10d %12s%s\n", k, v, v<10 ? v : ("0x"+v.to_s(16)), comment
549
+ if v.to_s.size > 10 || v < 10
550
+ # don't show decimal
551
+ printf "%30s: %6s %16s%s\n", k, "", v.to_s(16), comment
552
+ else
553
+ printf "%30s: %10d %12s%s\n", k, v, v.to_s(16), comment
554
+ end
555
+ end
556
+ when PEdump::IMAGE_DATA_DIRECTORY
557
+ # in CLR headers
558
+ if v.va.to_i != 0 && v.size.to_i != 0
559
+ printf "%30s: {va: %x, size: %x}\n", k, v.va, v.size
560
+ else
561
+ printf "%30s:\n", k
475
562
  end
476
563
  when Struct
477
564
  # IMAGE_FILE_HEADER:
@@ -481,15 +568,44 @@ class PEdump::CLI
481
568
  when Time
482
569
  printf "%30s: %24s\n", k, v.strftime('"%Y-%m-%d %H:%M:%S"')
483
570
  else
484
- printf "%30s: %24s\n", k, v.to_s.inspect
571
+ if v.is_a?(Array) && v.all?{ |x| x.is_a?(Struct) }
572
+ a = v
573
+ printf "%30s: %24s\n", k, a[0]
574
+ a[1..-1].each do |v|
575
+ printf "%30s %24s\n", "", v
576
+ end
577
+ else
578
+ printf "%30s: %24s\n", k, v.to_s.inspect
579
+ end
485
580
  end
486
581
  end
487
582
  end
488
583
 
489
584
  def dump_table data, opts = {}
585
+ case data
586
+ when File
587
+ # tail
588
+ printf "0x%x (%d) bytes starting at 0x%x:\n\n", data.size-data.tell, data.size-data.tell, data.tell
589
+
590
+ a = data.read(4096).to_hexdump.split("\n", 11)
591
+ if a.size > 10
592
+ a[10] = "..."
593
+ end
594
+ puts a.join("\n")
595
+ return
596
+ when PEdump::CLR::TablesHash
597
+ return dump_clr_tables data
598
+ when PEdump::CLR::StringsHash
599
+ data.each do |offset, str|
600
+ printf "%8x: %s\n", offset, str.inspect
601
+ end
602
+ return
603
+ end
604
+
490
605
  if data.is_a?(Struct)
491
606
  return dump_res_dir(data) if data.is_a?(PEdump::IMAGE_RESOURCE_DIRECTORY)
492
607
  return dump_exports(data) if data.is_a?(PEdump::IMAGE_EXPORT_DIRECTORY)
608
+
493
609
  dump_generic_table data
494
610
  elsif data.is_a?(Enumerable) && data.map(&:class).uniq.size == 1
495
611
  case data.first
@@ -515,8 +631,10 @@ class PEdump::CLI
515
631
  dump_security data
516
632
  when PEdump::NE::Segment
517
633
  dump_ne_segments data
634
+ when PEdump::CLR::MetadataStreamHeader
635
+ dump_clr_streams data
518
636
  else
519
- puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
637
+ puts "[?] dump_table: don't know how to dump: #{data.inspect[0,50]}" unless (data.respond_to?(:empty?) && data.empty?)
520
638
  end
521
639
  elsif data.is_a?(PEdump::DOSStub)
522
640
  data.hexdump
@@ -532,6 +650,136 @@ class PEdump::CLI
532
650
  end
533
651
  end
534
652
 
653
+ def dump_clr_streams data
654
+ clr_header = @pedump.clr_header
655
+ clr_data_file_ofs = clr_header.MetaData.va.to_i > 0 && @pedump.va2file(clr_header.MetaData.va)
656
+
657
+ fmt = "%8x %8x %-32s\n"
658
+ printf fmt.tr('x','s'), *%w'offset size name'
659
+ data.each do |s|
660
+ printf fmt, s.offset, s.size, s.name.inspect
661
+ if clr_data_file_ofs && s.offset < clr_header.MetaData.size
662
+ if s.name == "#-" || s.name == "#~"
663
+ pos = clr_data_file_ofs + s.offset
664
+ @pedump.io.seek(pos)
665
+ hdr = PEdump::CLR::MetadataTableStreamHeader.read(@pedump.io)
666
+ dump hdr
667
+ end
668
+ end
669
+ end
670
+ end
671
+
672
+ def dump_clr_tables data
673
+ strings = @pedump.clr_strings rescue {}
674
+ strings ||= {}
675
+ blob_stream = @pedump.clr_streams.find{ |s| s.name == "#Blob" }
676
+
677
+ clr_header = @pedump.clr_header
678
+ clr_data_file_ofs = clr_header.MetaData.va.to_i > 0 && @pedump.va2file(clr_header.MetaData.va)
679
+ blob_file_ofs = blob_stream.offset + clr_data_file_ofs if blob_stream
680
+ blob_size = blob_stream&.size
681
+
682
+ prev_signature = nil
683
+ data.each do |key, table|
684
+ table_idx = PEdump::CLR::MetadataTableStreamHeader::FLAGS.keys.index(key)
685
+ printf "# %02x %s: [%d]\n", table_idx, key, table.size
686
+ table.each_with_index do |row, idx|
687
+ next if idx == 0 && row.nil?
688
+
689
+ if @options[:tokens]
690
+ token = (table_idx << 24) | idx
691
+ printf "%08x: ", token
692
+ else
693
+ printf "%6x: ", idx
694
+ end
695
+ print row.to_table
696
+
697
+ comments = []
698
+ if row.respond_to?(:decode_Class)
699
+ table_key, idx = row.decode_Class
700
+ if table_key && idx
701
+ referred_row = data[table_key]&.at(idx)
702
+ comments << referred_row&.get_name(strings)
703
+ end
704
+ end
705
+
706
+ signature = nil
707
+ if row.respond_to?(:Signature) && blob_file_ofs && blob_size && row.Signature < blob_size
708
+ @pedump.io.seek(blob_file_ofs + row.Signature + 1)
709
+ signature = PEdump::CLR::Signature.read(@pedump.io, key)
710
+ end
711
+
712
+ comments << row.get_name(strings) || row.to_s
713
+ if comments.any?
714
+ name_with_ns = comments.join(key == :MemberRef ? '.' : ' ')
715
+ if signature
716
+ prefix =
717
+ if signature.respond_to?(:ret_type)
718
+ "#{signature.ret_type} "
719
+ elsif signature.respond_to?(:type)
720
+ "#{signature.type} "
721
+ else
722
+ ''
723
+ end
724
+
725
+ suffix = ''
726
+ if signature.respond_to?(:params) && signature.params
727
+ suffix = '(' + signature.params.join(', ') + ')'
728
+ end
729
+
730
+ name_with_ns = prefix + name_with_ns + suffix
731
+ name_with_ns.gsub!(/(?:CLASS|VALUE) (\h{7,8})/) do |orig|
732
+ id = $1.to_i(16)
733
+ table_id = id >> 24
734
+ table_key = PEdump::CLR::MetadataTableStreamHeader::FLAGS.keys[table_id]
735
+ idx = id & 0xffffff
736
+ referred_row = data[table_key]&.at(idx)
737
+ referred_name = referred_row&.get_name(strings) || referred_row&.to_s
738
+ referred_name || orig
739
+ end
740
+ name_with_ns.gsub!(/System\.Nullable\`1 <([^<>]+)>/, '\1?')
741
+ end
742
+ printf " %s", name_with_ns
743
+ end
744
+ puts
745
+
746
+ if @options[:verbose] > 0
747
+ was = false
748
+ h = row.to_h
749
+ h.keys.each do |field_key|
750
+ if row.respond_to?("decode_#{field_key}")
751
+ was = true
752
+ a = row.send("decode_#{field_key}")
753
+ printf "%20s: %-20s", field_key, a.inspect
754
+ table_key, idx = a
755
+ if table_key && idx
756
+ referred_row = data[table_key]&.at(idx)
757
+ if referred_row
758
+ printf "%s", referred_row.get_name(strings) || referred_row.to_s
759
+ end
760
+ end
761
+ puts
762
+ end
763
+ if field_key == :Signature && signature && prev_signature != row.Signature
764
+ printf "%20s: ", "Signature"
765
+ @pedump.io.seek(blob_file_ofs + row.Signature + 1)
766
+ sig_data = @pedump.io.read(32) # not real length
767
+ puts sig_data.bytes.map{ |b| "%02x" % b }.join(' ')
768
+
769
+ printf "%20s ", ""
770
+ p signature
771
+
772
+ was = true
773
+ prev_signature = row.Signature
774
+ end
775
+ end
776
+ puts if was
777
+ end
778
+ end
779
+ puts
780
+ end
781
+ end
782
+
535
783
  def dump_security data
536
784
  return unless data
537
785
  data.each do |win_cert|
@@ -675,7 +923,8 @@ class PEdump::CLI
675
923
  data.each do |x|
676
924
  case x
677
925
  when PEdump::IMAGE_IMPORT_DESCRIPTOR
678
- (Array(x.original_first_thunk) + Array(x.first_thunk)).uniq.each do |f|
926
+ # (Array(x.original_first_thunk) + Array(x.first_thunk)).uniq.each do |f|
927
+ (x.original_first_thunk || x.first_thunk).each do |f|
679
928
  next unless f
680
929
  # imported function
681
930
  printf fmt,
@@ -869,6 +1118,8 @@ class PEdump::CLI
869
1118
  extract_resource a[1]
870
1119
  when 'section'
871
1120
  extract_section a[1]
1121
+ when 'tail'
1122
+ extract_tail a[1]
872
1123
  else
873
1124
  raise "invalid #{x.inspect}"
874
1125
  end
@@ -931,6 +1182,12 @@ class PEdump::CLI
931
1182
  _copy_stream @pedump.io, $stdout, section.SizeOfRawData, section.PointerToRawData
932
1183
  end
933
1184
 
1185
+ def extract_tail offset
1186
+ io = @pedump.tail
1187
+ io.seek(offset.to_i(16), IO::SEEK_CUR) if offset
1188
+ _copy_stream io, $stdout
1189
+ end
1190
+
934
1191
  def set_dll_char x
935
1192
  @pedump.pe.image_optional_header.DllCharacteristics = x.to_i(0)
936
1193
  io = @pedump.io.reopen(@file_name,'rb+')
@@ -977,7 +1234,7 @@ class PEdump::CLI
977
1234
 
978
1235
  # https://github.com/zed-0xff/pedump/issues/44
979
1236
  # https://redmine.ruby-lang.org/issues/12280
980
- def _copy_stream(src, dst, src_length = nil, src_offset = 0)
1237
+ def _copy_stream(src, dst, src_length = nil, src_offset = nil)
981
1238
  IO::copy_stream(src, dst, src_length, src_offset)
982
1239
  rescue NotImplementedError # `copy_stream': pread() not implemented (NotImplementedError)
983
1240
  src_length ||= src.size - src_offset
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+ #coding: binary
3
+
4
+ # https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/readytorun-format.md
5
+ class PEdump
6
+ module CLR
7
+ class READYTORUN_HEADER < IOStruct.new(
8
+ 'LSS',
9
+
10
+ :Signature,
11
+ :MajorVersion,
12
+ :MinorVersion,
13
+ :CoreHeader # READYTORUN_CORE_HEADER - dynamic size!
14
+ )
15
+
16
+ MAGIC = 0x00525452 # 'RTR\0'
17
+
18
+ def valid?
19
+ self.Signature == MAGIC
20
+ end
21
+
22
+ def self.read io
23
+ super.tap do |r|
24
+ r.CoreHeader = READYTORUN_CORE_HEADER.read(io)
25
+ end
26
+ end
27
+ end
28
+
29
+ class READYTORUN_CORE_HEADER < IOStruct.new('LL', :Flags, :NumberOfSections, :Sections)
30
+ FLAGS = {
31
+ 0x01 => 'PLATFORM_NEUTRAL_SOURCE', # Set if the original IL image was platform neutral. The platform neutrality is part of assembly name. This flag can be used to reconstruct the full original assembly name.
32
+ 0x02 => 'COMPOSITE', # The image represents a composite R2R file resulting from a combined compilation of a larger number of input MSIL assemblies.
33
+ 0x04 => 'PARTIAL',
34
+ 0x08 => 'NONSHARED_PINVOKE_STUBS', # PInvoke stubs compiled into image are non-shareable (no secret parameter)
35
+ 0x10 => 'EMBEDDED_MSIL', # Input MSIL is embedded in the R2R image.
36
+ 0x20 => 'COMPONENT', # is a component assembly of a composite R2R image
37
+ 0x40 => 'MULTIMODULE_VERSION_BUBBLE', # has multiple modules within its version bubble (For versions before version 6.3, all modules are assumed to possibly have this characteristic)
38
+ 0x80 => 'UNRELATED_R2R_CODE' # has code in it that would not be naturally encoded into this module
39
+ }
40
+
41
+ def flags
42
+ FLAGS.find_all{ |k,v| (self.Flags & k) != 0 }.map(&:last)
43
+ end
44
+
45
+ def self.read io
46
+ super.tap do |r|
47
+ r.Sections = r.NumberOfSections.times.map do
48
+ READYTORUN_SECTION.read(io)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/readytorun-format.md#readytorun_section
55
+ class READYTORUN_SECTION < IOStruct.new('L',
56
+ :Type,
57
+ :Section # IMAGE_DATA_DIRECTORY
58
+ )
59
+
60
+ SECTION_TYPES = {
61
+ 100 => "CompilerIdentifier", # Image
62
+ 101 => "ImportSections", # Image
63
+ 102 => "RuntimeFunctions", # Image
64
+ 103 => "MethodDefEntryPoints", # Assembly
65
+ 104 => "ExceptionInfo", # Assembly
66
+ 105 => "DebugInfo", # Assembly
67
+ 106 => "DelayLoadMethodCallThunks", # Assembly
68
+ 107 => "AvailableTypes", # (obsolete - used by an older format)
69
+ 108 => "AvailableTypes", # Assembly
70
+ 109 => "InstanceMethodEntryPoints", # Image
71
+ 110 => "InliningInfo", # Assembly (added in V2.1)
72
+ 111 => "ProfileDataInfo", # Image (added in V2.2)
73
+ 112 => "ManifestMetadata", # Image (added in V2.3)
74
+ 113 => "AttributePresence", # Assembly (added in V3.1)
75
+ 114 => "InliningInfo2", # Image (added in V4.1)
76
+ 115 => "ComponentAssemblies", # Image (added in V4.1)
77
+ 116 => "OwnerCompositeExecutable", # Image (added in V4.1)
78
+ 117 => "PgoInstrumentationData", # Image (added in V5.2)
79
+ 118 => "ManifestAssemblyMvids", # Image (added in V5.3)
80
+ 119 => "CrossModuleInlineInfo", # Image (added in V6.3)
81
+ 120 => "HotColdMap", # Image (added in V8.0)
82
+ 121 => "MethodIsGenericMap", # Assembly (Added in V9.0)
83
+ 122 => "EnclosingTypeMap", # Assembly (Added in V9.0)
84
+ 123 => "TypeGenericInfoMap", # Assembly (Added in V9.0)
85
+ }
86
+
87
+ def to_s
88
+ "<%s Type=%3d va=%8x size=%8x> %s" % [self.class, self.Type, self.Section.va, self.Section.size, SECTION_TYPES[self.Type] || "?"]
89
+ end
90
+
91
+ def self.read io
92
+ super.tap do |r|
93
+ r.Section = IMAGE_DATA_DIRECTORY.read(io)
94
+ end
95
+ end
96
+ end
97
+ end # module CLR
98
+
99
+ def clr_readytorun f=@io
100
+ return nil unless hdr = clr_header(f)
101
+
102
+ dir = hdr.ManagedNativeHeader
103
+ return nil if !dir || (dir.va.to_i == 0 && dir.size.to_i == 0)
104
+
105
+ file_offset = va2file(dir.va)
106
+ return nil unless file_offset
107
+
108
+ f.seek(file_offset)
109
+ magic = f.read(4).unpack1('L')
110
+ return nil if magic != CLR::READYTORUN_HEADER::MAGIC
111
+
112
+ f.seek(file_offset)
113
+ CLR::READYTORUN_HEADER.read(f)
114
+ end
115
+ end