diff --git a/src/lib.rs b/src/lib.rs index 12cb5af..fc13ef1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,12 @@ pub mod error { use core::fmt::Display; + use crate::Version; + #[derive(Debug, PartialEq, Eq)] pub enum BaseBlockVerifyError { BadSignature, + IncompatibleVersion(Version), IncompatibleMajorVersion, IncompatibleMinorVersion, BadFileType, @@ -16,14 +19,21 @@ pub mod error { impl Display for BaseBlockVerifyError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str(match self { - BaseBlockVerifyError::BadSignature => "Bad Signature", - BaseBlockVerifyError::IncompatibleMajorVersion => "Incompatible Major Version", - BaseBlockVerifyError::IncompatibleMinorVersion => "Incompatible Minor Version", - BaseBlockVerifyError::BadFileType => "Bad File Type (self.kind)", - BaseBlockVerifyError::BadFormat => "Bad Format", - BaseBlockVerifyError::Cluster => "Not the correct Cluster", - }) + match self { + BaseBlockVerifyError::BadSignature => f.write_str("Bad Signature"), + BaseBlockVerifyError::IncompatibleVersion(version) => { + f.write_fmt(format_args!("{:?}", version)) + } + BaseBlockVerifyError::IncompatibleMajorVersion => { + f.write_str("Incompatible Major Version") + } + BaseBlockVerifyError::IncompatibleMinorVersion => { + f.write_str("Incompatible Minor Version") + } + BaseBlockVerifyError::BadFileType => f.write_str("Bad File Type (self.kind)"), + BaseBlockVerifyError::BadFormat => f.write_str("Bad Format"), + BaseBlockVerifyError::Cluster => f.write_str("Not the correct Cluster"), + } } } @@ -100,11 +110,27 @@ pub mod cell_data { data_len: u32, data: Offset, data_type: u32, - flags: u16, + flags: KeyValueFlags, spare: u16, value_name: ZstPtr, } + bitflags! { + + #[repr(C)] + #[derive(Zeroable, Pod)] + pub struct KeyValueFlags: u16 { + /// Value name is an ASCII string, possibly an extended ASCII string + /// (otherwise it is a UTF-16LE string) + const VALUE_COMP_NAME = 0x1; + /// Is a tombstone value (the flag is used starting from Insider + /// Preview builds of Windows 10 "Redstone 1"), a tombstone value + /// also has the Data type field set to REG_NONE, the Data size + /// field set to 0, and the Data offset field set to 0xFFFFFFFF + const IS_TOMBSTONE = 0x2; + } + } + /// KeySecurity items form a doubly linked list. A key security item may /// act as a list header or a list entry (the only difference here is the /// meaning of f_link and b_link fields). @@ -155,6 +181,12 @@ pub mod cell_data { data: &'a [u8], } + impl<'a> Cell<'a> { + pub fn into_any_cell(self) -> AnyCell<'a> { + self.into() + } + } + impl<'a> From<&'a super::RawCell> for Cell<'a> { fn from(raw: &'a super::RawCell) -> Self { let data = unsafe { @@ -446,22 +478,29 @@ pub mod cell_data { impl KeyValue { pub fn data_type(&self) -> DataType { - match self.data_type { - 0x00 => DataType::None, - 0x01 => DataType::Sz, - 0x02 => DataType::ExpandSz, - 0x03 => DataType::Binary, - 0x04 => DataType::DWordLE, - 0x05 => DataType::DWordBE, - 0x06 => DataType::Link, - 0x07 => DataType::MultiSz, - 0x08 => DataType::ResourceList, - 0x09 => DataType::FullResourceDescriptor, - 0x0a => DataType::ResourceRequirementsList, - 0x0b => DataType::QWordLE, - _ => DataType::Other(self.data_type), + self.data_type.into() + } + + pub fn small_data(&self) -> Option<[u8; 4]> { + let data_len = self.data_len; + let data = self.data.0; + + if data_len.bit(31) && self.data_len() <= 4 { + let mut ret = [0; 4]; + ret[..self.data_len()] + .copy_from_slice(&bytemuck::bytes_of(&data)[..self.data_len()]); + + Some(ret) + } else { + None } } + + pub fn data_len(&self) -> usize { + let data_len = self.data_len; + + BitRange::::bit_range(&data_len, 0, 31) as usize + } } impl CellHeader { @@ -497,6 +536,26 @@ pub mod cell_data { Other(u32), } + impl From for DataType { + fn from(value: u32) -> Self { + match value { + 0x00 => Self::None, + 0x01 => Self::Sz, + 0x02 => Self::ExpandSz, + 0x03 => Self::Binary, + 0x04 => Self::DWordLE, + 0x05 => Self::DWordBE, + 0x06 => Self::Link, + 0x07 => Self::MultiSz, + 0x08 => Self::ResourceList, + 0x09 => Self::FullResourceDescriptor, + 0x0a => Self::ResourceRequirementsList, + 0x0b => Self::QWordLE, + _ => Self::Other(value), + } + } + } + #[cfg(test)] mod test { use super::*; @@ -512,8 +571,10 @@ pub mod cell_data { } } +use core::borrow::Borrow; + use bytemuck::{Pod, Zeroable}; -use cell_data::Cell; +use cell_data::{AnyCell, Cell, KeyNode}; use error::BaseBlockVerifyError; use ptr::ZstPtr; @@ -523,35 +584,137 @@ extern crate alloc; #[repr(C, packed)] #[derive(Debug, Copy, Clone, Pod, Zeroable)] pub struct BaseBlockSequence { + /// This number is incremented by 1 in the beginning of a write operation on + /// the primary file before: u32, + /// This number is incremented by 1 at the end of a write operation on the + /// primary file, a primary sequence number and a secondary sequence number + /// should be equal after a successful write operation after: u32, } -#[repr(C, packed)] +#[repr(C)] #[derive(Debug, Copy, Clone, Pod, Zeroable)] pub struct Offset(u32); -#[repr(C, packed)] +#[derive(Debug)] +pub struct Hive<'a> { + base_block: &'a BaseBlock, + hive_bins: &'a [u8], +} + +impl<'a> Hive<'a> { + pub fn new(bytes: &'a [u8]) -> Option { + (bytes.len() >= 0x1000) + .then_some(()) + .and_then(|_| { + let base_block = bytemuck::from_bytes::(bytes); + let bytes = &bytes[0x1000..]; + let hive_bins_len = base_block.hive_bins_len(); + + (bytes.len() >= hive_bins_len).then_some((base_block, &bytes[..hive_bins_len])) + }) + .map(|(base_block, hive_bins)| Self { + base_block, + hive_bins, + }) + } + + pub fn root_key(&self) -> Option<&KeyNode> { + let offset = self.base_block.root_cell().offset()?; + let a = bytemuck::from_bytes::(&self.hive_bins[offset..]); + + let cell = Cell::from(a); + + if let AnyCell::KeyNode(key_node) = cell.into_any_cell() { + Some(key_node) + } else { + None + } + } +} + +#[repr(C, packed(2))] #[derive(Debug, Copy, Clone, Pod, Zeroable)] pub struct BaseBlock { + /// ASCII string signature equal to `b"regf"` signature: u32, sequence: BaseBlockSequence, - time_stmap: u64, - major: u32, - minor: u32, + /// FILETIME (UTC) + time_stamp: u64, + version: Version, + /// file_type, 0 means primary file kind: u32, + /// 1 means direct memory load format: u32, + /// Offset of a root cell in bytes, relative from the start of the hive bins data root_cell: Offset, - length: u32, - cluster: u32, + /// Size of the hive bins data in bytes + hive_bins_len: u32, + /// Logical sector size of the underlying disk in bytes divided by 512 + clustering_factor: u32, + /// UTF-16LE string (contains a partial file path to the primary file, or a file name of the primary file), used for debugging purposes file_name: [u16; 32], - reserved: [u32; 99], + win10_fields1: Win10BaseBlock1, + reserved: [u32; 83], + /// XOR-32 checksum of the previous 508 bytes checksum: u32, - reserved2: [u32; 0x37E], + reserved2: [u32; 0x372], + win10_fields2: Win10BaseBlock2, boot_type: u32, boot_recover: u32, } +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Pod, Zeroable)] +pub struct Win10BaseBlock1 { + rm_id: u128, + log_id: u128, + flags: u32, + tm_id: u128, + guid_signature: [u8; 4], + last_reorganized_timestamp: u64, +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Pod, Zeroable)] +pub struct Win10BaseBlock2 { + thaw_tm_id: u128, + thaw_rm_id: u128, + thaw_log_id: u128, +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Pod, Zeroable)] +pub struct Version { + major: u32, + minor: u32, +} + +impl Version { + pub const V1_3: Self = Self::new(1, 3); + pub const V1_4: Self = Self::new(1, 4); + pub const V1_5: Self = Self::new(1, 5); + pub const V1_6: Self = Self::new(1, 6); + + pub const fn new(major: u32, minor: u32) -> Self { + Self { major, minor } + } + + /// returns true if `&self` is any of `[Self::V1_3, Self::V1_4, Self::V1_5, Self::V1_6]`. + pub fn is_valid(&self) -> bool { + [Self::V1_3, Self::V1_4, Self::V1_5, Self::V1_6].contains(self) + } + + pub fn major(&self) -> u32 { + self.major + } + + pub fn minor(&self) -> u32 { + self.minor + } +} + #[repr(C, packed)] #[derive(Debug, Copy, Clone, Pod, Zeroable)] pub struct BinHeader { @@ -662,11 +825,22 @@ impl Offset { impl BaseBlock { const SIGNATURE: u32 = 0x66676572; - const MAJOR: u32 = 1; - const MINOR: u32 = 3; const FILE_TYPE_PRIMARY: u32 = 0; const BASE_FORMAT_MEMORY: u32 = 1; + #[cfg(feature = "alloc")] + pub fn debug_file_name(&self) -> Result { + use alloc::string::String; + let name = &self.file_name; + let name_len = name.iter().take_while(|&&b| b != 0).count(); + + String::from_utf16(&self.file_name[..name_len]) + } + + pub fn hive_bins_len(&self) -> usize { + self.hive_bins_len as usize + } + pub fn calculate_checksum(&self) -> u32 { let bytes = bytemuck::bytes_of(self); // let data = bytemuck::cast_slice::<_, u32>(&bytes[..508]); @@ -688,12 +862,9 @@ impl BaseBlock { (self.signature == Self::SIGNATURE) .then_some(()) .ok_or(BaseBlockVerifyError::BadSignature)?; - (self.major == Self::MAJOR) + (self.version.is_valid()) .then_some(()) - .ok_or(BaseBlockVerifyError::IncompatibleMajorVersion)?; - (self.minor == Self::MINOR) - .then_some(()) - .ok_or(BaseBlockVerifyError::IncompatibleMinorVersion)?; + .ok_or(BaseBlockVerifyError::IncompatibleVersion(self.version))?; (self.kind == Self::FILE_TYPE_PRIMARY) .then_some(()) .ok_or(BaseBlockVerifyError::BadFileType)?; @@ -701,7 +872,7 @@ impl BaseBlock { .then_some(()) .ok_or(BaseBlockVerifyError::BadFormat)?; - (self.cluster == 1) + (self.clustering_factor == 1) .then_some(()) .ok_or(BaseBlockVerifyError::BadSignature)?; Ok(self.dirty()) @@ -717,6 +888,10 @@ impl BaseBlock { pub fn dirty(&self) -> bool { self.sequence.dirty() || self.calculate_checksum() != self.checksum } + + pub fn root_cell(&self) -> Offset { + self.root_cell + } } impl BaseBlockSequence {