From 7d58b13163c95c1b7f61e2fbd3a6d812fff97fac Mon Sep 17 00:00:00 2001 From: forensicmatt Date: Tue, 10 Mar 2020 15:45:14 -0600 Subject: Added AttributeListAttr and AttributeListEntry structs and parsing Also added examples/tests for AttributeListAttr and AttributeListEntry parsing. --- src/attribute/mod.rs | 33 +++++++++- src/attribute/x20.rs | 181 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 193 insertions(+), 21 deletions(-) diff --git a/src/attribute/mod.rs b/src/attribute/mod.rs index 8ac11ce..e1842c4 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 { + 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 { match self { 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 +} +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( + mut stream: &mut S, + stream_size: Option + ) -> Result { + 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 = 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(stream: &mut S) -> Result { +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( + stream: &mut S + ) -> Result { let start_offset = stream.tell()?; - - trace!("Offset {}: AttributeListAttr", start_offset); - + let attribute_type = stream.read_u32::()?; let record_length = stream.read_u16::()?; let name_length = stream.read_u8()?; let name_offset = stream.read_u8()?; - let first_vcn = stream.read_u64::()?; - let base_reference = - MftReference::from_reader(stream).map_err(Error::failed_to_read_mft_reference)?; - let attribute_id = stream.read_u16::()?; + let lowest_vcn = stream.read_u64::()?; + let segment_reference = MftReference::from_reader(stream) + .map_err(Error::failed_to_read_mft_reference)?; + let reserved = stream.read_u16::()?; 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, }) } -- cgit v1.2.3