diff options
author | Omer Ben-Amram <omerbenamram@gmail.com> | 2019-05-12 17:15:39 +0300 |
---|---|---|
committer | Omer Ben-Amram <omerbenamram@gmail.com> | 2019-05-12 17:15:39 +0300 |
commit | 0720cb220035316fcf5bfb384b11cfbb748c9dc7 (patch) | |
tree | e5a95b17be01ab0a0ce4584cf8f20028c190f0bb /src | |
parent | e8f6804dca46eecd819aeeb61213da5a024a2e9b (diff) |
MFT attributes are now lazy
Diffstat (limited to 'src')
-rw-r--r-- | src/attribute.rs | 258 | ||||
-rw-r--r-- | src/attribute/header.rs | 243 | ||||
-rw-r--r-- | src/attribute/mod.rs | 83 | ||||
-rw-r--r-- | src/attribute/raw.rs | 15 | ||||
-rw-r--r-- | src/attribute/x10.rs (renamed from src/attr_x10.rs) | 8 | ||||
-rw-r--r-- | src/attribute/x30.rs (renamed from src/attr_x30.rs) | 2 | ||||
-rw-r--r-- | src/bin/mft_dump.rs | 4 | ||||
-rw-r--r-- | src/entry.rs | 147 | ||||
-rw-r--r-- | src/err.rs | 14 | ||||
-rw-r--r-- | src/lib.rs | 7 | ||||
-rw-r--r-- | src/mft.rs | 23 |
11 files changed, 447 insertions, 357 deletions
diff --git a/src/attribute.rs b/src/attribute.rs deleted file mode 100644 index 17faad2..0000000 --- a/src/attribute.rs +++ /dev/null @@ -1,258 +0,0 @@ -use crate::attr_x10::StandardInfoAttr; -use crate::attr_x30::FileNameAttr; -use crate::err::{self, Result}; -use crate::utils::read_utf16_string; -use crate::{utils, ReadSeek}; - - -use bitflags::bitflags; -use byteorder::{LittleEndian, ReadBytesExt}; - -use serde::{ser, Serialize}; -use std::io::Read; - -#[derive(Clone, Debug)] -pub struct RawAttribute(pub Vec<u8>); - -impl ser::Serialize for RawAttribute { - fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> - where - S: ser::Serializer, - { - serializer.serialize_str(&utils::to_hex_string(&self.0).to_string()) - } -} - -#[derive(Serialize, Clone, Debug)] -#[serde(untagged)] -pub enum AttributeContent { - Raw(RawAttribute), - AttrX10(StandardInfoAttr), - AttrX30(FileNameAttr), - None, -} - -bitflags! { - #[derive(Default)] - pub struct AttributeDataFlags: u16 { - const IS_COMPRESSED = 0x0001; - const COMPRESSION_MASK = 0x00FF; - const ENCRYPTED = 0x4000; - const SPARSE = 0x8000; - } -} - -pub fn serialize_attr_data_flags<S>( - item: &AttributeDataFlags, - serializer: S, -) -> ::std::result::Result<S::Ok, S::Error> -where - S: ser::Serializer, -{ - serializer.serialize_str(&format!("{:?}", item)) -} - -#[derive(Serialize, Clone, Debug, Default)] -pub struct AttributeHeader { - pub attribute_type: u32, - #[serde(skip_serializing)] - pub attribute_size: u32, - pub resident_flag: u8, // 0 -> resident; 1 -> non-resident - #[serde(skip_serializing)] - pub name_size: u8, - #[serde(skip_serializing)] - pub name_offset: u16, - #[serde(serialize_with = "serialize_attr_data_flags")] - pub data_flags: AttributeDataFlags, - pub id: u16, - pub name: String, - // 16 - pub residential_header: ResidentialHeader, -} - -impl AttributeHeader { - pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<AttributeHeader> { - let current_offset = stream.tell()?; - - let attribute_type = stream.read_u32::<LittleEndian>()?; - // The attribute list is terminated with 0xFFFFFFFF ($END). - if attribute_type == 0xFFFF_FFFF { - return Ok(AttributeHeader { - attribute_type: 0xFFFF_FFFF, - ..Default::default() - }); - } - - let attribute_size = stream.read_u32::<LittleEndian>()?; - let resident_flag = stream.read_u8()?; - let name_size = stream.read_u8()?; - let name_offset = stream.read_u16::<LittleEndian>()?; - let data_flags = AttributeDataFlags::from_bits_truncate(stream.read_u16::<LittleEndian>()?); - let id = stream.read_u16::<LittleEndian>()?; - - let residential_header = match resident_flag { - 0 => ResidentialHeader::Resident(ResidentHeader::new(stream)?), - 1 => ResidentialHeader::NonResident(NonResidentHeader::new(stream)?), - _ => { - return err::UnhandledResidentFlag { - flag: resident_flag, - offset: current_offset, - } - .fail(); - } - }; - - let name = if name_size > 0 { - read_utf16_string(stream, Some(name_size as usize))? - } else { - String::new() - }; - - Ok(AttributeHeader { - attribute_type, - attribute_size, - resident_flag, - name_size, - name_offset, - data_flags, - id, - name, - residential_header, - }) - } -} - -#[derive(Serialize, Clone, Debug)] -#[serde(untagged)] -pub enum ResidentialHeader { - None, - Resident(ResidentHeader), - NonResident(NonResidentHeader), -} - -impl Default for ResidentialHeader { - fn default() -> Self { - ResidentialHeader::None - } -} - -#[derive(Serialize, Clone, Debug)] -pub struct ResidentHeader { - #[serde(skip_serializing)] - pub data_size: u32, - #[serde(skip_serializing)] - pub data_offset: u16, - pub index_flag: u8, - #[serde(skip_serializing)] - pub padding: u8, -} - -impl ResidentHeader { - pub fn new<R: Read>(reader: &mut R) -> Result<ResidentHeader> { - Ok(ResidentHeader { - data_size: reader.read_u32::<LittleEndian>()?, - data_offset: reader.read_u16::<LittleEndian>()?, - index_flag: reader.read_u8()?, - padding: reader.read_u8()?, - }) - } -} - -#[derive(Serialize, Clone, Debug)] -pub struct NonResidentHeader { - pub vnc_first: u64, - pub vnc_last: u64, - #[serde(skip_serializing)] - pub datarun_offset: u16, - pub unit_compression_size: u16, - #[serde(skip_serializing)] - pub padding: u32, - pub size_allocated: u64, - pub size_real: u64, - pub size_compressed: u64, - // pub size_total_allocated: Option<u64> -} -impl NonResidentHeader { - pub fn new<R: Read>(reader: &mut R) -> Result<NonResidentHeader> { - let vnc_first = reader.read_u64::<LittleEndian>()?; - let vnc_last = reader.read_u64::<LittleEndian>()?; - let datarun_offset = reader.read_u16::<LittleEndian>()?; - let unit_compression_size = reader.read_u16::<LittleEndian>()?; - let padding = reader.read_u32::<LittleEndian>()?; - let size_allocated = reader.read_u64::<LittleEndian>()?; - let size_real = reader.read_u64::<LittleEndian>()?; - let size_compressed = reader.read_u64::<LittleEndian>()?; - - // if residential_header.unit_compression_size > 0 { - // residential_header.size_total_allocated = Some(reader.read_u64::<LittleEndian>()?); - // } - - Ok(NonResidentHeader { - vnc_first, - vnc_last, - datarun_offset, - unit_compression_size, - padding, - size_allocated, - size_real, - size_compressed, - }) - } -} - -#[derive(Serialize, Clone, Debug)] -pub struct MftAttribute { - pub header: AttributeHeader, - pub content: AttributeContent, -} - -#[cfg(test)] -mod tests { - use super::AttributeHeader; - use std::io::Cursor; - - #[test] - fn attribute_test_01_resident() { - let raw: &[u8] = &[ - 0x10, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - ]; - let mut cursor = Cursor::new(raw); - - let attribute_header = match AttributeHeader::from_stream(&mut cursor) { - Ok(attribute_header) => attribute_header, - Err(error) => panic!(error), - }; - - assert_eq!(attribute_header.attribute_type, 16); - assert_eq!(attribute_header.attribute_size, 96); - assert_eq!(attribute_header.resident_flag, 0); - assert_eq!(attribute_header.name_size, 0); - assert_eq!(attribute_header.name_offset, 0); - } - - #[test] - fn attribute_test_01_nonresident() { - let raw: &[u8] = &[ - 0x80, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x1E, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x33, 0x20, 0xC8, 0x00, 0x00, 0x00, - 0x0C, 0x32, 0xA0, 0x56, 0xE3, 0xE6, 0x24, 0x00, 0xFF, 0xFF, - ]; - - let mut cursor = Cursor::new(raw); - - let attribute_header = match AttributeHeader::from_stream(&mut cursor) { - Ok(attribute_header) => attribute_header, - Err(error) => panic!(error), - }; - - assert_eq!(attribute_header.attribute_type, 128); - assert_eq!(attribute_header.attribute_size, 80); - assert_eq!(attribute_header.resident_flag, 1); - assert_eq!(attribute_header.name_size, 0); - assert_eq!(attribute_header.name_offset, 64); - } -} diff --git a/src/attribute/header.rs b/src/attribute/header.rs new file mode 100644 index 0000000..13126d4 --- /dev/null +++ b/src/attribute/header.rs @@ -0,0 +1,243 @@ +use crate::attribute::{serialize_attr_data_flags, AttributeDataFlags, AttributeType}; +use crate::err::{self, Result}; +use crate::utils::read_utf16_string; +use crate::ReadSeek; + +use byteorder::{LittleEndian, ReadBytesExt}; +use num_traits::FromPrimitive; +use serde::Serialize; +use std::io::Read; + +/// Represents the union defined in +/// https://docs.microsoft.com/en-us/windows/desktop/devnotes/attribute-record-header +#[derive(Serialize, Clone, Debug)] +pub struct AttributeHeader { + pub type_code: AttributeType, + /// The size of the attribute record, in bytes. + /// This value reflects the required size for the record variant and is always rounded to the nearest quadword boundary. + pub record_length: u32, + /// If the FormCode member is RESIDENT_FORM (0x00), the union is a Resident structure. + /// If FormCode is NONRESIDENT_FORM (0x01), the union is a Nonresident structure. + pub form_code: u8, + pub residential_header: ResidentialHeader, + /// The size of the optional attribute name, in characters, or 0 if there is no attribute name. + /// The maximum attribute name length is 255 characters. + pub name_size: u8, + /// The offset of the attribute name from the start of the attribute record, in bytes. + /// If the NameLength member is 0, this member is undefined. + pub name_offset: Option<u16>, + #[serde(serialize_with = "serialize_attr_data_flags")] + pub data_flags: AttributeDataFlags, + /// The unique instance for this attribute in the file record. + pub instance: u16, + pub name: String, +} + +#[derive(Serialize, Clone, Debug)] +#[serde(untagged)] +pub enum ResidentialHeader { + Resident(ResidentHeader), + NonResident(NonResidentHeader), +} + +impl AttributeHeader { + /// Tries to read an AttributeHeader from the stream. + /// Will return `None` if the type code is $END. + pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<Option<AttributeHeader>> { + let type_code_value = stream.read_u32::<LittleEndian>()?; + + if type_code_value == 0xFFFF_FFFF { + return Ok(None); + } + + let type_code = match AttributeType::from_u32(type_code_value) { + Some(attribute_type) => attribute_type, + None => { + return err::UnknownAttributeType { + attribute_type: type_code_value, + } + .fail() + } + }; + + let attribute_size = stream.read_u32::<LittleEndian>()?; + let resident_flag = stream.read_u8()?; + let name_size = stream.read_u8()?; + let name_offset = { + // We always read the two bytes to advance the stream. + let value = stream.read_u16::<LittleEndian>()?; + if name_size > 0 { + Some(value) + } else { + None + } + }; + + let data_flags = AttributeDataFlags::from_bits_truncate(stream.read_u16::<LittleEndian>()?); + let id = stream.read_u16::<LittleEndian>()?; + + let residential_header = match resident_flag { + 0 => ResidentialHeader::Resident(ResidentHeader::new(stream)?), + 1 => ResidentialHeader::NonResident(NonResidentHeader::new(stream)?), + _ => { + return err::UnhandledResidentFlag { + flag: resident_flag, + offset: stream.tell()?, + } + .fail(); + } + }; + + let name = if name_size > 0 { + read_utf16_string(stream, Some(name_size as usize))? + } else { + String::new() + }; + + Ok(Some(AttributeHeader { + type_code, + record_length: attribute_size, + form_code: resident_flag, + name_size, + name_offset, + data_flags, + instance: id, + name, + residential_header, + })) + } +} + +#[derive(Serialize, Clone, Debug)] +pub struct ResidentHeader { + #[serde(skip_serializing)] + /// The size of the attribute value, in bytes. + pub data_size: u32, + #[serde(skip_serializing)] + /// The offset to the value from the start of the attribute record, in bytes. + pub data_offset: u16, + pub index_flag: u8, + #[serde(skip_serializing)] + pub padding: u8, +} + +impl ResidentHeader { + pub fn new<R: Read>(reader: &mut R) -> Result<ResidentHeader> { + Ok(ResidentHeader { + data_size: reader.read_u32::<LittleEndian>()?, + data_offset: reader.read_u16::<LittleEndian>()?, + index_flag: reader.read_u8()?, + padding: reader.read_u8()?, + }) + } +} + +#[derive(Serialize, Clone, Debug)] +pub struct NonResidentHeader { + /// The lowest virtual cluster number (VCN) covered by this attribute record. + pub vnc_first: u64, + /// The highest VCN covered by this attribute record. + pub vnc_last: u64, + #[serde(skip_serializing)] + /// The offset to the mapping pairs array from the start of the attribute record, in bytes. For more information, see Remarks. + pub datarun_offset: u16, + /// Reserved UCHAR[6] + pub unit_compression_size: u16, + #[serde(skip_serializing)] + pub padding: u32, + + /// The allocated size of the file, in bytes. + /// This value is an even multiple of the cluster size. + /// This member is not valid if the LowestVcn member is nonzero. + pub allocated_length: u64, + pub file_size: u64, + /// Contains the valid data size in number of bytes. + /// This value is not valid if the first VCN is nonzero. + pub valid_data_length: u64, + pub total_allocated: Option<u64>, +} + +impl NonResidentHeader { + pub fn new<R: Read>(reader: &mut R) -> Result<NonResidentHeader> { + let vnc_first = reader.read_u64::<LittleEndian>()?; + let vnc_last = reader.read_u64::<LittleEndian>()?; + let datarun_offset = reader.read_u16::<LittleEndian>()?; + let unit_compression_size = reader.read_u16::<LittleEndian>()?; + let padding = reader.read_u32::<LittleEndian>()?; + let allocated_length = reader.read_u64::<LittleEndian>()?; + let file_size = reader.read_u64::<LittleEndian>()?; + let valid_data_length = reader.read_u64::<LittleEndian>()?; + + let total_allocated = if unit_compression_size > 0 { + Some(reader.read_u64::<LittleEndian>()?) + } else { + None + }; + + Ok(NonResidentHeader { + vnc_first, + vnc_last, + datarun_offset, + unit_compression_size, + padding, + allocated_length, + file_size, + valid_data_length, + total_allocated, + }) + } +} + +#[cfg(test)] +mod tests { + use super::AttributeHeader; + use crate::attribute::AttributeType; + use std::io::Cursor; + + #[test] + fn attribute_test_01_resident() { + let raw: &[u8] = &[ + 0x10, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + ]; + + let mut cursor = Cursor::new(raw); + + let attribute_header = AttributeHeader::from_stream(&mut cursor) + .expect("Should not be $End") + .expect("Shold parse correctly"); + + assert_eq!( + attribute_header.type_code, + AttributeType::StandardInformation + ); + assert_eq!(attribute_header.record_length, 96); + assert_eq!(attribute_header.form_code, 0); + assert_eq!(attribute_header.name_size, 0); + assert_eq!(attribute_header.name_offset, None); + } + + #[test] + fn attribute_test_01_nonresident() { + let raw: &[u8] = &[ + 0x80, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x1E, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xEC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x33, 0x20, 0xC8, 0x00, 0x00, 0x00, + 0x0C, 0x32, 0xA0, 0x56, 0xE3, 0xE6, 0x24, 0x00, 0xFF, 0xFF, + ]; + + let mut cursor = Cursor::new(raw); + + let attribute_header = AttributeHeader::from_stream(&mut cursor) + .expect("Should not be $End") + .expect("Shold parse correctly"); + + assert_eq!(attribute_header.type_code, AttributeType::DATA); + assert_eq!(attribute_header.record_length, 80); + assert_eq!(attribute_header.form_code, 1); + assert_eq!(attribute_header.name_size, 0); + assert_eq!(attribute_header.name_offset, None); + } +} diff --git a/src/attribute/mod.rs b/src/attribute/mod.rs new file mode 100644 index 0000000..fab7f87 --- /dev/null +++ b/src/attribute/mod.rs @@ -0,0 +1,83 @@ +pub mod header; +pub mod raw; +pub mod x10; +pub mod x30; + +use crate::err::{self, Result}; +use bitflags::bitflags; +use num_traits::FromPrimitive; + +use crate::attribute::raw::RawAttribute; +use crate::attribute::x10::StandardInfoAttr; +use crate::attribute::x30::FileNameAttr; + +use crate::attribute::header::AttributeHeader; +use crate::ReadSeek; +use serde::{ser, Serialize}; +use std::io::{Cursor, Read, Seek}; + +#[derive(Serialize, Clone, Debug)] +pub struct Attribute { + pub header: AttributeHeader, + pub data: MftAttributeContent, +} + +#[derive(Serialize, Clone, Debug)] +#[serde(untagged)] +pub enum MftAttributeContent { + Raw(RawAttribute), + AttrX10(StandardInfoAttr), + AttrX30(FileNameAttr), + /// Empty - used when data is non resident. + None, +} + +/// MFT Possible attribute types, from https://docs.microsoft.com/en-us/windows/desktop/devnotes/attribute-list-entry +#[derive(Serialize, Debug, Clone, FromPrimitive, PartialOrd, PartialEq)] +#[repr(u32)] +pub enum AttributeType { + /// File attributes (such as read-only and archive), time stamps (such as file creation and last modified), and the hard link count. + StandardInformation = 0x10_u32, + /// A list of attributes that make up the file and the file reference of the MFT file record in which each attribute is located. + AttributeList = 0x20_u32, + /// The name of the file, in Unicode characters. + FileName = 0x30_u32, + /// An 16-byte object identifier assigned by the link-tracking service. + ObjectId = 0x40_u32, + /// The volume label. + /// Present in the $Volume file. + VolumeName = 0x60_u32, + /// The volume information. + /// Present in the $Volume file. + VolumeInformation = 0x70_u32, + /// The contents of the file. + DATA = 0x80_u32, + /// Used to implement filename allocation for large directories. + IndexRoot = 0x90_u32, + /// Used to implement filename allocation for large directories. + IndexAllocation = 0xA0_u32, + /// A bitmap index for a large directory. + BITMAP = 0xB0_u32, + /// The reparse point data. + ReparsePoint = 0xC0_u32, +} + +bitflags! { + #[derive(Default)] + pub struct AttributeDataFlags: u16 { + const IS_COMPRESSED = 0x0001; + const COMPRESSION_MASK = 0x00FF; + const ENCRYPTED = 0x4000; + const SPARSE = 0x8000; + } +} + +pub fn serialize_attr_data_flags<S>( + item: &AttributeDataFlags, + serializer: S, +) -> ::std::result::Result<S::Ok, S::Error> +where + S: ser::Serializer, +{ + serializer.serialize_str(&format!("{:?}", item)) +} diff --git a/src/attribute/raw.rs b/src/attribute/raw.rs new file mode 100644 index 0000000..bfc7fac --- /dev/null +++ b/src/attribute/raw.rs @@ -0,0 +1,15 @@ +use crate::utils; +use serde::ser; + +/// Placeholder attribute for currently unparsed attributes. +#[derive(Clone, Debug)] +pub struct RawAttribute(pub Vec<u8>); + +impl ser::Serialize for RawAttribute { + fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> + where + S: ser::Serializer, + { + serializer.serialize_str(&utils::to_hex_string(&self.0).to_string()) + } +} diff --git a/src/attr_x10.rs b/src/attribute/x10.rs index 3366714..3b0f1fa 100644 --- a/src/attr_x10.rs +++ b/src/attribute/x10.rs @@ -1,12 +1,12 @@ use crate::err::{self, Result}; +use crate::ReadSeek; use byteorder::{LittleEndian, ReadBytesExt}; use chrono::{DateTime, Utc}; use log::trace; use serde::Serialize; -use std::io::Read; -use winstructs::timestamp::WinTimestamp; use snafu::ResultExt; +use winstructs::timestamp::WinTimestamp; #[derive(Serialize, Debug, Clone)] pub struct StandardInfoAttr { @@ -32,7 +32,7 @@ impl StandardInfoAttr { /// Parse a raw buffer. /// /// ``` - /// use mft::attr_x10::StandardInfoAttr; + /// use mft::attribute::x10::StandardInfoAttr; /// # use std::io::Cursor; /// # fn test_standard_information() { /// let attribute_buffer: &[u8] = &[ @@ -58,7 +58,7 @@ impl StandardInfoAttr { /// assert_eq!(attribute.usn, 8768215144); /// # } /// ``` - pub fn from_reader<R: Read>(reader: &mut R) -> Result<StandardInfoAttr> { + pub fn from_reader<S: ReadSeek>(reader: &mut S) -> Result<StandardInfoAttr> { trace!("StandardInfoAttr"); let created = WinTimestamp::from_reader(reader) .context(err::FailedToReadWindowsTime)? diff --git a/src/attr_x30.rs b/src/attribute/x30.rs index 63564e7..9ab6069 100644 --- a/src/attr_x30.rs +++ b/src/attribute/x30.rs @@ -39,7 +39,7 @@ impl FileNameAttr { /// Parse a raw buffer. /// /// ``` - /// use mft::attr_x30::FileNameAttr; + /// use mft::attribute::x30::FileNameAttr; /// # use std::io::Cursor; /// # fn test_filename_attribute() { /// let attribute_buffer: &[u8] = &[ diff --git a/src/bin/mft_dump.rs b/src/bin/mft_dump.rs index 3f7037d..b516f76 100644 --- a/src/bin/mft_dump.rs +++ b/src/bin/mft_dump.rs @@ -1,11 +1,11 @@ use clap::{App, Arg}; use env_logger; use log::{info, warn}; -use mft::mft::MftHandler; +use mft::mft::MftParser; fn process_file(filename: &str, indent: bool) -> bool { info!("Opening file {}", filename); - let mut mft_handler = match MftHandler::from_path(filename) { + let mut mft_handler = match MftParser::from_path(filename) { Ok(mft_handler) => mft_handler, Err(error) => { warn!("Could not parse file: {} [error: {}]", filename, error); diff --git a/src/entry.rs b/src/entry.rs index 6f45b5c..4ae44f3 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,8 +1,5 @@ -use crate::err::{self, Result}; - -use crate::attr_x10::StandardInfoAttr; -use crate::attr_x30::FileNameAttr; use crate::enumerator::PathMapping; +use crate::err::{self, Result}; use crate::{attribute, ReadSeek}; use log::debug; @@ -13,12 +10,17 @@ use winstructs::ntfs::mft_reference::MftReference; use byteorder::{LittleEndian, ReadBytesExt}; use bitflags::bitflags; -use serde::{ser, Serialize}; +use serde::{ser, Serialize, Serializer}; -use crate::attribute::MftAttribute; -use std::io::Cursor; +use crate::attribute::header::{AttributeHeader, ResidentialHeader}; +use crate::attribute::x10::StandardInfoAttr; +use crate::attribute::{Attribute, AttributeType, MftAttributeContent}; + +use crate::attribute::raw::RawAttribute; +use crate::attribute::x30::FileNameAttr; use std::io::Read; use std::io::SeekFrom; +use std::io::{Cursor, Seek}; //https://github.com/libyal/libfsntfs/blob/master/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc#5-the-master-file-table-mft @@ -120,10 +122,11 @@ impl EntryHeader { } } +// TODO: manually implement serialize to dump attributes. #[derive(Serialize, Debug)] pub struct MftEntry { pub header: EntryHeader, - pub attributes: Vec<MftAttribute>, + pub data: Vec<u8>, } impl MftEntry { @@ -133,11 +136,9 @@ impl MftEntry { // Get Header let entry_header = EntryHeader::from_reader(&mut cursor, entry)?; - let attributes = Self::read_attributes(&entry_header, &mut cursor)?; - Ok(MftEntry { header: entry_header, - attributes, + data: buffer, }) } @@ -150,12 +151,12 @@ impl MftEntry { } pub fn get_pathmap(&self) -> Option<PathMapping> { - for attribute in self.attributes.iter() { - if let attribute::AttributeContent::AttrX30(ref attrib) = attribute.content { + for attribute in self.iter_attributes().filter_map(|a| a.ok()) { + if let attribute::MftAttributeContent::AttrX30(ref attrib) = attribute.data { if attrib.namespace != 2 { return Some(PathMapping { name: attrib.name.clone(), - parent: attrib.parent.clone(), + parent: attrib.parent, }); } } @@ -196,66 +197,68 @@ impl MftEntry { // } // } - fn read_attributes<S: ReadSeek>( - header: &EntryHeader, - buffer: &mut S, - ) -> Result<Vec<MftAttribute>> { - let mut current_offset = buffer.seek(SeekFrom::Start(u64::from(header.fst_attr_offset)))?; - - let mut attributes = vec![]; - - loop { - let attribute_header = attribute::AttributeHeader::from_stream(buffer)?; - - // The attribute list is terminated with 0xFFFFFFFF ($END). - if attribute_header.attribute_type == 0xFFFF_FFFF { - break; - } - - match attribute_header.residential_header { - attribute::ResidentialHeader::Resident(ref header) => { - // Create attribute content to parse buffer into - // Get attribute contents - let attr_content = match attribute_header.attribute_type { - 0x10 => attribute::AttributeContent::AttrX10( - StandardInfoAttr::from_reader(buffer)?, - ), - 0x30 => { - attribute::AttributeContent::AttrX30(FileNameAttr::from_reader(buffer)?) - } - _ => { - let mut content_buffer = vec![0; header.data_size as usize]; - buffer.read_exact(&mut content_buffer)?; - - attribute::AttributeContent::Raw(attribute::RawAttribute( - content_buffer, - )) + fn iter_attributes(&self) -> impl Iterator<Item = Result<Attribute>> + '_ { + let mut cursor = Cursor::new(&self.data); + let start_offset = u64::from(self.header.fst_attr_offset); + + std::iter::from_fn(move || { + match cursor + .seek(SeekFrom::Start(start_offset)) + .eager_context(err::IoError) + { + Ok(_) => {} + Err(e) => return Some(Err(e)), + }; + + match AttributeHeader::from_stream(&mut cursor) { + Ok(maybe_header) => match maybe_header { + Some(header) => { + // Check if the header is resident, and if it is, read the attribute content. + match header.residential_header { + ResidentialHeader::Resident(ref resident) => match header.type_code { + AttributeType::StandardInformation => { + match StandardInfoAttr::from_reader(&mut cursor) { + Ok(content) => Some(Ok(Attribute { + header, + data: MftAttributeContent::AttrX10(content), + })), + Err(e) => Some(Err(e)), + } + } + AttributeType::FileName => { + match FileNameAttr::from_reader(&mut cursor) { + Ok(content) => Some(Ok(Attribute { + header, + data: MftAttributeContent::AttrX30(content), + })), + Err(e) => Some(Err(e)), + } + } + _ => { + let mut data = vec![0_u8; resident.data_size as usize]; + + match cursor.read_exact(&mut data).eager_context(err::IoError) { + Ok(_) => {} + Err(err) => return Some(Err(err)), + }; + + Some(Ok(Attribute { + header, + data: MftAttributeContent::Raw(RawAttribute(data)), + })) + } + }, + ResidentialHeader::NonResident(_) => Some(Ok(Attribute { + header, + data: MftAttributeContent::None, + })), } - }; - - attributes.push(attribute::MftAttribute { - header: attribute_header.clone(), - content: attr_content, - }); - } - attribute::ResidentialHeader::NonResident(_) => { - // No content, so push header into attributes - attributes.push(attribute::MftAttribute { - header: attribute_header.clone(), - content: attribute::AttributeContent::None, - }); - } - attribute::ResidentialHeader::None => { - // Not sure about this... - } + } + None => None, + }, + Err(e) => Some(Err(e)), } - - current_offset = buffer.seek(SeekFrom::Start( - current_offset + u64::from(attribute_header.attribute_size), - ))?; - } - - Ok(attributes) + }) } } @@ -1,4 +1,4 @@ -use snafu::Snafu; +use snafu::{Backtrace, Snafu}; use std::path::PathBuf; use std::{io, result}; @@ -8,7 +8,10 @@ pub type Result<T> = result::Result<T, Error>; #[snafu(visibility(pub(crate)))] pub enum Error { #[snafu(display("An I/O error has occurred: {}", "source"))] - IoError { source: std::io::Error }, + IoError { + source: std::io::Error, + backtrace: Backtrace, + }, #[snafu(display("Failed to open file {}: {}", path.display(), source))] FailedToOpenFile { path: PathBuf, @@ -18,6 +21,8 @@ pub enum Error { InvalidFilename, #[snafu(display("Bad signature: {:04X}", bad_sig))] InvalidEntrySignature { bad_sig: u32 }, + #[snafu(display("Unknown `AttributeType`: {:04X}", attribute_type))] + UnknownAttributeType { attribute_type: u32 }, #[snafu(display("Unhandled resident flag: {} (offset: {})", flag, offset))] UnhandledResidentFlag { flag: u8, offset: u64 }, #[snafu(display("Expected usa_offset `{}` to equal 48", offset))] @@ -32,6 +37,9 @@ pub enum Error { impl From<io::Error> for Error { fn from(err: io::Error) -> Self { - Error::IoError { source: err } + Error::IoError { + source: err, + backtrace: Backtrace::new(), + } } } @@ -1,11 +1,12 @@ +#[macro_use] +extern crate num_derive; + use std::io::{self, Read, Seek, SeekFrom}; -pub mod err; -pub mod attr_x10; -pub mod attr_x30; pub mod attribute; pub mod entry; pub mod enumerator; +pub mod err; pub mod mft; pub(crate) mod utils; @@ -4,12 +4,12 @@ use crate::err::{self, Result}; use log::debug; use snafu::ResultExt; -use std::fs::File; +use std::fs::{self, File}; use std::io::{BufReader, Read, Seek, SeekFrom}; use std::path::Path; use winstructs::ntfs::mft_reference::MftReference; -pub struct MftHandler { +pub struct MftParser { file: BufReader<File>, path_enumerator: PathEnumerator, entry_size: u32, @@ -17,22 +17,17 @@ pub struct MftHandler { size: u64, } -impl MftHandler { - pub fn from_path(filename: impl AsRef<Path>) -> Result<MftHandler> { +impl MftParser { + pub fn from_path(filename: impl AsRef<Path>) -> Result<MftParser> { let f = filename.as_ref(); - let mut mft_fh = File::open(f).context(err::FailedToOpenFile { path: f.to_owned() })?; + let mft_fh = File::open(f).context(err::FailedToOpenFile { path: f.to_owned() })?; + let size = fs::metadata(f)?.len(); - // TODO: remove this, and find a better way - let size = match mft_fh.seek(SeekFrom::End(0)) { - Err(e) => panic!("Error: {}", e), - Ok(size) => size, - }; + let file = BufReader::with_capacity(4096, mft_fh); - let filehandle = BufReader::with_capacity(4096, mft_fh); - - Ok(MftHandler { - file: filehandle, + Ok(MftParser { + file, path_enumerator: PathEnumerator::new(), entry_size: 1024, offset: 0, |