From c103f961ffca9828fb4ce36a836ea75532df0841 Mon Sep 17 00:00:00 2001 From: matthew seyer Date: Thu, 20 Apr 2017 22:42:12 -0500 Subject: initial commit --- Cargo.toml | 28 ++++ examples/parse_entry.rs | 85 ++++++++++++ examples/serialized_structs.rs | 84 ++++++++++++ src/attr_x10.rs | 79 +++++++++++ src/attr_x30.rs | 95 +++++++++++++ src/attribute.rs | 222 +++++++++++++++++++++++++++++++ src/entry.rs | 219 ++++++++++++++++++++++++++++++ src/errors.rs | 53 ++++++++ src/lib.rs | 16 +++ src/main.rs | 87 ++++++++++++ src/mft.rs | 76 +++++++++++ src/utils.rs | 10 ++ testdata/attribute_nonresident_001 | Bin 0 -> 80 bytes testdata/attribute_nonresident_002 | Bin 0 -> 80 bytes testdata/attribute_resident_001 | Bin 0 -> 24 bytes testdata/entry_long_name_and_res_ads_002 | Bin 0 -> 1024 bytes testdata/entry_super_long_name_001 | Bin 0 -> 1024 bytes 17 files changed, 1054 insertions(+) create mode 100644 Cargo.toml create mode 100644 examples/parse_entry.rs create mode 100644 examples/serialized_structs.rs create mode 100644 src/attr_x10.rs create mode 100644 src/attr_x30.rs create mode 100644 src/attribute.rs create mode 100644 src/entry.rs create mode 100644 src/errors.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/mft.rs create mode 100644 src/utils.rs create mode 100644 testdata/attribute_nonresident_001 create mode 100644 testdata/attribute_nonresident_002 create mode 100644 testdata/attribute_resident_001 create mode 100644 testdata/entry_long_name_and_res_ads_002 create mode 100644 testdata/entry_super_long_name_001 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e2d4717 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "RustyMft" +version = "0.1.0" +authors = ["matthew seyer "] + +[lib] +name = "rustymft" +crate-type = ["cdylib","lib"] + +[dependencies] +log = "*" +clap = "*" +encoding = "0.2" +byteorder = "*" +bitflags = "0.7" +serde = "0.9" +serde_derive = "0.9" +serde_json = "0.9" +seek_bufread = "~1.2" + +[dependencies.r-winstructs] +version = "*" +branch = "master" +git = "https://github.com/forensicmatt/r-winstructs" + +[dependencies.chrono] +version = "*" +features = ["serde", "rustc-serialize"] diff --git a/examples/parse_entry.rs b/examples/parse_entry.rs new file mode 100644 index 0000000..53fa76e --- /dev/null +++ b/examples/parse_entry.rs @@ -0,0 +1,85 @@ +extern crate serde_json; +extern crate rwinstructs; +extern crate rustymft; +use rwinstructs::serialize; +use rustymft::entry; + +fn print_entry_01(){ + let entry_buffer: &[u8] = &[ + 0x46,0x49,0x4C,0x45,0x30,0x00,0x03,0x00,0x2E,0xB5,0x10,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0x38,0x00,0x01,0x00,0x28,0x03,0x00,0x00,0x00,0x04,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x2F,0x00,0x00,0x00, + 0x05,0x00,0x65,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x18,0x00,0x00,0x00, + 0xC5,0x9A,0xD7,0x96,0x6E,0xB9,0xD2,0x01,0x92,0x56,0x54,0xB8,0x6E,0xB9,0xD2,0x01, + 0x92,0x56,0x54,0xB8,0x6E,0xB9,0xD2,0x01,0xC5,0x9A,0xD7,0x96,0x6E,0xB9,0xD2,0x01, + 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0C,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x70,0x2B,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x28,0x02,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x0A,0x02,0x00,0x00,0x18,0x00,0x01,0x00, + 0x27,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xC5,0x9A,0xD7,0x96,0x6E,0xB9,0xD2,0x01, + 0xC5,0x9A,0xD7,0x96,0x6E,0xB9,0xD2,0x01,0xED,0x6E,0x47,0xA7,0x6E,0xB9,0xD2,0x01, + 0xC5,0x9A,0xD7,0x96,0x6E,0xB9,0xD2,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xE4,0x00,0x74,0x00,0x69,0x00,0x6D,0x00,0x65,0x00,0x5F,0x00,0x66,0x00,0x6F,0x00, + 0x72,0x00,0x5F,0x00,0x61,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00, + 0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00, + 0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00, + 0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00, + 0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00, + 0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00, + 0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00, + 0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00, + 0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00, + 0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00, + 0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00, + 0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00, + 0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00, + 0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00, + 0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00, + 0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x05,0x00, + 0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00, + 0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00, + 0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00, + 0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00, + 0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00, + 0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00, + 0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00, + 0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00, + 0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00, + 0x65,0x00,0x72,0x00,0x5F,0x00,0x73,0x00,0x75,0x00,0x70,0x00,0x65,0x00,0x72,0x00, + 0x5F,0x00,0x6C,0x00,0x6F,0x00,0x6E,0x00,0x67,0x00,0x6E,0x00,0x61,0x00,0x6D,0x00, + 0x65,0x00,0x2E,0x00,0x74,0x00,0x78,0x00,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00, + 0x10,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x61,0x63,0x56,0x9C,0xC8,0x24,0xE7,0x11, + 0xBF,0xBD,0x40,0xE2,0x30,0x3A,0x39,0x8D,0x80,0x00,0x00,0x00,0x38,0x00,0x00,0x00, + 0x00,0x00,0x18,0x00,0x00,0x00,0x06,0x00,0x1F,0x00,0x00,0x00,0x18,0x00,0x00,0x00, + 0x6A,0x75,0x73,0x74,0x20,0x74,0x65,0x73,0x74,0x69,0x6E,0x67,0x20,0x61,0x20,0x73, + 0x75,0x70,0x65,0x72,0x20,0x6C,0x6F,0x6E,0x67,0x20,0x6E,0x61,0x6D,0x65,0x21,0x00, + 0xFF,0xFF,0xFF,0xFF,0x82,0x79,0x47,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00 + ]; + + let mft_entry = entry::MftEntry::new(entry_buffer.to_vec(),None).unwrap(); + + println!("{}",serde_json::to_string_pretty(&mft_entry).unwrap()); +} + +fn main() { + unsafe { + serialize::U64_SERIALIZATION = serialize::U64Serialization::AsString + } + print_entry_01() +} diff --git a/examples/serialized_structs.rs b/examples/serialized_structs.rs new file mode 100644 index 0000000..5bbc190 --- /dev/null +++ b/examples/serialized_structs.rs @@ -0,0 +1,84 @@ +extern crate serde_json; +extern crate rwinstructs; +extern crate rustymft; +use rwinstructs::serialize; +use rustymft::entry; +use rustymft::attribute; +use std::io::Cursor; + +fn print_entry_header(){ + let header_buffer: &[u8] = &[ + 0x46,0x49,0x4C,0x45,0x30,0x00,0x03,0x00,0xCC,0xB3,0x7D,0x84,0x0C,0x00,0x00,0x00, + 0x05,0x00,0x01,0x00,0x38,0x00,0x05,0x00,0x48,0x03,0x00,0x00,0x00,0x04,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0xD5,0x95,0x00,0x00, + 0x53,0x57,0x81,0x37,0x00,0x00,0x00,0x00 + ]; + + let entry_header = match entry::EntryHeader::new(header_buffer,None) { + Ok(entry_header) => entry_header, + Err(error) => panic!(error) + }; + + println!("{}",serde_json::to_string_pretty(&entry_header).unwrap()); +} +fn print_attribute_header_01(){ + 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 attribute_buffer = Cursor::new(raw); + + let attribute_header = match attribute::AttributeHeader::new(attribute_buffer) { + Ok(attribute_header) => attribute_header, + Err(error) => panic!(error) + }; + + println!("{}",serde_json::to_string_pretty(&attribute_header).unwrap()); +} +fn print_attribute_header_02() { + 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 attribute_buffer = Cursor::new(raw); + + let attribute_header = match attribute::AttributeHeader::new(attribute_buffer) { + Ok(attribute_header) => attribute_header, + Err(error) => panic!(error) + }; + + println!("{}",serde_json::to_string_pretty(&attribute_header).unwrap()); +} +fn print_attribute_header_03() { + let raw: &[u8] = &[ + 0x80,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x04,0x40,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x11,0xD1,0x00,0x00,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x1F,0x11,0x0D,0x00,0x00,0x00, + 0x00,0xF0,0x1F,0x11,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x24,0x00,0x42,0x00,0x61,0x00,0x64,0x00,0x04,0xFF,0x11,0xD1,0x00,0x00,0x00,0x00 + ]; + + let attribute_buffer = Cursor::new(raw); + + let attribute_header = match attribute::AttributeHeader::new(attribute_buffer) { + Ok(attribute_header) => attribute_header, + Err(error) => panic!(error) + }; + + println!("{}",serde_json::to_string_pretty(&attribute_header).unwrap()); +} + +fn main(){ + unsafe { + serialize::U64_SERIALIZATION = serialize::U64Serialization::AsString + } + print_entry_header(); + print_attribute_header_01(); + print_attribute_header_02(); + print_attribute_header_03(); +} diff --git a/src/attr_x10.rs b/src/attr_x10.rs new file mode 100644 index 0000000..aeaa771 --- /dev/null +++ b/src/attr_x10.rs @@ -0,0 +1,79 @@ +use errors::{MftError}; +use rwinstructs::timestamp::{WinTimestamp}; +use rwinstructs::serialize::{serialize_u64}; +use byteorder::{ReadBytesExt, LittleEndian}; +use std::io::Read; +use std::mem; + +#[derive(Serialize, Debug)] +pub struct StandardInformationAttribute { + pub created: WinTimestamp, + pub modified: WinTimestamp, + pub mft_modified: WinTimestamp, + pub accessed: WinTimestamp, + pub file_flags: u32, + pub max_version: u32, + pub version: u32, + pub class_id: u32, + pub owner_id: u32, + pub security_id: u32, + #[serde(serialize_with = "serialize_u64")] + pub quota: u64, + #[serde(serialize_with = "serialize_u64")] + pub usn: u64 +} +impl StandardInformationAttribute { + pub fn new(mut reader: R) -> Result { + let mut attribute: StandardInformationAttribute = unsafe { + mem::zeroed() + }; + + attribute.created = WinTimestamp(reader.read_u64::()?); + attribute.modified = WinTimestamp(reader.read_u64::()?); + attribute.mft_modified = WinTimestamp(reader.read_u64::()?); + attribute.accessed = WinTimestamp(reader.read_u64::()?); + attribute.file_flags = reader.read_u32::()?; + attribute.max_version = reader.read_u32::()?; + attribute.version = reader.read_u32::()?; + attribute.class_id = reader.read_u32::()?; + attribute.owner_id = reader.read_u32::()?; + attribute.security_id = reader.read_u32::()?; + attribute.quota = reader.read_u64::()?; + attribute.usn = reader.read_u64::()?; + + Ok(attribute) + } +} + +#[cfg(test)] +mod tests { + use super::StandardInformationAttribute; + + #[test] + fn si_attribute_test_01() { + let attribute_buffer: &[u8] = &[ + 0x2F,0x6D,0xB6,0x6F,0x0C,0x97,0xCE,0x01,0x56,0xCD,0x1A,0x75,0x73,0xB5,0xCE,0x01, + 0x56,0xCD,0x1A,0x75,0x73,0xB5,0xCE,0x01,0x56,0xCD,0x1A,0x75,0x73,0xB5,0xCE,0x01, + 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xB0,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x68,0x58,0xA0,0x0A,0x02,0x00,0x00,0x00 + ]; + + let attribute = match StandardInformationAttribute::new(attribute_buffer) { + Ok(attribute) => attribute, + Err(error) => panic!(error) + }; + + assert_eq!(attribute.created.0, 130207518909951279); + assert_eq!(attribute.modified.0, 130240946730880342); + assert_eq!(attribute.mft_modified.0, 130240946730880342); + assert_eq!(attribute.accessed.0, 130240946730880342); + assert_eq!(attribute.file_flags, 32); + assert_eq!(attribute.max_version, 0); + assert_eq!(attribute.version, 0); + assert_eq!(attribute.class_id, 0); + assert_eq!(attribute.security_id, 1456); + assert_eq!(attribute.quota, 0); + assert_eq!(attribute.usn, 8768215144); + } +} diff --git a/src/attr_x30.rs b/src/attr_x30.rs new file mode 100644 index 0000000..3d4503b --- /dev/null +++ b/src/attr_x30.rs @@ -0,0 +1,95 @@ +use errors::{MftError}; +use rwinstructs::timestamp::{WinTimestamp}; +use rwinstructs::reference::{MftReference}; +use rwinstructs::serialize::{serialize_u64}; +use byteorder::{ReadBytesExt, LittleEndian}; +use encoding::{Encoding, DecoderTrap}; +use encoding::all::UTF_16LE; +use std::io::Read; +use std::mem; + +#[derive(Serialize, Debug)] +pub struct FileNameAttribute { + pub parent: MftReference, + pub created: WinTimestamp, + pub modified: WinTimestamp, + pub mft_modified: WinTimestamp, + pub accessed: WinTimestamp, + #[serde(serialize_with = "serialize_u64")] + pub logical_size: u64, + #[serde(serialize_with = "serialize_u64")] + pub physical_size: u64, + pub flags: u32, + pub reparse_value: u32, + pub name_length: u8, + pub namespace: u8, + pub name: String +} +impl FileNameAttribute { + pub fn new(mut reader: R) -> Result { + let mut attribute: FileNameAttribute = unsafe { + mem::zeroed() + }; + + attribute.parent = MftReference(reader.read_u64::()?); + attribute.created = WinTimestamp(reader.read_u64::()?); + attribute.modified = WinTimestamp(reader.read_u64::()?); + attribute.mft_modified = WinTimestamp(reader.read_u64::()?); + attribute.accessed = WinTimestamp(reader.read_u64::()?); + attribute.logical_size = reader.read_u64::()?; + attribute.physical_size = reader.read_u64::()?; + attribute.flags = reader.read_u32::()?; + attribute.reparse_value = reader.read_u32::()?; + attribute.name_length = reader.read_u8()?; + attribute.namespace = reader.read_u8()?; + + let mut name_buffer = vec![0; (attribute.name_length as usize * 2) as usize]; + reader.read_exact(&mut name_buffer)?; + + attribute.name = match UTF_16LE.decode(&name_buffer,DecoderTrap::Ignore){ + Ok(filename) => filename, + Err(error) => return Err( + MftError::decode_error( + format!("Error decoding name in filename attribute. [{}]",error) + ) + ) + }; + + Ok(attribute) + } +} + +#[cfg(test)] +mod tests { + use super::FileNameAttribute; + + #[test] + fn fn_attribute_test_01() { + let attribute_buffer: &[u8] = &[ + 0x05,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0xD5,0x2D,0x48,0x58,0x43,0x5F,0xCE,0x01, + 0xD5,0x2D,0x48,0x58,0x43,0x5F,0xCE,0x01,0xD5,0x2D,0x48,0x58,0x43,0x5F,0xCE,0x01, + 0xD5,0x2D,0x48,0x58,0x43,0x5F,0xCE,0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x03,0x24,0x00,0x4C,0x00,0x6F,0x00,0x67,0x00,0x46,0x00,0x69,0x00,0x6C,0x00, + 0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ]; + + let attribute = match FileNameAttribute::new(attribute_buffer) { + Ok(attribute) => attribute, + Err(error) => panic!(error) + }; + + assert_eq!(attribute.parent.0, 1407374883553285); + assert_eq!(attribute.created.0, 130146182088895957); + assert_eq!(attribute.modified.0, 130146182088895957); + assert_eq!(attribute.mft_modified.0, 130146182088895957); + assert_eq!(attribute.accessed.0, 130146182088895957); + assert_eq!(attribute.logical_size, 67108864); + assert_eq!(attribute.physical_size, 67108864); + assert_eq!(attribute.flags, 6); + assert_eq!(attribute.reparse_value, 0); + assert_eq!(attribute.name_length, 8); + assert_eq!(attribute.namespace, 3); + assert_eq!(attribute.name, "$LogFile"); + } +} diff --git a/src/attribute.rs b/src/attribute.rs new file mode 100644 index 0000000..4a2ad08 --- /dev/null +++ b/src/attribute.rs @@ -0,0 +1,222 @@ +use errors::{MftError}; +use rwinstructs::serialize::{serialize_u64}; +use byteorder::{ReadBytesExt, LittleEndian}; +use encoding::{Encoding, DecoderTrap}; +use encoding::all::UTF_16LE; +use serde::{ser}; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::mem; + +bitflags! { + pub flags AttributeDataFlags: u16 { + const IS_COMPRESSED = 0x0001, + const COMPRESSION_MASK = 0x00FF, + const ENCRYPTED = 0x4000, + const SPARSE = 0x8000 + } +} + +pub fn serialize_attr_data_flags(&item: &AttributeDataFlags, serializer: S) + -> Result where S: ser::Serializer +{ + serializer.serialize_str(&format!("{:?}", item)) +} + +#[derive(Serialize, Debug)] +pub struct AttributeHeader { + pub attribute_type: u32, + pub attribute_size: u32, + pub resident_flag: u8, // 0 -> resident; 1 -> non-resident + pub name_size: u8, + 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 new(mut reader: R) -> Result { + let mut attribute_header: AttributeHeader = unsafe { + mem::zeroed() + }; + + let current_offset = reader.seek(SeekFrom::Current(0))?; + // println!("at offset {}",current_offset); + + attribute_header.attribute_type = reader.read_u32::()?; + if attribute_header.attribute_type == 0xFFFFFFFF { + return Ok(attribute_header); + } + attribute_header.attribute_size = reader.read_u32::()?; + attribute_header.resident_flag = reader.read_u8()?; + attribute_header.name_size = reader.read_u8()?; + attribute_header.name_offset = reader.read_u16::()?; + attribute_header.data_flags = AttributeDataFlags::from_bits_truncate( + reader.read_u16::()? + ); + attribute_header.id = reader.read_u16::()?; + + if attribute_header.resident_flag == 0 { + attribute_header.residential_header = ResidentialHeader::Resident( + ResidentHeader::new( + &mut reader + )? + ); + } else if attribute_header.resident_flag == 1 { + attribute_header.residential_header = ResidentialHeader::NonResident( + NonResidentHeader::new( + &mut reader + )? + ); + } else { + panic!("Unhandled resident flag: {}",attribute_header.resident_flag); + } + + if attribute_header.name_size > 0 { + // Seek to offset + reader.seek( + SeekFrom::Start( + current_offset + attribute_header.name_offset as u64 + ) + )?; + + let mut name_buffer = vec![0; (attribute_header.name_size * 2) as usize]; + reader.read_exact(&mut name_buffer)?; + + attribute_header.name = match UTF_16LE.decode(&name_buffer,DecoderTrap::Ignore){ + Ok(filename) => filename, + Err(error) => return Err( + MftError::decode_error( + format!("Error decoding filename in header. [{}]",error) + ) + ) + }; + } + + Ok(attribute_header) + } +} + +#[derive(Serialize, Debug)] +#[serde(untagged)] +pub enum ResidentialHeader{ + None, + Resident(ResidentHeader), + NonResident(NonResidentHeader) +} + +#[derive(Serialize, Debug)] +pub struct ResidentHeader{ + pub data_size: u32, + pub data_offset: u16, + pub index_flag: u8, + pub padding: u8, +} +impl ResidentHeader { + pub fn new(mut reader: R) -> Result { + let mut residential_header: ResidentHeader = unsafe { + mem::zeroed() + }; + + residential_header.data_size = reader.read_u32::()?; + residential_header.data_offset = reader.read_u16::()?; + residential_header.index_flag = reader.read_u8()?; + residential_header.padding = reader.read_u8()?; + + Ok(residential_header) + } +} + +#[derive(Serialize, Debug)] +pub struct NonResidentHeader{ + #[serde(serialize_with = "serialize_u64")] + pub vnc_first: u64, + #[serde(serialize_with = "serialize_u64")] + pub vnc_last: u64, + pub datarun_offset: u16, + pub unit_compression_size: u16, + pub padding: u32, + #[serde(serialize_with = "serialize_u64")] + pub size_allocated: u64, + #[serde(serialize_with = "serialize_u64")] + pub size_real: u64, + #[serde(serialize_with = "serialize_u64")] + pub size_compressed: u64, + // pub size_total_allocated: Option +} +impl NonResidentHeader { + pub fn new(mut reader: R) -> Result { + let mut residential_header: NonResidentHeader = unsafe { + mem::zeroed() + }; + + residential_header.vnc_first = reader.read_u64::()?; + residential_header.vnc_last = reader.read_u64::()?; + residential_header.datarun_offset = reader.read_u16::()?; + residential_header.unit_compression_size = reader.read_u16::()?; + residential_header.padding = reader.read_u32::()?; + residential_header.size_allocated = reader.read_u64::()?; + residential_header.size_real = reader.read_u64::()?; + residential_header.size_compressed = reader.read_u64::()?; + + // if residential_header.unit_compression_size > 0 { + // residential_header.size_total_allocated = Some(reader.read_u64::()?); + // } + + Ok(residential_header) + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + use super::AttributeHeader; + + #[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 attribute_buffer = Cursor::new(raw); + + let attribute_header = match AttributeHeader::new(attribute_buffer) { + 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 attribute_buffer = Cursor::new(raw); + + let attribute_header = match AttributeHeader::new(attribute_buffer) { + 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/entry.rs b/src/entry.rs new file mode 100644 index 0000000..74f4a6c --- /dev/null +++ b/src/entry.rs @@ -0,0 +1,219 @@ +use errors::{MftError}; +use attribute; +use utils; +use attr_x10; +use attr_x30; +use rwinstructs::reference::{MftReference}; +use rwinstructs::serialize::{serialize_u64}; +use byteorder::{ReadBytesExt, LittleEndian}; +use serde::{ser}; +use std::io::Cursor; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::mem; + +//https://github.com/libyal/libfsntfs/blob/master/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc#5-the-master-file-table-mft + +bitflags! { + pub flags EntryFlags: u16 { + const ALLOCATED = 0x01, + const INDEX_PRESENT = 0x02, + const UNKNOWN_1 = 0x04, + const UNKNOWN_2 = 0x08 + } +} +pub fn serialize_entry_flags(&item: &EntryFlags, serializer: S) -> Result + where S: ser::Serializer +{ + serializer.serialize_str(&format!("{:?}", item)) +} + +#[derive(Serialize, Debug)] +pub struct EntryHeader{ + pub signature: u32, + pub usa_offset: u16, + pub usa_size: u16, + #[serde(serialize_with = "serialize_u64")] + pub logfile_sequence_number: u64, + pub sequence: u16, + pub hard_link_count: u16, + pub fst_attr_offset: u16, + #[serde(serialize_with = "serialize_entry_flags")] + pub flags: EntryFlags, + pub entry_size_real: u32, + pub entry_size_allocated: u32, + pub base_reference: MftReference, + pub next_attribute_id: u16, + #[serde(skip_serializing)] + pub padding: Option, + pub record_number: Option, + pub update_sequence_value: u32 +} +impl EntryHeader{ + pub fn new(mut reader: R, entry: Option) -> Result { + let mut entry_header: EntryHeader = unsafe { + mem::zeroed() + }; + + entry_header.signature = reader.read_u32::()?; + entry_header.usa_offset = reader.read_u16::()?; + entry_header.usa_size = reader.read_u16::()?; + entry_header.logfile_sequence_number = reader.read_u64::()?; + entry_header.sequence = reader.read_u16::()?; + entry_header.hard_link_count = reader.read_u16::()?; + entry_header.fst_attr_offset = reader.read_u16::()?; + entry_header.flags = EntryFlags::from_bits_truncate( + reader.read_u16::()? + ); + entry_header.entry_size_real = reader.read_u32::()?; + entry_header.entry_size_allocated = reader.read_u32::()?; + entry_header.base_reference = MftReference(reader.read_u64::()?); + entry_header.next_attribute_id = reader.read_u16::()?; + + if entry_header.usa_offset == 48 { + entry_header.padding = Some(reader.read_u16::()?); + entry_header.record_number = Some(reader.read_u32::()?); + } else { + panic!( + "Unhandled update sequence array offset: {}", + entry_header.usa_offset + ) + } + + Ok(entry_header) + } +} + +#[derive(Serialize, Debug)] +pub struct MftEntry{ + pub header: EntryHeader, + pub attr_standard_info: Option>, + pub attr_filename: Option> +} +impl MftEntry{ + pub fn new(mut buffer: Vec, entry: Option) -> Result { + let mut mft_entry: MftEntry = unsafe { + mem::zeroed() + }; + + // Get Header + mft_entry.header = EntryHeader::new( + buffer.as_slice(), + entry + )?; + + // Fixup buffer + mft_entry.buffer_fixup( + &mut buffer + ); + + mft_entry.read_attributes( + Cursor::new(buffer.as_slice()) + )?; + + Ok(mft_entry) + } + + pub fn buffer_fixup(&self, mut buffer: &mut[u8]){ + // start offset (skip the first value (+2)) + let so = (self.header.usa_offset + 2) as usize; + // array length + let al = (self.header.usa_size - 1 * 2) as usize; + + let fixup_values = &buffer[ + (self.header.usa_offset + 2) as usize.. + ((self.header.usa_offset + 2)+((self.header.usa_size - 1) * 2)) as usize + ].to_vec(); + + for i in 0..(self.header.usa_size-1) { + let ofs = (i * 512) as usize; + *buffer.get_mut(ofs + 510).unwrap() = fixup_values[i as usize]; + *buffer.get_mut(ofs + 511).unwrap() = fixup_values[(i+1) as usize]; + } + } + + fn read_attributes(&mut self, mut buffer: Rs) -> Result{ + let mut current_offset = buffer.seek( + SeekFrom::Start(self.header.fst_attr_offset as u64) + )?; + let attr_count: u32 = 0; + + while true { + let attribute_header = attribute::AttributeHeader::new( + &mut buffer + )?; + + match attribute_header.attribute_type { + 0x10 => { + let si_attr = attr_x10::StandardInformationAttribute::new(&mut buffer)?; + + if self.attr_standard_info.is_none(){ + self.attr_standard_info = Some(Vec::new()); + } + + self.attr_standard_info.as_mut().unwrap().push(si_attr); + }, + 0x30 => { + let fn_attr = attr_x30::FileNameAttribute::new(&mut buffer)?; + if self.attr_filename.is_none(){ + self.attr_filename = Some(Vec::new()); + } + + self.attr_filename.as_mut().unwrap().push(fn_attr); + }, + 0xFFFFFFFF => { + // println!("END OF ATTRIBUTES"); + break; + }, + _ => { + // println!( + // "UNHANDLED ATTRIBUTE: 0x{:04X} at offset {}", + // attribute_header.attribute_type, + // current_offset + // ); + } + } + + current_offset = buffer.seek( + SeekFrom::Start(current_offset + attribute_header.attribute_size as u64) + )?; + } + Ok(attr_count) + } +} + +#[cfg(test)] +mod tests { + use super::EntryHeader; + + #[test] + fn mft_header_test_01() { + let header_buffer: &[u8] = &[ + 0x46,0x49,0x4C,0x45,0x30,0x00,0x03,0x00,0xCC,0xB3,0x7D,0x84,0x0C,0x00,0x00,0x00, + 0x05,0x00,0x01,0x00,0x38,0x00,0x05,0x00,0x48,0x03,0x00,0x00,0x00,0x04,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0xD5,0x95,0x00,0x00, + 0x53,0x57,0x81,0x37,0x00,0x00,0x00,0x00 + ]; + + let entry_header = match EntryHeader::new(header_buffer,None) { + Ok(entry_header) => entry_header, + Err(error) => panic!(error) + }; + + assert_eq!(entry_header.signature, 1162627398); + assert_eq!(entry_header.usa_offset, 48); + assert_eq!(entry_header.usa_size, 3); + assert_eq!(entry_header.logfile_sequence_number, 53762438092); + assert_eq!(entry_header.sequence, 5); + assert_eq!(entry_header.hard_link_count, 1); + assert_eq!(entry_header.fst_attr_offset, 56); + assert_eq!(entry_header.flags.bits(), 5); + assert_eq!(entry_header.entry_size_real, 840); + assert_eq!(entry_header.entry_size_allocated, 1024); + assert_eq!(entry_header.base_reference.0, 0); + assert_eq!(entry_header.next_attribute_id, 6); + assert_eq!(entry_header.padding, Some(0)); + assert_eq!(entry_header.record_number, Some(38357)); + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..769fa94 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,53 @@ +use std::fmt; +use std::fmt::Display; +use std::io; + +#[derive(Debug)] +pub enum ErrorKind { + IoError, + InvalidFileSignature, + Utf16Error +} + +#[derive(Debug)] +pub struct MftError { + /// Formated error message + pub message: String, + /// The type of error + pub kind: ErrorKind, + /// Any additional information passed along, such as the argument name that caused the error + pub info: Option>, +} + +impl MftError{ + #[allow(dead_code)] + pub fn invalid_file_signature(err: String)->Self{ + MftError { + message: format!("{}",err), + kind: ErrorKind::InvalidFileSignature, + info: Some(vec![]), + } + } + #[allow(dead_code)] + pub fn decode_error(err: String)->Self{ + MftError { + message: format!("{}",err), + kind: ErrorKind::Utf16Error, + info: Some(vec![]), + } + } +} + +impl From for MftError { + fn from(err: io::Error) -> Self { + MftError { + message: format!("{}",err), + kind: ErrorKind::IoError, + info: Some(vec![]), + } + } +} + +impl Display for MftError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "{}", self.message) } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..dafa329 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +#[macro_use] extern crate serde_derive; +#[macro_use] extern crate serde_json; +#[macro_use] extern crate bitflags; +#[macro_use] extern crate log; +extern crate seek_bufread; +extern crate byteorder; +extern crate rwinstructs; +extern crate encoding; +extern crate serde; +pub mod errors; +pub mod utils; +pub mod mft; +pub mod entry; +pub mod attribute; +pub mod attr_x10; +pub mod attr_x30; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f5b3a19 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,87 @@ +#[macro_use] extern crate log; +extern crate rustymft; +extern crate rwinstructs; +extern crate serde_json; +extern crate serde; +extern crate clap; +use log::LogLevel::Debug; +use clap::{App, Arg}; +use rustymft::mft::{MftHandler}; +use rwinstructs::reference; +use rwinstructs::serialize; +use serde::Serializer; +use serde::ser::SerializeSeq; +use std::fs; +use std::io; + +fn process_directory(directory: &str, serializer: S) where S: serde::Serializer { + let mut seq = serializer.serialize_seq(None).unwrap(); + for entry in fs::read_dir(directory).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_file() { + let path_string = path.into_os_string().into_string().unwrap(); + if path_string.ends_with("mft"){ + process_file(&path_string,&mut seq); + } + } + } + seq.end().unwrap(); +} + +fn process_file(filename: &str, serializer: &mut S) -> bool { + // Check if file is a prefetch file + let mut mft_handler = match MftHandler::new(filename) { + Ok(mft_handler) => mft_handler, + Err(error) => { + warn!("Could not parse file: {} [error: {}]", filename, error); + return false; + } + }; + + for i in 0 .. mft_handler.get_entry_count() { + let mft_entry = mft_handler.entry(i).unwrap(); + serializer.serialize_element(&mft_entry).unwrap(); + } + + return true; +} + +fn is_directory(source: &str)->bool{ + fs::metadata(source).unwrap().file_type().is_dir() +} + +fn main() { + let source_arg = Arg::with_name("source") + .short("s") + .long("source") + .value_name("FILE") + .help("The source path. Can be a file or a directory.") + .required(true) + .takes_value(true); + + let options = App::new("RustyMft") + .version("0.0.0") + .author("Matthew Seyer ") + .about("Parse $MFT.") + .arg(source_arg) + .get_matches(); + + // Set Reference Display Options + unsafe{reference::NESTED_REFERENCE = true;} + unsafe{serialize::U64_SERIALIZATION = serialize::U64Serialization::AsString;} + + let source = options.value_of("source").unwrap(); + + let mut serializer = serde_json::Serializer::pretty( + io::stdout() + ); + + if is_directory(source) { + panic!("Directory source is not implemented yet."); + } else { + let mut seq = serializer.serialize_seq(None).unwrap(); + process_file(source,&mut seq); + seq.end().unwrap(); + } +} diff --git a/src/mft.rs b/src/mft.rs new file mode 100644 index 0000000..6654f65 --- /dev/null +++ b/src/mft.rs @@ -0,0 +1,76 @@ +use seek_bufread::BufReader; +use errors::{MftError}; +use entry::{MftEntry}; +use std::fs::File; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::mem; + +pub struct MftHandler { + filehandle: BufReader, + _entry_size: u32, + _offset: u64, + _size: u64 +} +impl MftHandler{ + pub fn new(filename: &str) -> Result { + let mut mft_handler: MftHandler = unsafe { + mem::zeroed() + }; + + let mut mft_fh = match File::open(filename) { + Ok(usn_fh) => usn_fh, + // Handle error here + Err(error) => panic!("Error: {}",error) + }; + + // get file size + mft_handler._size = match mft_fh.seek(SeekFrom::End(0)){ + Err(e) => panic!("Error: {}",e), + Ok(size) => size + }; + + mft_handler.filehandle = BufReader::with_capacity( + 4096, + mft_fh + ); + + mft_handler.set_entry_size(1024); + + Ok(mft_handler) + } + + pub fn set_entry_size(&mut self, entry_size: u32){ + self._entry_size = entry_size + } + + pub fn get_entry_count(&self)->u64 { + self._size / self._entry_size as u64 + } + + pub fn entry(&mut self, entry: u64) -> Result { + self.filehandle.seek( + SeekFrom::Start(entry * self._entry_size as u64) + ).unwrap(); + + let mut entry_buffer = vec![0; self._entry_size as usize]; + self.filehandle.read_exact(&mut entry_buffer)?; + + let mft_entry = self.entry_from_buffer( + entry_buffer, + entry + )?; + + Ok(mft_entry) + } + + pub fn entry_from_buffer(&mut self, mut buffer: Vec, entry: u64) -> Result { + let mft_entry = MftEntry::new( + buffer, + Some(entry) + )?; + + Ok(mft_entry) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..7bda5c9 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,10 @@ +pub fn to_hex_string(bytes: &Vec) -> String { + let strs: Vec = bytes.iter() + .map(|b| format!("{:02X}", b)) + .collect(); + strs.join("") +} + +pub fn print_buffer_as_hex(buffer: &[u8]) { + println!("{}",to_hex_string(&buffer.to_vec())); +} diff --git a/testdata/attribute_nonresident_001 b/testdata/attribute_nonresident_001 new file mode 100644 index 0000000..313f32a Binary files /dev/null and b/testdata/attribute_nonresident_001 differ diff --git a/testdata/attribute_nonresident_002 b/testdata/attribute_nonresident_002 new file mode 100644 index 0000000..e7765bc Binary files /dev/null and b/testdata/attribute_nonresident_002 differ diff --git a/testdata/attribute_resident_001 b/testdata/attribute_resident_001 new file mode 100644 index 0000000..c1f73a8 Binary files /dev/null and b/testdata/attribute_resident_001 differ diff --git a/testdata/entry_long_name_and_res_ads_002 b/testdata/entry_long_name_and_res_ads_002 new file mode 100644 index 0000000..6402332 Binary files /dev/null and b/testdata/entry_long_name_and_res_ads_002 differ diff --git a/testdata/entry_super_long_name_001 b/testdata/entry_super_long_name_001 new file mode 100644 index 0000000..3c956ff Binary files /dev/null and b/testdata/entry_super_long_name_001 differ -- cgit v1.2.3