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 Ben-Amram <omerbenamram@gmail.com>2019-05-29 23:40:46 +0300
committerOmer Ben-Amram <omerbenamram@gmail.com>2019-05-29 23:40:46 +0300
commit93c466bbb54211368881f5dbff9ad88bb286ecf3 (patch)
tree3ea2d4f854539c5cc63f4f6981874b8021534405
parent455980680f4793bbc8aab8e146a456073da9e984 (diff)
fixed attributes with base reference
-rw-r--r--src/attribute/header.rs16
-rw-r--r--src/attribute/mod.rs15
-rw-r--r--src/attribute/x20.rs64
-rw-r--r--src/entry.rs28
-rw-r--r--src/mft.rs76
5 files changed, 161 insertions, 38 deletions
diff --git a/src/attribute/header.rs b/src/attribute/header.rs
index 3d3121d..df19697 100644
--- a/src/attribute/header.rs
+++ b/src/attribute/header.rs
@@ -6,7 +6,7 @@ use crate::ReadSeek;
use byteorder::{LittleEndian, ReadBytesExt};
use num_traits::FromPrimitive;
use serde::Serialize;
-use std::io::Read;
+use std::io::{Read, SeekFrom};
/// Represents the union defined in
/// https://docs.microsoft.com/en-us/windows/desktop/devnotes/attribute-record-header
@@ -43,6 +43,8 @@ impl MftAttributeHeader {
/// 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<MftAttributeHeader>> {
+ let attribute_header_start_offset = stream.tell()?;
+
let type_code_value = stream.read_u32::<LittleEndian>()?;
if type_code_value == 0xFFFF_FFFF {
@@ -76,8 +78,8 @@ impl MftAttributeHeader {
let id = stream.read_u16::<LittleEndian>()?;
let residential_header = match resident_flag {
- 0 => ResidentialHeader::Resident(ResidentHeader::new(stream)?),
- 1 => ResidentialHeader::NonResident(NonResidentHeader::new(stream)?),
+ 0 => ResidentialHeader::Resident(ResidentHeader::from_stream(stream)?),
+ 1 => ResidentialHeader::NonResident(NonResidentHeader::from_stream(stream)?),
_ => {
return err::UnhandledResidentFlag {
flag: resident_flag,
@@ -89,6 +91,10 @@ impl MftAttributeHeader {
// Name is optional, and will not be present if size == 0.
let name = if name_size > 0 {
+ stream.seek(SeekFrom::Start(
+ attribute_header_start_offset
+ + u64::from(name_offset.expect("name_size > 0 is invariant")),
+ ))?;
read_utf16_string(stream, Some(name_size as usize))?
} else {
String::new()
@@ -122,7 +128,7 @@ pub struct ResidentHeader {
}
impl ResidentHeader {
- pub fn new<R: Read>(reader: &mut R) -> Result<ResidentHeader> {
+ pub fn from_stream<R: Read>(reader: &mut R) -> Result<ResidentHeader> {
Ok(ResidentHeader {
data_size: reader.read_u32::<LittleEndian>()?,
data_offset: reader.read_u16::<LittleEndian>()?,
@@ -158,7 +164,7 @@ pub struct NonResidentHeader {
}
impl NonResidentHeader {
- pub fn new<R: Read>(reader: &mut R) -> Result<NonResidentHeader> {
+ pub fn from_stream<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>()?;
diff --git a/src/attribute/mod.rs b/src/attribute/mod.rs
index 8b3acde..c397a2e 100644
--- a/src/attribute/mod.rs
+++ b/src/attribute/mod.rs
@@ -1,6 +1,7 @@
pub mod header;
pub mod raw;
pub mod x10;
+pub mod x20;
pub mod x30;
pub mod x40;
pub mod x80;
@@ -13,6 +14,7 @@ use bitflags::bitflags;
use crate::attribute::raw::RawAttribute;
use crate::attribute::x10::StandardInfoAttr;
+use crate::attribute::x20::AttributeListAttr;
use crate::attribute::x30::FileNameAttr;
use crate::attribute::header::{MftAttributeHeader, ResidentHeader};
@@ -37,6 +39,9 @@ impl MftAttributeContent {
MftAttributeType::StandardInformation => Ok(MftAttributeContent::AttrX10(
StandardInfoAttr::from_reader(stream)?,
)),
+ MftAttributeType::AttributeList => Ok(MftAttributeContent::AttrX20(
+ AttributeListAttr::from_stream(stream)?,
+ )),
MftAttributeType::FileName => Ok(MftAttributeContent::AttrX30(
FileNameAttr::from_stream(stream)?,
)),
@@ -61,6 +66,13 @@ impl MftAttributeContent {
)?)),
}
}
+
+ pub fn into_file_name(self) -> Option<FileNameAttr> {
+ match self {
+ MftAttributeContent::AttrX30(content) => Some(content),
+ _ => None,
+ }
+ }
}
#[derive(Serialize, Clone, Debug)]
@@ -69,6 +81,7 @@ pub enum MftAttributeContent {
Raw(RawAttribute),
AttrX80(DataAttr),
AttrX10(StandardInfoAttr),
+ AttrX20(AttributeListAttr),
AttrX30(FileNameAttr),
AttrX40(ObjectIdAttr),
AttrX90(IndexRootAttr),
@@ -88,6 +101,8 @@ pub enum MftAttributeType {
FileName = 0x30_u32,
/// An 16-byte object identifier assigned by the link-tracking service.
ObjectId = 0x40_u32,
+ /// File's access control list and security properties
+ SecurityDescriptor = 0x50_u32,
/// The volume label.
/// Present in the $Volume file.
VolumeName = 0x60_u32,
diff --git a/src/attribute/x20.rs b/src/attribute/x20.rs
new file mode 100644
index 0000000..1309de4
--- /dev/null
+++ b/src/attribute/x20.rs
@@ -0,0 +1,64 @@
+use crate::err::{self, Result};
+use crate::ReadSeek;
+use log::trace;
+use snafu::OptionExt;
+
+use byteorder::{LittleEndian, ReadBytesExt};
+use encoding::all::UTF_16LE;
+use encoding::{DecoderTrap, Encoding};
+
+use serde::Serialize;
+
+use snafu::ResultExt;
+use std::io::SeekFrom;
+use winstructs::ntfs::mft_reference::MftReference;
+
+#[derive(Serialize, Clone, Debug)]
+pub struct AttributeListAttr {
+ pub attribute_type: u32,
+ pub record_length: u16,
+ pub first_vcn: u64,
+ pub base_reference: MftReference,
+ pub attribute_id: u16,
+ pub name: String,
+}
+
+impl AttributeListAttr {
+ pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<AttributeListAttr> {
+ 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).context(err::FailedToReadMftReference)?;
+ let attribute_id = stream.read_u16::<LittleEndian>()?;
+
+ let name = if name_length > 0 {
+ stream.seek(SeekFrom::Start(start_offset + u64::from(name_offset)))?;
+
+ let mut name_buffer = vec![0; (name_length as usize * 2) as usize];
+ stream.read_exact(&mut name_buffer)?;
+
+ match UTF_16LE.decode(&name_buffer, DecoderTrap::Ignore) {
+ Ok(s) => s,
+ Err(_e) => return err::InvalidFilename {}.fail(),
+ }
+ } else {
+ String::new()
+ };
+
+ Ok(AttributeListAttr {
+ attribute_type,
+ record_length,
+ first_vcn,
+ base_reference,
+ attribute_id,
+ name,
+ })
+ }
+}
diff --git a/src/entry.rs b/src/entry.rs
index 1ed3fec..296ec2f 100644
--- a/src/entry.rs
+++ b/src/entry.rs
@@ -13,11 +13,13 @@ use serde::ser::{self, SerializeStruct, Serializer};
use serde::Serialize;
use crate::attribute::header::{MftAttributeHeader, ResidentialHeader};
+use crate::attribute::x30::{FileNameAttr, FileNamespace};
use crate::attribute::{MftAttribute, MftAttributeContent, MftAttributeType};
use std::io::Read;
use std::io::SeekFrom;
use std::io::{Cursor, Seek};
+use std::path::PathBuf;
const SEQUENCE_NUMBER_STRIDE: usize = 512;
@@ -150,6 +152,32 @@ impl MftEntry {
})
}
+ /// Retrieves most human-readable representation of a file path entry.
+ /// Will prefer `Win32` file name attributes, and fallback to `Dos` paths.
+ pub fn find_best_name_attribute(&self) -> Option<FileNameAttr> {
+ let file_name_attributes: Vec<FileNameAttr> = self
+ .iter_attributes()
+ .filter_map(Result::ok)
+ .filter_map(|a| a.data.into_file_name())
+ .collect();
+
+ // Try to find a human-readable filename first
+ let win32_filename = file_name_attributes
+ .iter()
+ .find(|a| [FileNamespace::Win32, FileNamespace::Win32AndDos].contains(&a.namespace));
+
+ match win32_filename {
+ Some(filename) => Some(filename.clone()),
+ None => {
+ // Try to take anything
+ match file_name_attributes.iter().next() {
+ Some(filename) => Some(filename.clone()),
+ None => None,
+ }
+ }
+ }
+ }
+
/// Applies the update sequence array fixups.
/// 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,
diff --git a/src/mft.rs b/src/mft.rs
index 020a69c..94b0e62 100644
--- a/src/mft.rs
+++ b/src/mft.rs
@@ -91,19 +91,42 @@ impl<T: ReadSeek> MftParser<T> {
(0..total_entries).map(move |i| self.get_entry(i))
}
+ fn inner_get_entry(&mut self, parent_entry_id: u64, entry_name: Option<&str>) -> PathBuf {
+ let cached_entry = self.entries_cache.cache_get(&parent_entry_id);
+
+ // If my parent path is known, then my path is parent's full path + my name.
+ // Else, retrieve and cache my parent's path.
+ if let Some(cached_parent_path) = cached_entry {
+ match entry_name {
+ Some(name) => cached_parent_path.clone().join(name),
+ None => cached_parent_path.clone(),
+ }
+ } else {
+ let path = match self.get_entry(parent_entry_id).ok() {
+ Some(parent) => match self.get_full_path_for_entry(&parent) {
+ Ok(Some(path)) => path,
+ // 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]"),
+ };
+
+ self.entries_cache.cache_set(parent_entry_id, path.clone());
+ match entry_name {
+ Some(name) => path.join(name),
+ None => path,
+ }
+ }
+ }
+
/// Gets the full path for an entry.
/// Caches computations.
pub fn get_full_path_for_entry(&mut self, entry: &MftEntry) -> Result<Option<PathBuf>> {
let entry_id = entry.header.record_number;
-
- for attribute in entry.iter_attributes().filter_map(|a| a.ok()) {
- if let AttrX30(filename_header) = attribute.data {
- if ![FileNamespace::Win32, FileNamespace::Win32AndDos]
- .contains(&filename_header.namespace)
- {
- continue;
- }
-
+ match entry.find_best_name_attribute() {
+ Some(filename_header) => {
let parent_entry_id = filename_header.parent.entry;
// MFT entry 5 is the root path.
@@ -120,27 +143,10 @@ impl<T: ReadSeek> MftParser<T> {
}
if parent_entry_id > 0 {
- let cached_entry = self.entries_cache.cache_get(&parent_entry_id);
-
- // If my parent path is known, then my path is parent's full path + my name.
- // Else, retrieve and cache my parent's path.
- if let Some(cached_parent_path) = cached_entry {
- return Ok(Some(cached_parent_path.clone().join(filename_header.name)));
- } else {
- let path = match self.get_entry(parent_entry_id).ok() {
- Some(parent) => match self.get_full_path_for_entry(&parent) {
- Ok(Some(path)) => path,
- // 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]"),
- };
-
- self.entries_cache.cache_set(parent_entry_id, path.clone());
- return Ok(Some(path.join(filename_header.name)));
- }
+ Ok(Some(self.inner_get_entry(
+ parent_entry_id,
+ Some(&filename_header.name),
+ )))
} else {
trace!("Found orphaned entry ID {}", entry_id);
@@ -148,12 +154,16 @@ impl<T: ReadSeek> MftParser<T> {
self.entries_cache
.cache_set(entry.header.record_number, orphan.clone());
- return Ok(Some(orphan));
+
+ Ok(Some(orphan))
}
}
+ None => match entry.header.base_reference.entry {
+ // I don't have a parent reference, and no X30 attribute. Though luck.
+ 0 => Ok(None),
+ parent_entry_id => Ok(Some(self.inner_get_entry(parent_entry_id, None))),
+ },
}
-
- Ok(None)
}
}