diff options
author | Omer Ben-Amram <omerbenamram@gmail.com> | 2019-05-21 15:03:09 +0300 |
---|---|---|
committer | Omer Ben-Amram <omerbenamram@gmail.com> | 2019-05-21 15:03:09 +0300 |
commit | e99b3b44bbdd1081b6963edbefdec8cce3e8188f (patch) | |
tree | 48364b48bba321d72ad97c32c8ef083ff637f942 | |
parent | b183ce8557ed741a678fb9bd18f1d332082e71a8 (diff) |
added more attributes
-rw-r--r-- | src/attribute/mod.rs | 52 | ||||
-rw-r--r-- | src/attribute/raw.rs | 44 | ||||
-rw-r--r-- | src/attribute/x40.rs | 40 | ||||
-rw-r--r-- | src/attribute/x80.rs | 31 | ||||
-rw-r--r-- | src/attribute/x90.rs | 31 | ||||
-rw-r--r-- | src/bin/mft_dump.rs | 3 | ||||
-rw-r--r-- | src/entry.rs | 68 | ||||
-rw-r--r-- | src/err.rs | 2 | ||||
-rw-r--r-- | src/mft.rs | 10 |
9 files changed, 219 insertions, 62 deletions
diff --git a/src/attribute/mod.rs b/src/attribute/mod.rs index eca250e..9729e38 100644 --- a/src/attribute/mod.rs +++ b/src/attribute/mod.rs @@ -2,18 +2,23 @@ pub mod header; pub mod raw; pub mod x10; pub mod x30; +pub mod x40; +pub mod x80; +pub mod x90; -use crate::impl_serialize_for_bitflags; +use crate::err::{self, Result}; +use crate::{impl_serialize_for_bitflags, ReadSeek}; 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::MftAttributeHeader; - +use crate::attribute::header::{MftAttributeHeader, ResidentHeader}; +use crate::attribute::x40::ObjectIdAttr; +use crate::attribute::x80::DataAttr; +use crate::attribute::x90::IndexRootAttr; use serde::Serialize; #[derive(Serialize, Clone, Debug)] @@ -22,12 +27,51 @@ pub struct MftAttribute { pub data: MftAttributeContent, } +impl MftAttributeContent { + pub fn from_stream_resident<S: ReadSeek>( + stream: &mut S, + header: &MftAttributeHeader, + resident: &ResidentHeader, + ) -> Result<Self> { + match header.type_code { + MftAttributeType::StandardInformation => Ok(MftAttributeContent::AttrX10( + StandardInfoAttr::from_reader(stream)?, + )), + MftAttributeType::FileName => Ok(MftAttributeContent::AttrX30( + FileNameAttr::from_stream(stream)?, + )), + // Resident DATA + MftAttributeType::DATA => Ok(MftAttributeContent::AttrX80(DataAttr::from_stream( + stream, + resident.data_size as usize, + )?)), + // Always Resident + MftAttributeType::ObjectId => Ok(MftAttributeContent::AttrX40( + ObjectIdAttr::from_stream(stream, resident.data_size as usize)?, + )), + // Always Resident + MftAttributeType::IndexRoot => Ok(MftAttributeContent::AttrX90( + IndexRootAttr::from_stream(stream)?, + )), + // An unparsed resident attribute + _ => Ok(MftAttributeContent::Raw(RawAttribute::from_stream( + stream, + header.type_code.clone(), + resident.data_size as usize, + )?)), + } + } +} + #[derive(Serialize, Clone, Debug)] #[serde(untagged)] pub enum MftAttributeContent { Raw(RawAttribute), + AttrX80(DataAttr), AttrX10(StandardInfoAttr), AttrX30(FileNameAttr), + AttrX40(ObjectIdAttr), + AttrX90(IndexRootAttr), /// Empty - used when data is non resident. None, } diff --git a/src/attribute/raw.rs b/src/attribute/raw.rs index bfc7fac..1e24969 100644 --- a/src/attribute/raw.rs +++ b/src/attribute/raw.rs @@ -1,15 +1,37 @@ -use crate::utils; -use serde::ser; +use crate::attribute::MftAttributeType; +use crate::err::{self, Result}; +use crate::{utils, ReadSeek}; +use serde::{ser, Serialize}; +use snafu::ResultExt; /// 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()) +#[derive(Serialize, Clone, Debug)] +pub struct RawAttribute { + pub attribute_type: MftAttributeType, + #[serde(serialize_with = "data_as_hex")] + pub data: Vec<u8>, +} + +impl RawAttribute { + pub fn from_stream<S: ReadSeek>( + stream: &mut S, + attribute_type: MftAttributeType, + data_size: usize, + ) -> Result<Self> { + let mut data = vec![0_u8; data_size]; + + stream.read_exact(&mut data).context(err::IoError)?; + + Ok(RawAttribute { + attribute_type, + data, + }) } } + +fn data_as_hex<S>(x: &Vec<u8>, s: S) -> std::result::Result<S::Ok, S::Error> +where + S: ser::Serializer, +{ + s.serialize_str(&utils::to_hex_string(x)) +} diff --git a/src/attribute/x40.rs b/src/attribute/x40.rs new file mode 100644 index 0000000..beea829 --- /dev/null +++ b/src/attribute/x40.rs @@ -0,0 +1,40 @@ +use crate::err::{self, Result}; +use crate::ReadSeek; +use serde::Serialize; +use snafu::ResultExt; +use winstructs::guid::Guid; + +/// $Data Attribute +#[derive(Serialize, Clone, Debug)] +pub struct ObjectIdAttr { + /// Unique Id assigned to file + pub object_id: Guid, + /// Volume where file was created + pub birth_volume_id: Option<Guid>, + /// Original Object Id of file + pub birth_object_id: Option<Guid>, + /// Domain in which object was created + pub domain_id: Option<Guid>, +} + +impl ObjectIdAttr { + /// Data size should be either 16 or 64 + pub fn from_stream<S: ReadSeek>(stream: &mut S, data_size: usize) -> Result<ObjectIdAttr> { + let object_id = Guid::from_stream(stream).context(err::FailedToReadGuid)?; + let (birth_volume_id, birth_object_id, domain_id) = if data_size == 64 { + let g1 = Guid::from_stream(stream).context(err::FailedToReadGuid)?; + let g2 = Guid::from_stream(stream).context(err::FailedToReadGuid)?; + let g3 = Guid::from_stream(stream).context(err::FailedToReadGuid)?; + (Some(g1), Some(g2), Some(g3)) + } else { + (None, None, None) + }; + + Ok(ObjectIdAttr { + object_id, + birth_volume_id, + birth_object_id, + domain_id, + }) + } +} diff --git a/src/attribute/x80.rs b/src/attribute/x80.rs new file mode 100644 index 0000000..f194a0e --- /dev/null +++ b/src/attribute/x80.rs @@ -0,0 +1,31 @@ +use crate::err::{self, Result}; +use crate::{utils, ReadSeek}; +use serde::ser; +use snafu::ResultExt; + +/// $Data Attribute +#[derive(Clone, Debug)] +pub struct DataAttr(Vec<u8>); + +impl DataAttr { + pub fn from_stream<S: ReadSeek>(stream: &mut S, data_size: usize) -> Result<DataAttr> { + let mut data = vec![0_u8; data_size]; + + stream.read_exact(&mut data).context(err::IoError)?; + + Ok(DataAttr(data)) + } + + pub fn data(&self) -> &[u8] { + &self.0 + } +} + +impl ser::Serialize for DataAttr { + 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/attribute/x90.rs b/src/attribute/x90.rs new file mode 100644 index 0000000..e64c33c --- /dev/null +++ b/src/attribute/x90.rs @@ -0,0 +1,31 @@ +use crate::err::{self, Result}; +use crate::ReadSeek; +use byteorder::{LittleEndian, ReadBytesExt}; + +use serde::Serialize; + +/// $IndexRoot Attribute +#[derive(Serialize, Clone, Debug)] +pub struct IndexRootAttr { + /// Unique Id assigned to file + pub attribute_type: u32, + /// Collation rule used to sort the index entries. + /// If type is $FILENAME, this must be COLLATION_FILENAME + pub collation_rule: u32, + /// The index entry size + pub index_entry_size: u32, + /// The index entry number of cluster blocks + pub index_entry_number_of_cluster_blocks: u32, +} + +impl IndexRootAttr { + /// Data size should be either 16 or 64 + pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<IndexRootAttr> { + Ok(IndexRootAttr { + attribute_type: stream.read_u32::<LittleEndian>()?, + collation_rule: stream.read_u32::<LittleEndian>()?, + index_entry_size: stream.read_u32::<LittleEndian>()?, + index_entry_number_of_cluster_blocks: stream.read_u32::<LittleEndian>()?, + }) + } +} diff --git a/src/bin/mft_dump.rs b/src/bin/mft_dump.rs index f8d9da0..f16ee90 100644 --- a/src/bin/mft_dump.rs +++ b/src/bin/mft_dump.rs @@ -93,7 +93,8 @@ impl FlatMftEntryWithName { FlatMftEntryWithName { entry_id: entry.header.record_number, - signature: String::from_utf8(entry.header.signature.to_ascii_uppercase()).unwrap(), + signature: String::from_utf8(entry.header.signature.to_ascii_uppercase()) + .expect("It should be either FILE or BAAD (valid utf-8)"), sequence: entry.header.sequence, hard_link_count: entry.header.hard_link_count, flags: entry.header.flags, diff --git a/src/entry.rs b/src/entry.rs index f1d6dbc..d1aeb54 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -18,6 +18,9 @@ use crate::attribute::{MftAttribute, MftAttributeContent, MftAttributeType}; use crate::attribute::raw::RawAttribute; use crate::attribute::x30::FileNameAttr; +use crate::attribute::x40::ObjectIdAttr; +use crate::attribute::x80::DataAttr; +use crate::attribute::x90::IndexRootAttr; use std::io::Read; use std::io::SeekFrom; use std::io::{Cursor, Seek}; @@ -206,8 +209,16 @@ impl MftEntry { self.header.flags.bits() & 0x02 != 0 } - /// Returns an iterator over the attributes of the entry. + /// Returns an iterator over all the attributes of the entry. pub fn iter_attributes(&self) -> impl Iterator<Item = Result<MftAttribute>> + '_ { + self.iter_attributes_matching(None) + } + + /// Returns an iterator over the attributes in the list given in `types`, skips other attributes. + pub fn iter_attributes_matching( + &self, + types: Option<&[MftAttributeType]>, + ) -> impl Iterator<Item = Result<MftAttribute>> + '_ { let mut cursor = Cursor::new(&self.data); let mut offset = u64::from(self.header.first_attribute_record_offset); let mut exhausted = false; @@ -232,45 +243,24 @@ impl MftEntry { offset += u64::from(header.record_length); // 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 { - MftAttributeType::StandardInformation => { - match StandardInfoAttr::from_reader(&mut cursor) { - Ok(content) => Some(Ok(MftAttribute { - header, - data: MftAttributeContent::AttrX10(content), - })), - Err(e) => Some(Err(e)), - } - } - MftAttributeType::FileName => { - match FileNameAttr::from_stream(&mut cursor) { - Ok(content) => Some(Ok(MftAttribute { - header, - data: MftAttributeContent::AttrX30(content), - })), - Err(e) => Some(Err(e.into())), - } - } - _ => { - let mut data = vec![0_u8; resident.data_size as usize]; - - match cursor.read_exact(&mut data).context(err::IoError) { - Ok(_) => {} - Err(err) => return Some(Err(err.into())), - }; - - Some(Ok(MftAttribute { - header, - data: MftAttributeContent::Raw(RawAttribute(data)), - })) + let attribute_content = match header.residential_header { + ResidentialHeader::Resident(ref resident) => { + match MftAttributeContent::from_stream_resident( + &mut cursor, + &header, + resident, + ) { + Ok(content) => content, + Err(e) => return Some(Err(e)), } - }, - ResidentialHeader::NonResident(_) => Some(Ok(MftAttribute { - header, - data: MftAttributeContent::None, - })), - } + } + ResidentialHeader::NonResident(_) => MftAttributeContent::None, + }; + + Some(Ok(MftAttribute { + header, + data: attribute_content, + })) } None => None, }, @@ -40,6 +40,8 @@ pub enum Error { FailedToReadMftReference { source: winstructs::err::Error }, #[snafu(display("Failed to read WindowsTime: `{}`", source))] FailedToReadWindowsTime { source: winstructs::err::Error }, + #[snafu(display("Failed to read GUID: `{}`", source))] + FailedToReadGuid { source: winstructs::err::Error }, #[snafu(display("An unexpected error has occurred: {}", detail))] Any { detail: String }, } @@ -126,13 +126,9 @@ impl<T: ReadSeek> MftParser<T> { let path = match self.get_entry(parent_entry_id).ok() { Some(parent) => match self.get_full_path_for_entry(&parent) { Ok(Some(path)) => path, - _ => { - return err::Any { - detail: "Unexpected missing parent.\ - This is a bug, please report it at report at https://github.com/omerbenamram/mft/issues", - } - .fail() - } + // I have a parent, which doesn't have a filename attribute. + // Default to root. + _ => PathBuf::new(), }, // Parent is maybe corrupted or incomplete, use a sentinel instead. None => PathBuf::from("[Unknown]"), |