use crate::{file::parser::Parser, Result};
use std::fmt;
use std::io::Write;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Cor20Flags(pub u32);
impl fmt::Display for Cor20Flags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
if self.0 & 0x0001 != 0 {
parts.push("ILOnly");
}
if self.0 & 0x0002 != 0 {
parts.push("32BitRequired");
}
if self.0 & 0x0004 != 0 {
parts.push("StrongNameSigned");
}
if self.0 & 0x0008 != 0 {
parts.push("NativeEntryPoint");
}
if self.0 & 0x0010 != 0 {
parts.push("TrackDebugData");
}
if self.0 & 0x0001_0000 != 0 {
parts.push("32BitPreferred");
}
if parts.is_empty() {
write!(f, "None")
} else {
write!(f, "{}", parts.join(", "))
}
}
}
impl From<u32> for Cor20Flags {
fn from(v: u32) -> Self {
Self(v)
}
}
newtype_ops!(Cor20Flags, u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Cor20Header {
pub cb: u32,
pub major_runtime_version: u16,
pub minor_runtime_version: u16,
pub meta_data_rva: u32,
pub meta_data_size: u32,
pub flags: Cor20Flags,
pub entry_point_token: u32,
pub resource_rva: u32,
pub resource_size: u32,
pub strong_name_signature_rva: u32,
pub strong_name_signature_size: u32,
pub code_manager_table_rva: u32,
pub code_manager_table_size: u32,
pub vtable_fixups_rva: u32,
pub vtable_fixups_size: u32,
pub export_address_table_jmp_rva: u32,
pub export_address_table_jmp_size: u32,
pub managed_native_header_rva: u32,
pub managed_native_header_size: u32,
}
impl Cor20Header {
pub fn read(data: &[u8]) -> Result<Cor20Header> {
const VALID_FLAGS: u32 = 0x0000_001F;
if data.len() < 72 {
return Err(out_of_bounds_error!());
}
let mut parser = Parser::new(data);
let cb = parser.read_le::<u32>()?;
if cb != 72 {
return Err(malformed_error!(
"Cor20Header: invalid CLR header size: expected 72, got {} [ECMA-335 §II.25.3.3]",
cb
));
}
let major_runtime_version = parser.read_le::<u16>()?;
let minor_runtime_version = parser.read_le::<u16>()?;
if major_runtime_version == 0 || major_runtime_version > 10 {
return Err(malformed_error!(
"Cor20Header: invalid major runtime version: {} [ECMA-335 §II.25.3.3]",
major_runtime_version
));
}
let meta_data_rva = parser.read_le::<u32>()?;
if meta_data_rva == 0 {
return Err(malformed_error!(
"Cor20Header: metadata RVA cannot be zero [ECMA-335 §II.25.3.3]"
));
}
let meta_data_size = parser.read_le::<u32>()?;
if meta_data_size == 0 {
return Err(malformed_error!(
"Cor20Header: metadata size cannot be zero [ECMA-335 §II.25.3.3]"
));
} else if meta_data_size > 0x1000_0000 {
return Err(malformed_error!(
"Cor20Header: metadata size {} exceeds reasonable limit (256MB) [ECMA-335 §II.25.3.3]",
meta_data_size
));
}
let flags = parser.read_le::<u32>()?;
if flags & !VALID_FLAGS != 0 {
return Err(malformed_error!(
"Cor20Header: invalid CLR flags: 0x{:08X} contains undefined bits [ECMA-335 §II.25.3.3]",
flags
));
}
let entry_point_token = parser.read_le::<u32>()?;
let resource_rva = parser.read_le::<u32>()?;
let resource_size = parser.read_le::<u32>()?;
if (resource_rva == 0 && resource_size != 0) || (resource_rva != 0 && resource_size == 0) {
return Err(malformed_error!(
"Cor20Header: resource RVA/size mismatch (RVA={}, size={}) [ECMA-335 §II.25.3.3]",
resource_rva,
resource_size
));
}
let strong_name_signature_rva = parser.read_le::<u32>()?;
let strong_name_signature_size = parser.read_le::<u32>()?;
if (strong_name_signature_rva == 0 && strong_name_signature_size != 0)
|| (strong_name_signature_rva != 0 && strong_name_signature_size == 0)
{
return Err(malformed_error!(
"Cor20Header: strong name RVA/size mismatch (RVA={}, size={}) [ECMA-335 §II.25.3.3]",
strong_name_signature_rva,
strong_name_signature_size
));
}
let code_manager_table_rva = parser.read_le::<u32>()?;
let code_manager_table_size = parser.read_le::<u32>()?;
if code_manager_table_rva != 0 || code_manager_table_size != 0 {
return Err(malformed_error!(
"Cor20Header: Code Manager Table fields must be zero (reserved) [ECMA-335 §II.25.3.3]"
));
}
let vtable_fixups_rva = parser.read_le::<u32>()?;
let vtable_fixups_size = parser.read_le::<u32>()?;
if (vtable_fixups_rva == 0 && vtable_fixups_size != 0)
|| (vtable_fixups_rva != 0 && vtable_fixups_size == 0)
{
return Err(malformed_error!(
"Cor20Header: VTable fixups RVA/size mismatch (RVA={}, size={}) [ECMA-335 §II.25.3.3]",
vtable_fixups_rva,
vtable_fixups_size
));
}
let export_address_table_jmp_rva = parser.read_le::<u32>()?;
let export_address_table_jmp_size = parser.read_le::<u32>()?;
if export_address_table_jmp_rva != 0 || export_address_table_jmp_size != 0 {
return Err(malformed_error!(
"Cor20Header: Export Address Table Jump fields must be zero (reserved) [ECMA-335 §II.25.3.3]"
));
}
let managed_native_header_rva = parser.read_le::<u32>()?;
let managed_native_header_size = parser.read_le::<u32>()?;
Ok(Cor20Header {
cb,
major_runtime_version,
minor_runtime_version,
meta_data_rva,
meta_data_size,
flags: Cor20Flags(flags),
entry_point_token,
resource_rva,
resource_size,
strong_name_signature_rva,
strong_name_signature_size,
code_manager_table_rva,
code_manager_table_size,
vtable_fixups_rva,
vtable_fixups_size,
export_address_table_jmp_rva,
export_address_table_jmp_size,
managed_native_header_rva,
managed_native_header_size,
})
}
#[must_use]
pub const fn size() -> u64 {
72
}
pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&self.cb.to_le_bytes())?;
writer.write_all(&self.major_runtime_version.to_le_bytes())?;
writer.write_all(&self.minor_runtime_version.to_le_bytes())?;
writer.write_all(&self.meta_data_rva.to_le_bytes())?;
writer.write_all(&self.meta_data_size.to_le_bytes())?;
writer.write_all(&self.flags.to_le_bytes())?;
writer.write_all(&self.entry_point_token.to_le_bytes())?;
writer.write_all(&self.resource_rva.to_le_bytes())?;
writer.write_all(&self.resource_size.to_le_bytes())?;
writer.write_all(&self.strong_name_signature_rva.to_le_bytes())?;
writer.write_all(&self.strong_name_signature_size.to_le_bytes())?;
writer.write_all(&self.code_manager_table_rva.to_le_bytes())?;
writer.write_all(&self.code_manager_table_size.to_le_bytes())?;
writer.write_all(&self.vtable_fixups_rva.to_le_bytes())?;
writer.write_all(&self.vtable_fixups_size.to_le_bytes())?;
writer.write_all(&self.export_address_table_jmp_rva.to_le_bytes())?;
writer.write_all(&self.export_address_table_jmp_size.to_le_bytes())?;
writer.write_all(&self.managed_native_header_rva.to_le_bytes())?;
writer.write_all(&self.managed_native_header_size.to_le_bytes())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crafted() {
#[rustfmt::skip]
let header_bytes = [
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
let parsed_header = Cor20Header::read(&header_bytes).unwrap();
assert_eq!(parsed_header.cb, 72);
assert_eq!(parsed_header.major_runtime_version, 2);
assert_eq!(parsed_header.minor_runtime_version, 3);
assert_eq!(parsed_header.meta_data_rva, 0x04000000);
assert_eq!(parsed_header.meta_data_size, 0x05000000);
assert_eq!(parsed_header.flags, 0);
assert_eq!(parsed_header.entry_point_token, 0x07000000);
assert_eq!(parsed_header.resource_rva, 0);
assert_eq!(parsed_header.resource_size, 0);
assert_eq!(parsed_header.strong_name_signature_rva, 0);
assert_eq!(parsed_header.strong_name_signature_size, 0);
assert_eq!(parsed_header.code_manager_table_rva, 0);
assert_eq!(parsed_header.code_manager_table_size, 0);
assert_eq!(parsed_header.vtable_fixups_rva, 0);
assert_eq!(parsed_header.vtable_fixups_size, 0);
assert_eq!(parsed_header.export_address_table_jmp_rva, 0);
assert_eq!(parsed_header.export_address_table_jmp_size, 0);
assert_eq!(parsed_header.managed_native_header_rva, 0);
assert_eq!(parsed_header.managed_native_header_size, 0);
}
#[test]
fn test_zero_metadata_rva() {
#[rustfmt::skip]
let header_bytes = [
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
let err =
Cor20Header::read(&header_bytes).expect_err("Expected error for zero metadata RVA");
assert!(
err.to_string().contains("metadata RVA cannot be zero"),
"Expected error about metadata RVA, got: {}",
err
);
}
#[test]
fn test_zero_metadata_size() {
#[rustfmt::skip]
let header_bytes = [
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
let err =
Cor20Header::read(&header_bytes).expect_err("Expected error for zero metadata size");
assert!(
err.to_string().contains("metadata size cannot be zero"),
"Expected error about metadata size, got: {}",
err
);
}
#[test]
fn test_invalid_flags() {
#[rustfmt::skip]
let header_bytes = [
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
let err =
Cor20Header::read(&header_bytes).expect_err("Expected error for invalid CLR flags");
assert!(
err.to_string().contains("invalid CLR flags"),
"Expected error about invalid CLR flags, got: {}",
err
);
}
#[test]
fn test_invalid_strong_name_signature() {
#[rustfmt::skip]
let header_bytes = [
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
let err =
Cor20Header::read(&header_bytes).expect_err("Expected error for invalid strong name");
assert!(
err.to_string().contains("strong name RVA/size mismatch"),
"Expected error about strong name mismatch, got: {}",
err
);
}
#[test]
fn test_invalid_vtable_fixups() {
#[rustfmt::skip]
let header_bytes = [
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
let err =
Cor20Header::read(&header_bytes).expect_err("Expected error for invalid VTable fixups");
assert!(
err.to_string().contains("VTable fixups RVA/size mismatch"),
"Expected error about VTable fixups mismatch, got: {}",
err
);
}
#[test]
fn test_write_roundtrip() {
#[rustfmt::skip]
let original_bytes = [
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ];
let header = Cor20Header::read(&original_bytes).unwrap();
let mut written_bytes = Vec::new();
header.write_to(&mut written_bytes).unwrap();
assert_eq!(written_bytes.len(), 72);
assert_eq!(written_bytes.as_slice(), &original_bytes);
let reparsed = Cor20Header::read(&written_bytes).unwrap();
assert_eq!(header, reparsed);
}
}