Skip to main content

frnsc_hive/
hive.rs

1use forensic_rs::{
2    err::ForensicError, notifications::NotificationType, notify_info, prelude::ForensicResult, traits::vfs::VirtualFile
3};
4
5use crate::cell::{read_cell, HiveCell};
6
7pub struct HivePrimaryFile {
8    pub base_block: BaseBlock,
9}
10
11#[derive(Debug)]
12pub struct BaseBlock {
13    /// Primary sequence number.
14    /// This number is incremented by 1 in the beginning of a write operation on the primary file
15    pub prim_sequence: u32,
16    /// Secondary sequence number.
17    /// This number is incremented by 1 at the end of a write operation on the primary file,
18    /// a primary sequence number and a secondary sequence number should be equal after a successful write operation
19    pub sec_sequence: u32,
20    /// FILETIME (UTC)
21    pub last_written_timestamp: u64,
22    /// Major version of a hive writer
23    pub major_version: u32,
24    /// Minor version of a hive writer
25    pub minor_version: u32,
26    /// 0 means primary file
27    pub file_type: u32,
28    /// 1 means direct memory load
29    pub file_format: u32,
30    /// Offset of a root cell in bytes, relative from the start of the hive bins data
31    pub root_cell_offset: u32,
32    /// Size of the hive bins data in bytes
33    pub hive_bins_data_size: u32,
34    /// Logical sector size of the underlying disk in bytes divided by 512
35    pub clustering_factor: u32,
36    /// UTF-16LE string (contains a partial file path to the primary file, or a file name of the primary file), used for debugging purposes
37    pub file_name: [u8; 64],
38    /// XOR-32 checksum of the previous 508 bytes
39    pub checksum: u32,
40    /// This field has no meaning on a disk
41    pub boot_type: u32,
42    /// This field has no meaning on a disk
43    pub boot_recovery: u32,
44}
45
46#[repr(C, packed)]
47pub struct BaseBlockWXP {
48    /// ASCII String regf
49    pub signature: u32,
50    /// Primary sequence number.
51    /// This number is incremented by 1 in the beginning of a write operation on the primary file
52    pub prim_sequence: u32,
53    /// Secondary sequence number.
54    /// This number is incremented by 1 at the end of a write operation on the primary file,
55    /// a primary sequence number and a secondary sequence number should be equal after a successful write operation
56    pub sec_sequence: u32,
57    /// FILETIME (UTC)
58    pub last_written_timestamp: u64,
59    /// Major version of a hive writer
60    pub major_version: u32,
61    /// Minor version of a hive writer
62    pub minor_version: u32,
63    /// 0 means primary file
64    pub file_type: u32,
65    /// 1 means direct memory load
66    pub file_format: u32,
67    /// Offset of a root cell in bytes, relative from the start of the hive bins data
68    pub root_cell_offset: u32,
69    /// Size of the hive bins data in bytes
70    pub hive_bins_data_size: u32,
71    /// Logical sector size of the underlying disk in bytes divided by 512
72    pub clustering_factor: u32,
73    /// UTF-16LE string (contains a partial file path to the primary file, or a file name of the primary file), used for debugging purposes
74    pub file_name: [u8; 64],
75    pub reserved: [u8; 56],
76    /// ASCII string "OfRg" The Offline Registry Library (offreg.dll) is writing the following additional fields to the base block when the hive is serialized
77    pub offreg_signature: u32,
78    /// This is the only value used "1"
79    pub offreg_flags: u32,
80    pub reserved1: [u8; 332],
81    /// XOR-32 checksum of the previous 508 bytes
82    pub checksum: u32,
83    /// FILETIME UTC. Writed by offreg.dll
84    pub serialization_timestamp: u64,
85    pub reserved2: [u8; 3568],
86    /// This field has no meaning on a disk
87    pub boot_type: u32,
88    /// This field has no meaning on a disk
89    pub boot_recovery: u32,
90}
91
92#[repr(C, packed)]
93pub struct BaseBlockW10 {
94    /// ASCII String regf
95    pub signature: u32,
96    /// Primary sequence number.
97    /// This number is incremented by 1 in the beginning of a write operation on the primary file
98    pub prim_sequence: u32,
99    /// Secondary sequence number.
100    /// This number is incremented by 1 at the end of a write operation on the primary file,
101    /// a primary sequence number and a secondary sequence number should be equal after a successful write operation
102    pub sec_sequence: u32,
103    /// FILETIME (UTC)
104    pub last_written_timestamp: u64,
105    /// Major version of a hive writer
106    pub major_version: u32,
107    /// Minor version of a hive writer
108    pub minor_version: u32,
109    /// 0 means primary file
110    pub file_type: u32,
111    /// 1 means direct memory load
112    pub file_format: u32,
113    /// Offset of a root cell in bytes, relative from the start of the hive bins data
114    pub root_cell_offset: u32,
115    /// Size of the hive bins data in bytes
116    pub hive_bins_data_size: u32,
117    /// Logical sector size of the underlying disk in bytes divided by 512
118    pub clustering_factor: u32,
119    /// UTF-16LE string (contains a partial file path to the primary file, or a file name of the primary file), used for debugging purposes
120    pub file_name: [u8; 64],
121    /// Resource Manager GUID
122    pub rm_id: u128,
123    /// GUID used to generate a file name of a physical log file
124    pub log_id: u128,
125    /// Bit mask
126    pub flags: u32,
127    /// GUID used to generate a file name of a log file stream for the Transaction Manager
128    pub tm_id: u128,
129    /// ASCII string "rmtm" unless hive is frozen
130    pub guid_signature: u32,
131    /// FILETIME (UTC)
132    pub last_reorganized_timestamp: u64,
133    /// ASCII string "OfRg" The Offline Registry Library (offreg.dll) is writing the following additional fields to the base block when the hive is serialized
134    pub offreg_signature: u32,
135    /// This is the only value used "1"
136    pub offreg_flags: u32,
137    pub reserved1: [u8; 324],
138    /// XOR-32 checksum of the previous 508 bytes
139    pub checksum: u32,
140    /// FILETIME UTC. Writed by offreg.dll
141    pub serialization_timestamp: u64,
142    pub reserved2: [u8; 3568],
143    /// This field has no meaning on a disk
144    pub boot_type: u32,
145    /// This field has no meaning on a disk
146    pub boot_recovery: u32,
147}
148
149#[repr(C, packed)]
150#[derive(Debug)]
151pub struct HiveBinHeader {
152    /// ASCII string hbin
153    pub signature: u32,
154    /// Offset of a current hive bin in bytes, relative from the start of the hive bins data
155    pub offset: u32,
156    /// Size of a current hive bin in bytes
157    pub size: u32,
158    pub reserved: u64,
159    /// FILETIME (UTC), defined for the first hive bin only (see below)
160    pub timestamp: u64,
161    /// Spare (or MemAlloc). This field has no meaning on a disk (see below)
162    pub spare: u32,
163}
164
165impl From<BaseBlockW10> for BaseBlock {
166    fn from(value: BaseBlockW10) -> Self {
167        BaseBlock {
168            prim_sequence: value.prim_sequence,
169            sec_sequence: value.sec_sequence,
170            last_written_timestamp: value.last_written_timestamp,
171            major_version: value.major_version,
172            minor_version: value.minor_version,
173            file_type: value.file_type,
174            file_format: value.file_format,
175            root_cell_offset: value.root_cell_offset,
176            hive_bins_data_size: value.hive_bins_data_size,
177            clustering_factor: value.clustering_factor,
178            file_name: value.file_name,
179            checksum: value.checksum,
180            boot_type: value.boot_type,
181            boot_recovery: value.boot_recovery,
182        }
183    }
184}
185impl From<&BaseBlockW10> for BaseBlock {
186    fn from(value: &BaseBlockW10) -> Self {
187        BaseBlock {
188            prim_sequence: value.prim_sequence,
189            sec_sequence: value.sec_sequence,
190            last_written_timestamp: value.last_written_timestamp,
191            major_version: value.major_version,
192            minor_version: value.minor_version,
193            file_type: value.file_type,
194            file_format: value.file_format,
195            root_cell_offset: value.root_cell_offset,
196            hive_bins_data_size: value.hive_bins_data_size,
197            clustering_factor: value.clustering_factor,
198            file_name: value.file_name,
199            checksum: value.checksum,
200            boot_type: value.boot_type,
201            boot_recovery: value.boot_recovery,
202        }
203    }
204}
205
206impl From<BaseBlockWXP> for BaseBlock {
207    fn from(value: BaseBlockWXP) -> Self {
208        BaseBlock {
209            prim_sequence: value.prim_sequence,
210            sec_sequence: value.sec_sequence,
211            last_written_timestamp: value.last_written_timestamp,
212            major_version: value.major_version,
213            minor_version: value.minor_version,
214            file_type: value.file_type,
215            file_format: value.file_format,
216            root_cell_offset: value.root_cell_offset,
217            hive_bins_data_size: value.hive_bins_data_size,
218            clustering_factor: value.clustering_factor,
219            file_name: value.file_name,
220            checksum: value.checksum,
221            boot_type: value.boot_type,
222            boot_recovery: value.boot_recovery,
223        }
224    }
225}
226impl From<&BaseBlockWXP> for BaseBlock {
227    fn from(value: &BaseBlockWXP) -> Self {
228        BaseBlock {
229            prim_sequence: value.prim_sequence,
230            sec_sequence: value.sec_sequence,
231            last_written_timestamp: value.last_written_timestamp,
232            major_version: value.major_version,
233            minor_version: value.minor_version,
234            file_type: value.file_type,
235            file_format: value.file_format,
236            root_cell_offset: value.root_cell_offset,
237            hive_bins_data_size: value.hive_bins_data_size,
238            clustering_factor: value.clustering_factor,
239            file_name: value.file_name,
240            checksum: value.checksum,
241            boot_type: value.boot_type,
242            boot_recovery: value.boot_recovery,
243        }
244    }
245}
246
247pub fn is_win10_format(buff: &[u8]) -> bool {
248    if buff.len() != 4096 {
249        return false;
250    }
251    &buff[164..168] == b"rmtm"
252}
253
254pub fn is_valid_base_block(buff: &[u8]) -> bool {
255    if buff.len() != 4096 {
256        return false;
257    }
258    &buff[0..4] == b"regf"
259}
260
261pub fn is_hive_data(buff: &[u8]) -> bool {
262    if buff.len() != 32 {
263        return false;
264    }
265    &buff[0..4] == b"hbin"
266}
267
268pub fn checksum_is_correct(buff: &[u8]) -> bool {
269    let mut c: i32 = 0;
270    for i in (0..508).step_by(4) {
271        let v = i32::from_ne_bytes(buff[i..i + 4].try_into().unwrap_or_default());
272        c ^= v;
273    }
274    if c == -1 {
275        c = -2;
276    } else if c == 0 {
277        c = -1;
278    }
279    let checksum = i32::from_ne_bytes(buff[508..512].try_into().unwrap_or_default());
280    c == checksum
281}
282
283pub enum KernelTransactionManagerFlags {
284    LockedHive = 0x00000001,
285    DefragmentedHive = 0x00000002,
286}
287
288pub fn read_base_block(file: &mut Box<dyn VirtualFile>) -> ForensicResult<BaseBlock> {
289    let mut buffer = vec![0u8; 4096];
290    file.read_exact(&mut buffer)?;
291    if !is_valid_base_block(&buffer) {
292        return Err(ForensicError::bad_format_str("Invalid base block"));
293    }
294    if !checksum_is_correct(&buffer) {
295        notify_info!(
296            NotificationType::Informational,
297            "Incorrect checksum for Hive"
298        );
299    }
300    if is_win10_format(&buffer) {
301        let (head, body, tail) = unsafe { buffer.align_to::<BaseBlockW10>() };
302        if !head.is_empty() || !tail.is_empty() {
303            return Err(ForensicError::bad_format_str("Invalid base block for Windows 10 format"));
304        }
305        let base_block = &body[0];
306        return Ok(base_block.into());
307    }
308    let (head, body, tail) = unsafe { buffer.align_to::<BaseBlockWXP>() };
309    if !head.is_empty() || !tail.is_empty() {
310        return Err(ForensicError::bad_format_str("Invalid base block for windows XP format"));
311    }
312    let base_block = &body[0];
313    Ok(base_block.into())
314}
315pub fn read_bin_header(file: &mut Box<dyn VirtualFile>) -> ForensicResult<HiveBinHeader> {
316    let mut buffer = vec![0u8; 32];
317    file.read_exact(&mut buffer)?;
318    if !is_hive_data(&buffer) {
319        return Err(ForensicError::bad_format_str("Invalid HiveBin header: signature does not match"));
320    }
321    let (head, body, tail) = unsafe { buffer.align_to::<HiveBinHeader>() };
322    if !head.is_empty() || !tail.is_empty() {
323        return Err(ForensicError::bad_format_str("Invalid HiveBin header: not a header"));
324    }
325    let header = &body[0];
326    Ok(HiveBinHeader {
327        signature: header.signature,
328        offset: header.offset,
329        size: header.size,
330        reserved: header.reserved,
331        timestamp: header.timestamp,
332        spare: header.spare,
333    })
334}
335
336pub fn read_hive_bin_at_file_position(
337    file: &mut Box<dyn VirtualFile>,
338) -> ForensicResult<(HiveBinHeader, Vec<u8>)> {
339    let initial_offset = file.stream_position().unwrap_or_default();
340    let header = match read_bin_header(file) {
341        Ok(v) => v,
342        Err(e) => {
343            file.seek(std::io::SeekFrom::Start(initial_offset))?;
344            return Err(e);
345        }
346    };
347    if header.size < 32 {
348        file.seek(std::io::SeekFrom::Start(initial_offset))?;
349        return Err(ForensicError::bad_format_str("Invalid HiveBin header: too small"));
350    }
351    let mut buff = vec![0u8; header.size as usize - 32];
352    file.read_exact(&mut buff)?;
353    Ok((header, buff))
354}
355
356pub fn read_cells(data: &[u8], bin_offset: u64) -> ForensicResult<Vec<HiveCell>> {
357    let len = data.len();
358    if len < 4 {
359        return Err(ForensicError::bad_format_str("Invalid HiveCell: too small"));
360    }
361    let mut offset = 0;
362    let mut list = Vec::with_capacity(128);
363    loop {
364        if offset > len {
365            return Err(ForensicError::bad_format_str("Invalid HiveCell: offset at position larger than the buffer"));
366        } else if offset == len {
367            break;
368        }
369        // Size of a current cell in bytes, including this field (aligned to 8 bytes): the size is positive if a cell is unallocated or negative if a cell is allocated (use absolute values for calculations)
370        let cell_len_i =
371            i32::from_ne_bytes(data[offset..offset + 4].try_into().unwrap_or_default());
372        let cell_len = cell_len_i.unsigned_abs() as usize;
373        if offset + cell_len > len {
374            return Err(ForensicError::bad_format_str("Invalid HiveCell: end position larger than the buffer"));
375        }
376        let cell_data = &data[offset..offset + cell_len];
377        let cell = match read_cell(cell_data, bin_offset + offset as u64) {
378            Ok(v) => v,
379            Err(_e) => {
380                offset += cell_len;
381                continue;
382            }
383        };
384        list.push(cell);
385        // TODO
386        offset += cell_len;
387    }
388    Ok(list)
389}
390
391#[cfg(test)]
392mod tst {
393    use super::*;
394    use crate::tst::*;
395    #[test]
396    fn can_read_hive_header_block() {
397        init_tst();
398        let mut fs = init_virtual_fs();
399        let mut sam_file = read_sam_hive(&mut fs);
400        let base_block = read_base_block(&mut sam_file).unwrap();
401        assert_eq!(
402            str_to_unicode_with_ending(b"\\SystemRoot\\System32\\Config\\SAM"),
403            &base_block.file_name[0..64]
404        );
405        let mut sec_file: Box<dyn VirtualFile> = read_sec_hive(&mut fs);
406        let base_block = read_base_block(&mut sec_file).unwrap();
407        assert_eq!(
408            str_to_unicode_with_ending(b"emRoot\\System32\\Config\\SECURITY"),
409            &base_block.file_name[0..64]
410        );
411        assert_no_notifications();
412    }
413
414    #[test]
415    fn can_read_sam_hive_data() {
416        init_tst();
417        let mut fs = init_virtual_fs();
418        let mut sam_file = read_sam_hive(&mut fs);
419        let base_block = read_base_block(&mut sam_file).unwrap();
420        assert_no_notifications();
421        // Position to 4096 + offset -32 (header)
422        sam_file
423            .seek(std::io::SeekFrom::Start(
424                4096 + base_block.root_cell_offset as u64 - 32,
425            ))
426            .unwrap();
427        let hive_bin = read_hive_bin_at_file_position(&mut sam_file).unwrap();
428        read_cells(&hive_bin.1, 4096 + base_block.root_cell_offset as u64 - 32).unwrap();
429        let mut i = 0;
430        let mut offset = 4096 + base_block.root_cell_offset as u64 - 32;
431        let mut cell_offset = 32;
432        loop {
433            if offset >= base_block.hive_bins_data_size.into() {
434                break;
435            }
436            i = i + 1;
437            sam_file.seek(std::io::SeekFrom::Start(offset)).unwrap();
438            let hive_bin = read_hive_bin_at_file_position(&mut sam_file).unwrap();
439            let cells = read_cells(&hive_bin.1, cell_offset).unwrap();
440            println!("{:?}", cells);
441            offset += hive_bin.0.size as u64;
442            cell_offset += hive_bin.0.size as u64;
443        }
444    }
445    #[test]
446    fn can_read_security_hive_data() {
447        init_tst();
448        let mut fs = init_virtual_fs();
449        let mut sec_file = read_sec_hive(&mut fs);
450        let base_block = read_base_block(&mut sec_file).unwrap();
451        assert_no_notifications();
452        // Position to 4096 + offset -32 (header)
453
454        let mut i = 0;
455        let mut offset = 4096 + base_block.root_cell_offset as u64 - 32;
456
457        loop {
458            if offset >= base_block.hive_bins_data_size.into() {
459                break;
460            }
461            i = i + 1;
462            sec_file.seek(std::io::SeekFrom::Start(offset)).unwrap();
463            let hive_bin = read_hive_bin_at_file_position(&mut sec_file).unwrap();
464            let cells = read_cells(
465                &hive_bin.1,
466                offset - 4064 - base_block.root_cell_offset as u64,
467            )
468            .unwrap();
469            println!("{:?}", cells);
470            offset += hive_bin.0.size as u64;
471        }
472    }
473
474    #[test]
475    #[ignore]
476    fn can_read_software_hive_data() {
477        init_tst();
478        let mut fs = init_virtual_fs();
479        let mut sec_file = read_software_hive(&mut fs);
480        let base_block = read_base_block(&mut sec_file).unwrap();
481        assert_no_notifications();
482        // Position to 4096 + offset -32 (header)
483
484        let mut i = 0;
485        let mut offset = 4096 + base_block.root_cell_offset as u64 - 32;
486
487        loop {
488            if offset >= base_block.hive_bins_data_size.into() {
489                break;
490            }
491            i = i + 1;
492            sec_file.seek(std::io::SeekFrom::Start(offset)).unwrap();
493            let hive_bin = read_hive_bin_at_file_position(&mut sec_file).unwrap();
494            let cells = read_cells(
495                &hive_bin.1,
496                offset - 4064 - base_block.root_cell_offset as u64,
497            )
498            .unwrap();
499            println!("{:?}", cells);
500            offset += hive_bin.0.size as u64;
501        }
502    }
503
504    #[test]
505    fn can_read_supersecretadmin_hive_data() {
506        init_tst();
507        let mut fs = init_virtual_fs();
508        let mut sec_file = read_supersecretadmin_hive(&mut fs);
509        let base_block = read_base_block(&mut sec_file).unwrap();
510        assert_no_notifications();
511        // Position to 4096 + offset -32 (header)
512
513        let mut i = 0;
514        let mut offset = 4096 + base_block.root_cell_offset as u64 - 32;
515
516        loop {
517            if offset >= base_block.hive_bins_data_size.into() {
518                break;
519            }
520            i = i + 1;
521            sec_file.seek(std::io::SeekFrom::Start(offset)).unwrap();
522            let hive_bin = read_hive_bin_at_file_position(&mut sec_file).unwrap();
523            let cells = read_cells(
524                &hive_bin.1,
525                offset - 4064 - base_block.root_cell_offset as u64,
526            )
527            .unwrap();
528            println!("{:?}", cells);
529            offset += hive_bin.0.size as u64;
530        }
531    }
532}