///|
/// .NET metadata (ECMA-335) decoded from the CLR metadata root.
///
/// This is not a decompiler: method bodies and IL stay untouched. It reads the
/// stream directory, counts every present metadata table, and decodes the rows
/// used to identify an assembly, its references, types and methods.
///|
pub(all) struct MetadataStream {
name : String
offset : Int32
size : Int32
} derive(@debug.Debug)
///|
pub(all) struct MetadataTable {
id : Int32
name : String
rows : Int32
} derive(@debug.Debug)
///|
pub(all) struct MetadataType {
namespace_name : String
name : String
flags : Int32
} derive(@debug.Debug)
///|
pub(all) struct MetadataMethod {
name : String
rva : Int32
flags : Int32
impl_flags : Int32
} derive(@debug.Debug)
///|
pub(all) struct MetadataAssemblyReference {
name : String
version : String
flags : Int32
public_key_or_token : Int32
} derive(@debug.Debug)
///|
pub(all) struct ClrMetadata {
streams : Array[MetadataStream]
tables : Array[MetadataTable]
module_name : String?
assembly_name : String?
assembly_version : String?
assembly_flags : Int32
assembly_references : Array[MetadataAssemblyReference]
type_definitions : Array[MetadataType]
type_references : Array[MetadataType]
method_definitions : Array[MetadataMethod]
member_reference_names : Array[String]
} derive(@debug.Debug)
///|
pub fn metadata_table_name(id : Int32) -> String {
match id {
0 => "Module"
1 => "TypeRef"
2 => "TypeDef"
3 => "FieldPtr"
4 => "Field"
5 => "MethodPtr"
6 => "MethodDef"
7 => "ParamPtr"
8 => "Param"
9 => "InterfaceImpl"
10 => "MemberRef"
11 => "Constant"
12 => "CustomAttribute"
13 => "FieldMarshal"
14 => "DeclSecurity"
15 => "ClassLayout"
16 => "FieldLayout"
17 => "StandAloneSig"
18 => "EventMap"
19 => "EventPtr"
20 => "Event"
21 => "PropertyMap"
22 => "PropertyPtr"
23 => "Property"
24 => "MethodSemantics"
25 => "MethodImpl"
26 => "ModuleRef"
27 => "TypeSpec"
28 => "ImplMap"
29 => "FieldRVA"
30 => "ENCLog"
31 => "ENCMap"
32 => "Assembly"
33 => "AssemblyProcessor"
34 => "AssemblyOS"
35 => "AssemblyRef"
36 => "AssemblyRefProcessor"
37 => "AssemblyRefOS"
38 => "File"
39 => "ExportedType"
40 => "ManifestResource"
41 => "NestedClass"
42 => "GenericParam"
43 => "MethodSpec"
44 => "GenericParamConstraint"
_ => "Table(" + id.to_string() + ")"
}
}
///|
/// Read and decode the CLR metadata root and its `#~` / `#-` tables.
pub fn Pe::clr_metadata(self : Pe) -> ClrMetadata? raise PeError {
let directory = match self.directory(14) {
None => return None
Some(directory) => directory
}
if directory.virtual_address == 0 || directory.size == 0 {
return None
}
let header = match self.reader_at_rva(directory.virtual_address) {
Some(reader) => reader
None =>
raise PeError::Malformed(
"CLR header RVA " +
hex(directory.virtual_address) +
" is not backed by file bytes",
0,
)
}
header.skip(8)
let metadata_rva = header.u32()
let metadata_size = header.u32()
let metadata_offset = match self.rva_to_offset(metadata_rva) {
Some(offset) => offset
None =>
raise PeError::Malformed(
"CLR metadata RVA " + hex(metadata_rva) + " is not backed by file bytes",
0,
)
}
let metadata_end = metadata_offset + metadata_size
if metadata_size < 32 || metadata_end > self.bytes.length() {
raise PeError::Malformed(
"CLR metadata root extends past the end of the file", metadata_offset,
)
}
let root = Reader::at(self.bytes, metadata_offset)
if root.u32_raw() != 0x424A5342 {
raise PeError::BadMagic(
"expected the BSJB CLR metadata signature", metadata_offset,
)
}
root.skip(4)
root.skip(4)
let version_length = root.u32()
if version_length > 4096 || root.position() + version_length > metadata_end {
raise PeError::Malformed(
"invalid CLR metadata version length",
root.position(),
)
}
let _runtime_version = root.ascii(version_length)
root.skip(align4(root.position()) - root.position())
let _flags = root.u16()
let stream_count = root.u16()
if stream_count > 64 {
raise PeError::Malformed(
"too many CLR metadata streams",
root.position() - 2,
)
}
let streams : Array[MetadataStream] = []
let mut stream_index = 0
while stream_index < stream_count {
let stream_offset = root.u32()
let stream_size = root.u32()
let name_start = root.position()
let name = root.cstring(32)
root.skip(
align4(root.position() - name_start) - (root.position() - name_start),
)
if stream_offset.to_int64() + stream_size.to_int64() >
metadata_size.to_int64() {
raise PeError::Malformed(
"CLR metadata stream extends past the metadata root", name_start,
)
}
streams.push({ name, offset: stream_offset, size: stream_size, })
stream_index = stream_index + 1
}
let table_stream = match find_metadata_stream(streams, "#~") {
Some(value) => Some(value)
None => find_metadata_stream(streams, "#-")
}
let table_stream = match table_stream {
Some(value) => value
None =>
return Some({
streams,
tables: [],
module_name: None,
assembly_name: None,
assembly_version: None,
assembly_flags: 0,
assembly_references: [],
type_definitions: [],
type_references: [],
method_definitions: [],
member_reference_names: [],
})
}
let string_stream = find_metadata_stream(streams, "#Strings")
let string_stream = match string_stream {
Some(value) => value
None =>
raise PeError::Malformed(
"CLR metadata has no #Strings stream", metadata_offset,
)
}
let uncompressed = table_stream.name == "#-"
let table_offset = metadata_offset + table_stream.offset
let table_end = table_offset + table_stream.size
if table_stream.size < 24 || table_end > metadata_end {
raise PeError::Malformed("invalid CLR table stream", table_offset)
}
let tables = Reader::at(self.bytes, table_offset)
tables.skip(4)
let _major = tables.u8()
let _minor = tables.u8()
let heap_sizes = tables.u8()
tables.skip(1)
let valid = tables.u64()
let _sorted = tables.u64()
let rows = zero_rows()
let mut table_id = 0
while table_id < 64 {
if table_present(valid, table_id) {
rows[table_id] = tables.u32()
}
table_id = table_id + 1
}
let first_row_offset = tables.position()
let table_offsets = zero_offsets()
let table_infos : Array[MetadataTable] = []
let mut current_offset = first_row_offset.to_int64()
table_id = 0
while table_id < 64 {
if table_present(valid, table_id) {
if rows[table_id] > 1000000 {
raise PeError::Unsupported(
"CLR metadata table " +
metadata_table_name(table_id) +
" has more than one million rows",
)
}
table_offsets[table_id] = current_offset
table_infos.push({
id: table_id,
name: metadata_table_name(table_id),
rows: rows[table_id],
})
let row_size = metadata_row_size(table_id, rows, heap_sizes, uncompressed)
current_offset = current_offset +
row_size.to_int64() * rows[table_id].to_int64()
}
table_id = table_id + 1
}
if current_offset > table_end.to_int64() {
raise PeError::Malformed(
"CLR metadata tables extend past the table stream", table_end,
)
}
let string_start = metadata_offset + string_stream.offset
let string_end = string_start + string_stream.size
if string_end > metadata_end {
raise PeError::Malformed("invalid CLR #Strings stream", string_start)
}
let module_name = if rows[0] > 0 {
let row = Reader::at(self.bytes, table_offsets[0].to_int())
row.skip(2)
let index = read_metadata_index(
row,
metadata_heap_width(heap_sizes, 1, uncompressed),
)
Some(
read_metadata_string(
self.bytes,
string_start,
string_end,
index,
"Module name",
),
)
} else {
None
}
let mut assembly_name : String? = None
let mut assembly_version : String? = None
let mut assembly_flags = 0
if rows[32] > 0 {
let row = Reader::at(self.bytes, table_offsets[32].to_int())
row.skip(4)
let major = row.u16()
let minor = row.u16()
let build = row.u16()
let revision = row.u16()
assembly_flags = row.u32()
let blob_size = metadata_heap_width(heap_sizes, 4, uncompressed)
row.skip(blob_size)
let name_index = read_metadata_index(
row,
metadata_heap_width(heap_sizes, 1, uncompressed),
)
assembly_version = Some(
version_text((major << 16) | minor, (build << 16) | revision),
)
assembly_name = Some(
read_metadata_string(
self.bytes,
string_start,
string_end,
name_index,
"Assembly name",
),
)
}
let type_definitions = read_type_definitions(
self.bytes,
rows,
table_offsets,
heap_sizes,
uncompressed,
string_start,
string_end,
)
let type_references = read_type_references(
self.bytes,
rows,
table_offsets,
heap_sizes,
uncompressed,
string_start,
string_end,
)
let method_definitions = read_method_definitions(
self.bytes,
rows,
table_offsets,
heap_sizes,
uncompressed,
string_start,
string_end,
)
let assembly_references = read_assembly_references(
self.bytes,
rows,
table_offsets,
heap_sizes,
uncompressed,
string_start,
string_end,
)
let member_reference_names = read_member_reference_names(
self.bytes,
rows,
table_offsets,
heap_sizes,
uncompressed,
string_start,
string_end,
)
Some({
streams,
tables: table_infos,
module_name,
assembly_name,
assembly_version,
assembly_flags,
assembly_references,
type_definitions,
type_references,
method_definitions,
member_reference_names,
})
}
///|
fn find_metadata_stream(
streams : Array[MetadataStream],
name : String,
) -> MetadataStream? {
for stream in streams {
if stream.name == name {
return Some(stream)
}
}
None
}
///|
fn zero_rows() -> Array[Int32] {
let rows : Array[Int32] = []
let mut i = 0
while i < 64 {
rows.push(0)
i = i + 1
}
rows
}
///|
fn zero_offsets() -> Array[Int64] {
let offsets : Array[Int64] = []
let mut i = 0
while i < 64 {
offsets.push(0L)
i = i + 1
}
offsets
}
///|
fn table_present(valid : Int64, id : Int32) -> Bool {
((valid >> id) & 1L) != 0L
}
///|
fn metadata_heap_width(heap_sizes : Int32, mask : Int32, wide : Bool) -> Int32 {
if wide || (heap_sizes & mask) != 0 {
4
} else {
2
}
}
///|
fn metadata_table_width(
table : Int32,
rows : Array[Int32],
wide : Bool,
) -> Int32 {
if wide || rows[table] >= 65536 {
4
} else {
2
}
}
///|
fn metadata_coded_width(
kind : Int32,
rows : Array[Int32],
wide : Bool,
) -> Int32 {
if wide {
return 4
}
let targets : Array[Int32] = match kind {
0 => [2, 1, 27]
1 => [4, 8, 23]
2 =>
[
6, 4, 1, 2, 8, 9, 10, 0, 14, 23, 20, 17, 26, 27, 32, 35, 38, 39, 40, 42,
44, 43,
]
3 => [4, 8]
4 => [2, 6, 32]
5 => [2, 1, 26, 6, 27]
6 => [20, 23]
7 => [6, 10]
8 => [4, 6]
9 => [38, 35, 39]
10 => [6, 10]
11 => [0, 26, 35, 1]
12 => [2, 6]
_ => []
}
let mut largest = 0
for target in targets {
if rows[target] > largest {
largest = rows[target]
}
}
let tag_bits = match kind {
0 => 2
1 => 2
2 => 5
3 => 1
4 => 2
5 => 3
6 => 1
7 => 1
8 => 1
9 => 2
10 => 3
11 => 2
12 => 1
_ => 16
}
if largest >= 1 << (16 - tag_bits) {
4
} else {
2
}
}
///|
fn metadata_row_size(
id : Int32,
rows : Array[Int32],
heap_sizes : Int32,
wide : Bool,
) -> Int32 raise PeError {
let string_size = metadata_heap_width(heap_sizes, 1, wide)
let guid_size = metadata_heap_width(heap_sizes, 2, wide)
let blob_size = metadata_heap_width(heap_sizes, 4, wide)
let table = fn(target) { metadata_table_width(target, rows, wide) }
let coded = fn(kind) { metadata_coded_width(kind, rows, wide) }
match id {
0 => 2 + string_size + guid_size * 3
1 => coded(11) + string_size * 2
2 => 4 + string_size * 2 + coded(0) + table(4) + table(6)
3 => table(4)
4 => 2 + string_size + blob_size
5 => table(6)
6 => 4 + 4 + string_size + blob_size + table(8)
7 => table(8)
8 => 4 + string_size
9 => table(2) + coded(0)
10 => coded(5) + string_size + blob_size
11 => 2 + coded(1) + blob_size
12 => coded(2) + coded(10) + blob_size
13 => coded(3) + blob_size
14 => 2 + coded(4) + blob_size
15 => 6 + table(2)
16 => 4 + table(4)
17 => blob_size
18 => table(2) + table(20)
19 => table(20)
20 => 2 + string_size + coded(0)
21 => table(2) + table(23)
22 => table(23)
23 => 2 + string_size + blob_size
24 => 2 + table(6) + coded(6)
25 => table(2) + coded(7) * 2
26 => string_size
27 => blob_size
28 => 2 + coded(8) + string_size + table(26)
29 => 4 + table(4)
30 => 8
31 => 4
32 => 16 + blob_size + string_size * 2
33 => 4
34 => 12
35 => 12 + blob_size + string_size * 2 + blob_size
36 => 4 + table(35)
37 => 12 + table(35)
38 => 4 + string_size + blob_size
39 => 8 + string_size * 2 + coded(9)
40 => 8 + string_size + coded(9)
41 => table(2) * 2
42 => 4 + coded(12) + string_size
43 => coded(7) + blob_size
44 => table(42) + coded(0)
_ =>
raise PeError::Unsupported(
"unsupported CLR metadata table " + metadata_table_name(id),
)
}
}
///|
fn read_metadata_string(
bytes : Bytes,
start : Int32,
end : Int32,
index : Int32,
label : String,
) -> String raise PeError {
if index == 0 {
return ""
}
if index < 0 || start + index >= end {
raise PeError::Malformed(
"CLR " +
label +
" index " +
index.to_string() +
" lies outside #Strings (size " +
(end - start).to_string() +
")",
start,
)
}
let reader = Reader::at(bytes, start + index)
reader.cstring(end - start - index)
}
///|
fn read_metadata_index(reader : Reader, width : Int32) -> Int32 raise PeError {
if width == 4 {
reader.u32()
} else {
reader.u16()
}
}
///|
fn read_type_definitions(
bytes : Bytes,
rows : Array[Int32],
offsets : Array[Int64],
heap_sizes : Int32,
wide : Bool,
string_start : Int32,
string_end : Int32,
) -> Array[MetadataType] raise PeError {
let out : Array[MetadataType] = []
let row_size = metadata_row_size(2, rows, heap_sizes, wide)
let string_size = metadata_heap_width(heap_sizes, 1, wide)
let mut i = 0
while i < rows[2] {
let row = Reader::at(bytes, offsets[2].to_int() + row_size * i)
let flags = row.u32()
let name_index = read_metadata_index(row, string_size)
let namespace_index = read_metadata_index(row, string_size)
out.push({
namespace_name: read_metadata_string(
bytes, string_start, string_end, namespace_index, "TypeDef namespace",
),
name: read_metadata_string(
bytes, string_start, string_end, name_index, "TypeDef name",
),
flags,
})
i = i + 1
}
out
}
///|
fn read_type_references(
bytes : Bytes,
rows : Array[Int32],
offsets : Array[Int64],
heap_sizes : Int32,
wide : Bool,
string_start : Int32,
string_end : Int32,
) -> Array[MetadataType] raise PeError {
let out : Array[MetadataType] = []
let row_size = metadata_row_size(1, rows, heap_sizes, wide)
let scope_size = metadata_coded_width(11, rows, wide)
let string_size = metadata_heap_width(heap_sizes, 1, wide)
let mut i = 0
while i < rows[1] {
let row = Reader::at(bytes, offsets[1].to_int() + row_size * i)
row.skip(scope_size)
let name_index = read_metadata_index(row, string_size)
let namespace_index = read_metadata_index(row, string_size)
out.push({
namespace_name: read_metadata_string(
bytes, string_start, string_end, namespace_index, "TypeRef namespace",
),
name: read_metadata_string(
bytes, string_start, string_end, name_index, "TypeRef name",
),
flags: 0,
})
i = i + 1
}
out
}
///|
fn read_method_definitions(
bytes : Bytes,
rows : Array[Int32],
offsets : Array[Int64],
heap_sizes : Int32,
wide : Bool,
string_start : Int32,
string_end : Int32,
) -> Array[MetadataMethod] raise PeError {
let out : Array[MetadataMethod] = []
let row_size = metadata_row_size(6, rows, heap_sizes, wide)
let string_size = metadata_heap_width(heap_sizes, 1, wide)
let mut i = 0
while i < rows[6] {
let row = Reader::at(bytes, offsets[6].to_int() + row_size * i)
let rva = row.u32()
let impl_flags = row.u16()
let flags = row.u16()
let name_index = read_metadata_index(row, string_size)
out.push({
name: read_metadata_string(
bytes, string_start, string_end, name_index, "MethodDef name",
),
rva,
flags,
impl_flags,
})
i = i + 1
}
out
}
///|
fn read_assembly_references(
bytes : Bytes,
rows : Array[Int32],
offsets : Array[Int64],
heap_sizes : Int32,
wide : Bool,
string_start : Int32,
string_end : Int32,
) -> Array[MetadataAssemblyReference] raise PeError {
let out : Array[MetadataAssemblyReference] = []
let row_size = metadata_row_size(35, rows, heap_sizes, wide)
let blob_size = metadata_heap_width(heap_sizes, 4, wide)
let string_size = metadata_heap_width(heap_sizes, 1, wide)
let mut i = 0
while i < rows[35] {
let row = Reader::at(bytes, offsets[35].to_int() + row_size * i)
let major = row.u16()
let minor = row.u16()
let build = row.u16()
let revision = row.u16()
let flags = row.u32()
let public_key_or_token = read_metadata_index(row, blob_size)
let name_index = read_metadata_index(row, string_size)
out.push({
name: read_metadata_string(
bytes, string_start, string_end, name_index, "AssemblyRef name",
),
version: version_text((major << 16) | minor, (build << 16) | revision),
flags,
public_key_or_token,
})
i = i + 1
}
out
}
///|
fn read_member_reference_names(
bytes : Bytes,
rows : Array[Int32],
offsets : Array[Int64],
heap_sizes : Int32,
wide : Bool,
string_start : Int32,
string_end : Int32,
) -> Array[String] raise PeError {
let out : Array[String] = []
let row_size = metadata_row_size(10, rows, heap_sizes, wide)
let class_size = metadata_coded_width(5, rows, wide)
let string_size = metadata_heap_width(heap_sizes, 1, wide)
let mut i = 0
while i < rows[10] {
let row = Reader::at(bytes, offsets[10].to_int() + row_size * i)
row.skip(class_size)
let name_index = read_metadata_index(row, string_size)
out.push(
read_metadata_string(
bytes, string_start, string_end, name_index, "MemberRef name",
),
)
i = i + 1
}
out
}