Skip to main content

regf/structures/
base_block.rs

1//! Base block (file header) structure.
2//!
3//! The base block is 4096 bytes in length and contains the file header information.
4
5use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
6use std::io::{self, Cursor, Read, Write};
7
8use crate::error::{Error, Result};
9use crate::structures::{calculate_checksum, BASE_BLOCK_SIZE, REGF_SIGNATURE};
10
11/// File type values.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u32)]
14pub enum FileType {
15    /// Primary hive file.
16    Primary = 0,
17    /// Transaction log (old format, Windows XP+).
18    TransactionLog = 1,
19    /// Transaction log (Windows NT/2000).
20    TransactionLogLegacy = 2,
21    /// Transaction log (new format, Windows 8.1+).
22    TransactionLogNew = 6,
23}
24
25impl TryFrom<u32> for FileType {
26    type Error = Error;
27
28    fn try_from(value: u32) -> Result<Self> {
29        match value {
30            0 => Ok(FileType::Primary),
31            1 => Ok(FileType::TransactionLog),
32            2 => Ok(FileType::TransactionLogLegacy),
33            6 => Ok(FileType::TransactionLogNew),
34            _ => Err(Error::CorruptHive(format!("Unknown file type: {}", value))),
35        }
36    }
37}
38
39/// File format values.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(u32)]
42pub enum FileFormat {
43    /// Direct memory load.
44    DirectMemoryLoad = 1,
45}
46
47impl TryFrom<u32> for FileFormat {
48    type Error = Error;
49
50    fn try_from(value: u32) -> Result<Self> {
51        match value {
52            1 => Ok(FileFormat::DirectMemoryLoad),
53            _ => Err(Error::CorruptHive(format!("Unknown file format: {}", value))),
54        }
55    }
56}
57
58bitflags::bitflags! {
59    /// Flags for the base block.
60    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
61    pub struct BaseBlockFlags: u32 {
62        /// KTM locked the hive (pending or anticipated transactions).
63        const KTM_LOCKED = 0x00000001;
64        /// Hive has been defragmented / supports layered keys (Windows 10+).
65        const DEFRAGMENTED_OR_LAYERED = 0x00000002;
66    }
67}
68
69/// Offline Registry Library signature.
70pub const OFRG_SIGNATURE: &[u8; 4] = b"OfRg";
71
72/// Special bits in the Last reorganized timestamp field.
73pub mod reorganized_bits {
74    /// Hive was defragmented during the latest reorganization.
75    pub const DEFRAGMENTED: u64 = 0x01;
76    /// Access history of key nodes was cleared during the latest reorganization.
77    pub const ACCESS_HISTORY_CLEARED: u64 = 0x02;
78    /// Mask for special bits (first 2 bits).
79    pub const SPECIAL_BITS_MASK: u64 = 0x03;
80}
81
82/// Offline Registry Library (offreg.dll) metadata.
83/// These fields are written when a hive is serialized by the Offline Registry Library.
84#[derive(Debug, Clone, Default)]
85pub struct OfflineRegistryInfo {
86    /// Whether OfRg signature was found.
87    pub present: bool,
88    /// Flags (typically 1).
89    pub flags: u32,
90    /// Serialization timestamp (FILETIME, at offset 512).
91    pub serialization_timestamp: u64,
92}
93
94/// The base block (file header) of a registry hive.
95///
96/// This is the first 4096 bytes of a primary registry file.
97#[derive(Debug, Clone)]
98pub struct BaseBlock {
99    /// Signature: "regf"
100    pub signature: [u8; 4],
101    /// Primary sequence number (incremented at start of write).
102    pub primary_sequence: u32,
103    /// Secondary sequence number (incremented at end of write).
104    pub secondary_sequence: u32,
105    /// Last written timestamp (FILETIME).
106    pub last_written: u64,
107    /// Major version of hive writer.
108    pub major_version: u32,
109    /// Minor version of hive writer.
110    pub minor_version: u32,
111    /// File type.
112    pub file_type: u32,
113    /// File format.
114    pub file_format: u32,
115    /// Offset of root cell relative to hive bins data.
116    pub root_cell_offset: u32,
117    /// Size of hive bins data in bytes.
118    pub hive_bins_data_size: u32,
119    /// Clustering factor (logical sector size / 512).
120    pub clustering_factor: u32,
121    /// File name (UTF-16LE, partial path or filename).
122    pub file_name: [u8; 64],
123    /// Resource Manager GUID (Windows Vista+).
124    pub rm_id: [u8; 16],
125    /// Log file GUID (Windows Vista+).
126    pub log_id: [u8; 16],
127    /// Flags (Windows Vista+).
128    pub flags: u32,
129    /// Transaction Manager GUID (Windows Vista+).
130    pub tm_id: [u8; 16],
131    /// GUID signature: "rmtm" (Windows Vista+).
132    pub guid_signature: [u8; 4],
133    /// Last reorganized timestamp (Windows 8+).
134    pub last_reorganized: u64,
135    /// Checksum of first 508 bytes.
136    pub checksum: u32,
137    /// Thaw Transaction Manager GUID (no meaning on disk, used for shadow copy recovery).
138    pub thaw_tm_id: [u8; 16],
139    /// Thaw Resource Manager GUID (no meaning on disk, used for shadow copy recovery).
140    pub thaw_rm_id: [u8; 16],
141    /// Thaw Log file GUID (no meaning on disk, used for shadow copy recovery).
142    pub thaw_log_id: [u8; 16],
143    /// Boot type (no meaning on disk).
144    pub boot_type: u32,
145    /// Boot recover (no meaning on disk).
146    pub boot_recover: u32,
147    /// Offline Registry Library metadata (if present).
148    pub offline_registry: OfflineRegistryInfo,
149}
150
151impl Default for BaseBlock {
152    fn default() -> Self {
153        Self {
154            signature: *REGF_SIGNATURE,
155            primary_sequence: 1,
156            secondary_sequence: 1,
157            last_written: 0,
158            major_version: 1,
159            minor_version: 6,
160            file_type: 0,
161            file_format: 1,
162            root_cell_offset: 32, // Typical offset after first hive bin header
163            hive_bins_data_size: 4096,
164            clustering_factor: 1,
165            file_name: [0; 64],
166            rm_id: [0; 16],
167            log_id: [0; 16],
168            flags: 0,
169            tm_id: [0; 16],
170            guid_signature: [0; 4],
171            last_reorganized: 0,
172            checksum: 0,
173            thaw_tm_id: [0; 16],
174            thaw_rm_id: [0; 16],
175            thaw_log_id: [0; 16],
176            boot_type: 0,
177            boot_recover: 0,
178            offline_registry: OfflineRegistryInfo::default(),
179        }
180    }
181}
182
183impl BaseBlock {
184    /// Parse a base block from a byte slice.
185    pub fn parse(data: &[u8]) -> Result<Self> {
186        if data.len() < BASE_BLOCK_SIZE {
187            return Err(Error::BufferTooSmall {
188                needed: BASE_BLOCK_SIZE,
189                available: data.len(),
190            });
191        }
192
193        let mut cursor = Cursor::new(data);
194
195        // Read signature
196        let mut signature = [0u8; 4];
197        cursor.read_exact(&mut signature)?;
198
199        if &signature != REGF_SIGNATURE {
200            return Err(Error::InvalidSignature {
201                expected: String::from_utf8_lossy(REGF_SIGNATURE).to_string(),
202                found: String::from_utf8_lossy(&signature).to_string(),
203            });
204        }
205
206        let primary_sequence = cursor.read_u32::<LittleEndian>()?;
207        let secondary_sequence = cursor.read_u32::<LittleEndian>()?;
208        let last_written = cursor.read_u64::<LittleEndian>()?;
209        let major_version = cursor.read_u32::<LittleEndian>()?;
210        let minor_version = cursor.read_u32::<LittleEndian>()?;
211        let file_type = cursor.read_u32::<LittleEndian>()?;
212        let file_format = cursor.read_u32::<LittleEndian>()?;
213        let root_cell_offset = cursor.read_u32::<LittleEndian>()?;
214        let hive_bins_data_size = cursor.read_u32::<LittleEndian>()?;
215        let clustering_factor = cursor.read_u32::<LittleEndian>()?;
216
217        let mut file_name = [0u8; 64];
218        cursor.read_exact(&mut file_name)?;
219
220        // Windows Vista+ fields (offset 112)
221        let mut rm_id = [0u8; 16];
222        cursor.read_exact(&mut rm_id)?;
223
224        let mut log_id = [0u8; 16];
225        cursor.read_exact(&mut log_id)?;
226
227        let flags = cursor.read_u32::<LittleEndian>()?;
228
229        let mut tm_id = [0u8; 16];
230        cursor.read_exact(&mut tm_id)?;
231
232        let mut guid_signature = [0u8; 4];
233        cursor.read_exact(&mut guid_signature)?;
234
235        let last_reorganized = cursor.read_u64::<LittleEndian>()?;
236
237        // Skip to checksum at offset 508
238        cursor.set_position(508);
239        let checksum = cursor.read_u32::<LittleEndian>()?;
240
241        // Verify checksum
242        let calculated_checksum = calculate_checksum(data);
243        if checksum != calculated_checksum {
244            return Err(Error::ChecksumMismatch {
245                expected: checksum,
246                calculated: calculated_checksum,
247            });
248        }
249
250        // Thaw GUIDs at offsets 4040, 4056, 4072 (used for shadow copy recovery)
251        cursor.set_position(4040);
252        let mut thaw_tm_id = [0u8; 16];
253        cursor.read_exact(&mut thaw_tm_id)?;
254
255        let mut thaw_rm_id = [0u8; 16];
256        cursor.read_exact(&mut thaw_rm_id)?;
257
258        let mut thaw_log_id = [0u8; 16];
259        cursor.read_exact(&mut thaw_log_id)?;
260
261        // Boot type and recover at offsets 4088 and 4092
262        cursor.set_position(4088);
263        let boot_type = cursor.read_u32::<LittleEndian>()?;
264        let boot_recover = cursor.read_u32::<LittleEndian>()?;
265
266        // Check for Offline Registry Library (OfRg) signature
267        // Can be at offset 176 (current versions) or 168 (legacy versions)
268        let mut offline_registry = OfflineRegistryInfo::default();
269        
270        // Try offset 176 first (current versions: 6.2, 6.3, 10.0)
271        cursor.set_position(176);
272        let mut ofrg_sig = [0u8; 4];
273        cursor.read_exact(&mut ofrg_sig)?;
274        
275        if &ofrg_sig == OFRG_SIGNATURE {
276            offline_registry.present = true;
277            offline_registry.flags = cursor.read_u32::<LittleEndian>()?;
278            // Serialization timestamp is at offset 512
279            cursor.set_position(512);
280            offline_registry.serialization_timestamp = cursor.read_u64::<LittleEndian>()?;
281        } else {
282            // Try offset 168 (legacy version: 6.1)
283            cursor.set_position(168);
284            cursor.read_exact(&mut ofrg_sig)?;
285            if &ofrg_sig == OFRG_SIGNATURE {
286                offline_registry.present = true;
287                offline_registry.flags = cursor.read_u32::<LittleEndian>()?;
288                cursor.set_position(512);
289                offline_registry.serialization_timestamp = cursor.read_u64::<LittleEndian>()?;
290            }
291        }
292
293        Ok(Self {
294            signature,
295            primary_sequence,
296            secondary_sequence,
297            last_written,
298            major_version,
299            minor_version,
300            file_type,
301            file_format,
302            root_cell_offset,
303            hive_bins_data_size,
304            clustering_factor,
305            file_name,
306            rm_id,
307            log_id,
308            flags,
309            tm_id,
310            guid_signature,
311            last_reorganized,
312            checksum,
313            thaw_tm_id,
314            thaw_rm_id,
315            thaw_log_id,
316            boot_type,
317            boot_recover,
318            offline_registry,
319        })
320    }
321
322    /// Write the base block to a writer.
323    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
324        let mut buffer = vec![0u8; BASE_BLOCK_SIZE];
325
326        {
327            let mut cursor = Cursor::new(&mut buffer[..]);
328
329            cursor.write_all(&self.signature)?;
330            cursor.write_u32::<LittleEndian>(self.primary_sequence)?;
331            cursor.write_u32::<LittleEndian>(self.secondary_sequence)?;
332            cursor.write_u64::<LittleEndian>(self.last_written)?;
333            cursor.write_u32::<LittleEndian>(self.major_version)?;
334            cursor.write_u32::<LittleEndian>(self.minor_version)?;
335            cursor.write_u32::<LittleEndian>(self.file_type)?;
336            cursor.write_u32::<LittleEndian>(self.file_format)?;
337            cursor.write_u32::<LittleEndian>(self.root_cell_offset)?;
338            cursor.write_u32::<LittleEndian>(self.hive_bins_data_size)?;
339            cursor.write_u32::<LittleEndian>(self.clustering_factor)?;
340            cursor.write_all(&self.file_name)?;
341            cursor.write_all(&self.rm_id)?;
342            cursor.write_all(&self.log_id)?;
343            cursor.write_u32::<LittleEndian>(self.flags)?;
344            cursor.write_all(&self.tm_id)?;
345            cursor.write_all(&self.guid_signature)?;
346            cursor.write_u64::<LittleEndian>(self.last_reorganized)?;
347        }
348
349        // Calculate and write checksum
350        let checksum = calculate_checksum(&buffer);
351        buffer[508..512].copy_from_slice(&checksum.to_le_bytes());
352
353        // Write thaw GUIDs at offsets 4040, 4056, 4072
354        buffer[4040..4056].copy_from_slice(&self.thaw_tm_id);
355        buffer[4056..4072].copy_from_slice(&self.thaw_rm_id);
356        buffer[4072..4088].copy_from_slice(&self.thaw_log_id);
357
358        // Write boot type and recover at the end
359        buffer[4088..4092].copy_from_slice(&self.boot_type.to_le_bytes());
360        buffer[4092..4096].copy_from_slice(&self.boot_recover.to_le_bytes());
361
362        writer.write_all(&buffer)
363    }
364
365    /// Check if the hive is dirty (needs recovery).
366    pub fn is_dirty(&self) -> bool {
367        self.primary_sequence != self.secondary_sequence
368    }
369
370    /// Get the file type as an enum.
371    pub fn get_file_type(&self) -> Result<FileType> {
372        FileType::try_from(self.file_type)
373    }
374
375    /// Get the file format as an enum.
376    pub fn get_file_format(&self) -> Result<FileFormat> {
377        FileFormat::try_from(self.file_format)
378    }
379
380    /// Get the flags.
381    pub fn get_flags(&self) -> BaseBlockFlags {
382        BaseBlockFlags::from_bits_truncate(self.flags)
383    }
384
385    /// Get the file name as a string.
386    pub fn get_file_name(&self) -> String {
387        // UTF-16LE encoded, null-terminated
388        let u16_values: Vec<u16> = self.file_name
389            .chunks_exact(2)
390            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
391            .take_while(|&c| c != 0)
392            .collect();
393
394        String::from_utf16_lossy(&u16_values)
395    }
396
397    /// Set the file name from a string.
398    pub fn set_file_name(&mut self, name: &str) {
399        let mut file_name = [0u8; 64];
400        let u16_values: Vec<u16> = name.encode_utf16().collect();
401
402        for (i, &value) in u16_values.iter().take(31).enumerate() {
403            let bytes = value.to_le_bytes();
404            file_name[i * 2] = bytes[0];
405            file_name[i * 2 + 1] = bytes[1];
406        }
407
408        self.file_name = file_name;
409    }
410
411    /// Update timestamps and sequence numbers before writing.
412    pub fn prepare_for_write(&mut self) {
413        use chrono::Utc;
414        use crate::structures::datetime_to_filetime;
415
416        self.primary_sequence = self.primary_sequence.wrapping_add(1);
417        self.last_written = datetime_to_filetime(Utc::now());
418    }
419
420    /// Check if the hive was defragmented during the latest reorganization.
421    /// (Based on bit 0 of the last_reorganized timestamp)
422    pub fn was_defragmented(&self) -> bool {
423        (self.last_reorganized & reorganized_bits::DEFRAGMENTED) != 0
424    }
425
426    /// Check if access history was cleared during the latest reorganization.
427    /// (Based on bit 1 of the last_reorganized timestamp)
428    pub fn was_access_history_cleared(&self) -> bool {
429        (self.last_reorganized & reorganized_bits::ACCESS_HISTORY_CLEARED) != 0
430    }
431
432    /// Get the actual last reorganized timestamp (without special bits).
433    pub fn get_last_reorganized_time(&self) -> u64 {
434        self.last_reorganized & !reorganized_bits::SPECIAL_BITS_MASK
435    }
436
437    /// Check if the hive was created/serialized by the Offline Registry Library.
438    pub fn is_offline_registry(&self) -> bool {
439        self.offline_registry.present
440    }
441
442    /// Mark write as complete.
443    pub fn complete_write(&mut self) {
444        self.secondary_sequence = self.primary_sequence;
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use super::*;
451
452    #[test]
453    fn test_default_base_block() {
454        let block = BaseBlock::default();
455        assert_eq!(&block.signature, REGF_SIGNATURE);
456        assert_eq!(block.major_version, 1);
457        assert!(!block.is_dirty());
458    }
459
460    #[test]
461    fn test_file_name() {
462        let mut block = BaseBlock::default();
463        block.set_file_name("SYSTEM");
464        assert_eq!(block.get_file_name(), "SYSTEM");
465    }
466}
467