From 8a754c135effec7aa9f5b3c1099629490d9cf32c Mon Sep 17 00:00:00 2001 From: Janis Date: Wed, 23 Nov 2022 01:34:16 +0100 Subject: [PATCH] initial commit --- .gitignore | 2 + Cargo.toml | 17 + rust-toolchain.toml | 2 + src/lib.rs | 768 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 789 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 rust-toolchain.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..232274e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "winreg" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["alloc", "nightly"] +nightly = [] +alloc = [] + +[dependencies] +log = "0.4" +bytemuck = {version = "1.12.3", features = ["derive", "min_const_generics"]} +bitfield = "0.14.0" +bitflags = "1.3.2" \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..12cb5af --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,768 @@ +#![cfg_attr(feature = "nightly", feature(error_in_core))] +#![no_std] + +pub mod error { + use core::fmt::Display; + + #[derive(Debug, PartialEq, Eq)] + pub enum BaseBlockVerifyError { + BadSignature, + IncompatibleMajorVersion, + IncompatibleMinorVersion, + BadFileType, + BadFormat, + Cluster, + } + + 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", + }) + } + } + + #[cfg(feature = "nightly")] + impl core::error::Error for BaseBlockVerifyError {} +} + +pub mod ptr { + + use bytemuck::{Pod, Zeroable}; + + #[repr(transparent)] + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] + pub struct ZstPtr; + + unsafe impl Zeroable for ZstPtr { + fn zeroed() -> Self { + Self + } + } + + unsafe impl Pod for ZstPtr {} + + impl ZstPtr { + pub fn ptr(&self) -> *const () { + core::ptr::addr_of!(*self).cast() + } + + pub fn ptr_mut(&mut self) -> *mut () { + core::ptr::addr_of_mut!(*self).cast() + } + } + + #[test] + fn zst_ptr() { + use core::mem::size_of; + + assert_eq!(size_of::(), 0); + + let ptr = ZstPtr; + let ref r = ptr; + + assert_eq!(ptr.ptr(), r as *const ZstPtr as *const ()); + } +} + +pub mod cell_data { + use core::marker::PhantomData; + + use bitfield::{Bit, BitRange}; + use bitflags::bitflags; + use bytemuck::{Pod, Zeroable}; + + use crate::{ptr::ZstPtr, Offset}; + + #[repr(C)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + pub struct FastEntry { + offset: Offset, + name_hint: [u8; 4], + } + + #[repr(C)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + pub struct HashEntry { + offset: Offset, + name_hash: [u8; 4], + } + + #[repr(C, packed)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + pub struct KeyValue { + name_len: u16, + data_len: u32, + data: Offset, + data_type: u32, + flags: u16, + spare: u16, + value_name: ZstPtr, + } + + /// 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). + /// When a key security item acts as a list header, flink and blink point to + /// the first and the last entries of this list respectively. If a list is + /// empty, flink and blink point to a list header (i.e. to a current cell). + /// When a key security item acts as a list entry, flink and blink point to + /// the next and the previous entries of this list respectively. If there is + /// no next entry in a list, flink points to a list header. If there is no + /// previous entry in a list, blink points to a list header. + #[repr(C, packed)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + pub struct KeySecurity { + reserved: u16, + /// offset in bytes relative to the start of hive bins data. + f_link: Offset, + /// offset in bytes relative to the start of hive bins data. + b_link: Offset, + reference_count: u32, + security_descriptor_len: u32, + security_descriptor: ZstPtr, + } + + /// The [`BigData`] cell type is used to reference data larger than 16344 bytes (when the Minor version field of the base block is greater than 3). + /// the data `segments` points at is a list of `segments_len` [Offsets](Offset). + /// A data segment is stored in the data field of a [Cell] pointed by the Data segment offset field. A data segment has the maximum size of 16344 bytes. + + /// `segments` of a [BigData] record, except the last one, always have the maximum size. + #[repr(C, packed)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + pub struct BigData { + segments_len: u16, + segments: Offset, + } + + /// Cells contain data which is prefixed with a 2 byte ascii signature + /// indicating what kind of actual data is stored in this cell + #[repr(C, packed)] + #[derive(Debug, Clone, PartialEq, Eq, Copy, Zeroable, Pod)] + pub struct CellHeader { + /// 2 byte ASCII signature as represented by constants in CellData + signature: [u8; 2], + } + + #[derive(Debug)] + pub struct Cell<'a> { + header: CellHeader, + data: &'a [u8], + } + + impl<'a> From<&'a super::RawCell> for Cell<'a> { + fn from(raw: &'a super::RawCell) -> Self { + let data = unsafe { + core::slice::from_raw_parts::<'a, u8>(raw.data.ptr().cast(), raw.size as usize) + }; + + let header = *bytemuck::from_bytes(&data[..2]); + let data = &data[2..]; + + Self { header, data } + } + } + + impl<'a> From> for AnyCell<'a> { + fn from(cell: Cell<'a>) -> Self { + match cell.header { + CellHeader::FAST_LEAF => { + Self::FastLeaf(bytemuck::from_bytes::>(cell.data)) + } + CellHeader::INDEX_LEAF => { + Self::IndexLeaf(bytemuck::from_bytes::>(cell.data)) + } + CellHeader::HASH_LEAF => { + Self::HashLeaf(bytemuck::from_bytes::>(cell.data)) + } + CellHeader::INDEX_ROOT => { + Self::IndexRoot(bytemuck::from_bytes::>(cell.data)) + } + CellHeader::KEY_NODE => Self::KeyNode(bytemuck::from_bytes::(cell.data)), + CellHeader::KEY_VALUE => { + Self::KeyValue(bytemuck::from_bytes::(cell.data)) + } + CellHeader::KEY_SECURITY => { + Self::KeySecurity(bytemuck::from_bytes::(cell.data)) + } + CellHeader::BIG_DATA => Self::BigData(bytemuck::from_bytes::(cell.data)), + _ => unimplemented!(), + } + } + } + + /// Helper enum to match against a generic CellData + pub enum AnyCell<'a> { + IndexLeaf(&'a Leaf), + FastLeaf(&'a Leaf), + HashLeaf(&'a Leaf), + IndexRoot(&'a Leaf), + KeyNode(&'a KeyNode), + KeyValue(&'a KeyValue), + KeySecurity(&'a KeySecurity), + BigData(&'a BigData), + } + + #[repr(C, packed)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + pub struct KeyNode { + flags: KeyNodeFlags, + last_written: u64, + access: u32, + parent: Offset, + subkeys_len: u32, + volatile_subkeys_len: u32, + subkeys: Offset, + volatile_subkeys: Offset, + key_values_len: u32, + key_values: Offset, + key_security: Offset, + class_name: Offset, + largest_subkey_len: u32, + largest_subkey_class_len: u32, + largest_value_name_len: u32, + largest_value_data_len: u32, + work_var: u32, + key_name_len: u16, + class_name_len: u16, + key_name: ZstPtr, + } + + #[derive(Debug)] + pub struct SubkeyNameLengthField { + subkey_name_length: u16, + virt_flags: VirtualizationControlFlags, + user_flags: UserFlags, + debug: DebugFlags, + } + + impl SubkeyNameLengthField { + pub fn subkey_name_length(&self) -> u16 { + self.subkey_name_length + } + + pub fn virt_flags(&self) -> VirtualizationControlFlags { + self.virt_flags + } + + pub fn user_flags(&self) -> UserFlags { + self.user_flags + } + + pub fn debug(&self) -> DebugFlags { + self.debug + } + } + + bitflags! { + #[repr(C)] + #[derive(Zeroable, Pod)] + pub struct VirtualizationControlFlags: u8 { + const DONT_VIRTUALIZE = 0x2; + const DONT_SILENT_FAIL = 0x4; + const RECURSE_FLAG = 0x8; + + } + } + + bitflags! { + #[repr(C)] + #[derive(Zeroable, Pod)] + pub struct KeyNodeFlags: u16 { + /// Is volatile (not used, a key node on a disk isn't expected to + /// have this flag set) + const VOLATILE = 0x0001; + /// Is the mount point of another hive (a key node on a disk isn't + /// expected to have this flag set) + const HIVE_EXIT = 0x0002; + /// Is the root key for this hive + const HIVE_ENTRY = 0x0004; + /// This key can't be deleted + const NO_DELETE = 0x0008; + /// This key is a symlink (a target key is specified as a UTF-16LE + /// string (REG_LINK) in a value named "SymbolicLinkValue", example: + /// \REGISTRY\MACHINE\SOFTWARE\Classes\Wow6432Node) + const SYM_LINK = 0x0010; + /// Key name is an ASCII string, possibly an extended ASCII string + /// (otherwise it is a UTF-16LE string) + const COMP_NAME = 0x0020; + /// Is a predefined handle (a handle is stored in the Number of key + /// values field) + const PREDEF_HANDLE = 0x0040; + /// This key was virtualized at least once + const VIRTUAL_SOURCE = 0x0080; + /// Is virtual + const VIRTUAL_TARGET = 0x0100; + /// Is a part of a virtual store path + const VIRTUAL_STORE = 0x0200; + } + } + + bitflags! { + #[repr(C)] + #[derive(Zeroable, Pod)] + pub struct DebugFlags: u8 { + /// This key is opened + const BREAK_ON_OPEN = 0x01; + /// This key is deleted + const BREAK_ON_DELETE = 0x02; + /// A key security item is changed for this key + const BREAK_ON_SECURITY_CHANGE = 0x04; + /// A subkey of this key is created + const BREAK_ON_CREATE_SUBKEY = 0x08; + /// A subkey of this key is deleted + const BREAK_ON_DELETE_SUBKEY = 0x10; + /// A value is set to this key + const BREAK_ON_SET_VALUE = 0x20; + /// A value is deleted from this key + const BREAK_ON_DELETE_VALUE = 0x40; + /// This key is virtualized + const BREAK_ON_KEY_VIRTUALIZE = 0x80; + } + } + + bitflags! { + #[repr(C)] + #[derive(Zeroable, Pod)] + pub struct UserFlags: u8 { + /// Is a 32-bit key: this key was created through the Wow64 + /// subsystem or this key shall not be used by a 64-bit program + /// (e.g. by a 64-bit driver during the boot) + const KEY_32_BIT = 0x1; + /// This key was created by the reflection process (when reflecting + /// a key from another view) + const REFLECTION_KEY = 0x2; + /// Disable registry reflection for this key + const DISABLE_REFLECTION = 0x4; + /// In the old location of the User flags field: execute the int 3 + /// instruction on an access to this key (both retail and checked + /// Windows kernels), this bit was superseded by the Debug field + /// (see below); in the new location of the User flags field: + /// disable registry reflection for this key if a corresponding key + /// exists in another view and it wasn't created by a caller (see + /// below) + const TRIGGER_INT3_OR_DISABLE_REFLECTION = 0x8; + } + } + + bitflags! { + #[repr(C)] + #[derive(Zeroable, Pod)] + pub struct InnerAccessBits: u8 { + const PRE_BOOT_REG_INIT = 0x1; + const POST_BOOT_REG_INIT = 0x2; + } + } + + #[repr(C, packed)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + pub struct AccessBits { + access_bits: InnerAccessBits, + layered_key_bit_fields: LayeredKeyBitFields, + } + + impl From for AccessBits { + fn from(value: u32) -> Self { + let access_bits = InnerAccessBits::from_bits_truncate(value.bit_range(0, 8)); + let layered_key_bit_fields = LayeredKeyBitFields(value.bit_range(8, 16)); + + Self { + access_bits, + layered_key_bit_fields, + } + } + } + + #[repr(C, packed)] + #[derive(Debug, Clone, Copy, Zeroable, Pod)] + struct LayeredKeyBitFields(u8); + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum LayerSemantics { + Zero = 0x0, + IsTombstone, + IsSupersedeLocal, + IsSupersedeTree, + } + + impl LayeredKeyBitFields { + pub fn inherit_class(&self) -> bool { + self.0.bit(0) + } + + pub fn layer_semantics(&self) -> LayerSemantics { + match BitRange::::bit_range(&self.0, 6, 8) { + 0 => LayerSemantics::Zero, + 1 => LayerSemantics::IsTombstone, + 2 => LayerSemantics::IsSupersedeLocal, + 3 => LayerSemantics::IsSupersedeTree, + _ => unreachable!(), + } + } + } + + impl KeyNode { + pub fn largest_subkey_name_length_vista_xpsp3(&self) -> SubkeyNameLengthField { + let field = self.largest_subkey_len; + SubkeyNameLengthField { + subkey_name_length: field.bit_range(0, 16), + virt_flags: VirtualizationControlFlags::from_bits_truncate(field.bit_range(16, 20)), + user_flags: UserFlags::from_bits_truncate(field.bit_range(20, 24)), + debug: DebugFlags::from_bits_truncate(field.bit_range(24, 32)), + } + } + } + + /// A general view of a *Leaf Cell, which can either be Index, Fast or Hash. + /// All 3 of these CellData structs share the same common layout and only + /// differ in what data represents their element + #[repr(C)] + #[derive(Debug)] + pub struct Leaf { + len: u16, + elements: ZstPtr, + _phantom: PhantomData, + } + + impl Clone for Leaf { + fn clone(&self) -> Self { + Self { + len: self.len, + elements: ZstPtr, + _phantom: PhantomData, + } + } + } + + impl Copy for Leaf {} + unsafe impl Zeroable for Leaf {} + unsafe impl Pod for Leaf {} + + 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), + } + } + } + + impl CellHeader { + const INDEX_LEAF: Self = Self::from_sig(b"li"); + const FAST_LEAF: Self = Self::from_sig(b"lf"); + const HASH_LEAF: Self = Self::from_sig(b"lh"); + const INDEX_ROOT: Self = Self::from_sig(b"ri"); + const KEY_NODE: Self = Self::from_sig(b"nk"); + const KEY_VALUE: Self = Self::from_sig(b"vk"); + const KEY_SECURITY: Self = Self::from_sig(b"sk"); + const BIG_DATA: Self = Self::from_sig(b"bd"); + + #[allow(dead_code)] + const fn from_sig(sig: &[u8; 2]) -> Self { + Self { signature: *sig } + } + } + + #[derive(Debug, Clone, Copy)] + pub enum DataType { + None, + Sz, + ExpandSz, + Binary, + DWordLE, + DWordBE, + Link, + MultiSz, + ResourceList, + FullResourceDescriptor, + ResourceRequirementsList, + QWordLE, + Other(u32), + } + + #[cfg(test)] + mod test { + use super::*; + use core::mem::size_of; + + #[test] + fn sizes() { + assert_eq!(size_of::(), 8, "size_of::() != 8"); + assert_eq!(size_of::(), 8, "size_of::() != 8"); + assert_eq!(size_of::(), 74, "size_of::() != 74"); + assert_eq!(size_of::(), 18, "size_of::() != 18"); + } + } +} + +use bytemuck::{Pod, Zeroable}; +use cell_data::Cell; +use error::BaseBlockVerifyError; +use ptr::ZstPtr; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct BaseBlockSequence { + before: u32, + after: u32, +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct Offset(u32); + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct BaseBlock { + signature: u32, + sequence: BaseBlockSequence, + time_stmap: u64, + major: u32, + minor: u32, + kind: u32, + format: u32, + root_cell: Offset, + length: u32, + cluster: u32, + file_name: [u16; 32], + reserved: [u32; 99], + checksum: u32, + reserved2: [u32; 0x37E], + boot_type: u32, + boot_recover: u32, +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct BinHeader { + signature: [u8; 4], + offset: u32, + size: u32, + reserved: u64, + timestamp: u64, + _spare: u32, +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct RawCell { + size: i32, + data: ZstPtr, +} + +impl RawCell { + pub fn size(&self) -> usize { + self.size.abs() as usize + } +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct RawBin { + header: BinHeader, + cells: ZstPtr, +} + +impl RawBin { + pub fn len(&self) -> usize { + self.header.size as usize - core::mem::size_of::() + } + + pub fn cells_bytes<'a>(&'a self) -> &'a [u8] { + unsafe { core::slice::from_raw_parts::<'a, u8>(self.cells.ptr().cast(), self.len()) } + } + + pub fn iter(&self) -> CellIterator { + CellIterator { + bin: self, + offset: 0, + } + } +} + +#[derive(Debug)] +pub struct CellIterator<'a> { + bin: &'a RawBin, + offset: usize, +} + +impl<'a> Iterator for CellIterator<'a> { + type Item = Cell<'a>; + + fn next(&mut self) -> Option { + if self.offset >= self.bin.len() { + None + } else { + let bytes = self.bin.cells_bytes(); + let next = bytemuck::try_from_bytes::(&bytes[self.offset..]).ok()?; + + self.offset += next.size(); + + Some(next.into()) + } + } +} + +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct Bin<'a> { + raw: &'a RawBin, + cells: alloc::vec::Vec>, +} + +#[cfg(feature = "alloc")] +impl<'a> Bin<'a> { + pub fn header(&self) -> &BinHeader { + &self.raw.header + } + + pub fn cells(&self) -> &[Cell] { + self.cells.as_ref() + } +} + +#[cfg(feature = "alloc")] +impl<'a> From<&'a RawBin> for Bin<'a> { + fn from(bin: &'a RawBin) -> Self { + let cells = bin.iter().collect::>(); + + Self { raw: bin, cells } + } +} + +impl Offset { + pub fn offset(&self) -> Option { + if self.0 == 0xffffffff { + None + } else { + Some(self.0 as usize) + } + } +} + +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; + + pub fn calculate_checksum(&self) -> u32 { + let bytes = bytemuck::bytes_of(self); + // let data = bytemuck::cast_slice::<_, u32>(&bytes[..508]); + let data = unsafe { core::slice::from_raw_parts(bytes.as_ptr().cast::(), 127) }; + + let checksum = data.iter().clone().fold(0, |a, &b| a ^ b); + + if checksum == 0xffffffff { + 0xfffffffe + } else if checksum == 0 { + 1u32 + } else { + checksum + } + } + + /// returns either any error encountered when verifying this base block or the dirtiness + pub fn verify(&self) -> Result { + (self.signature == Self::SIGNATURE) + .then_some(()) + .ok_or(BaseBlockVerifyError::BadSignature)?; + (self.major == Self::MAJOR) + .then_some(()) + .ok_or(BaseBlockVerifyError::IncompatibleMajorVersion)?; + (self.minor == Self::MINOR) + .then_some(()) + .ok_or(BaseBlockVerifyError::IncompatibleMinorVersion)?; + (self.kind == Self::FILE_TYPE_PRIMARY) + .then_some(()) + .ok_or(BaseBlockVerifyError::BadFileType)?; + (self.format == Self::BASE_FORMAT_MEMORY) + .then_some(()) + .ok_or(BaseBlockVerifyError::BadFormat)?; + + (self.cluster == 1) + .then_some(()) + .ok_or(BaseBlockVerifyError::BadSignature)?; + Ok(self.dirty()) + } + + /// returns true if this block is dirty + /// + /// A hive is considered to be dirty (i.e. requiring recovery) when a base + /// block in a primary file contains a wrong checksum, or its primary + /// sequence number doesn't match its secondary sequence number. If a hive + /// isn't dirty, but a transaction log file (new format) contains subsequent + /// log entries, they are ignored. + pub fn dirty(&self) -> bool { + self.sequence.dirty() || self.calculate_checksum() != self.checksum + } +} + +impl BaseBlockSequence { + pub fn dirty(&self) -> bool { + self.before != self.after + } + + pub fn get(&self) -> (u32, u32) { + (self.before, self.after) + } +} + +#[cfg(test)] +mod aligns_and_size { + use super::*; + use core::mem::size_of; + + extern crate std; + + #[test] + fn size_of_base_block() { + assert_eq!(size_of::(), 0x1000); + } + + #[test] + fn size_of_base_block_sequence() { + assert_eq!(size_of::(), 8); + } + + #[test] + fn size_of_bin_header() { + assert_eq!(size_of::(), 32); + } + + #[test] + fn size_of_raw_cell() { + assert_eq!(size_of::(), 4); + } + + #[test] + fn offset_of_cells() { + assert_eq!(bytemuck::offset_of!(RawBin::zeroed(), RawBin, cells), 32); + } + + #[test] + fn offset_of_cell_data() { + assert_eq!(bytemuck::offset_of!(RawCell::zeroed(), RawCell, data), 4); + } +}