Skip to content

Commit

Permalink
Parsing corrections and removing obsolete dependancies (#18)
Browse files Browse the repository at this point in the history
* implemented first part of mft parsing

* corrected mft parsing

* added picking files

* finished refactoring

* fixed clippy
  • Loading branch information
NikolaMilosa authored Nov 16, 2023
1 parent 410dbdd commit 3f16192
Show file tree
Hide file tree
Showing 9 changed files with 930 additions and 330 deletions.
760 changes: 619 additions & 141 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ env_logger = "0.10.0"
log = "0.4.19"
dialoguer = "0.10.4"
memmap2 = "0.9.0"
tsk = { git = "https://github.com/NikolaMilosa/libtsk-rs"}
mft = { git = "https://github.com/omerbenamram/mft/" }
thiserror = "1"

[dependencies.chrono]
version = "0.4"
default-features = false
features = ["clock", "serde", "std"]
20 changes: 0 additions & 20 deletions dockerfiles/Dockerfile.x86_64-pc-windows-gnu

This file was deleted.

15 changes: 0 additions & 15 deletions dockerfiles/Dockerfile.x86_64-unknown-linux-gnu

This file was deleted.

16 changes: 8 additions & 8 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use clap::{arg, Parser};
use log::info;

use crate::errors::UndeleteError;
use crate::errors::{Error, Result};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
Expand Down Expand Up @@ -44,17 +44,17 @@ impl Cli {
info!("Configured with image: {}", self.image.display());
}

pub fn parse_and_validate(self) -> Result<Self, UndeleteError> {
pub fn parse_and_validate(self) -> Result<Self> {
if !self.image.exists() {
return Err(UndeleteError::Parse(
"Specified image is Non-existant!".to_string(),
));
return Err(Error::Any {
detail: format!("Specified image does not exist: {}", self.image.display()),
});
}

if !self.output_dir.exists() && !self.dry_run {
return Err(UndeleteError::Parse(
"Specified output directory is Non-existant!".to_string(),
));
return Err(Error::Any {
detail: "Specified output directory is Non-existant!".to_string(),
});
}

Ok(self)
Expand Down
37 changes: 25 additions & 12 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
use tsk::tsk_fs::FSType;
use std::path::PathBuf;
use thiserror::Error;

#[derive(Debug)]
pub enum UndeleteError {
Parse(String),
Initialization(String),
UnsupportedFs(FSType),
General(String),
StdIO(String),
Write(String),
pub type Result<T> = ::std::result::Result<T, Error>;

#[derive(Debug, Error)]
pub enum Error {
#[error("An I/O error has occurred")]
Io {
#[from]
source: std::io::Error,
},
#[error("Failed to open file {}", path.display())]
FailedToOpenFile {
path: PathBuf,
source: std::io::Error,
},
#[error("An unexpected error has occurred in mft: {}", detail)]
Mft { detail: String },
#[error("An unexpected error has occurred: {}", detail)]
Any { detail: String },
}

impl From<std::io::Error> for UndeleteError {
fn from(e: std::io::Error) -> Self {
UndeleteError::StdIO(e.to_string())
impl From<mft::err::Error> for Error {
fn from(source: mft::err::Error) -> Self {
Self::Mft {
detail: source.to_string(),
}
}
}
187 changes: 71 additions & 116 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,154 +1,109 @@
use std::io::Read;

use clap::Parser;
use cli::Cli;
use dialoguer::MultiSelect;
use errors::UndeleteError;
use errors::Result;
use log::info;
use tsk::bindings::TSK_FS_FILE_READ_FLAG_ENUM::TSK_FS_FILE_READ_FLAG_NONE;
use tsk::tsk_fs::{FSType, TskFs};
use tsk::tsk_fs_dir::TskFsDir;
use undelete_entry::UndeleteEntry;
use mft::MftParser;
use reader::Reader;

use crate::undelete_entry::UndeleteEntry;

mod cli;
mod errors;
mod reader;
mod undelete_entry;

fn main() -> Result<(), UndeleteError> {
fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();

let args = cli::Cli::parse_and_validate(Cli::parse())?;
args.display();

let image_info = tsk::TskImg::from_utf8_sing(args.image.as_path()).map_err(|err| {
UndeleteError::Initialization(format!("Failed to get image info: {:?}", err))
})?;
let reader = Reader::from_path(args.image)?;

let tskfs = TskFs::from_fs_offset(&image_info, 0)
.map_err(|err| UndeleteError::Parse(format!("Couldn't parse image file: {:?}", err)))?;
let mft = reader.read_mft()?;
info!("MFT size: {}", mft.len());

match tskfs.fs_type() {
FSType::NTFS => info!("Running: {:?}", tskfs.fs_type()),
_ => return Err(UndeleteError::UnsupportedFs(tskfs.fs_type())),
};
let mut parser = MftParser::from_buffer(mft)?;

let unallocated = get_all_unallocated_files_from_dir("/", &tskfs)?;
let found = parser
.iter_entries()
.filter_map(|e| match e {
Ok(entry) if !entry.is_dir() && !entry.is_allocated() => Some(entry.into()),
_ => None,
})
.filter(|e: &UndeleteEntry| !e.filename.is_empty())
.filter(|e| !e.is_allocated)
.collect::<Vec<_>>();

let found = found
.iter()
.map(|e| {
let entry = parser.get_entry(e.record_number).unwrap();
let full_path = match parser.get_full_path_for_entry(&entry) {
Ok(Some(path)) => path.to_str().unwrap().to_string(),
_ => e.filename.clone(),
};

UndeleteEntry {
filename: full_path,
record_number: e.record_number,
is_allocated: e.is_allocated,
}
})
.collect::<Vec<_>>();

let chosen = MultiSelect::new()
.with_prompt("Use UP and DOWN arrows to scroll up and down\nUse SPACE to select/unselect an option\nUse ENTER to finish\nChoose files to undelete")
.max_length(10)
.items(&unallocated)
.items(&found)
.interact()?;

for file in chosen {
let entry = unallocated.get(file).unwrap();
let full_path_relative_to_img = entry.get_full_path();
let new_path = args.output_dir.join(&full_path_relative_to_img);

let file = tskfs.file_open_meta(entry.inode).map_err(|err| {
UndeleteError::General(format!("Couldn't open file {}: {:?}", entry.name, err))
})?;

let mut handle = file
.get_file_handle(file.get_attr().unwrap(), TSK_FS_FILE_READ_FLAG_NONE)
.map_err(|err| {
UndeleteError::StdIO(format!(
"Coudln't get file handle for file {}: {:?}",
entry.name, err
))
})?;

let file_size = file
.get_meta()
.map_err(|err| {
UndeleteError::General(format!(
"Couldn't get file meta for file {}: {:?}",
entry.name, err
))
})?
.size();

let mut buf: Vec<u8> = Vec::with_capacity(file_size.try_into().unwrap());

handle.read_to_end(&mut buf).map_err(|err| {
UndeleteError::StdIO(format!(
"Couldn't read into buffer for file {}: {:?}",
entry.name, err
))
})?;
if !args.dry_run && chosen.is_empty() {
return Err(errors::Error::Any {
detail: "No files selected".to_string(),
});
}

if args.dry_run {
info!("Would write '{}' to disk", new_path.display());
continue;
}
let mut errors = vec![];

if std::fs::metadata(new_path.parent().unwrap()).is_err() {
std::fs::create_dir_all(new_path.parent().unwrap()).map_err(|err| {
UndeleteError::General(format!(
"Couldn't create parent directories for full path '{}': {:?}",
new_path.display(),
err,
))
})?;
}
for i in chosen {
let undelete_entry = &found[i];
let total_output_dir = args
.output_dir
.join(undelete_entry.filename.replace(['[', ']'], ""));

match std::fs::write(&new_path, buf) {
Ok(_) => info!("Successfully wrote '{}' to disk", new_path.display()),
Err(e) => return Err(UndeleteError::Write(e.to_string())),
if args.dry_run {
info!("Would write to {}", total_output_dir.display());
continue;
}
}

Ok(())
}
let entry = parser.get_entry(undelete_entry.record_number)?;

fn get_all_unallocated_files_from_dir(
dir: &str,
tskfs: &TskFs,
) -> Result<Vec<UndeleteEntry>, UndeleteError> {
let mut current_entries = vec![];

info!("Running getting unallocated for {}", dir);

for item in TskFsDir::from_path(tskfs, dir)
.map_err(|err| {
UndeleteError::General(format!(
"Recursive getting files failed for path '{}': {:?}",
dir, err
))
})?
.get_name_iter()
{
if item.is_dir()
&& item.name().unwrap() != "."
&& item.name().unwrap() != ".."
&& !item.name().unwrap().starts_with('$')
{
current_entries.extend(get_all_unallocated_files_from_dir(
&format!(
"{}/{}",
match dir {
"/" => "",
rest => rest,
},
&item.name().unwrap()
),
tskfs,
)?);
continue;
if std::fs::metadata(total_output_dir.parent().unwrap()).is_err() {
if let Err(e) = std::fs::create_dir_all(total_output_dir.parent().unwrap()) {
errors.push(e);
continue;
};
}

if item.is_allocated() || item.name().unwrap() == "." || item.name().unwrap() == ".." {
if let Err(e) = std::fs::write(
total_output_dir.as_path(),
reader.read_data_from_entry(entry)?,
) {
errors.push(e);
continue;
}
};
info!("Successfully written to {}", total_output_dir.display());
}

current_entries.push(UndeleteEntry {
name: item.name().unwrap(),
inode: item.get_inode(),
dir: dir.to_string(),
})
if !errors.is_empty() {
return Err(errors::Error::Any {
detail: format!("{} errors occurred", errors.len()),
});
}

Ok(current_entries)
Ok(())
}
Loading

0 comments on commit 3f16192

Please # to comment.