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/clr.rb ADDED
@@ -0,0 +1,709 @@
1
+ #coding: binary
2
+ require 'iostruct'
3
+
4
+ require_relative 'clr/signature'
5
+ require_relative 'clr/readytorun'
6
+
7
+ class PEdump
8
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.25.3.3-cli-header.md
9
+ class IMAGE_COR20_HEADER < IOStruct.new(
10
+ 'V S2 Q VV Q6',
11
+ :cb,
12
+ :MajorRuntimeVersion,
13
+ :MinorRuntimeVersion,
14
+ :MetaData, # IMAGE_DATA_DIRECTORY
15
+ :Flags,
16
+ :EntryPointToken,
17
+ # IMAGE_DATA_DIRECTORies:
18
+ :Resources,
19
+ :StrongNameSignature,
20
+ :CodeManagerTable,
21
+ :VTableFixups,
22
+ :ExportAddressTableJumps,
23
+ :ManagedNativeHeader
24
+ )
25
+ FLAGS = {
26
+ 0x00001 => 'ILONLY',
27
+ 0x00002 => '32BITREQUIRED',
28
+ 0x00004 => 'IL_LIBRARY',
29
+ 0x00008 => 'STRONGNAMESIGNED',
30
+ 0x00010 => 'NATIVE_ENTRYPOINT',
31
+ 0x10000 => 'TRACKDEBUGDATA',
32
+ }
33
+
34
+ FLAGS.each{ |k,v| const_set("COMIMAGE_FLAGS_#{v}", k) }
35
+
36
+ def flags
37
+ FLAGS.find_all{ |k,v| (self.Flags & k) != 0 }.map(&:last)
38
+ end
39
+
40
+ def self.read io
41
+ super.tap do |r|
42
+ %i'MetaData Resources StrongNameSignature CodeManagerTable VTableFixups ExportAddressTableJumps ManagedNativeHeader'.each do |field|
43
+ next unless r[field] # broken CLR header
44
+
45
+ r[field] = IMAGE_DATA_DIRECTORY.read([r[field]].pack('Q'))
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ module CLR
52
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.24.2.1-metadata-root.md
53
+ class MetadataHeader < IOStruct.new(
54
+ 'V S2 VV',
55
+ :Magic,
56
+ :MajorVersion,
57
+ :MinorVersion,
58
+ :ExtraData,
59
+ :VersionLength, # aligned to 4 bytes
60
+ :Version,
61
+ :Flags, # maybe 1 byte of flags + 1 byte of padding
62
+ :NumberOfStreams
63
+ )
64
+ MAGIC = 0x424A5342 # 'BSJB'
65
+
66
+ def valid?
67
+ Magic == MAGIC
68
+ end
69
+
70
+ def self.read io
71
+ super.tap do |r|
72
+ r.Version = io.read(r.VersionLength).unpack1('A*')
73
+ r.Flags = io.read(2).unpack1('S')
74
+ r.NumberOfStreams = io.read(2).unpack1('v')
75
+ end
76
+ end
77
+ end
78
+
79
+ class MetadataStreamHeader < IOStruct.new(
80
+ 'VV',
81
+ :offset,
82
+ :size,
83
+ :name # a zero-terminated ASCII string no longer than 31 characters. The name might be shorter, case the size of the stream header is correspondingly
84
+ # reduced, padded to the 4-byte boundary.
85
+ )
86
+
87
+ def self.read io
88
+ super.tap do |r|
89
+ r.name = ''
90
+ nread = 0
91
+ while !io.eof? && r.name.size < 32
92
+ c = io.read(1)
93
+ nread += 1
94
+ break if c == "\0"
95
+
96
+ r.name << c
97
+ end
98
+ if !io.eof? && nread % 4 != 0
99
+ io.read(4 - nread % 4) # padding
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.24.2.6-metadata-stream.md
106
+ class MetadataTableStreamHeader < IOStruct.new('V CC CCQQ', :Reserved, :MajorVersion, :MinorVersion, :HeapSizes, :Reserved2, :Valid, :Sorted, :Rows)
107
+ attr_accessor :sizes_hash
108
+
109
+ HEAP_SIZES_MANY_STRINGS = 1 # Size of "#Strings" stream ≥ 216
110
+ HEAP_SIZES_MANY_GUIDS = 2 # Size of "#GUID" stream ≥ 216
111
+ HEAP_SIZES_MANY_BLOBS = 4 # Size of "#Blob" stream ≥ 216
112
+
113
+ FLAGS = {
114
+ Module: 0x1,
115
+ TypeRef: 0x2,
116
+ TypeDef: 0x4,
117
+ FiedPtr: 0x8,
118
+ Field: 0x10,
119
+ MethodPtr: 0x20,
120
+ MethodDef: 0x40,
121
+ ParamPtr: 0x80,
122
+ Param: 0x100,
123
+ InterfaceImpl: 0x200,
124
+ MemberRef: 0x400,
125
+ Constant: 0x800,
126
+ CustomAttribute: 0x1000,
127
+ FieldMarshal: 0x2000,
128
+ DeclSecurity: 0x4000,
129
+ ClassLayout: 0x8000,
130
+ FieldLayout: 0x10000,
131
+ StandAloneSig: 0x20000,
132
+ EventMap: 0x40000,
133
+ EventPtr: 0x80000,
134
+ Event: 0x100000,
135
+ PropertyMap: 0x200000,
136
+ PropertyPtr: 0x400000,
137
+ Property: 0x800000,
138
+ MethodSemantics: 0x1000000,
139
+ MethodImpl: 0x2000000,
140
+ ModuleRef: 0x4000000,
141
+ TypeSpec: 0x8000000,
142
+ ImplMap: 0x10000000,
143
+ FieldRVA: 0x20000000,
144
+ EnCLog: 0x40000000,
145
+ EnCMap: 0x80000000,
146
+ Assembly: 0x100000000,
147
+ AssemblyProcessor: 0x200000000,
148
+ AssemblyOS: 0x400000000,
149
+ AssemblyRef: 0x800000000,
150
+ AssemblyRefProcessor: 0x1000000000,
151
+ AssemblyRefOS: 0x2000000000,
152
+ File: 0x4000000000,
153
+ ExportedType: 0x8000000000,
154
+ ManifestResource: 0x10000000000,
155
+ NestedClass: 0x20000000000,
156
+ GenericParam: 0x40000000000,
157
+ MethodSpec: 0x80000000000,
158
+ GenericParamConstraint: 0x100000000000,
159
+ Document: 0x1000000000000,
160
+ MethodDebugInformation: 0x2000000000000,
161
+ LocalScope: 0x4000000000000,
162
+ LocalVariable: 0x8000000000000,
163
+ LocalConstant: 0x10000000000000,
164
+ ImportScope: 0x20000000000000,
165
+ StateMachineMethod: 0x40000000000000,
166
+ CustomDebugInformation: 0x80000000000000,
167
+ }
168
+ INVERSE_FLAGS = FLAGS.invert
169
+
170
+ def known_table? key
171
+ FLAGS.key?(key)
172
+ end
173
+
174
+ def heap_sizes
175
+ sizes = []
176
+ sizes << 'MANY_STRINGS' if self.HeapSizes & HEAP_SIZES_MANY_STRINGS != 0
177
+ sizes << 'MANY_GUIDS' if self.HeapSizes & HEAP_SIZES_MANY_GUIDS != 0
178
+ sizes << 'MANY_BLOBS' if self.HeapSizes & HEAP_SIZES_MANY_BLOBS != 0
179
+ sizes
180
+ end
181
+
182
+ def valid_flags
183
+ FLAGS.find_all{ |k,v| (self.Valid & v) != 0 }.map(&:first)
184
+ end
185
+
186
+ def sorted_flags
187
+ FLAGS.find_all{ |k,v| (self.Sorted & v) != 0 }.map(&:first)
188
+ end
189
+
190
+ def valid?
191
+ self.Reserved == 0 && self.MajorVersion == 2 && self.MinorVersion == 0
192
+ end
193
+
194
+ def self.read io
195
+ super.tap do |r|
196
+ idx = 1
197
+ r.sizes_hash = {}
198
+ n = r.Valid
199
+ while n > 0
200
+ if n & 1 != 0
201
+ key = INVERSE_FLAGS[idx].to_sym
202
+ r.sizes_hash[key] = io.read(4).unpack1('V')
203
+ end
204
+ n >>= 1
205
+ idx <<= 1
206
+ end
207
+ r.Rows = r.sizes_hash.values
208
+ end
209
+ end
210
+ end
211
+
212
+ # reference types
213
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.24.2.6-metadata-stream.md
214
+ CustomAttributeType = [:not_used, :not_used, :MethodDef, :MemberRef, :not_used] # see spec. last :not used is important for it to be 3 bits in size
215
+ HasConstant = [:Field, :Param, :Property]
216
+ HasCustomAttribute = [:MethodDef, :Field, :TypeRef, :TypeDef, :Param, :InterfaceImpl, :MemberRef, :Module, :DeclSecurity, :Property, :Event, :StandAloneSig, :ModuleRef, :TypeSpec, :Assembly, :AssemblyRef, :File, :ExportedType, :ManifestResource, :GenericParam, :GenericParamConstraint, :MethodSpec] # XXX not checked thoroughly
217
+ HasDeclSecurity = [:TypeDef, :MethodDef, :Assembly]
218
+ HasFieldMarshal = [:Field, :Param]
219
+ HasSemantics = [:Event, :Property]
220
+ Implementation = [:File, :AssemblyRef, :ExportedType]
221
+ MemberForwarded = [:Field, :MethodDef]
222
+ MemberRefParent = [:TypeDef, :TypeRef, :ModuleRef, :MethodDef, :TypeSpec]
223
+ MethodDefOrRef = [:MethodDef, :MemberRef]
224
+ ResolutionScope = [:Module, :ModuleRef, :AssemblyRef, :TypeRef]
225
+ TypeDefOrRef = [:TypeDef, :TypeRef, :TypeSpec]
226
+ TypeOrMethodDef = [:TypeDef, :MethodDef]
227
+
228
+ module DynTableMethods
229
+ def get_name(strings)
230
+ h = self.to_h
231
+ if h[:TypeNamespace] || h[:TypeName]
232
+ if h[:TypeNamespace] != 0
233
+ "#{strings[h[:TypeNamespace]]}.#{strings[h[:TypeName]]}"
234
+ else
235
+ strings[h[:TypeName]]
236
+ end
237
+ elsif h[:Name]
238
+ strings[h[:Name]]
239
+ elsif h[:ImportName]
240
+ strings[h[:ImportName]]
241
+ else
242
+ nil
243
+ end
244
+ end
245
+ end
246
+
247
+ TableDefs = {
248
+ # undocumented?
249
+ FieldPtr: { Field: :Field },
250
+ MethodPtr: { Method: :MethodDef },
251
+ ParamPtr: { Param: :Param },
252
+ EventPtr: { Event: :Event },
253
+ PropertyPtr: { Property: :Property },
254
+
255
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.30-module_0x00.md
256
+ Module: {
257
+ Generation: 2, # a 2-byte value, reserved, shall be zero)
258
+ Name: :string, # an index into the String heap
259
+ Mvid: :guid, # an index into the Guid heap
260
+ EncId: :guid, # an index into the Guid heap; reserved, shall be zero
261
+ EncBaseId: :guid, # an index into the Guid heap; reserved, shall be zero
262
+ },
263
+
264
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.38-typeref-0x01.md
265
+ TypeRef: {
266
+ ResolutionScope: ResolutionScope,
267
+ TypeName: :string,
268
+ TypeNamespace: :string,
269
+ },
270
+
271
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.37-typedef-0x02.md
272
+ TypeDef: {
273
+ Flags: 4, # a 4-byte bitmask of TypeAttributes
274
+ TypeName: :string,
275
+ TypeNamespace: :string,
276
+ Extends: TypeDefOrRef,
277
+ FieldList: :Field,
278
+ MethodList: :MethodDef,
279
+ },
280
+
281
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.15-field-0x04.md
282
+ Field: {
283
+ Flags: 2, # a 2-byte bitmask of FieldAttributes
284
+ Name: :string,
285
+ Signature: :blob,
286
+ },
287
+
288
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.26-methoddef-0x06.md
289
+ MethodDef: {
290
+ RVA: 4, # a 4-byte constant
291
+ ImplFlags: 2, # a 2-byte bitmask of MethodImplAttributes
292
+ Flags: 2, # a 2-byte bitmask of MethodAttributes
293
+ Name: :string,
294
+ Signature: :blob,
295
+ ParamList: :Param,
296
+ },
297
+
298
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.33-param-0x08.md
299
+ Param: {
300
+ Flags: 2, # a 2-byte bitmask of ParamAttributes
301
+ Sequence: 2, # a 2-byte constant
302
+ Name: :string,
303
+ },
304
+
305
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.23-interfaceimpl-0x09.md
306
+ InterfaceImpl: {
307
+ Class: :TypeDef,
308
+ Interface: TypeDefOrRef,
309
+ },
310
+
311
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.25-memberref-0x0a.md
312
+ MemberRef: {
313
+ Class: MemberRefParent,
314
+ Name: :string,
315
+ Signature: :blob,
316
+ },
317
+
318
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.9-constant-0x0b.md
319
+ Constant: {
320
+ Type: 2, # a 1-byte constant, followed by a 1-byte padding zero
321
+ Parent: HasConstant,
322
+ Value: :blob,
323
+ },
324
+
325
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.10-customattribute-0x0c.md
326
+ CustomAttribute: {
327
+ Parent: HasCustomAttribute,
328
+ Type: CustomAttributeType,
329
+ Value: :blob,
330
+ },
331
+
332
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.17-fieldmarshal-0x0d.md
333
+ FieldMarshal: {
334
+ Parent: HasFieldMarshal,
335
+ NativeType: :blob,
336
+ },
337
+
338
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.11-declsecurity-0x0e.md
339
+ DeclSecurity: {
340
+ Action: 2, # a 2-byte constant
341
+ Parent: HasDeclSecurity,
342
+ PermissionSet: :blob,
343
+ },
344
+
345
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.8-classlayout-0x0f.md
346
+ ClassLayout: {
347
+ PackingSize: 2, # a 2-byte constant
348
+ ClassSize: 4, # a 4-byte constant
349
+ Parent: :TypeDef,
350
+ },
351
+
352
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.16-fieldlayout-0x10.md
353
+ FieldLayout: {
354
+ Offset: 4, # a 4-byte constant
355
+ Field: :Field,
356
+ },
357
+
358
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.36-standalonesig-0x11.md
359
+ StandAloneSig: {
360
+ Signature: :blob,
361
+ },
362
+
363
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.12-eventmap-0x12.md
364
+ EventMap: {
365
+ Parent: :TypeDef,
366
+ EventList: :Event,
367
+ },
368
+
369
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.13-event-0x14.md
370
+ Event: {
371
+ EventFlags: 2, # a 2-byte bitmask of EventAttributes
372
+ Name: :string,
373
+ EventType: TypeDefOrRef,
374
+ },
375
+
376
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.35-propertymap-0x15.md
377
+ PropertyMap: {
378
+ Parent: :TypeDef,
379
+ PropertyList: :Property,
380
+ },
381
+
382
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.34-property-0x17.md
383
+ Property: {
384
+ Flags: 2, # a 2-byte bitmask of PropertyAttributes
385
+ Name: :string,
386
+ Type: :blob,
387
+ },
388
+
389
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.28-methodsemantics-0x18.md
390
+ MethodSemantics: {
391
+ Semantics: 2, # a 2-byte bitmask of MethodSemanticsAttributes
392
+ Method: :MethodDef,
393
+ Association: HasSemantics,
394
+ },
395
+
396
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.27-methodimpl-0x19.md
397
+ MethodImpl: {
398
+ Class: :TypeDef,
399
+ MethodBody: MethodDefOrRef,
400
+ MethodDeclaration: MethodDefOrRef,
401
+ },
402
+
403
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.31-moduleref-0x1a.md
404
+ ModuleRef: {
405
+ Name: :string,
406
+ },
407
+
408
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.39-typespec-0x1b.md
409
+ TypeSpec: {
410
+ Signature: :blob,
411
+ },
412
+
413
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.22-implmap-0x1c.md
414
+ ImplMap: {
415
+ MappingFlags: 2, # a 2-byte bitmask of PInvokeAttributes
416
+ MemberForwarded: MemberForwarded,
417
+ ImportName: :string,
418
+ ImportScope: :ModuleRef,
419
+ },
420
+
421
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.18-fieldrva-0x1d.md
422
+ FieldRVA: {
423
+ RVA: 4, # a 4-byte constant
424
+ Field: :Field,
425
+ },
426
+
427
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.2-assembly-0x20.md
428
+ Assembly: {
429
+ HashAlgId: 4, # a 4-byte constant
430
+ MajorVersion: 2, # a 2-byte constant
431
+ MinorVersion: 2, # a 2-byte constant
432
+ BuildNumber: 2, # a 2-byte constant
433
+ RevisionNumber: 2, # a 2-byte constant
434
+ Flags: 4, # a 4-byte bitmask of AssemblyFlags
435
+ PublicKey: :blob,
436
+ Name: :string,
437
+ Culture: :string,
438
+ },
439
+
440
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.4-assemblyprocessor-0x21.md
441
+ AssemblyProcessor: {
442
+ Processor: 4, # a 4-byte constant
443
+ },
444
+
445
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.3-assemblyos-0x22.md
446
+ AssemblyOS: {
447
+ OSPlatformId: 4, # a 4-byte constant
448
+ OSMajorVersion: 4, # a 4-byte constant
449
+ OSMinorVersion: 4, # a 4-byte constant
450
+ },
451
+
452
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.5-assemblyref-0x23.md
453
+ AssemblyRef: {
454
+ MajorVersion: 2, # a 2-byte constant
455
+ MinorVersion: 2, # a 2-byte constant
456
+ BuildNumber: 2, # a 2-byte constant
457
+ RevisionNumber: 2, # a 2-byte constant
458
+ Flags: 4, # a 4-byte bitmask of AssemblyFlags
459
+ PublicKeyOrToken: :blob,
460
+ Name: :string,
461
+ Culture: :string,
462
+ HashValue: :blob,
463
+ },
464
+
465
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.7-assemblyrefprocessor-0x24.md
466
+ AssemblyRefProcessor: {
467
+ Processor: 4, # a 4-byte constant
468
+ AssemblyRef: :AssemblyRef,
469
+ },
470
+
471
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.6-assemblyrefos-0x25.md
472
+ AssemblyRefOS: {
473
+ OSPlatformId: 4, # a 4-byte constant
474
+ OSMajorVersion: 4, # a 4-byte constant
475
+ OSMinorVersion: 4, # a 4-byte constant
476
+ AssemblyRef: :AssemblyRef,
477
+ },
478
+
479
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.19-file-0x26.md
480
+ File: {
481
+ Flags: 4, # a 4-byte bitmask of FileAttributes
482
+ Name: :string,
483
+ HashValue: :blob,
484
+ },
485
+
486
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.24-manifestresource-0x28.md
487
+ ManifestResource: {
488
+ Offset: 4, # a 4-byte constant
489
+ Flags: 4, # a 4-byte bitmask of ManifestResourceAttributes
490
+ Name: :string,
491
+ Implementation: Implementation,
492
+ },
493
+
494
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.32-nestedclass-0x29.md
495
+ NestedClass: {
496
+ NestedClass: :TypeDef,
497
+ EnclosingClass: :TypeDef,
498
+ },
499
+
500
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.20-genericparam-0x2a.md
501
+ GenericParam: {
502
+ Number: 2, # a 2-byte constant
503
+ Flags: 2, # a 2-byte bitmask of GenericParamAttributes
504
+ Owner: TypeOrMethodDef,
505
+ Name: :string,
506
+ },
507
+
508
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.29-methodspec-0x2b.md
509
+ MethodSpec: {
510
+ Method: MethodDefOrRef,
511
+ Instantiation: :blob,
512
+ },
513
+
514
+ # https://github.com/stakx/ecma-335/blob/master/docs/ii.22.21-genericparamconstraint-0x2c.md
515
+ GenericParamConstraint: {
516
+ Owner: :GenericParam,
517
+ Constraint: TypeDefOrRef,
518
+ }
519
+ }
520
+
521
+ # needed only for pedump CLI format guessing
522
+ class TablesHash < Hash; end
523
+ class StringsHash < Hash; end
524
+
525
+ def self._create_dynamic_class fields, hdr, name: nil
526
+ table_idx_bits = {}
527
+ string_keys = []
528
+
529
+ decl = fields.map do |k,v|
530
+ case v
531
+ when 2
532
+ 'S'
533
+ when 4
534
+ 'V'
535
+ when :blob
536
+ hdr.HeapSizes & MetadataTableStreamHeader::HEAP_SIZES_MANY_BLOBS != 0 ? 'V' : 'S'
537
+ when :guid
538
+ hdr.HeapSizes & MetadataTableStreamHeader::HEAP_SIZES_MANY_GUIDS != 0 ? 'V' : 'S'
539
+ when :string
540
+ string_keys << k
541
+ hdr.HeapSizes & MetadataTableStreamHeader::HEAP_SIZES_MANY_STRINGS != 0 ? 'V' : 'S'
542
+ when Array
543
+ # pointer to table i out of n possible tables
544
+ n = v.size
545
+ table_idx_bits[k] = bits_for_table_idx = Math.log2(n).ceil
546
+ max_rows = 2**(16 - bits_for_table_idx)
547
+ v.each{ |table_id| raise "Unknown table: #{table_id}" unless table_id == :not_used || hdr.known_table?(table_id) }
548
+ v.any?{ |table_id| hdr.sizes_hash[table_id].to_i >= max_rows } ? 'V' : 'S'
549
+ when Symbol
550
+ raise "Unknown table: #{v}" unless hdr.known_table?(v)
551
+ hdr.sizes_hash[v].to_i < 2**16 ? 'S' : 'V'
552
+ else
553
+ raise "Unknown field type #{v.inspect}"
554
+ end
555
+ end.join
556
+
557
+ IOStruct.new(decl, *fields.keys, inspect_name_override: name).tap do |klass|
558
+ klass.const_set(:STRING_KEYS, string_keys)
559
+ klass.include(DynTableMethods)
560
+ fields.each do |k,v|
561
+ case v
562
+ when Array
563
+ # define 'decode_...' method
564
+ idx_bits = table_idx_bits[k]
565
+ table_id_mask = (1 << idx_bits) - 1
566
+ klass.instance_eval do
567
+ define_method("decode_#{k}") do
568
+ val = self[k]
569
+ table_id = val & table_id_mask
570
+ table_key = v[table_id]
571
+ val >>= idx_bits
572
+ [table_key, val]
573
+ end
574
+ end
575
+ end
576
+ end
577
+ end
578
+ end
579
+ end # module CLR
580
+
581
+ def clr_header f=@io
582
+ return nil unless pe(f) && pe(f).ioh && f
583
+
584
+ dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::CLR_Header]
585
+ return nil if !dir || (dir.va == 0 && dir.size == 0)
586
+
587
+ file_offset = va2file(dir.va)
588
+ return nil unless file_offset
589
+
590
+ if f.checked_seek(file_offset)
591
+ IMAGE_COR20_HEADER.read(f)
592
+ else
593
+ logger.warn "[?] CLR header beyond EOF"
594
+ nil
595
+ end
596
+ end
597
+
598
+ def clr_metadata f=@io
599
+ return nil unless hdr = clr_header(f)
600
+
601
+ dir = hdr&.MetaData
602
+ return nil if !dir || (dir.va.to_i == 0 || dir.size.to_i == 0)
603
+
604
+ file_offset = va2file(dir.va)
605
+ return nil unless file_offset
606
+
607
+ if f.checked_seek(file_offset)
608
+ CLR::MetadataHeader.read(f)
609
+ else
610
+ logger.warn "[?] CLR metadata header beyond EOF"
611
+ nil
612
+ end
613
+ end
614
+
615
+ def clr_streams f=@io
616
+ return nil unless metadata = clr_metadata(f)
617
+
618
+ streams = []
619
+ metadata.NumberOfStreams.times do
620
+ if stream = CLR::MetadataStreamHeader.read(f)
621
+ streams << stream
622
+ else
623
+ logger.warn "[?] Error reading CLR stream header"
624
+ break
625
+ end
626
+ end
627
+ streams
628
+ end
629
+
630
+ def clr_strings f=@io
631
+ return nil unless dir = clr_header(f)&.MetaData
632
+ return nil unless streams = clr_streams(f)
633
+
634
+ strings = CLR::StringsHash.new
635
+ streams.each do |stream|
636
+ next unless stream.name == '#Strings'
637
+
638
+ unless f.checked_seek(va2file(dir.va) + stream.offset)
639
+ logger.warn "[?] Error seeking to CLR strings stream"
640
+ return nil
641
+ end
642
+ pos = 0
643
+ while pos < stream.size && !f.eof?
644
+ s = f.gets("\0")
645
+ break unless s
646
+
647
+ ssize = s.bytesize
648
+ s.chomp!("\0")
649
+ s.force_encoding('utf-8')
650
+ strings[pos] = s
651
+ pos += ssize
652
+ end
653
+
654
+ break
655
+ end
656
+ strings
657
+ end
658
+
659
+ def clr_tables table_ids_or_f=nil
660
+ f = @io
661
+ table_ids = nil
662
+
663
+ case table_ids_or_f
664
+ when IO
665
+ f = table_ids_or_f
666
+ when String
667
+ table_ids = table_ids_or_f.split(/\W/).map(&:to_sym)
668
+ when Array
669
+ table_ids = table_ids_or_f
670
+ end
671
+
672
+ return nil unless dir = clr_header(f)&.MetaData
673
+ return nil unless streams = clr_streams(f)
674
+
675
+ @dynamic_classes ||= {}
676
+
677
+ tables = CLR::TablesHash.new
678
+ streams.each do |stream|
679
+ next if stream.name != '#~' && stream.name != '#-' # Metadata Table Stream
680
+
681
+ unless f.checked_seek(va2file(dir.va) + stream.offset)
682
+ logger.warn "[?] Error seeking to CLR table stream"
683
+ return nil
684
+ end
685
+
686
+ if hdr = CLR::MetadataTableStreamHeader.read(f)
687
+ hdr.sizes_hash.each do |key, nrows|
688
+ raise "Unknown table: #{key}" unless hdr.known_table?(key)
689
+
690
+ if fields = CLR::TableDefs[key]
691
+ klass = @dynamic_classes[key] ||= CLR::_create_dynamic_class(fields, hdr, name: key)
692
+ tables[key] = [nil] # 1-based index, 0-th element is NULL
693
+ nrows.times do
694
+ tables[key] << klass.read(f)
695
+ end
696
+ else
697
+ logger.warn "[?] Unknown CLR table: #{key}"
698
+ end
699
+ end
700
+ else
701
+ logger.warn "[?] Error reading CLR table stream header"
702
+ break
703
+ end
704
+ end
705
+ # tables are layed out sequentially in the file, so ALL of them should be read first, even if only some are requested
706
+ tables.delete_if{ |k,v| !table_ids.include?(k) } if table_ids
707
+ tables
708
+ end
709
+ end