-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Parsing corrections and removing obsolete dependancies (#18)
* implemented first part of mft parsing * corrected mft parsing * added picking files * finished refactoring * fixed clippy
- Loading branch information
1 parent
410dbdd
commit 3f16192
Showing
9 changed files
with
930 additions
and
330 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
Oops, something went wrong.