use std::collections::HashMap;
use object::pe::{
ImageDosHeader, ImageNtHeaders32, ImageNtHeaders64, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR,
IMAGE_FILE_DLL,
};
use object::read::pe::{DataDirectories, ImageNtHeaders};
use object::{Bytes, FileKind, LittleEndian as LE, Pod, ReadRef, U16, U32, U64};
use super::pe::utils as pe_utils;
use super::{Module, ModuleData, ModuleDataMap, ScanContext, StaticValue, Type, Value};
const MAX_PARAM_COUNT: u32 = 2000;
const MAX_GEN_PARAM_COUNT: u32 = 1000;
#[derive(Debug)]
pub struct Dotnet;
impl Module for Dotnet {
fn get_name(&self) -> &'static str {
"dotnet"
}
fn get_static_values(&self) -> HashMap<&'static str, StaticValue> {
HashMap::new()
}
fn get_dynamic_types(&self) -> HashMap<&'static str, Type> {
[
("is_dotnet", Type::Integer),
("version", Type::Bytes),
("module_name", Type::Bytes),
(
"streams",
Type::array(Type::object([
("name", Type::Bytes),
("offset", Type::Integer),
("size", Type::Integer),
])),
),
("number_of_streams", Type::Integer),
("guids", Type::array(Type::Bytes)),
("number_of_guids", Type::Integer),
(
"resources",
Type::array(Type::object([
("offset", Type::Integer),
("length", Type::Integer),
("name", Type::Bytes),
])),
),
("number_of_resources", Type::Integer),
(
"classes",
Type::array(Type::object([
("fullname", Type::Bytes),
("name", Type::Bytes),
("namespace", Type::Bytes),
("visibility", Type::Bytes),
("type", Type::Bytes),
("abstract", Type::Integer),
("sealed", Type::Integer),
("number_of_generic_parameters", Type::Integer),
("generic_parameters", Type::array(Type::Bytes)),
("number_of_base_types", Type::Integer),
("base_types", Type::array(Type::Bytes)),
("number_of_methods", Type::Integer),
(
"methods",
Type::array(Type::object([
("generic_parameters", Type::array(Type::Bytes)),
("number_of_generic_parameters", Type::Integer),
(
"parameters",
Type::array(Type::object([
("name", Type::Bytes),
("type", Type::Bytes),
])),
),
("number_of_parameters", Type::Integer),
("return_type", Type::Bytes),
("abstract", Type::Integer),
("final", Type::Integer),
("virtual", Type::Integer),
("static", Type::Integer),
("visibility", Type::Bytes),
("name", Type::Bytes),
])),
),
])),
),
("number_of_classes", Type::Integer),
(
"assembly_refs",
Type::array(Type::object([
(
"version",
Type::object([
("major", Type::Integer),
("minor", Type::Integer),
("build_number", Type::Integer),
("revision_number", Type::Integer),
]),
),
("public_key_or_token", Type::Bytes),
("name", Type::Bytes),
])),
),
("number_of_assembly_refs", Type::Integer),
(
"assembly",
Type::object([
(
"version",
Type::object([
("major", Type::Integer),
("minor", Type::Integer),
("build_number", Type::Integer),
("revision_number", Type::Integer),
]),
),
("name", Type::Bytes),
("culture", Type::Bytes),
]),
),
("modulerefs", Type::array(Type::Bytes)),
("number_of_modulerefs", Type::Integer),
("user_strings", Type::array(Type::Bytes)),
("number_of_user_strings", Type::Integer),
("typelib", Type::Bytes),
("constants", Type::array(Type::Bytes)),
("number_of_constants", Type::Integer),
("field_offsets", Type::array(Type::Integer)),
("number_of_field_offsets", Type::Integer),
]
.into()
}
fn setup_new_scan(&self, data_map: &mut ModuleDataMap) {
data_map.insert::<Self>(Data::default());
}
fn get_dynamic_values(&self, ctx: &mut ScanContext, out: &mut HashMap<&'static str, Value>) {
let Some(data) = ctx.module_data.get_mut::<Self>() else {
return;
};
if data.found_pe {
return;
}
let res = match FileKind::parse(ctx.region.mem) {
Ok(FileKind::Pe32) => {
parse_file::<ImageNtHeaders32>(ctx.region.mem, ctx.process_memory)
}
Ok(FileKind::Pe64) => {
parse_file::<ImageNtHeaders64>(ctx.region.mem, ctx.process_memory)
}
_ => None,
};
if let Some(values) = res {
*out = values;
data.found_pe = true;
}
}
}
#[derive(Default)]
pub struct Data {
found_pe: bool,
}
impl ModuleData for Dotnet {
type PrivateData = Data;
type UserData = ();
}
fn parse_file<HEADERS: ImageNtHeaders>(
mem: &[u8],
process_memory: bool,
) -> Option<HashMap<&'static str, Value>> {
let dos_header = ImageDosHeader::parse(mem).ok()?;
let mut offset = dos_header.nt_headers_offset().into();
let (nt_headers, data_dirs) = HEADERS::parse(mem, &mut offset).ok()?;
if process_memory {
let hdr = nt_headers.file_header();
let characteristics = hdr.characteristics.get(LE);
if (characteristics & IMAGE_FILE_DLL) != 0 {
return None;
}
}
let sections = pe_utils::SectionTable::new(nt_headers, mem, offset)?;
Some(
parse_file_inner(mem, data_dirs, sections)
.unwrap_or_else(|| [("is_dotnet", 0.into())].into()),
)
}
fn parse_file_inner(
mem: &[u8],
data_dirs: DataDirectories,
sections: pe_utils::SectionTable,
) -> Option<HashMap<&'static str, Value>> {
let dir = data_dirs.get(IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)?;
let cli_data = sections.get_dir_data(mem, *dir)?;
let cli_data = Bytes(cli_data);
let cli_header = cli_data.read_at::<CliHeader>(0).ok()?;
let metadata_root_offset: u64 = sections
.get_file_range_at(cli_header.metadata_rva.get(LE))?
.0
.into();
let metadata = cli_header.metadata(mem, §ions).ok()?;
let streams = get_streams(&metadata, metadata_root_offset);
let mut res: HashMap<&'static str, Value> = [
("is_dotnet", Value::Integer(1)),
("version", Value::bytes(metadata.version)),
("streams", Value::Array(streams)),
(
"number_of_streams",
Value::Integer(metadata.number_of_streams.into()),
),
]
.into();
add_guids(&metadata, &mut res);
add_user_strings(&metadata, &mut res);
let resource_base = sections
.get_file_range_at(cli_header.resources_metadata.get(LE))
.map(|v| u64::from(v.0));
add_metadata_tables(mem, &metadata, resource_base, sections, &mut res);
Some(res)
}
fn add_guids(metadata: &MetadataRoot, res: &mut HashMap<&'static str, Value>) {
let Some(stream_data) = metadata.get_stream(b"#GUID") else {
return;
};
let nb_guids = stream_data.len() / 16;
let guids = stream_data
.chunks_exact(16)
.take(16)
.map(|g| {
let a = u32::from_le_bytes([g[0], g[1], g[2], g[3]]);
let b = u16::from_le_bytes([g[4], g[5]]);
let c = u16::from_le_bytes([g[6], g[7]]);
let guid = format!(
"{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
a, b, c, g[8], g[9], g[10], g[11], g[12], g[13], g[14], g[15],
);
Value::Bytes(guid.into_bytes())
})
.collect();
res.extend([
("number_of_guids", nb_guids.into()),
("guids", Value::Array(guids)),
]);
}
fn add_user_strings(metadata: &MetadataRoot, res: &mut HashMap<&'static str, Value>) {
let Some(stream_data) = metadata.get_stream(b"#US") else {
return;
};
let mut strings = Vec::new();
let mut bytes = Bytes(stream_data);
let _ = bytes.skip(1);
while let Some(v) = read_blob(&mut bytes) {
if v.len() >= 2 {
strings.push(Value::bytes(&v[..(v.len() - 1)]));
}
}
res.extend([
("number_of_user_strings", strings.len().into()),
("user_strings", Value::Array(strings)),
]);
}
fn add_metadata_tables<'data>(
mem: &'data [u8],
metadata: &MetadataRoot<'data>,
resource_base: Option<u64>,
sections: pe_utils::SectionTable<'data>,
res: &mut HashMap<&'static str, Value>,
) {
let Some(stream_data) = metadata
.get_stream(b"#~")
.or_else(|| metadata.get_stream(b"#-"))
else {
return;
};
let strings_stream = metadata.get_stream(b"#Strings");
let blobs_stream = metadata.get_stream(b"#Blob");
let mut bytes = Bytes(stream_data);
let Ok(header) = bytes.read::<MetadataStreamHeader>() else {
return;
};
let valid = header.valid.get(LE);
let nb_table_kinds = valid.count_ones();
let rows = match bytes.read_bytes((nb_table_kinds as usize) * 4) {
Ok(v) => v.0,
Err(()) => return,
};
let mut table_counts = [0_u32; 64];
let mut rows_index = 0;
for (i, count) in table_counts.iter_mut().enumerate() {
if valid & (1 << i) == 0 {
continue;
}
*count = u32::from_le_bytes([
rows[rows_index],
rows[rows_index + 1],
rows[rows_index + 2],
rows[rows_index + 3],
]);
rows_index += 4;
if *count > 15_000 {
return;
}
}
let mut tables_data = TablesData::new(
mem,
bytes,
sections,
resource_base,
strings_stream,
blobs_stream,
&table_counts,
header.heap_sizes,
);
for table_index in 0..64 {
let nb_tables = table_counts[usize::from(table_index)];
if nb_tables == 0 {
continue;
}
if tables_data
.parse_table_type(table_index, nb_tables, res)
.is_err()
{
break;
}
}
tables_data.finalize(res);
}
mod table_type {
pub const MODULE: u8 = 0x00;
pub const TYPE_REF: u8 = 0x01;
pub const TYPE_DEF: u8 = 0x02;
pub const FIELD: u8 = 0x04;
pub const METHOD_DEF: u8 = 0x06;
pub const PARAM: u8 = 0x08;
pub const INTERFACE_IMPL: u8 = 0x09;
pub const MEMBER_REF: u8 = 0x0A;
pub const CONSTANT: u8 = 0x0B;
pub const CUSTOM_ATTRIBUTE: u8 = 0x0C;
pub const FIELD_MARSHALL: u8 = 0x0D;
pub const DECL_SECURITY: u8 = 0x0E;
pub const CLASS_LAYOUT: u8 = 0x0F;
pub const FIELD_LAYOUT: u8 = 0x10;
pub const STAND_ALONE_SIG: u8 = 0x11;
pub const EVENT_MAP: u8 = 0x12;
pub const EVENT: u8 = 0x14;
pub const PROPERTY_MAP: u8 = 0x15;
pub const PROPERTY: u8 = 0x17;
pub const METHOD_SEMANTICS: u8 = 0x18;
pub const METHOD_IMPL: u8 = 0x19;
pub const MODULE_REF: u8 = 0x1A;
pub const TYPE_SPEC: u8 = 0x1B;
pub const IMPL_MAP: u8 = 0x1C;
pub const FIELD_RVA: u8 = 0x1D;
pub const ASSEMBLY: u8 = 0x20;
pub const ASSEMBLY_PROCESSOR: u8 = 0x21;
pub const ASSEMBLY_OS: u8 = 0x22;
pub const ASSEMBLY_REF: u8 = 0x23;
pub const ASSEMBLY_REF_PROCESSOR: u8 = 0x24;
pub const ASSEMBLY_REF_OS: u8 = 0x25;
pub const FILE: u8 = 0x26;
pub const EXPORTED_TYPE: u8 = 0x27;
pub const MANIFEST_RESOURCE: u8 = 0x28;
pub const NESTED_CLASS: u8 = 0x29;
pub const GENERIC_PARAM: u8 = 0x2A;
pub const METHOD_SPEC: u8 = 0x2B;
pub const GENERIC_PARAM_CONSTRAINT: u8 = 0x2C;
pub const FIELD_PTR: u8 = 0x03;
pub const METHOD_PTR: u8 = 0x05;
pub const PARAM_PTR: u8 = 0x07;
pub const EVENT_PTR: u8 = 0x13;
pub const PROPERTY_PTR: u8 = 0x16;
pub const ENC_LOG: u8 = 0x1E;
pub const ENC_MAP: u8 = 0x1F;
}
struct TablesData<'data> {
mem: &'data [u8],
data: Bytes<'data>,
resource_base: Option<u64>,
sections: pe_utils::SectionTable<'data>,
strings_stream: Option<&'data [u8]>,
blobs_stream: Option<&'data [u8]>,
type_ref_table_data: Option<Bytes<'data>>,
type_spec_table_data: Option<Bytes<'data>>,
interface_impl_table_data: Option<Bytes<'data>>,
member_ref_table_data: Option<Bytes<'data>>,
classes: Vec<Class>,
methods: Vec<Method>,
param_names: Vec<Value>,
string_index_size: u8,
guid_index_size: u8,
blob_index_size: u8,
type_def_index_size: u8,
field_index_size: u8,
method_def_index_size: u8,
param_index_size: u8,
event_index_size: u8,
property_index_size: u8,
module_ref_index_size: u8,
assembly_ref_index_size: u8,
generic_param_index_size: u8,
type_def_or_ref_index_size: u8,
has_constant_index_size: u8,
has_custom_attribute_index_size: u8,
has_field_marshall_index_size: u8,
has_decl_security_index_size: u8,
member_ref_parent_index_size: u8,
has_semantics_index_size: u8,
method_def_or_ref_index_size: u8,
member_forwarded_index_size: u8,
implementation_index_size: u8,
custom_attribute_type_index_size: u8,
resolution_scope_index_size: u8,
type_or_method_def_index_size: u8,
}
impl<'data> TablesData<'data> {
#[allow(clippy::too_many_arguments)]
fn new(
mem: &'data [u8],
mut data: Bytes<'data>,
sections: pe_utils::SectionTable<'data>,
resource_base: Option<u64>,
strings_stream: Option<&'data [u8]>,
blobs_stream: Option<&'data [u8]>,
table_counts: &[u32; 64],
heap_sizes: u8,
) -> Self {
let wide_string_index = heap_sizes & 0x01 != 0;
let wide_guid_index = heap_sizes & 0x02 != 0;
let wide_blob_index = heap_sizes & 0x04 != 0;
if (heap_sizes & 0x40) != 0 {
let _r = data.skip(4);
}
let compute_index_size = |table_type: u8| {
if table_counts[usize::from(table_type)] >= (1 << 16) {
4
} else {
2
}
};
let compute_coded_index_size = |table_types: &[u8], max_log: u8| {
for table_type in table_types {
if table_counts[usize::from(*table_type)] >= (1 << max_log) {
return 4;
}
}
2
};
Self {
mem,
data,
sections,
resource_base,
strings_stream,
blobs_stream,
type_ref_table_data: None,
type_spec_table_data: None,
interface_impl_table_data: None,
member_ref_table_data: None,
string_index_size: if wide_string_index { 4 } else { 2 },
guid_index_size: if wide_guid_index { 4 } else { 2 },
blob_index_size: if wide_blob_index { 4 } else { 2 },
classes: Vec::new(),
methods: Vec::new(),
param_names: Vec::new(),
type_def_index_size: compute_index_size(table_type::TYPE_DEF),
field_index_size: compute_index_size(table_type::FIELD),
method_def_index_size: compute_index_size(table_type::METHOD_DEF),
param_index_size: compute_index_size(table_type::PARAM),
event_index_size: compute_index_size(table_type::EVENT),
property_index_size: compute_index_size(table_type::PROPERTY),
module_ref_index_size: compute_index_size(table_type::MODULE_REF),
assembly_ref_index_size: compute_index_size(table_type::ASSEMBLY_REF),
generic_param_index_size: compute_index_size(table_type::GENERIC_PARAM),
type_def_or_ref_index_size: compute_coded_index_size(
&[
table_type::TYPE_REF,
table_type::TYPE_DEF,
table_type::TYPE_SPEC,
],
14,
),
has_constant_index_size: compute_coded_index_size(
&[table_type::FIELD, table_type::PARAM, table_type::PROPERTY],
14,
),
has_custom_attribute_index_size: compute_coded_index_size(
&[
table_type::MODULE,
table_type::TYPE_REF,
table_type::TYPE_DEF,
table_type::FIELD,
table_type::METHOD_DEF,
table_type::PARAM,
table_type::INTERFACE_IMPL,
table_type::MEMBER_REF,
table_type::STAND_ALONE_SIG,
table_type::EVENT,
table_type::PROPERTY,
table_type::MODULE_REF,
table_type::TYPE_SPEC,
table_type::ASSEMBLY,
table_type::ASSEMBLY_REF,
table_type::FILE,
table_type::EXPORTED_TYPE,
table_type::MANIFEST_RESOURCE,
table_type::GENERIC_PARAM,
table_type::METHOD_SPEC,
table_type::GENERIC_PARAM_CONSTRAINT,
],
11,
),
has_field_marshall_index_size: compute_coded_index_size(
&[table_type::FIELD, table_type::PARAM],
15,
),
has_decl_security_index_size: compute_coded_index_size(
&[
table_type::TYPE_DEF,
table_type::METHOD_DEF,
table_type::ASSEMBLY,
],
14,
),
member_ref_parent_index_size: compute_coded_index_size(
&[
table_type::TYPE_REF,
table_type::TYPE_DEF,
table_type::METHOD_DEF,
table_type::MODULE_REF,
table_type::TYPE_SPEC,
],
13,
),
has_semantics_index_size: compute_coded_index_size(
&[table_type::EVENT, table_type::PROPERTY],
15,
),
method_def_or_ref_index_size: compute_coded_index_size(
&[table_type::MEMBER_REF, table_type::METHOD_DEF],
15,
),
member_forwarded_index_size: compute_coded_index_size(
&[table_type::FIELD, table_type::METHOD_DEF],
15,
),
implementation_index_size: compute_coded_index_size(
&[
table_type::FILE,
table_type::ASSEMBLY_REF,
table_type::EXPORTED_TYPE,
],
14,
),
custom_attribute_type_index_size: compute_coded_index_size(
&[table_type::METHOD_DEF, table_type::MEMBER_REF],
13,
),
resolution_scope_index_size: compute_coded_index_size(
&[
table_type::MODULE,
table_type::MODULE_REF,
table_type::ASSEMBLY_REF,
table_type::TYPE_REF,
],
14,
),
type_or_method_def_index_size: compute_coded_index_size(
&[table_type::TYPE_DEF, table_type::METHOD_DEF],
15,
),
}
}
fn finalize(mut self, res: &mut HashMap<&'static str, Value>) {
let mut last_method_index = self.methods.len();
for class in self.classes.iter().rev() {
if let Some(idx) = class.method_def_first_index {
let idx = idx as usize;
if idx <= self.methods.len() {
for i in idx..last_method_index {
if let Some(sig) = self.methods[i].signature.as_ref() {
let mut sig = Bytes(sig);
if let Some(sig) = self.parse_method_def_signature(
&mut sig,
&class.generic_params,
&self.methods[i].generic_params,
0,
) {
self.methods[i].set_signature(sig);
}
}
}
}
last_method_index = idx;
}
}
for method in self.methods.iter_mut().rev() {
if let Some(idx) = method.param_name_first_index {
let idx = idx as usize;
for (i, param) in method.params.iter_mut().enumerate() {
match self.param_names.get(idx + i) {
Some(param_name) => {
param.name = param_name.clone();
}
None => {
param.name = Value::Bytes(format!("P_{i}").into_bytes());
}
}
}
}
}
for class in self.classes.iter_mut().rev() {
if let Some(idx) = class.method_def_first_index {
let idx = idx as usize;
if idx <= self.methods.len() {
class.methods = self.methods.drain(idx..).map(Method::into_value).collect();
}
}
}
for i in 0..self.classes.len() {
let extends_index = self.classes[i].extends_index;
if let Some(base_class) =
self.get_type_fullname(extends_index, &self.classes[i].generic_params, &[], 0)
{
self.classes[i].base_types.push(Value::Bytes(base_class));
}
}
if let Some(mut interface_impl_table) = self.interface_impl_table_data.take() {
while let Some((class_index, type_def_or_ref_index)) =
self.read_interface(&mut interface_impl_table)
{
let Ok(class_index) = usize::try_from(class_index) else {
continue;
};
if class_index <= 1 || (class_index - 2) >= self.classes.len() {
continue;
}
if let Some(interface_name) = self.get_type_fullname(
type_def_or_ref_index,
&self.classes[class_index - 2].generic_params,
&[],
0,
) {
self.classes[class_index - 2]
.base_types
.push(Value::Bytes(interface_name));
}
}
}
let classes: Vec<Value> = self.classes.into_iter().map(Class::into_value).collect();
res.extend([
("number_of_classes", classes.len().into()),
("classes", Value::Array(classes)),
]);
}
fn parse_table_type(
&mut self,
table_index: u8,
nb_tables: u32,
res: &mut HashMap<&'static str, Value>,
) -> Result<(), ()> {
fn get_tables_len(nb_tables: u32, table_size: u8) -> Result<usize, ()> {
(nb_tables as usize)
.checked_mul(usize::from(table_size))
.ok_or(())
}
match table_index {
table_type::MODULE => self.parse_modules(nb_tables, res),
table_type::TYPE_REF => {
let len = get_tables_len(nb_tables, self.type_ref_table_size())?;
self.type_ref_table_data = Some(self.data.read_bytes(len)?);
Ok(())
}
table_type::TYPE_DEF => self.parse_type_defs(nb_tables),
table_type::FIELD => {
let len =
get_tables_len(nb_tables, 2 + self.string_index_size + self.blob_index_size)?;
self.data.skip(len)
}
table_type::METHOD_DEF => self.parse_method_defs(nb_tables),
table_type::PARAM => self.parse_params(nb_tables),
table_type::INTERFACE_IMPL => {
let len = get_tables_len(
nb_tables,
self.type_def_index_size + self.type_def_or_ref_index_size,
)?;
self.interface_impl_table_data = Some(self.data.read_bytes(len)?);
Ok(())
}
table_type::MEMBER_REF => {
let len = get_tables_len(nb_tables, self.member_ref_table_size())?;
self.member_ref_table_data = Some(self.data.read_bytes(len)?);
Ok(())
}
table_type::CONSTANT => self.parse_constants(nb_tables, res),
table_type::CUSTOM_ATTRIBUTE => self.parse_custom_attributes(nb_tables, res),
table_type::FIELD_MARSHALL => {
let len = get_tables_len(
nb_tables,
self.has_field_marshall_index_size + self.blob_index_size,
)?;
self.data.skip(len)
}
table_type::DECL_SECURITY => {
let len = get_tables_len(
nb_tables,
2 + self.has_decl_security_index_size + self.blob_index_size,
)?;
self.data.skip(len)
}
table_type::CLASS_LAYOUT => {
let len = get_tables_len(nb_tables, 6 + self.type_def_index_size)?;
self.data.skip(len)
}
table_type::FIELD_LAYOUT => {
let len = get_tables_len(nb_tables, 4 + self.field_index_size)?;
self.data.skip(len)
}
table_type::STAND_ALONE_SIG => {
let len = get_tables_len(nb_tables, self.blob_index_size)?;
self.data.skip(len)
}
table_type::EVENT_MAP => {
let len =
get_tables_len(nb_tables, self.type_def_index_size + self.event_index_size)?;
self.data.skip(len)
}
table_type::EVENT => {
let len = get_tables_len(
nb_tables,
2 + self.string_index_size + self.type_def_or_ref_index_size,
)?;
self.data.skip(len)
}
table_type::PROPERTY_MAP => {
let len = get_tables_len(
nb_tables,
self.type_def_index_size + self.property_index_size,
)?;
self.data.skip(len)
}
table_type::PROPERTY => {
let len =
get_tables_len(nb_tables, 2 + self.string_index_size + self.blob_index_size)?;
self.data.skip(len)
}
table_type::METHOD_SEMANTICS => {
let len = get_tables_len(
nb_tables,
2 + self.method_def_index_size + self.has_semantics_index_size,
)?;
self.data.skip(len)
}
table_type::METHOD_IMPL => {
let len = get_tables_len(
nb_tables,
self.type_def_index_size + 2 * self.method_def_or_ref_index_size,
)?;
self.data.skip(len)
}
table_type::MODULE_REF => self.parse_module_ref(nb_tables, res),
table_type::TYPE_SPEC => {
let len = get_tables_len(nb_tables, self.type_spec_table_size())?;
self.type_spec_table_data = Some(self.data.read_bytes(len)?);
Ok(())
}
table_type::IMPL_MAP => {
let len = get_tables_len(
nb_tables,
2 + self.member_forwarded_index_size
+ self.string_index_size
+ self.module_ref_index_size,
)?;
self.data.skip(len)
}
table_type::FIELD_RVA => self.parse_field_rva(nb_tables, res),
table_type::ASSEMBLY => self.parse_assembly_table(nb_tables, res),
table_type::ASSEMBLY_PROCESSOR => {
let len = get_tables_len(nb_tables, 4)?;
self.data.skip(len)
}
table_type::ASSEMBLY_OS => {
let len = get_tables_len(nb_tables, 12)?;
self.data.skip(len)
}
table_type::ASSEMBLY_REF => self.parse_assembly_ref_table(nb_tables, res),
table_type::ASSEMBLY_REF_PROCESSOR => {
let len = get_tables_len(nb_tables, 4 + self.assembly_ref_index_size)?;
self.data.skip(len)
}
table_type::ASSEMBLY_REF_OS => {
let len = get_tables_len(nb_tables, 12 + self.assembly_ref_index_size)?;
self.data.skip(len)
}
table_type::FILE => {
let len =
get_tables_len(nb_tables, 4 + self.string_index_size + self.blob_index_size)?;
self.data.skip(len)
}
table_type::EXPORTED_TYPE => {
let len = get_tables_len(
nb_tables,
8 + 2 * self.string_index_size + self.implementation_index_size,
)?;
self.data.skip(len)
}
table_type::MANIFEST_RESOURCE => self.parse_manifest_resource_table(nb_tables, res),
table_type::NESTED_CLASS => self.parse_nested_class(nb_tables),
table_type::GENERIC_PARAM => self.parse_generic_params(nb_tables),
table_type::METHOD_SPEC => {
let len = get_tables_len(
nb_tables,
self.method_def_or_ref_index_size + self.blob_index_size,
)?;
self.data.skip(len)
}
table_type::GENERIC_PARAM_CONSTRAINT => {
let len = get_tables_len(
nb_tables,
self.generic_param_index_size + self.type_def_or_ref_index_size,
)?;
self.data.skip(len)
}
table_type::FIELD_PTR => {
let len = get_tables_len(nb_tables, self.field_index_size)?;
self.data.skip(len)
}
table_type::METHOD_PTR => {
let len = get_tables_len(nb_tables, self.method_def_index_size)?;
self.data.skip(len)
}
table_type::PARAM_PTR => {
let len = get_tables_len(nb_tables, self.param_index_size)?;
self.data.skip(len)
}
table_type::EVENT_PTR => {
let len = get_tables_len(nb_tables, self.event_index_size)?;
self.data.skip(len)
}
table_type::PROPERTY_PTR => {
let len = get_tables_len(nb_tables, self.property_index_size)?;
self.data.skip(len)
}
table_type::ENC_LOG => {
let len = get_tables_len(nb_tables, 8)?;
self.data.skip(len)
}
table_type::ENC_MAP => {
let len = get_tables_len(nb_tables, 4)?;
self.data.skip(len)
}
_ => {
Err(())
}
}
}
fn parse_modules(&mut self, nb_tables: u32, res: &mut HashMap<&str, Value>) -> Result<(), ()> {
for i in 0..nb_tables {
self.data.skip(2)?; let name = self.read_string()?;
self.data.skip(3 * usize::from(self.guid_index_size))?;
if i == 0 {
let _r = res.insert("module_name", name.map(Value::bytes).into());
}
}
Ok(())
}
fn parse_type_defs(&mut self, nb_tables: u32) -> Result<(), ()> {
for i in 0..nb_tables {
let flags = read_u32(&mut self.data)?; let mut name = self.read_string()?;
if let Some(name) = name.as_mut() {
if let Some(pos) = name.iter().position(|b| *b == b'`') {
*name = &name[..pos];
}
}
let namespace = self.read_string()?;
let extends_index = read_index(&mut self.data, self.type_def_or_ref_index_size)?;
self.data.skip(usize::from(self.field_index_size))?;
let method_def_index = read_index(&mut self.data, self.method_def_index_size)?;
if i == 0 {
continue;
}
self.classes.push(Class {
flags,
name: name.map(ToOwned::to_owned),
namespace: namespace.map(ToOwned::to_owned),
methods: Vec::new(),
generic_params: Vec::new(),
method_def_first_index: if method_def_index == 0 {
None
} else {
Some(method_def_index - 1)
},
extends_index,
base_types: Vec::new(),
});
}
Ok(())
}
fn parse_method_defs(&mut self, nb_tables: u32) -> Result<(), ()> {
for _ in 0..nb_tables {
self.data.skip(6)?;
let flags = read_u16(&mut self.data)?;
let name = self.read_string()?;
let signature = self.read_blob()?;
let param_index = read_index(&mut self.data, self.param_index_size)?;
self.methods.push(Method {
flags,
name: name.map(ToOwned::to_owned),
generic_params: Vec::new(),
return_type: None,
params: Vec::new(),
param_name_first_index: if param_index == 0 {
None
} else {
Some(param_index - 1)
},
signature: signature.map(<[u8]>::to_vec),
});
}
Ok(())
}
fn parse_params(&mut self, nb_tables: u32) -> Result<(), ()> {
for _ in 0..nb_tables {
self.data.skip(4)?;
let name = self.read_string()?;
self.param_names
.push(Value::bytes(name.unwrap_or_default()));
}
Ok(())
}
fn parse_constants(
&mut self,
nb_tables: u32,
res: &mut HashMap<&str, Value>,
) -> Result<(), ()> {
let mut constants = Vec::new();
for _ in 0..nb_tables {
let ty = read_u16(&mut self.data)?;
self.data.skip(usize::from(self.has_constant_index_size))?;
let value = read_index(&mut self.data, self.blob_index_size)?;
if ty != 0x0e {
continue;
}
constants.push(if value == 0 {
Value::Bytes(Vec::new())
} else {
match self.get_blob(value as usize) {
Some(v) => Value::bytes(v),
None => Value::Undefined,
}
});
}
res.extend([
("number_of_constants", constants.len().into()),
("constants", Value::Array(constants)),
]);
Ok(())
}
fn parse_custom_attributes(
&mut self,
nb_tables: u32,
res: &mut HashMap<&str, Value>,
) -> Result<(), ()> {
let mut found_typelib = false;
let mut typelib = None;
for _ in 0..nb_tables {
let parent = read_index(&mut self.data, self.has_custom_attribute_index_size)?;
let typ = read_index(&mut self.data, self.custom_attribute_type_index_size)?;
if !found_typelib && self.custom_attribute_is_typelib(parent, typ) {
found_typelib = true;
typelib = self
.read_blob()?
.and_then(|blob| {
blob.strip_prefix(b"\x01\x00")
})
.and_then(|v| {
if v.is_empty() {
None
} else {
let packed_len = v[0];
if packed_len == 0x00 || packed_len == 0xFF {
None
} else {
v.get(1..=usize::from(packed_len))
}
}
});
} else {
self.data.skip(usize::from(self.blob_index_size))?;
}
}
let _r = res.insert("typelib", typelib.map(Value::bytes).into());
Ok(())
}
fn custom_attribute_is_typelib(&self, parent: u32, typ: u32) -> bool {
if (parent & 0x1F) != 14 {
return false;
}
if (typ & 0x07) != 3 {
return false;
}
let member_ref_index = typ >> 3;
let Some(mut member_ref_data) = self.member_ref_table_data.and_then(|table| {
get_record_in_table(table, self.member_ref_table_size(), member_ref_index)
}) else {
return false;
};
let Ok(class) = read_index(&mut member_ref_data, self.member_ref_parent_index_size) else {
return false;
};
if (class & 0x07) != 1 {
return false;
}
let type_ref_index = class >> 3;
let Some(mut type_ref_data) = self.type_ref_table_data.and_then(|table| {
get_record_in_table(table, self.type_ref_table_size(), type_ref_index)
}) else {
return false;
};
if type_ref_data
.skip(usize::from(self.resolution_scope_index_size))
.is_err()
{
return false;
}
let Ok(name) = read_index(&mut type_ref_data, self.string_index_size) else {
return false;
};
let Some(type_ref_name) = self.get_string(name as usize) else {
return false;
};
type_ref_name == b"GuidAttribute"
}
fn parse_module_ref(
&mut self,
nb_tables: u32,
res: &mut HashMap<&'static str, Value>,
) -> Result<(), ()> {
let modulerefs: Vec<Value> = (0..nb_tables)
.map(|_| match self.read_string()? {
Some(v) => Ok(Value::bytes(v)),
None => Ok(Value::Undefined),
})
.collect::<Result<Vec<Value>, ()>>()?;
res.extend([
("number_of_modulerefs", modulerefs.len().into()),
("modulerefs", Value::Array(modulerefs)),
]);
Ok(())
}
fn parse_field_rva(
&mut self,
nb_tables: u32,
res: &mut HashMap<&'static str, Value>,
) -> Result<(), ()> {
let modulerefs: Vec<Value> = (0..nb_tables)
.map(|_| {
let rva = read_u32(&mut self.data)?;
self.data.skip(usize::from(self.field_index_size))?;
Ok(pe_utils::va_to_file_offset(self.mem, &self.sections, rva).into())
})
.collect::<Result<Vec<Value>, ()>>()?;
res.extend([
("number_of_field_offsets", modulerefs.len().into()),
("field_offsets", Value::Array(modulerefs)),
]);
Ok(())
}
fn parse_assembly_table(
&mut self,
nb_tables: u32,
res: &mut HashMap<&'static str, Value>,
) -> Result<(), ()> {
for i in 0..nb_tables {
self.data.skip(4)?; let major_version = read_u16(&mut self.data)?;
let minor_version = read_u16(&mut self.data)?;
let build_number = read_u16(&mut self.data)?;
let revision_number = read_u16(&mut self.data)?;
self.data.skip(usize::from(4 + self.blob_index_size))?; let name = self.read_string()?;
let culture = self.read_string()?;
if i == 0 {
res.extend([(
"assembly",
Value::object([
(
"version",
Value::object([
("major", major_version.into()),
("minor", minor_version.into()),
("build_number", build_number.into()),
("revision_number", revision_number.into()),
]),
),
("name", name.map(Value::bytes).into()),
("culture", culture.map(Value::bytes).into()),
]),
)]);
}
}
Ok(())
}
fn parse_assembly_ref_table(
&mut self,
nb_tables: u32,
res: &mut HashMap<&'static str, Value>,
) -> Result<(), ()> {
let assembly_refs: Vec<Value> = (0..nb_tables)
.map(|_| {
let major_version = read_u16(&mut self.data)?;
let minor_version = read_u16(&mut self.data)?;
let build_number = read_u16(&mut self.data)?;
let revision_number = read_u16(&mut self.data)?;
self.data.skip(4)?; let public_key_or_token = match self.read_blob()? {
Some(v) if !v.is_empty() => Some(v),
_ => None,
};
let name = self.read_string()?;
self.data
.skip(usize::from(self.string_index_size + self.blob_index_size))?;
Ok(Value::object([
(
"version",
Value::object([
("major", major_version.into()),
("minor", minor_version.into()),
("build_number", build_number.into()),
("revision_number", revision_number.into()),
]),
),
(
"public_key_or_token",
public_key_or_token.map(Value::bytes).into(),
),
("name", name.map(Value::bytes).into()),
]))
})
.collect::<Result<Vec<Value>, ()>>()?;
res.extend([
("number_of_assembly_refs", assembly_refs.len().into()),
("assembly_refs", Value::Array(assembly_refs)),
]);
Ok(())
}
fn parse_manifest_resource_table(
&mut self,
nb_tables: u32,
res: &mut HashMap<&'static str, Value>,
) -> Result<(), ()> {
let mut resources = Vec::new();
for _ in 0..nb_tables {
let offset = read_u32(&mut self.data)?;
self.data.skip(4)?;
let name = self.read_string()?;
let implementation = read_index(&mut self.data, self.implementation_index_size)?;
let (real_offset, length) = if implementation == 0 {
let real_offset = self
.resource_base
.and_then(|base| base.checked_add(u64::from(offset)));
let length = real_offset
.and_then(|offset| self.mem.read_at::<U32<LE>>(offset).ok())
.map(|v| v.get(LE));
(real_offset.and_then(|v| v.checked_add(4)), length)
} else {
(None, None)
};
resources.push(Value::object([
("offset", real_offset.into()),
("length", length.into()),
("name", name.map(Value::bytes).into()),
]));
}
res.extend([
("number_of_resources", resources.len().into()),
("resources", Value::Array(resources)),
]);
Ok(())
}
fn parse_nested_class(&mut self, nb_tables: u32) -> Result<(), ()> {
for _ in 0..nb_tables {
let nested_class = read_index(&mut self.data, self.type_def_index_size)? as usize;
let enclosing_class = read_index(&mut self.data, self.type_def_index_size)? as usize;
if nested_class <= 1 || enclosing_class <= 1 {
continue;
}
let namespace = self
.classes
.get(enclosing_class - 2)
.and_then(Class::get_fullname);
if let Some(nested) = self.classes.get_mut(nested_class - 2) {
nested.namespace = namespace;
}
}
Ok(())
}
fn parse_generic_params(&mut self, nb_tables: u32) -> Result<(), ()> {
for _ in 0..nb_tables {
self.data.skip(4)?; let owner = read_index(&mut self.data, self.type_or_method_def_index_size)?;
let name = self.read_string()?;
let Some(owner_index) = ((owner >> 1) as usize).checked_sub(1) else {
continue;
};
if owner & 0x01 == 0 {
if owner_index >= 1 {
if let Some(class) = self.classes.get_mut(owner_index - 1) {
class.generic_params.push(name.map(ToOwned::to_owned));
}
}
} else if let Some(method) = self.methods.get_mut(owner_index) {
method.generic_params.push(name.map(ToOwned::to_owned));
}
}
Ok(())
}
fn read_interface(&self, interface_impl_table: &mut Bytes) -> Option<(u32, u32)> {
let class_index = read_index(interface_impl_table, self.type_def_index_size).ok()?;
let type_index = read_index(interface_impl_table, self.type_def_or_ref_index_size).ok()?;
Some((class_index, type_index))
}
fn get_type_fullname(
&self,
type_def_or_ref_index: u32,
class_gen_params: &[Option<Vec<u8>>],
method_gen_params: &[Option<Vec<u8>>],
rec_level: u8,
) -> Option<Vec<u8>> {
let tag = type_def_or_ref_index & 0x03;
let index = type_def_or_ref_index >> 2;
#[allow(clippy::if_same_then_else)]
if tag == 0 {
if index <= 1 {
None
} else {
let class = self.classes.get((index - 2) as usize)?;
class
.name
.as_ref()
.map(|name| build_fullname(class.namespace.as_deref(), name))
}
} else if tag == 1 {
let mut data =
get_record_in_table(self.type_ref_table_data?, self.type_ref_table_size(), index)?;
data.skip(usize::from(self.resolution_scope_index_size))
.ok()?;
let name_index = read_index(&mut data, self.string_index_size).ok()? as usize;
let mut name = self.get_string(name_index)?;
if let Some(pos) = name.iter().position(|b| *b == b'`') {
name = &name[..pos];
}
let ns_index = read_index(&mut data, self.string_index_size).ok()? as usize;
let ns = self.get_string(ns_index);
Some(build_fullname(ns, name))
} else if tag == 2 {
let mut data = get_record_in_table(
self.type_spec_table_data?,
self.type_spec_table_size(),
index,
)?;
let blob_index = read_index(&mut data, self.blob_index_size).ok()? as usize;
let mut sig = Bytes(self.get_blob(blob_index)?);
self.parse_sig_type(&mut sig, class_gen_params, method_gen_params, rec_level)
} else {
None
}
}
fn parse_method_def_signature(
&self,
sig: &mut Bytes,
class_gen_params: &[Option<Vec<u8>>],
method_gen_params: &[Option<Vec<u8>>],
rec_level: u8,
) -> Option<Signature> {
let flags = sig.read::<u8>().ok()?;
if (flags & 0x10) != 0 {
let _ = read_encoded_uint(sig)?;
}
let param_count = read_encoded_uint(sig)?;
if param_count > MAX_PARAM_COUNT {
return None;
}
let return_type =
self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level)?;
let params_types = (0..param_count)
.map(|_| self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level))
.collect();
Some(Signature {
return_type,
params_types,
})
}
fn parse_sig_type(
&self,
sig: &mut Bytes,
class_gen_params: &[Option<Vec<u8>>],
method_gen_params: &[Option<Vec<u8>>],
rec_level: u8,
) -> Option<Vec<u8>> {
let ty = sig.read::<u8>().ok()?;
if rec_level > 16 {
return None;
}
match ty {
0x01 => Some(b"void".to_vec()),
0x02 => Some(b"bool".to_vec()),
0x03 => Some(b"char".to_vec()),
0x04 => Some(b"sbyte".to_vec()),
0x05 => Some(b"byte".to_vec()),
0x06 => Some(b"short".to_vec()),
0x07 => Some(b"ushort".to_vec()),
0x08 => Some(b"int".to_vec()),
0x09 => Some(b"uint".to_vec()),
0x0a => Some(b"long".to_vec()),
0x0b => Some(b"ulong".to_vec()),
0x0c => Some(b"float".to_vec()),
0x0d => Some(b"double".to_vec()),
0x0e => Some(b"string".to_vec()),
0x0f => {
let inner_type =
self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level + 1)?;
let mut res = Vec::new();
res.extend(b"Ptr<");
res.extend(inner_type);
res.push(b'>');
Some(res)
}
0x10 => {
let inner_type =
self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level + 1)?;
let mut res = Vec::new();
res.extend(b"ref ");
res.extend(inner_type);
Some(res)
}
0x11 | 0x12 => {
let index = read_encoded_uint(sig)?;
self.get_type_fullname(index, class_gen_params, method_gen_params, rec_level + 1)
}
0x13 => {
let index = read_encoded_uint(sig)? as usize;
class_gen_params
.get(index)
.and_then(|v| v.as_ref())
.cloned()
}
0x14 => {
let ty =
self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level + 1)?;
let mut res = Vec::new();
res.extend(ty);
res.push(b'[');
let rank = read_encoded_uint(sig)? as usize;
let num_sizes = read_encoded_uint(sig)?;
let sizes: Vec<i64> = (0..num_sizes)
.map(|_| read_encoded_uint(sig).map(i64::from))
.collect::<Option<Vec<_>>>()?;
let num_lo_bounds = read_encoded_uint(sig)?;
let lo_bounds: Vec<i32> = (0..num_lo_bounds)
.map(|_| read_encoded_sint(sig))
.collect::<Option<Vec<_>>>()?;
for i in 0..rank {
if i > 0 {
res.push(b',');
}
match (sizes.get(i), lo_bounds.get(i)) {
(Some(size), Some(lo)) if *lo != 0 => {
res.extend(lo.to_string().as_bytes());
res.extend(b"...");
res.extend((i64::from(*lo) + *size - 1).to_string().as_bytes());
}
(Some(size), _) => res.extend(size.to_string().as_bytes()),
(None, Some(lo)) if *lo != 0 => {
res.extend(lo.to_string().as_bytes());
res.extend(b"...");
}
_ => (),
}
}
res.push(b']');
Some(res)
}
0x15 => {
let generic_type =
self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level)?;
let count = read_encoded_uint(sig)?;
if count > MAX_GEN_PARAM_COUNT {
return None;
}
let mut res = Vec::new();
res.extend(generic_type);
res.push(b'<');
for i in 0..count {
if i != 0 {
res.push(b',');
}
res.extend(self.parse_sig_type(
sig,
class_gen_params,
method_gen_params,
rec_level,
)?);
}
res.push(b'>');
Some(res)
}
0x16 => Some(b"TypedReference".to_vec()),
0x18 => Some(b"IntPtr".to_vec()),
0x19 => Some(b"UIntPtr".to_vec()),
0x1b => {
let Signature {
return_type,
params_types,
} = self.parse_method_def_signature(
sig,
class_gen_params,
method_gen_params,
rec_level + 1,
)?;
let mut res = Vec::new();
res.extend(b"FnPtr<");
res.extend(return_type);
res.push(b'(');
for (i, ptype) in params_types.into_iter().enumerate() {
if let Some(ptype) = ptype {
if i != 0 {
res.extend(b", ");
}
res.extend(ptype);
}
}
res.extend(b")>");
Some(res)
}
0x1c => Some(b"object".to_vec()),
0x1d => {
let inner_type =
self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level)?;
let mut res = Vec::new();
res.extend(inner_type);
res.extend(b"[]");
Some(res)
}
0x1e => {
let index = read_encoded_uint(sig)? as usize;
method_gen_params
.get(index)
.and_then(|v| v.as_ref())
.cloned()
}
0x1f | 0x20 => {
let _index = read_encoded_uint(sig)?;
self.parse_sig_type(sig, class_gen_params, method_gen_params, rec_level)
}
_ => None,
}
}
fn type_ref_table_size(&self) -> u8 {
self.resolution_scope_index_size + 2 * self.string_index_size
}
fn type_spec_table_size(&self) -> u8 {
self.blob_index_size
}
fn member_ref_table_size(&self) -> u8 {
self.member_ref_parent_index_size + self.string_index_size + self.blob_index_size
}
fn read_string(&mut self) -> Result<Option<&'data [u8]>, ()> {
let index = read_index(&mut self.data, self.string_index_size)? as usize;
Ok(self.get_string(index))
}
fn get_string(&self, index: usize) -> Option<&'data [u8]> {
if index == 0 {
return None;
}
let slice = self.strings_stream?.get(index..)?;
let pos = slice.iter().position(|c| *c == b'\0')?;
Some(&slice[..pos])
}
fn read_blob(&mut self) -> Result<Option<&'data [u8]>, ()> {
let index = read_index(&mut self.data, self.blob_index_size)? as usize;
Ok(self.get_blob(index))
}
fn get_blob(&self, index: usize) -> Option<&'data [u8]> {
let slice = self.blobs_stream?.get(index..)?;
read_blob(&mut Bytes(slice))
}
}
fn get_record_in_table(mut table: Bytes, record_size: u8, index: u32) -> Option<Bytes> {
if index == 0 {
return None;
}
let record_size = usize::from(record_size);
let offset = record_size.checked_mul((index - 1) as usize)?;
table.skip(offset).ok()?;
table.read_bytes(record_size).ok()
}
#[derive(Debug)]
struct Signature {
return_type: Vec<u8>,
params_types: Vec<Option<Vec<u8>>>,
}
#[derive(Debug)]
struct Class {
flags: u32,
name: Option<Vec<u8>>,
namespace: Option<Vec<u8>>,
methods: Vec<Value>,
method_def_first_index: Option<u32>,
generic_params: Vec<Option<Vec<u8>>>,
base_types: Vec<Value>,
extends_index: u32,
}
impl Class {
fn get_fullname(&self) -> Option<Vec<u8>> {
self.name
.as_ref()
.map(|name| build_fullname(self.namespace.as_deref(), name))
}
fn into_value(self) -> Value {
let typ = if self.flags & 0x20 != 0 {
b"interface".as_slice()
} else {
b"class"
};
let fullname = self.get_fullname();
let visibility = match self.flags & 0x07 {
0x00 | 0x05 => "internal",
0x01 | 0x02 => "public",
0x03 => "private",
0x04 => "protected",
0x06 => "private protected",
0x07 => "protected internal",
_ => "private",
};
let abstrac = i64::from((self.flags & 0x80) != 0);
let sealed = i64::from((self.flags & 0x100) != 0);
Value::object([
("fullname", fullname.into()),
("name", self.name.into()),
("namespace", self.namespace.unwrap_or_default().into()),
("visibility", Value::bytes(visibility)),
("type", Value::bytes(typ)),
("abstract", abstrac.into()),
("sealed", sealed.into()),
("number_of_methods", self.methods.len().into()),
("methods", Value::Array(self.methods)),
(
"number_of_generic_parameters",
self.generic_params.len().into(),
),
(
"generic_parameters",
Value::Array(self.generic_params.into_iter().map(Into::into).collect()),
),
("number_of_base_types", self.base_types.len().into()),
("base_types", Value::Array(self.base_types)),
])
}
}
#[derive(Debug)]
struct Method {
flags: u16,
name: Option<Vec<u8>>,
generic_params: Vec<Option<Vec<u8>>>,
return_type: Option<Vec<u8>>,
params: Vec<Param>,
param_name_first_index: Option<u32>,
signature: Option<Vec<u8>>,
}
#[derive(Debug)]
struct Param {
name: Value,
typ: Value,
}
impl Method {
fn set_signature(&mut self, signature: Signature) {
let Signature {
return_type,
params_types,
} = signature;
self.return_type = Some(return_type);
self.params = params_types
.into_iter()
.map(|param_type| Param {
name: Value::Undefined,
typ: param_type.map(Value::Bytes).into(),
})
.collect();
}
fn into_value(self) -> Value {
let Self {
flags,
name,
generic_params,
mut return_type,
params,
param_name_first_index: _param_name_first_index,
signature: _sig,
} = self;
let flag_static = i64::from((flags & 0x10) != 0);
let flag_final = i64::from((flags & 0x20) != 0);
let flag_virtual = i64::from((flags & 0x40) != 0);
let flag_abstract = i64::from((flags & 0x400) != 0);
let visibility = match flags & 0x07 {
0x01 => "private",
0x02 => "private protected",
0x03 => "internal",
0x04 => "protected",
0x05 => "protected internal",
0x06 => "public",
_ => "private",
};
let parameters: Vec<_> = params
.into_iter()
.map(|Param { name, typ }| Value::object([("name", name), ("type", typ)]))
.collect();
if let Some(name) = name.as_ref() {
if name == b".ctor" || name == b".cctor" {
return_type = None;
}
}
Value::object([
("abstract", flag_abstract.into()),
("final", flag_final.into()),
("virtual", flag_virtual.into()),
("static", flag_static.into()),
("visibility", Value::bytes(visibility)),
("name", name.into()),
("number_of_generic_parameters", generic_params.len().into()),
(
"generic_parameters",
Value::Array(generic_params.into_iter().map(Into::into).collect()),
),
("return_type", return_type.map(Value::Bytes).into()),
("number_of_parameters", parameters.len().into()),
("parameters", Value::Array(parameters)),
])
}
}
fn read_index(data: &mut Bytes, index_size: u8) -> Result<u32, ()> {
if index_size == 4 {
read_u32(data)
} else {
read_u16(data).map(u32::from)
}
}
fn read_u16(data: &mut Bytes) -> Result<u16, ()> {
data.read::<U16<LE>>().map(|v| v.get(LE))
}
fn read_u32(data: &mut Bytes) -> Result<u32, ()> {
data.read::<U32<LE>>().map(|v| v.get(LE))
}
fn read_blob<'data>(bytes: &mut Bytes<'data>) -> Option<&'data [u8]> {
let length = read_encoded_uint(bytes)? as usize;
bytes.read_slice(length).ok()
}
fn get_byte(bytes: &mut Bytes) -> Option<u8> {
let b = bytes.0.first()?;
let _ = bytes.skip(1);
Some(*b)
}
fn read_encoded_uint(bytes: &mut Bytes) -> Option<u32> {
let a = get_byte(bytes)?;
if a & 0x80 == 0 {
Some(u32::from(a))
} else if a & 0xC0 == 0x80 {
let a = a & 0x3F;
let b = get_byte(bytes)?;
Some(u32::from_le_bytes([b, a, 0, 0]))
} else if a & 0xE0 == 0xC0 {
let a = a & 0x1F;
let b = get_byte(bytes)?;
let c = get_byte(bytes)?;
let d = get_byte(bytes)?;
Some(u32::from_le_bytes([d, c, b, a]))
} else {
None
}
}
#[allow(clippy::cast_possible_wrap)]
fn read_encoded_sint(bytes: &mut Bytes) -> Option<i32> {
let a = get_byte(bytes)?;
if a & 0x80 == 0 {
let mut res = i32::from(a) >> 1;
if (a & 0x01) != 0 {
res |= 0xFF_FF_FF_C0u32 as i32;
}
Some(res)
} else if a & 0xC0 == 0x80 {
let a = a & 0x3F;
let b = get_byte(bytes)?;
let mut res = i32::from_le_bytes([b, a, 0, 0]) >> 1;
if (b & 0x01) != 0 {
res |= 0xFF_FF_E0_00u32 as i32;
}
Some(res)
} else if a & 0xE0 == 0xC0 {
let a = a & 0x1F;
let b = get_byte(bytes)?;
let c = get_byte(bytes)?;
let d = get_byte(bytes)?;
let mut res = i32::from_le_bytes([d, c, b, a]) >> 1;
if (d & 0x01) != 0 {
res |= 0xF0_00_00_00u32 as i32;
}
Some(res)
} else {
None
}
}
fn build_fullname(ns: Option<&[u8]>, name: &[u8]) -> Vec<u8> {
match ns {
Some(ns) => {
let mut full = Vec::with_capacity(ns.len() + name.len() + 1);
full.extend(ns);
full.push(b'.');
full.extend(name);
full
}
None => name.to_vec(),
}
}
fn get_streams(metadata: &MetadataRoot, metadata_root_offset: u64) -> Vec<Value> {
metadata
.streams()
.map(|stream| match stream {
Ok(stream) => {
Value::object([
("name", Value::bytes(stream.name)),
(
"offset",
metadata_root_offset
.checked_add(stream.offset.into())
.into(),
),
("size", Value::Integer(stream.size.into())),
])
}
Err(_) => Value::Undefined,
})
.collect()
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct MetadataStreamHeader {
pub reserved: U32<LE>,
pub major_version: u8,
pub minor_version: u8,
pub heap_sizes: u8,
pub reserved2: u8,
pub valid: U64<LE>,
pub sorted: U64<LE>,
}
unsafe impl Pod for MetadataStreamHeader {}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct CliHeader {
pub cb: U32<LE>,
pub major_runtime_version: U16<LE>,
pub minor_runtime_version: U16<LE>,
pub metadata_rva: U32<LE>,
pub metadata_size: U32<LE>,
pub flags: U32<LE>,
pub entry_point_token: U32<LE>,
pub resources_metadata: U32<LE>,
pub resources_size: U32<LE>,
pub strong_name_signature: U64<LE>,
pub code_manager_table: U64<LE>,
pub vtable_fixups: U64<LE>,
pub extra_address_table_jumps: U64<LE>,
pub managed_native_header: U64<LE>,
}
unsafe impl Pod for CliHeader {}
impl CliHeader {
fn metadata<'data, R: ReadRef<'data>>(
&self,
data: R,
sections: &pe_utils::SectionTable<'data>,
) -> Result<MetadataRoot<'data>, &'static str> {
let (offset, size) = sections
.get_file_range_at(self.metadata_rva.get(LE))
.ok_or("Invalid CLI metadata virtual address")?;
let data = data
.read_bytes_at(offset.into(), size.into())
.map_err(|()| "Invalid section file range")?
.get(..self.metadata_size.get(LE) as usize)
.ok_or("Invalid CLI metadata size")?;
MetadataRoot::parse(data)
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct MetadataRootHeader {
pub signature: U32<LE>,
pub major_version: U16<LE>,
pub minor_version: U16<LE>,
pub reserved: U32<LE>,
pub length: U32<LE>,
}
unsafe impl Pod for MetadataRootHeader {}
#[derive(Debug, Clone, Copy)]
struct MetadataRoot<'data> {
_header: MetadataRootHeader,
version: &'data [u8],
_flags: u16,
number_of_streams: u16,
streams_data: Bytes<'data>,
data: &'data [u8],
}
impl<'data> MetadataRoot<'data> {
fn parse(data: &'data [u8]) -> Result<Self, &'static str> {
let mut bytes = Bytes(data);
let header = *bytes
.read::<MetadataRootHeader>()
.map_err(|()| "Cannot read metadata root header")?;
let version = bytes
.read_slice(header.length.get(LE) as usize)
.map_err(|()| "Cannot read metadata root version")?;
let flags = bytes
.read::<U16<LE>>()
.map_err(|()| "Cannot read metadata root flags")?
.get(LE);
let number_of_streams = bytes
.read::<U16<LE>>()
.map_err(|()| "Cannot read number of streams")?
.get(LE);
let null_pos = version
.iter()
.position(|c| *c == b'\0')
.ok_or("Invalid version string in metadata root")?;
Ok(Self {
_header: header,
version: &version[..null_pos],
_flags: flags,
number_of_streams,
streams_data: bytes,
data,
})
}
fn streams(&self) -> StreamIterator<'_> {
StreamIterator {
nb_streams_left: self.number_of_streams,
data: self.streams_data,
}
}
fn get_stream(&self, name: &[u8]) -> Option<&'data [u8]> {
let stream = self
.streams()
.filter_map(Result::ok)
.find(|stream| stream.name == name)?;
self.data
.read_slice_at(u64::from(stream.offset), stream.size as usize)
.ok()
}
}
#[derive(Debug, Clone)]
struct StreamIterator<'data> {
nb_streams_left: u16,
data: Bytes<'data>,
}
impl<'data> Iterator for StreamIterator<'data> {
type Item = Result<StreamHeader<'data>, &'static str>;
fn next(&mut self) -> Option<Self::Item> {
if self.nb_streams_left > 0 {
self.nb_streams_left -= 1;
Some(StreamHeader::parse(&mut self.data))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct StreamHeader<'data> {
pub offset: u32,
pub size: u32,
pub name: &'data [u8],
}
impl<'data> StreamHeader<'data> {
fn parse(data: &mut Bytes<'data>) -> Result<Self, &'static str> {
let offset = data
.read::<U32<LE>>()
.map_err(|()| "Cannot read stream header offset")?
.get(LE);
let size = data
.read::<U32<LE>>()
.map_err(|()| "Cannot read stream header offset")?
.get(LE);
let name = data
.clone()
.read_string()
.map_err(|()| "Cannot read stream header name")?;
let nb_bytes = ((name.len() + 1) + 3) & !3;
data.skip(nb_bytes)
.map_err(|()| "Cannot skip past stream header name")?;
Ok(Self { offset, size, name })
}
}