use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, Cursor, Read, Write};
use crate::error::{Error, Result};
use crate::structures::{calculate_checksum, BASE_BLOCK_SIZE, REGF_SIGNATURE};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FileType {
Primary = 0,
TransactionLog = 1,
TransactionLogLegacy = 2,
TransactionLogNew = 6,
}
impl TryFrom<u32> for FileType {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
0 => Ok(FileType::Primary),
1 => Ok(FileType::TransactionLog),
2 => Ok(FileType::TransactionLogLegacy),
6 => Ok(FileType::TransactionLogNew),
_ => Err(Error::CorruptHive(format!("Unknown file type: {}", value))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FileFormat {
DirectMemoryLoad = 1,
}
impl TryFrom<u32> for FileFormat {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
1 => Ok(FileFormat::DirectMemoryLoad),
_ => Err(Error::CorruptHive(format!("Unknown file format: {}", value))),
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BaseBlockFlags: u32 {
const KTM_LOCKED = 0x00000001;
const DEFRAGMENTED_OR_LAYERED = 0x00000002;
}
}
pub const OFRG_SIGNATURE: &[u8; 4] = b"OfRg";
pub mod reorganized_bits {
pub const DEFRAGMENTED: u64 = 0x01;
pub const ACCESS_HISTORY_CLEARED: u64 = 0x02;
pub const SPECIAL_BITS_MASK: u64 = 0x03;
}
#[derive(Debug, Clone, Default)]
pub struct OfflineRegistryInfo {
pub present: bool,
pub flags: u32,
pub serialization_timestamp: u64,
}
#[derive(Debug, Clone)]
pub struct BaseBlock {
pub signature: [u8; 4],
pub primary_sequence: u32,
pub secondary_sequence: u32,
pub last_written: u64,
pub major_version: u32,
pub minor_version: u32,
pub file_type: u32,
pub file_format: u32,
pub root_cell_offset: u32,
pub hive_bins_data_size: u32,
pub clustering_factor: u32,
pub file_name: [u8; 64],
pub rm_id: [u8; 16],
pub log_id: [u8; 16],
pub flags: u32,
pub tm_id: [u8; 16],
pub guid_signature: [u8; 4],
pub last_reorganized: u64,
pub checksum: u32,
pub thaw_tm_id: [u8; 16],
pub thaw_rm_id: [u8; 16],
pub thaw_log_id: [u8; 16],
pub boot_type: u32,
pub boot_recover: u32,
pub offline_registry: OfflineRegistryInfo,
}
impl Default for BaseBlock {
fn default() -> Self {
Self {
signature: *REGF_SIGNATURE,
primary_sequence: 1,
secondary_sequence: 1,
last_written: 0,
major_version: 1,
minor_version: 6,
file_type: 0,
file_format: 1,
root_cell_offset: 32, hive_bins_data_size: 4096,
clustering_factor: 1,
file_name: [0; 64],
rm_id: [0; 16],
log_id: [0; 16],
flags: 0,
tm_id: [0; 16],
guid_signature: [0; 4],
last_reorganized: 0,
checksum: 0,
thaw_tm_id: [0; 16],
thaw_rm_id: [0; 16],
thaw_log_id: [0; 16],
boot_type: 0,
boot_recover: 0,
offline_registry: OfflineRegistryInfo::default(),
}
}
}
impl BaseBlock {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < BASE_BLOCK_SIZE {
return Err(Error::BufferTooSmall {
needed: BASE_BLOCK_SIZE,
available: data.len(),
});
}
let mut cursor = Cursor::new(data);
let mut signature = [0u8; 4];
cursor.read_exact(&mut signature)?;
if &signature != REGF_SIGNATURE {
return Err(Error::InvalidSignature {
expected: String::from_utf8_lossy(REGF_SIGNATURE).to_string(),
found: String::from_utf8_lossy(&signature).to_string(),
});
}
let primary_sequence = cursor.read_u32::<LittleEndian>()?;
let secondary_sequence = cursor.read_u32::<LittleEndian>()?;
let last_written = cursor.read_u64::<LittleEndian>()?;
let major_version = cursor.read_u32::<LittleEndian>()?;
let minor_version = cursor.read_u32::<LittleEndian>()?;
let file_type = cursor.read_u32::<LittleEndian>()?;
let file_format = cursor.read_u32::<LittleEndian>()?;
let root_cell_offset = cursor.read_u32::<LittleEndian>()?;
let hive_bins_data_size = cursor.read_u32::<LittleEndian>()?;
let clustering_factor = cursor.read_u32::<LittleEndian>()?;
let mut file_name = [0u8; 64];
cursor.read_exact(&mut file_name)?;
let mut rm_id = [0u8; 16];
cursor.read_exact(&mut rm_id)?;
let mut log_id = [0u8; 16];
cursor.read_exact(&mut log_id)?;
let flags = cursor.read_u32::<LittleEndian>()?;
let mut tm_id = [0u8; 16];
cursor.read_exact(&mut tm_id)?;
let mut guid_signature = [0u8; 4];
cursor.read_exact(&mut guid_signature)?;
let last_reorganized = cursor.read_u64::<LittleEndian>()?;
cursor.set_position(508);
let checksum = cursor.read_u32::<LittleEndian>()?;
let calculated_checksum = calculate_checksum(data);
if checksum != calculated_checksum {
return Err(Error::ChecksumMismatch {
expected: checksum,
calculated: calculated_checksum,
});
}
cursor.set_position(4040);
let mut thaw_tm_id = [0u8; 16];
cursor.read_exact(&mut thaw_tm_id)?;
let mut thaw_rm_id = [0u8; 16];
cursor.read_exact(&mut thaw_rm_id)?;
let mut thaw_log_id = [0u8; 16];
cursor.read_exact(&mut thaw_log_id)?;
cursor.set_position(4088);
let boot_type = cursor.read_u32::<LittleEndian>()?;
let boot_recover = cursor.read_u32::<LittleEndian>()?;
let mut offline_registry = OfflineRegistryInfo::default();
cursor.set_position(176);
let mut ofrg_sig = [0u8; 4];
cursor.read_exact(&mut ofrg_sig)?;
if &ofrg_sig == OFRG_SIGNATURE {
offline_registry.present = true;
offline_registry.flags = cursor.read_u32::<LittleEndian>()?;
cursor.set_position(512);
offline_registry.serialization_timestamp = cursor.read_u64::<LittleEndian>()?;
} else {
cursor.set_position(168);
cursor.read_exact(&mut ofrg_sig)?;
if &ofrg_sig == OFRG_SIGNATURE {
offline_registry.present = true;
offline_registry.flags = cursor.read_u32::<LittleEndian>()?;
cursor.set_position(512);
offline_registry.serialization_timestamp = cursor.read_u64::<LittleEndian>()?;
}
}
Ok(Self {
signature,
primary_sequence,
secondary_sequence,
last_written,
major_version,
minor_version,
file_type,
file_format,
root_cell_offset,
hive_bins_data_size,
clustering_factor,
file_name,
rm_id,
log_id,
flags,
tm_id,
guid_signature,
last_reorganized,
checksum,
thaw_tm_id,
thaw_rm_id,
thaw_log_id,
boot_type,
boot_recover,
offline_registry,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
let mut buffer = vec![0u8; BASE_BLOCK_SIZE];
{
let mut cursor = Cursor::new(&mut buffer[..]);
cursor.write_all(&self.signature)?;
cursor.write_u32::<LittleEndian>(self.primary_sequence)?;
cursor.write_u32::<LittleEndian>(self.secondary_sequence)?;
cursor.write_u64::<LittleEndian>(self.last_written)?;
cursor.write_u32::<LittleEndian>(self.major_version)?;
cursor.write_u32::<LittleEndian>(self.minor_version)?;
cursor.write_u32::<LittleEndian>(self.file_type)?;
cursor.write_u32::<LittleEndian>(self.file_format)?;
cursor.write_u32::<LittleEndian>(self.root_cell_offset)?;
cursor.write_u32::<LittleEndian>(self.hive_bins_data_size)?;
cursor.write_u32::<LittleEndian>(self.clustering_factor)?;
cursor.write_all(&self.file_name)?;
cursor.write_all(&self.rm_id)?;
cursor.write_all(&self.log_id)?;
cursor.write_u32::<LittleEndian>(self.flags)?;
cursor.write_all(&self.tm_id)?;
cursor.write_all(&self.guid_signature)?;
cursor.write_u64::<LittleEndian>(self.last_reorganized)?;
}
let checksum = calculate_checksum(&buffer);
buffer[508..512].copy_from_slice(&checksum.to_le_bytes());
buffer[4040..4056].copy_from_slice(&self.thaw_tm_id);
buffer[4056..4072].copy_from_slice(&self.thaw_rm_id);
buffer[4072..4088].copy_from_slice(&self.thaw_log_id);
buffer[4088..4092].copy_from_slice(&self.boot_type.to_le_bytes());
buffer[4092..4096].copy_from_slice(&self.boot_recover.to_le_bytes());
writer.write_all(&buffer)
}
pub fn is_dirty(&self) -> bool {
self.primary_sequence != self.secondary_sequence
}
pub fn get_file_type(&self) -> Result<FileType> {
FileType::try_from(self.file_type)
}
pub fn get_file_format(&self) -> Result<FileFormat> {
FileFormat::try_from(self.file_format)
}
pub fn get_flags(&self) -> BaseBlockFlags {
BaseBlockFlags::from_bits_truncate(self.flags)
}
pub fn get_file_name(&self) -> String {
let u16_values: Vec<u16> = self.file_name
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.take_while(|&c| c != 0)
.collect();
String::from_utf16_lossy(&u16_values)
}
pub fn set_file_name(&mut self, name: &str) {
let mut file_name = [0u8; 64];
let u16_values: Vec<u16> = name.encode_utf16().collect();
for (i, &value) in u16_values.iter().take(31).enumerate() {
let bytes = value.to_le_bytes();
file_name[i * 2] = bytes[0];
file_name[i * 2 + 1] = bytes[1];
}
self.file_name = file_name;
}
pub fn prepare_for_write(&mut self) {
use chrono::Utc;
use crate::structures::datetime_to_filetime;
self.primary_sequence = self.primary_sequence.wrapping_add(1);
self.last_written = datetime_to_filetime(Utc::now());
}
pub fn was_defragmented(&self) -> bool {
(self.last_reorganized & reorganized_bits::DEFRAGMENTED) != 0
}
pub fn was_access_history_cleared(&self) -> bool {
(self.last_reorganized & reorganized_bits::ACCESS_HISTORY_CLEARED) != 0
}
pub fn get_last_reorganized_time(&self) -> u64 {
self.last_reorganized & !reorganized_bits::SPECIAL_BITS_MASK
}
pub fn is_offline_registry(&self) -> bool {
self.offline_registry.present
}
pub fn complete_write(&mut self) {
self.secondary_sequence = self.primary_sequence;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_base_block() {
let block = BaseBlock::default();
assert_eq!(&block.signature, REGF_SIGNATURE);
assert_eq!(block.major_version, 1);
assert!(!block.is_dirty());
}
#[test]
fn test_file_name() {
let mut block = BaseBlock::default();
block.set_file_name("SYSTEM");
assert_eq!(block.get_file_name(), "SYSTEM");
}
}