Skip to content

Commit

Permalink
fix cell carving
Browse files Browse the repository at this point in the history
  • Loading branch information
janstarke committed Apr 17, 2024
1 parent 7b91240 commit 1cc52a6
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 78 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nt_hive2"
version = "4.2.0"
version = "4.2.1"
edition = "2021"
authors = ["Jan Starke <[email protected]>", "Muteb Alqahtani <[email protected]>"]
license = "GPL-3.0"
Expand Down
7 changes: 4 additions & 3 deletions src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ use crate::*;
/// the size of the cell as a 32bit value, but [CellHeader] enriches this by
/// some additional information
#[derive_binread]
#[derive(Eq, PartialEq)]
#[derive(Eq, PartialEq, Debug)]
pub struct CellHeader {
// The cell size must be a multiple of 8 bytes
#[br(temp, assert(raw_size != 0))]
raw_size: i32,

#[br(calc((raw_size as i64).abs().try_into().unwrap()))]
#[br(calc((raw_size as i64).abs().try_into().unwrap()),
assert(size & 0b111 == 0, "size should be aligned to 8 bytes"))]
size: usize,

#[br(calc(raw_size > 0))]
Expand Down Expand Up @@ -80,7 +81,7 @@ impl CellHeader {
/// For conveniance reasons, [Hive] already presents the method [read_structure](Hive::read_structure),
/// which does basically the same.
///
#[derive(BinRead, Eq, PartialEq)]
#[derive(BinRead, Eq, PartialEq, Debug)]
#[br(import_tuple(data_args: A))]
pub struct Cell<T, A: Any + Copy>
where
Expand Down
4 changes: 3 additions & 1 deletion src/hive/hive_bin_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ where
match HiveBin::new(Rc::clone(&self.hive)) {
Ok(hivebin) => {
self.expected_end = current_start + *hivebin.size() as u64;
assert_eq!(self.expected_end & 0xfff, 0, "hivebins must be alligned at 4k boundaries");
assert_eq!(self.expected_end & 0xfff, 0, "hivebins must be aligned at 4k boundaries");

log::trace!("found new hivebin at 0x{current_start:08x} with length {}, ending at 0x{:08x}",
hivebin.size(), self.expected_end);
return Some(hivebin)
}
Err(why) => {
Expand Down
4 changes: 2 additions & 2 deletions src/hive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use hive_status::*;
pub use hive_with_logs::*;
pub use offset::*;

use crate::hivebin::{CellLookAhead, HiveBin};
use crate::hivebin::{CellContent, HiveBin};
use crate::nk::KeyNode;
use crate::nk::{KeyNodeFlags, KeyNodeWithMagic};
use crate::transactionlog::{ApplicationResult, TransactionLogsEntry};
Expand Down Expand Up @@ -306,7 +306,7 @@ where
.flat_map(|hb| hb.cells())
.filter(|selector| !selector.header().is_deleted())
{
if let CellLookAhead::NK(nk) = cell.content() {
if let CellContent::NK(nk) = cell.content() {
if nk.flags.contains(KeyNodeFlags::KEY_HIVE_ENTRY) {
return Some(*cell.offset());
}
Expand Down
81 changes: 45 additions & 36 deletions src/hivebin/cell_iterator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::cell::RefCell;
use std::fmt::Debug;
use std::io::{ErrorKind, Seek};
use std::rc::Rc;

Expand Down Expand Up @@ -27,23 +28,38 @@ where
Self {
hive,
hivebin_size: (*hivebin.size()).try_into().unwrap(),

// we assume that we already consumed the header
consumed_bytes: hivebin.header_size().into(),
}
}

fn parse<T: BinRead>(&self) -> Option<T> {
let r: BinResult<T> = self.hive.borrow_mut().read_le();
match r {
Ok(t) => Some(t),
Err(why) => {
if let binread::Error::Io(kind) = &why {
if kind.kind() != ErrorKind::UnexpectedEof {
log::warn!("parser error: {}", why);
}
}
None
}
fn next_cell(&mut self) -> BinResult<Option<CellSelector>> {
const CELL_HEADER_SIZE: usize = 4;

// if there is not enough space in this hivebin, give up
if self.consumed_bytes + CELL_HEADER_SIZE >= self.hivebin_size {
return Ok(None);
}

let cell_offset = self.hive.borrow_mut().stream_position().unwrap();

let header: CellHeader = self.hive.borrow_mut().read_le()?;

let cell_size = header.size();
let content: CellContent = self.hive.borrow_mut().read_le()?;
self.consumed_bytes += cell_size;

let cell_selector = CellSelector {
offset: Offset(cell_offset.try_into().unwrap()),
header,
content,
};
self.hive.borrow_mut().seek(std::io::SeekFrom::Start(
cell_offset + u64::try_from(cell_size).unwrap(),
))?;

Ok(Some(cell_selector))
}
}

Expand All @@ -54,27 +70,19 @@ where
type Item = CellSelector;

fn next(&mut self) -> Option<Self::Item> {
const CELL_HEADER_SIZE: usize = 4;

// if there is not enough space in this hivebin, give up
if self.consumed_bytes + CELL_HEADER_SIZE >= self.hivebin_size {
return None;
}

let cell_offset = self.hive.borrow_mut().stream_position().unwrap();

if let Some(header) = self.parse::<CellHeader>() {
if let Some(lookahead) = self.parse::<CellLookAhead>() {
self.consumed_bytes += header.size();
return Some(CellSelector {
offset: Offset(cell_offset.try_into().unwrap()),
header,
content: lookahead,
});
match self.next_cell() {
Ok(v) => v,
Err(why) => {
if let binread::Error::Io(kind) = &why {
if kind.kind() != ErrorKind::UnexpectedEof {
log::warn!("parser error: {}", why);
}
} else {
log::warn!("parser error: {}", why);
}
None
}
}

None
}
}

Expand All @@ -83,11 +91,12 @@ where
pub struct CellSelector {
offset: Offset,
header: CellHeader,
content: CellLookAhead,
content: CellContent,
}

#[derive_binread]
pub enum CellLookAhead {
#[derive(Debug)]
pub enum CellContent {
#[br(magic = b"nk")]
NK(KeyNode),
#[br(magic = b"vk")]
Expand Down Expand Up @@ -130,7 +139,7 @@ pub enum CellLookAhead {
#[br(count=count)]
items: Vec<IndexRootListElement>,
},

#[allow(clippy::upper_case_acronyms)]
UNKNOWN,
}
Expand All @@ -143,7 +152,7 @@ pub enum CellLookAheadConversionError {
DifferentCellTypeExpected,
}

impl CellLookAhead {
impl CellContent {
pub fn is_nk(&self) -> bool {
matches!(self, Self::NK(_))
}
Expand All @@ -154,7 +163,7 @@ impl TryInto<KeyNode> for CellSelector {

fn try_into(self) -> Result<KeyNode, Self::Error> {
match self.content {
CellLookAhead::NK(nk) => Ok(nk),
CellContent::NK(nk) => Ok(nk),
_ => Err(CellLookAheadConversionError::DifferentCellTypeExpected),
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/nk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub struct KeyNodeWithMagic(KeyNode);
/// represents a registry key node (as documented in <https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md#key-node>)
#[allow(dead_code)]
#[derive_binread]
#[derive(Debug)]
pub struct KeyNode {
#[br(parse_with=parse_node_flags)]
pub(crate) flags: KeyNodeFlags,
Expand Down Expand Up @@ -95,6 +96,7 @@ pub struct KeyNode {

#[br( if(key_values_count > 0 && key_values_list_offset != u32::MAX),
parse_with=read_values,
restore_position,
args(key_values_list.as_ref(), ))]
values: Vec<KeyValue>,

Expand Down
73 changes: 41 additions & 32 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use std::{io::{Read, Seek}};
use std::io::{Read, Seek};

use binread::{ReadOptions, BinResult, BinRead, BinReaderExt};
use binread::{BinRead, BinReaderExt, BinResult, ReadOptions};
use chrono::{DateTime, Utc};
use encoding_rs::{ISO_8859_15, UTF_16LE};
use winstructs::timestamp::WinTimestamp;

pub (crate) fn parse_string<R: Read + Seek>(reader: &mut R, ro: &ReadOptions, params: (bool,))
-> BinResult<String> {
pub(crate) fn parse_string<R: Read + Seek>(
reader: &mut R,
ro: &ReadOptions,
params: (bool,),
) -> BinResult<String> {
let raw_string = Vec::<u8>::read_options(reader, ro, ())?;

let (cow, _, had_errors) =
if params.0 {
let (cow, _, had_errors) = if params.0 {
ISO_8859_15.decode(&raw_string[..])
} else {
UTF_16LE.decode(&raw_string[..])
Expand All @@ -19,45 +21,50 @@ pub (crate) fn parse_string<R: Read + Seek>(reader: &mut R, ro: &ReadOptions, pa
if had_errors {
Err(binread::error::Error::Custom {
pos: ro.offset,
err: Box::new(format!("unable to decode String at offset 0x{:08x}", ro.offset))})
err: Box::new(format!(
"unable to decode String at offset 0x{:08x}",
ro.offset
)),
})
} else {
Ok(cow.to_string())
}
}

pub (crate) fn parse_reg_sz(raw_string: &[u8]) -> BinResult<String> {
pub(crate) fn parse_reg_sz(raw_string: &[u8]) -> BinResult<String> {
let res = parse_reg_sz_raw(raw_string)?;
Ok(res.trim_end_matches(char::from(0)).to_string())
}

pub fn parse_reg_sz_raw(raw_string: &[u8]) -> BinResult<String> {
let (cow, _, had_errors) = UTF_16LE.decode(raw_string);
if ! had_errors {
if !had_errors {
Ok(cow.to_string())
} else {

let (cow, _, had_errors) = ISO_8859_15.decode(raw_string);
if had_errors {
Err(binread::error::Error::Custom {
pos: 0,
err: Box::new("unable to decode RegSZ string")})
err: Box::new("unable to decode RegSZ string"),
})
} else {
//assert_eq!(raw_string.len(), cow.len());
Ok(cow.to_string())
}
}
}

pub (crate) fn parse_reg_multi_sz(raw_string: &[u8]) -> BinResult<Vec<String>> {
let mut multi_string: Vec<String> = parse_reg_sz_raw(raw_string)?.split('\0')
pub(crate) fn parse_reg_multi_sz(raw_string: &[u8]) -> BinResult<Vec<String>> {
let mut multi_string: Vec<String> = parse_reg_sz_raw(raw_string)?
.split('\0')
.map(|x| x.to_owned())
.collect();

// due to the way split() works we have an empty string after the last \0 character
// and due to the way RegMultiSZ works we have an additional empty string between the
// last two \0 characters.
// those additional empty strings will be deleted afterwards:
assert!(! multi_string.len() >= 2);
assert!(!multi_string.len() >= 2);
//assert_eq!(multi_string.last().unwrap().len(), 0);
multi_string.pop();

Expand All @@ -69,43 +76,45 @@ pub (crate) fn parse_reg_multi_sz(raw_string: &[u8]) -> BinResult<Vec<String>> {
Ok(multi_string)
}

pub (crate) fn parse_timestamp<R: Read + Seek>(reader: &mut R, _ro: &ReadOptions, _: ())
-> BinResult<DateTime<Utc>> {
let raw_timestamp: [u8;8] = reader.read_le()?;
pub(crate) fn parse_timestamp<R: Read + Seek>(
reader: &mut R,
_ro: &ReadOptions,
_: (),
) -> BinResult<DateTime<Utc>> {
let raw_timestamp: [u8; 8] = reader.read_le()?;
let timestamp = WinTimestamp::new(&raw_timestamp).unwrap();
Ok(timestamp.to_datetime())
}

pub const U32_FIRST_BIT: u32 = 1 << (u32::BITS - 1);
pub const INV_U32_FIRST_BIT: u32 = ! (1 << (u32::BITS - 1));
pub (crate) trait HasFirstBitSet {
fn has_first_bit_set (val: &Self) -> bool;
pub const INV_U32_FIRST_BIT: u32 = !(1 << (u32::BITS - 1));
pub(crate) trait HasFirstBitSet {
fn has_first_bit_set(val: &Self) -> bool;
}

impl HasFirstBitSet for u32 {
fn has_first_bit_set (val: &Self) -> bool {
fn has_first_bit_set(val: &Self) -> bool {
val & U32_FIRST_BIT == U32_FIRST_BIT
}
}

pub(crate) const BIG_DATA_SEGMENT_SIZE: u32 = 16344;
pub (crate) fn without_first_bit(val: u32) -> u32 {
pub(crate) fn without_first_bit(val: u32) -> u32 {
val & INV_U32_FIRST_BIT
}


#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_has_first_bit_set () {
assert!(u32::has_first_bit_set(& 0x8000_0000));
assert!(u32::has_first_bit_set(& 0xFFFF_FFFF));
assert!(! u32::has_first_bit_set(& 0x7FFF_FFFF));
assert!(! u32::has_first_bit_set(& 0));
assert!(! u32::has_first_bit_set(& 1));
assert!(! u32::has_first_bit_set(& (i32::MAX as u32)));
fn test_has_first_bit_set() {
assert!(u32::has_first_bit_set(&0x8000_0000));
assert!(u32::has_first_bit_set(&0xFFFF_FFFF));
assert!(!u32::has_first_bit_set(&0x7FFF_FFFF));
assert!(!u32::has_first_bit_set(&0));
assert!(!u32::has_first_bit_set(&1));
assert!(!u32::has_first_bit_set(&(i32::MAX as u32)));
}

#[test]
Expand All @@ -118,4 +127,4 @@ mod tests {
assert_eq!(16, without_first_bit(0x8000_0010));
assert_eq!(32, without_first_bit(0x8000_0020));
}
}
}
Loading

0 comments on commit 1cc52a6

Please sign in to comment.