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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +85 -34
- data/README.md +89 -72
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/pedump +1 -1
- data/data/jc-userdb.txt +0 -8
- data/data/signatures.txt +1 -2
- data/data/userdb.txt +0 -8
- data/lib/pedump/cli.rb +305 -48
- data/lib/pedump/clr/readytorun.rb +115 -0
- data/lib/pedump/clr/signature.rb +318 -0
- data/lib/pedump/clr.rb +709 -0
- data/lib/pedump/logger.rb +1 -1
- data/lib/pedump.rb +41 -5
- data/pedump.gemspec +8 -5
- metadata +8 -8
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
|
-
%
|
|
38
|
-
%
|
|
39
|
-
%
|
|
40
|
-
).
|
|
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 - %
|
|
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
|
-
|
|
88
|
+
|
|
89
|
+
opts.separator ''
|
|
90
|
+
KNOWN_ACTIONS.sort.each do |t|
|
|
75
91
|
a = [
|
|
76
|
-
"--#{t.to_s.tr('_','-')}",
|
|
77
|
-
|
|
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
|
-
|
|
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 :
|
|
347
|
-
|
|
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.
|
|
356
|
-
|
|
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
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
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) <
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|