Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read non-resident attribute data runs + resident Index Entries in Index Root attribute #118

Merged
merged 2 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added samples/entry_data_run_at_offset
Binary file not shown.
Binary file added samples/entry_multiple_index_root_entries
Binary file not shown.
Binary file added samples/entry_single_file
Binary file not shown.
86 changes: 86 additions & 0 deletions src/attribute/data_run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use serde::Serialize;

// adapted from https://github.com/rkapl/ntfs-reclaim/blob/a68e87b21c12631311fc3f279f5b03bd8f23d57b/src/data_runs.rs
// original didn't support sparse clusters

#[derive(Serialize, Debug, Copy, Clone, Eq, PartialEq)]
pub enum RunType {
Standard,
Sparse,
}

#[derive(Serialize, Debug, Copy, Clone, Eq, PartialEq)]
pub struct DataRun {
pub lcn_offset: u64,
pub lcn_length: u64,
pub run_type: RunType
}

fn decode_run_value<T: Iterator<Item = u8>>(it: &mut T, bytes: u8) -> Option<u64> {
let mut acc = 0u64;
for _ in 0..bytes {
let v = it.next()?;
acc = (acc >> 8) | ((v as u64) << 56);
}
acc >>= (8 - bytes) * 8;
Some(acc)
}

fn decode_run_svalue<T: Iterator<Item = u8>>(it: &mut T, bytes: u8) -> Option<i64> {
let mut acc = decode_run_value(it, bytes)? as i64;
// sign extend
acc <<= (8 - bytes) * 8;
acc >>= (8 - bytes) * 8;
Some(acc)
}

pub fn decode_data_runs(runs: &[u8]) -> Option<Vec<DataRun>> {
let mut it = runs.iter().copied();
let mut out: Vec<DataRun> = Vec::new();

loop {
let h = it.next()?;
if h == 0 {
break;
}
let offset_size = (h & 0xF0) >> 4;
let length_size = h & 0x0F;
if offset_size > 8 || length_size > 8 {
return None
}
let length = decode_run_value(&mut it, length_size)?;
let abs_offset;
let run_type;
if offset_size != 0 { // offset_size of 0 == sparse cluster
if let Some(last) = out.last() {
let rel_offset = decode_run_svalue(&mut it, offset_size)?;
abs_offset = (last.lcn_offset as i64 + rel_offset) as u64;
} else {
abs_offset = decode_run_value(&mut it, offset_size)?;
}
run_type = RunType::Standard;
}
else {
abs_offset = 0;
run_type = RunType::Sparse;
}
out.push(DataRun {
lcn_offset: abs_offset,
lcn_length: length,
run_type
});
}
Some(out)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_value_decode() {
assert_eq!(decode_run_value(&mut vec![0x34, 0x56].into_iter(), 2), Some(0x5634));
assert_eq!(decode_run_svalue(&mut vec![0xE0].into_iter(), 1), Some(-0x20));
assert_eq!(decode_run_svalue(&mut vec![0xE0].into_iter(), 2), None);
}
}
4 changes: 3 additions & 1 deletion src/attribute/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct MftAttributeHeader {
/// The unique instance for this attribute in the file record.
pub instance: u16,
pub name: String,
/// start of the attribute; used for calculating relative offsets
pub start_offset: u64
}

#[derive(Serialize, Clone, Debug)]
Expand Down Expand Up @@ -107,6 +109,7 @@ impl MftAttributeHeader {
instance: id,
name,
residential_header,
start_offset: attribute_header_start_offset
}))
}
}
Expand All @@ -120,7 +123,6 @@ pub struct ResidentHeader {
/// 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,
}

Expand Down
28 changes: 26 additions & 2 deletions src/attribute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub mod x30;
pub mod x40;
pub mod x80;
pub mod x90;
pub mod non_resident_attr;
pub mod data_run;

use crate::err::Result;
use crate::impl_serialize_for_bitflags;
Expand All @@ -19,10 +21,11 @@ use crate::attribute::x10::StandardInfoAttr;
use crate::attribute::x20::AttributeListAttr;
use crate::attribute::x30::FileNameAttr;

use crate::attribute::header::{MftAttributeHeader, ResidentHeader};
use crate::attribute::header::{MftAttributeHeader, ResidentHeader, NonResidentHeader};
use crate::attribute::x40::ObjectIdAttr;
use crate::attribute::x80::DataAttr;
use crate::attribute::x90::IndexRootAttr;
use crate::attribute::non_resident_attr::NonResidentAttr;
use serde::Serialize;

#[derive(Serialize, Clone, Debug)]
Expand All @@ -31,7 +34,17 @@ pub struct MftAttribute {
pub data: MftAttributeContent,
}

impl MftAttributeContent {
impl MftAttributeContent {
pub fn from_stream_non_resident<S: Read + Seek>(
stream: &mut S,
header: &MftAttributeHeader,
resident: &NonResidentHeader,
) -> Result<Self> {
Ok(MftAttributeContent::DataRun(
NonResidentAttr::from_stream(stream, header, resident)?,
))
}

pub fn from_stream_resident<S: Read + Seek>(
stream: &mut S,
header: &MftAttributeHeader,
Expand Down Expand Up @@ -99,6 +112,7 @@ impl MftAttributeContent {
_ => None,
}
}

/// Converts the given attributes into a `ObjectIdAttr`, consuming the object attribute object.
pub fn into_object_id(self) -> Option<ObjectIdAttr> {
match self {
Expand All @@ -113,6 +127,7 @@ impl MftAttributeContent {
_ => None,
}
}

/// Converts the given attributes into a `DataAttr`, consuming the object attribute object.
pub fn into_data(self) -> Option<DataAttr> {
match self {
Expand All @@ -128,6 +143,14 @@ impl MftAttributeContent {
_ => None,
}
}

/// Converts the given attributes into a `NonResidentAttr`, consuming the object attribute object.
pub fn into_data_runs(self) -> Option<NonResidentAttr> {
match self {
MftAttributeContent::DataRun(content) => Some(content),
_ => None,
}
}
}

#[derive(Serialize, Clone, Debug)]
Expand All @@ -140,6 +163,7 @@ pub enum MftAttributeContent {
AttrX40(ObjectIdAttr),
AttrX80(DataAttr),
AttrX90(IndexRootAttr),
DataRun(NonResidentAttr),
/// Empty - used when data is non resident.
None,
}
Expand Down
42 changes: 42 additions & 0 deletions src/attribute/non_resident_attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::err::{Error, Result};
use crate::attribute::header::{MftAttributeHeader, NonResidentHeader};
use crate::attribute::data_run::{DataRun, decode_data_runs};

use std::io::{Read, Seek, SeekFrom};
use serde::Serialize;

#[derive(Serialize, Clone, Debug)]
pub struct NonResidentAttr {
pub data_runs: Vec<DataRun>
}

impl NonResidentAttr {
pub fn from_stream<S: Read + Seek>(
stream: &mut S,
header: &MftAttributeHeader,
resident: &NonResidentHeader,
) -> Result<Self> {
let data_run_bytes_count = (header.record_length - u32::from(resident.datarun_offset)) as usize;
let mut data_run_bytes = vec![0_u8; data_run_bytes_count];
if resident.valid_data_length != 0 {
stream.seek(SeekFrom::Start(header.start_offset + u64::from(resident.datarun_offset)))?;
stream.read_exact(&mut data_run_bytes)?;
if let Some(data_runs) = decode_data_runs(&data_run_bytes) {
Ok(Self {
data_runs
})
}
else {
Err(Error::FailedToDecodeDataRuns {
bad_data_runs: data_run_bytes,
})
}
}
else {
let data_runs = Vec::new();
Ok(Self {
data_runs
})
}
}
}
1 change: 1 addition & 0 deletions src/attribute/x20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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.
Expand Down
2 changes: 1 addition & 1 deletion src/attribute/x30.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub enum FileNamespace {
Win32AndDos = 3,
}

#[derive(Serialize, Clone, Debug)]
#[derive(Serialize, Clone, Debug, PartialEq)]
pub struct FileNameAttr {
pub parent: MftReference,
pub created: DateTime<Utc>,
Expand Down
Loading