Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/windirstat/mft.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOmer BenAmram <omerbenamram@gmail.com>2020-04-15 00:20:49 +0300
committerGitHub <noreply@github.com>2020-04-15 00:20:49 +0300
commit5c3ce2e6933160c2b3418a567060cdcea26050b9 (patch)
treeba3edc2ebc0e4de510036e0e5734a0346c15e9e9
parent441dfc676c85086505d8a01ffaf96009e6de0512 (diff)
parenta46d9c9702f1de78ac1be88b525fecc7db03f612 (diff)
Merge pull request #38 from forensicmatt/attribute-updates
Attribute updates
-rw-r--r--CHANGELOG.md12
-rw-r--r--Cargo.toml2
-rw-r--r--samples/entry_102130_fixup_issuebin0 -> 1024 bytes
-rw-r--r--src/attribute/mod.rs45
-rw-r--r--src/attribute/x20.rs181
-rw-r--r--src/entry.rs39
-rw-r--r--tests/test_entry.rs17
7 files changed, 262 insertions, 34 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 878e430..dd589d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.5.2] - 2020-03-10
+
+### 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
### Fixed
diff --git a/Cargo.toml b/Cargo.toml
index f5dd314..5fdb27a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ repository = "https://github.com/omerbenamram/mft"
license = "MIT/Apache-2.0"
readme = "README.md"
-version = "0.5.2-alpha.0"
+version = "0.5.2"
authors = ["Omer Ben-Amram <omerbenamram@gmail.com>"]
edition = "2018"
diff --git a/samples/entry_102130_fixup_issue b/samples/entry_102130_fixup_issue
new file mode 100644
index 0000000..60d1b7c
--- /dev/null
+++ b/samples/entry_102130_fixup_issue
Binary files differ
diff --git a/src/attribute/mod.rs b/src/attribute/mod.rs
index 8ac11ce..481d69c 100644
--- a/src/attribute/mod.rs
+++ b/src/attribute/mod.rs
@@ -10,6 +10,8 @@ pub mod x90;
use crate::err::Result;
use crate::{impl_serialize_for_bitflags, ReadSeek};
+use std::io::Cursor;
+
use bitflags::bitflags;
use crate::attribute::raw::RawAttribute;
@@ -39,9 +41,26 @@ impl MftAttributeContent {
MftAttributeType::StandardInformation => Ok(MftAttributeContent::AttrX10(
StandardInfoAttr::from_reader(stream)?,
)),
- MftAttributeType::AttributeList => Ok(MftAttributeContent::AttrX20(
- AttributeListAttr::from_stream(stream)?,
- )),
+ MftAttributeType::AttributeList => {
+ // An attribute list is a buffer of attribute entries which are varying sizes if
+ // the attributes contain names. Thus, we must know when to stop reading. To
+ // do this, we will create a buffer of the attribute, and stop reading attribute
+ // entries when we reach the end of the buffer.
+ let content_size = resident.data_size;
+
+ let mut attribute_buffer = vec![0; content_size as usize];
+ stream.read_exact(&mut attribute_buffer)?;
+
+ // Create a new stream that the attribute will read from.
+ let mut new_stream = Cursor::new(attribute_buffer);
+
+ let attr_list = AttributeListAttr::from_stream(
+ &mut new_stream,
+ Some(content_size as u64)
+ )?;
+
+ Ok(MftAttributeContent::AttrX20(attr_list))
+ },
MftAttributeType::FileName => Ok(MftAttributeContent::AttrX30(
FileNameAttr::from_stream(stream)?,
)),
@@ -67,6 +86,14 @@ impl MftAttributeContent {
}
}
+ /// Converts the given attributes into a 'AttributeListAttr', consuming the object attribute object.
+ pub fn into_attribute_list(self) -> Option<AttributeListAttr> {
+ match self {
+ MftAttributeContent::AttrX20(content) => Some(content),
+ _ => None,
+ }
+ }
+
/// Converts the given attributes into a `IndexRootAttr`, consuming the object attribute object.
pub fn into_index_root(self) -> Option<IndexRootAttr> {
match self {
@@ -109,11 +136,11 @@ impl MftAttributeContent {
#[serde(untagged)]
pub enum MftAttributeContent {
Raw(RawAttribute),
- AttrX80(DataAttr),
AttrX10(StandardInfoAttr),
AttrX20(AttributeListAttr),
AttrX30(FileNameAttr),
AttrX40(ObjectIdAttr),
+ AttrX80(DataAttr),
AttrX90(IndexRootAttr),
/// Empty - used when data is non resident.
None,
@@ -158,10 +185,15 @@ pub enum MftAttributeType {
}
bitflags! {
+ /// Flag sources:
+ /// https://github.com/EricZimmerman/MFT/blob/3bed2626ee85e9a96a6db70a17407d0c3696056a/MFT/Attributes/StandardInfo.cs#L10
+ /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/ca28ec38-f155-4768-81d6-4bfeb8586fc9
+ ///
pub struct FileAttributeFlags: u32 {
const FILE_ATTRIBUTE_READONLY = 0x0000_0001;
const FILE_ATTRIBUTE_HIDDEN = 0x0000_0002;
const FILE_ATTRIBUTE_SYSTEM = 0x0000_0004;
+ const FILE_ATTRIBUTE_DIRECTORY = 0x0000_0010;
const FILE_ATTRIBUTE_ARCHIVE = 0x0000_0020;
const FILE_ATTRIBUTE_DEVICE = 0x0000_0040;
const FILE_ATTRIBUTE_NORMAL = 0x0000_0080;
@@ -172,6 +204,11 @@ bitflags! {
const FILE_ATTRIBUTE_OFFLINE = 0x0000_1000;
const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x0000_2000;
const FILE_ATTRIBUTE_ENCRYPTED = 0x0000_4000;
+ const FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x0000_8000;
+ const FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x0002_0000;
+ const FILE_ATTRIBUTE_HAS_EA = 0x0004_0000;
+ const FILE_ATTRIBUTE_IS_DIRECTORY = 0x1000_0000;
+ const FILE_ATTRIBUTE_INDEX_VIEW = 0x2000_0000;
}
}
diff --git a/src/attribute/x20.rs b/src/attribute/x20.rs
index 8437823..d196ae9 100644
--- a/src/attribute/x20.rs
+++ b/src/attribute/x20.rs
@@ -1,6 +1,5 @@
use crate::err::{Error, Result};
use crate::ReadSeek;
-use log::trace;
use byteorder::{LittleEndian, ReadBytesExt};
use encoding::all::UTF_16LE;
@@ -11,30 +10,174 @@ use serde::Serialize;
use std::io::SeekFrom;
use winstructs::ntfs::mft_reference::MftReference;
+
+/// The AttributeListAttr represents the $20 attribute, which contains a list
+/// of attribute entries in child entries.
+///
#[derive(Serialize, Clone, Debug)]
pub struct AttributeListAttr {
+ /// A list of AttributeListEntry that make up this AttributeListAttr
+ pub entries: Vec<AttributeListEntry>
+}
+impl AttributeListAttr {
+ /// Read AttributeListAttr from stream. Stream should be the size of the attribute's data itself
+ /// if no stream_size is passed in.
+ ///
+ /// # Example
+ ///
+ /// Parse a raw buffer.
+ ///
+ /// ```
+ /// use mft::attribute::x20::AttributeListAttr;
+ /// # use std::io::Cursor;
+ /// let attribute_content_buffer: &[u8] = &[
+ /// 0x10,0x00,0x00,0x00,0x20,0x00,0x00,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /// 0x23,0x27,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x12,0x07,0x80,0xF8,0xFF,0xFF,
+ /// 0x30,0x00,0x00,0x00,0x20,0x00,0x00,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /// 0x23,0x27,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x00,0x00,0x69,0x00,0x6E,0x00,
+ /// 0x30,0x00,0x00,0x00,0x20,0x00,0x00,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /// 0x0F,0xCF,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x8A,0x0C,0xA0,0xF8,0xFF,0xFF,
+ /// 0x90,0x00,0x00,0x00,0x28,0x00,0x04,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /// 0x0F,0xCF,0x01,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x24,0x00,0x49,0x00,0x33,0x00,
+ /// 0x30,0x00,0x79,0x00,0x73,0x00,0xAD,0xEF,0xA0,0x00,0x00,0x00,0x28,0x00,0x04,0x1A,
+ /// 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xCF,0x01,0x00,0x00,0x00,0x02,0x00,
+ /// 0x02,0x00,0x24,0x00,0x49,0x00,0x33,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x78,0x56,
+ /// 0xB0,0x00,0x00,0x00,0x28,0x00,0x04,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /// 0x0F,0xCF,0x01,0x00,0x00,0x00,0x02,0x00,0x03,0x00,0x24,0x00,0x49,0x00,0x33,0x00,
+ /// 0x30,0x00,0x00,0x00,0x00,0x00,0x65,0x00,0x00,0x01,0x00,0x00,0x30,0x00,0x09,0x1A,
+ /// 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0x27,0x00,0x00,0x00,0x00,0x01,0x00,
+ /// 0x08,0x00,0x24,0x00,0x54,0x00,0x58,0x00,0x46,0x00,0x5F,0x00,0x44,0x00,0x41,0x00,
+ /// 0x54,0x00,0x41,0x00,0x00,0x00,0x00,0x00
+ /// ];
+ ///
+ /// let attribute_list = AttributeListAttr::from_stream(
+ /// &mut Cursor::new(attribute_content_buffer),
+ /// None
+ /// ).unwrap();
+ ///
+ /// assert_eq!(attribute_list.entries.len(), 7);
+ /// ```
+ pub fn from_stream<S: ReadSeek>(
+ mut stream: &mut S,
+ stream_size: Option<u64>
+ ) -> Result<AttributeListAttr> {
+ let mut start_offset = stream.tell()?;
+ let end_offset = match stream_size {
+ Some(s) => s,
+ None => {
+ // If no stream size was passed in we seek to the end of the stream,
+ // then tell to get the ending offset, then seek back to the start,
+ // thus, its better to just pass the stream size.
+ stream.seek(
+ SeekFrom::End(0)
+ )?;
+
+ let offset = stream.tell()?;
+
+ stream.seek(
+ SeekFrom::Start(0)
+ )?;
+
+ offset
+ }
+ };
+
+ let mut entries: Vec<AttributeListEntry> = Vec::new();
+
+ // iterate attribute content parsing attribute list entries
+ while start_offset < end_offset {
+ // parse the entry from the stream
+ let attr_entry = AttributeListEntry::from_stream(
+ &mut stream
+ )?;
+
+ // update the starting offset
+ start_offset += attr_entry.record_length as u64;
+
+ // add attribute entry to entries vec
+ entries.push(attr_entry);
+
+ // seek the stream to next start offset to avoid padding
+ stream.seek(
+ SeekFrom::Start(start_offset)
+ )?;
+ }
+
+ Ok(Self{
+ entries
+ })
+ }
+}
+
+
+/// An AttributeListAttr is made up off multiple AttributeListEntry structs.
+/// https://docs.microsoft.com/en-us/windows/win32/devnotes/attribute-list-entry
+///
+#[derive(Serialize, Clone, Debug)]
+pub struct AttributeListEntry {
+ /// The attribute code
pub attribute_type: u32,
+ /// This entry length
pub record_length: u16,
- pub first_vcn: u64,
- pub base_reference: MftReference,
- pub attribute_id: u16,
+ /// Attribute name length (0 means no name)
+ pub name_length: u8,
+ /// Attribute name offset
+ pub name_offset: u8,
+ /// This member is zero unless the attribute requires multiple file record
+ /// segments and unless this entry is a reference to a segment other than the first one.
+ /// In this case, this value is the lowest VCN that is described by the referenced segment.
+ pub lowest_vcn: u64,
+ /// The segments MFT reference
+ pub segment_reference: MftReference,
+ /// The attribute's id
+ pub reserved: u16,
+ /// The attribute's name
pub name: String,
}
-
-impl AttributeListAttr {
- pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<AttributeListAttr> {
+impl AttributeListEntry {
+ /// Create AttributeListEntry from a stream.
+ ///
+ /// # Example
+ ///
+ /// Parse a raw buffer.
+ ///
+ /// ```
+ /// use mft::attribute::x20::AttributeListEntry;
+ /// # use std::io::Cursor;
+ /// let attribute_buffer: &[u8] = &[
+ /// 0x10,0x00,0x00,0x00,0x20,0x00,0x00,0x1A,
+ /// 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ /// 0x23,0x27,0x00,0x00,0x00,0x00,0x01,0x00,
+ /// 0x00,0x00,0x12,0x07,0x80,0xF8,0xFF,0xFF
+ /// ];
+ ///
+ /// let attribute_entry = AttributeListEntry::from_stream(
+ /// &mut Cursor::new(attribute_buffer)
+ /// ).unwrap();
+ ///
+ /// assert_eq!(attribute_entry.attribute_type, 16);
+ /// assert_eq!(attribute_entry.record_length, 32);
+ /// assert_eq!(attribute_entry.name_length, 0);
+ /// assert_eq!(attribute_entry.name_offset, 26);
+ /// assert_eq!(attribute_entry.lowest_vcn, 0);
+ /// assert_eq!(attribute_entry.segment_reference.entry, 10019);
+ /// assert_eq!(attribute_entry.segment_reference.sequence, 1);
+ /// assert_eq!(attribute_entry.reserved, 0);
+ /// assert_eq!(attribute_entry.name, "".to_string());
+ /// ```
+ pub fn from_stream<S: ReadSeek>(
+ stream: &mut S
+ ) -> Result<AttributeListEntry> {
let start_offset = stream.tell()?;
-
- trace!("Offset {}: AttributeListAttr", start_offset);
-
+
let attribute_type = stream.read_u32::<LittleEndian>()?;
let record_length = stream.read_u16::<LittleEndian>()?;
let name_length = stream.read_u8()?;
let name_offset = stream.read_u8()?;
- let first_vcn = stream.read_u64::<LittleEndian>()?;
- let base_reference =
- MftReference::from_reader(stream).map_err(Error::failed_to_read_mft_reference)?;
- let attribute_id = stream.read_u16::<LittleEndian>()?;
+ let lowest_vcn = stream.read_u64::<LittleEndian>()?;
+ let segment_reference = MftReference::from_reader(stream)
+ .map_err(Error::failed_to_read_mft_reference)?;
+ let reserved = stream.read_u16::<LittleEndian>()?;
let name = if name_length > 0 {
stream.seek(SeekFrom::Start(start_offset + u64::from(name_offset)))?;
@@ -50,12 +193,14 @@ impl AttributeListAttr {
String::new()
};
- Ok(AttributeListAttr {
+ Ok(AttributeListEntry {
attribute_type,
record_length,
- first_vcn,
- base_reference,
- attribute_id,
+ name_length,
+ name_offset,
+ lowest_vcn,
+ segment_reference,
+ reserved,
name,
})
}
diff --git a/src/entry.rs b/src/entry.rs
index 1ce8f35..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<u8>,
+ /// 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<bool>
}
impl ser::Serialize for MftEntry {
@@ -36,10 +40,11 @@ impl ser::Serialize for MftEntry {
where
S: Serializer,
{
- let mut state = serializer.serialize_struct("Color", 2)?;
+ let mut state = serializer.serialize_struct("MftEntry", 2)?;
let attributes: Vec<MftAttribute> = 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<bool> {
+ 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