From a46d9c9702f1de78ac1be88b525fecc7db03f612 Mon Sep 17 00:00:00 2001 From: forensicmatt Date: Mon, 23 Mar 2020 13:18:51 -0600 Subject: Warn on fixup validation instead of fail Warn that not all fixup array items were validated, but continue parsing. Added `valid_fixup` to MftEntry. Added tests. --- CHANGELOG.md | 4 ++++ samples/entry_102130_fixup_issue | Bin 0 -> 1024 bytes src/entry.rs | 37 +++++++++++++++++++++++++++---------- tests/test_entry.rs | 17 +++++++++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 samples/entry_102130_fixup_issue create mode 100644 tests/test_entry.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c4f1d..dd589d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Attribute list parsing +### Changed +- Warn on fixup mismatch instead of fail + ### Added - Additional File Attribute flags +- `valid_fixup` field to MftEntry ## [0.5.1] - 2020-02-06 diff --git a/samples/entry_102130_fixup_issue b/samples/entry_102130_fixup_issue new file mode 100644 index 0000000..60d1b7c Binary files /dev/null and b/samples/entry_102130_fixup_issue differ diff --git a/src/entry.rs b/src/entry.rs index f1a0b53..0c0bb48 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,7 +1,7 @@ use crate::err::{Error, Result}; use crate::impl_serialize_for_bitflags; -use log::trace; +use log::{trace, warn}; use winstructs::ntfs::mft_reference::MftReference; @@ -29,6 +29,10 @@ pub const FILE_HEADER: &[u8; 4] = b"FILE"; pub struct MftEntry { pub header: EntryHeader, pub data: Vec, + /// Valid fixup allows you to check if the fixup value in the entry's blocks + /// matched the fixup array value. It is optional because in the case of + /// from_buffer_skip_fixup(), no fixup is even checked, thus, valid_fixup is None + pub valid_fixup: Option } impl ser::Serialize for MftEntry { @@ -40,6 +44,7 @@ impl ser::Serialize for MftEntry { let attributes: Vec = self.iter_attributes().filter_map(Result::ok).collect(); state.serialize_field("header", &self.header)?; state.serialize_field("attributes", &attributes)?; + state.serialize_field("valid_fixup", &self.valid_fixup)?; state.end() } } @@ -175,13 +180,16 @@ impl MftEntry { let entry_header = EntryHeader::from_reader(&mut cursor, entry_number)?; trace!("Number of sectors: {:#?}", entry_header); - if entry_header.is_valid() { - Self::apply_fixups(&entry_header, &mut buffer)?; - } + let valid_fixup = if entry_header.is_valid() { + Some(Self::apply_fixups(&entry_header, &mut buffer)?) + } else { + None + }; Ok(MftEntry { header: entry_header, data: buffer, + valid_fixup }) } @@ -203,6 +211,7 @@ impl MftEntry { Ok(MftEntry { header: entry_header, data: buffer, + valid_fixup: None }) } @@ -236,7 +245,10 @@ impl MftEntry { /// https://docs.microsoft.com/en-us/windows/desktop/devnotes/multi-sector-header /// **Note**: The fixup will be written at the end of each 512-byte stride, /// even if the device has more (or less) than 512 bytes per sector. - fn apply_fixups(header: &EntryHeader, buffer: &mut [u8]) -> Result<()> { + /// The returned result is true if all fixup blocks had the fixup array value, or + /// false if a block's fixup value did not match the array's value. + fn apply_fixups(header: &EntryHeader, buffer: &mut [u8]) -> Result { + let mut valid_fixup = true; let number_of_fixups = u32::from(header.usa_size - 1); trace!("Number of fixups: {}", number_of_fixups); @@ -262,17 +274,22 @@ impl MftEntry { &mut buffer[end_of_sector_bytes_start_offset..end_of_sector_bytes_end_offset]; if end_of_sector_bytes != update_sequence { - return Err(Error::FailedToApplyFixup { + // An item in the block did not match the fixup array value + warn!( + "[entry: {}] fixup bytes are not equal to update sequence value - stride_number: {}, end_of_sector_bytes: {:?}, fixup_bytes: {:?}", + header.record_number, stride_number, - end_of_sector_bytes: end_of_sector_bytes.to_vec(), - fixup_bytes: fixup_bytes.to_vec(), - }); + end_of_sector_bytes.to_vec(), + fixup_bytes.to_vec() + ); + + valid_fixup = false; } end_of_sector_bytes.copy_from_slice(&fixup_bytes); } - Ok(()) + Ok(valid_fixup) } pub fn is_allocated(&self) -> bool { diff --git a/tests/test_entry.rs b/tests/test_entry.rs new file mode 100644 index 0000000..c95f2c7 --- /dev/null +++ b/tests/test_entry.rs @@ -0,0 +1,17 @@ +use mft::entry::MftEntry; +use serde_json; + +#[test] +fn test_entry_invalid_fixup_value() { + let mft_entry_buffer = include_bytes!("../samples/entry_102130_fixup_issue"); + + let entry = MftEntry::from_buffer( + mft_entry_buffer.to_vec(), + 102130 + ).expect("Failed to parse entry"); + + assert_eq!(entry.valid_fixup, Some(false)); + + let mft_json_value = serde_json::to_value(&entry).expect("Error serializing MftEntry"); + assert_eq!(mft_json_value["valid_fixup"], serde_json::value::Value::from(false)); +} \ No newline at end of file -- cgit v1.2.3