diff --git a/Cargo.lock b/Cargo.lock index 866b016..8ea5a59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ "flate2", "futures-core", @@ -178,17 +178,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.3.0" @@ -375,9 +364,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", "libc", @@ -496,7 +485,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -538,7 +527,7 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -738,7 +727,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -760,14 +749,14 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] name = "deflate64" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" [[package]] name = "deranged" @@ -797,12 +786,12 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] name = "dfir-toolkit" -version = "0.11.1" +version = "0.11.2" dependencies = [ "anyhow", "assert-json-diff", @@ -828,6 +817,7 @@ dependencies = [ "encoding_rs", "encoding_rs_io", "evtx", + "exitcode", "flate2", "forensic-rs", "frnsc-prefetch", @@ -891,7 +881,7 @@ dependencies = [ "dfirtk-eventdata", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -932,7 +922,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1123,6 +1113,12 @@ dependencies = [ "winstructs", ] +[[package]] +name = "exitcode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" + [[package]] name = "fastrand" version = "2.1.0" @@ -1244,7 +1240,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1363,15 +1359,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -1556,7 +1543,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", "windows-sys 0.52.0", ] @@ -1620,7 +1607,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1784,9 +1771,9 @@ dependencies = [ [[package]] name = "nt_hive2" -version = "4.2.2" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8ca887a8de781cf30bf9398bd8775d729b18617379ea02e6b10e4ca3ee24a2" +checksum = "ae8c624d54324d957e912a85352be5098202234bff95c351b66877f0d2d89634" dependencies = [ "anyhow", "binread", @@ -1864,7 +1851,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -1913,7 +1900,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -1970,7 +1957,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2013,7 +2000,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2141,9 +2128,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "powerfmt" @@ -2231,7 +2218,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "version_check", "yansi 1.0.1", ] @@ -2317,9 +2304,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -2532,7 +2519,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2726,7 +2713,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2748,9 +2735,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.71" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -2828,11 +2815,10 @@ dependencies = [ [[package]] name = "termsize" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b7ef97a4926a63d0c1751922fe1339556c68ab2c4ac791c3b1cff0dc7c6c67" +checksum = "6f11ff5c25c172608d5b85e2fb43ee9a6d683a7f4ab7f96ae07b3d8b590368fd" dependencies = [ - "atty", "libc", "winapi", ] @@ -2845,22 +2831,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -2913,9 +2899,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", @@ -2944,7 +2930,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3133,7 +3119,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -3167,7 +3153,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3480,7 +3466,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] @@ -3500,14 +3486,14 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.72", ] [[package]] name = "zip" -version = "2.1.3" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +checksum = "b895748a3ebcb69b9d38dcfdf21760859a4b0d0b0015277640c2ef4c69640e6f" dependencies = [ "aes", "arbitrary", diff --git a/Cargo.toml b/Cargo.toml index 6a99572..5a853a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dfir-toolkit" -version = "0.11.1" +version = "0.11.2" edition = "2021" authors = ["Jan Starke ", "Deborah Mahn "] description = "CLI tools for digital forensics and incident response" @@ -106,7 +106,7 @@ pol_export = [] evtxscan = ["evtx"] evtxcat = ["evtx", "colored_json", "term-table", "termsize"] evtxls = ["evtx", "colored", "lazy-regex", "regex", "sigpipe", "dfirtk-eventdata"] -evtxanalyze = ["evtx", "dfirtk-sessionevent-derive", "dfirtk-eventdata"] +evtxanalyze = ["evtx", "dfirtk-sessionevent-derive", "dfirtk-eventdata", "exitcode"] evtx2bodyfile = ["evtx", "getset", "ouroboros", "indicatif"] ipgrep = [] ts2date = ["regex"] @@ -161,6 +161,7 @@ colored = {version = "2", optional=true} lazy-regex = {version = "3.0.0", optional=true} sigpipe = {version = "0", optional=true} phf = {version = "0.11", optional=true} +exitcode = {version="1.1.2", optional=true} # evtx2bodyfile indicatif = {version="0.17", optional=true} diff --git a/src/bin/evtxanalyze/cli.rs b/src/bin/evtxanalyze/cli.rs index f86e2d9..2cc7a5c 100644 --- a/src/bin/evtxanalyze/cli.rs +++ b/src/bin/evtxanalyze/cli.rs @@ -1,5 +1,6 @@ use std::{io::stdout, path::PathBuf}; +use anyhow::bail; use clap::{Parser, Subcommand, ValueEnum, ValueHint}; use dfir_toolkit::common::HasVerboseFlag; use log::LevelFilter; @@ -82,6 +83,20 @@ impl Cli { evtx_files_dir, session_id, } => { + if !evtx_files_dir.exists() { + bail!( + "directory '{}' does not exist. Aborting now.", + evtx_files_dir.to_string_lossy() + ) + } + + if !evtx_files_dir.is_dir() { + bail!( + "'{}' is no directory. Aborting now.", + evtx_files_dir.to_string_lossy() + ); + } + let sessions = SessionStore::import(evtx_files_dir, true)?; match sessions.find_session(session_id) { None => log::error!("no value found for session id {session_id}"), @@ -105,6 +120,20 @@ impl Cli { evtx_files_dir, include_anonymous, } => { + if !evtx_files_dir.exists() { + bail!( + "directory '{}' does not exist. Aborting now.", + evtx_files_dir.to_string_lossy() + ) + } + + if !evtx_files_dir.is_dir() { + bail!( + "'{}' is no directory. Aborting now.", + evtx_files_dir.to_string_lossy() + ); + } + let sessions = SessionStore::import(evtx_files_dir, *include_anonymous)?; let mut csv_writer = csv::Writer::from_writer(stdout()); @@ -119,9 +148,8 @@ impl Cli { } } - impl HasVerboseFlag for Cli { - fn log_level_filter(&self)-> LevelFilter { + fn log_level_filter(&self) -> LevelFilter { self.verbose.log_level_filter() } -} \ No newline at end of file +} diff --git a/src/bin/evtxanalyze/main.rs b/src/bin/evtxanalyze/main.rs index 923d1d5..d6a3e9b 100644 --- a/src/bin/evtxanalyze/main.rs +++ b/src/bin/evtxanalyze/main.rs @@ -1,6 +1,7 @@ use cli::{Cli, Command}; -use pstree::display_pstree; use dfir_toolkit::common::FancyParser; +use log::log_enabled; +use pstree::display_pstree; mod cli; mod pstree; @@ -9,10 +10,25 @@ mod sessions; fn main() -> anyhow::Result<()> { let cli = Cli::parse_cli(); - match &cli.command { + let result = match &cli.command { //TODO: move `display_pstree` into `impl Cli` Command::PsTree { .. } => display_pstree(&cli), Command::Sessions { .. } => cli.display_sessions(), Command::Session { .. } => cli.display_single_session(), + }; + + if let Err(why) = result { + log::error!("{why}"); + if let Some(cause) = why.source() { + log::error!("caused by: {cause}"); + } + if log_enabled!(log::Level::Warn) { + for line in format!("{}", why.backtrace()).lines() { + log::warn!("{line}"); + } + } + std::process::exit(exitcode::DATAERR); } + + Ok(()) } diff --git a/src/bin/evtxanalyze/pstree/mod.rs b/src/bin/evtxanalyze/pstree/mod.rs index 6ca0a7e..ad2dec0 100644 --- a/src/bin/evtxanalyze/pstree/mod.rs +++ b/src/bin/evtxanalyze/pstree/mod.rs @@ -3,6 +3,8 @@ pub mod unique_pid; use std::{ cell::RefCell, collections::{BTreeMap, HashMap, HashSet}, + io::stdout, + ops::Deref, rc::{Rc, Weak}, }; @@ -12,7 +14,10 @@ pub(crate) use process::*; use regex::Regex; use serde_json::{json, Value}; -use crate::{cli::{Command, Format}, pstree::unique_pid::UniquePid}; +use crate::{ + cli::{Command, Format}, + pstree::unique_pid::UniquePid, +}; use super::Cli; @@ -21,7 +26,7 @@ pub(crate) fn display_pstree(cli: &Cli) -> anyhow::Result<()> { Command::PsTree { username, evtx_file, - format + format, } => { let username_regex = username .as_ref() @@ -37,21 +42,39 @@ pub(crate) fn display_pstree(cli: &Cli) -> anyhow::Result<()> { let mut parser = EvtxParser::from_path(evtx_file)?; let mut unique_pids = HashMap::new(); - let events: HashMap<_, _> = parser - .records_json_value() - .map(|r| r.expect("error reading event")) - .map(Process::try_from) - .filter_map(|r| r.expect("invalid event")) - .filter(has_username) - .map(|e| { - let pid = UniquePid::from(&e); - unique_pids - .entry(e.new_process_id) - .or_insert_with(HashSet::new) - .insert(pid.clone()); - (pid, Rc::new(RefCell::new(e))) - }) - .collect(); + let mut events = HashMap::new(); + let mut handled_records = 0; + let mut expected_records: usize = 0; + for record in parser.records_json_value() { + expected_records += 1; + match record { + Err(why) => { + log::error!("error while parsing a record; read {handled_records} until now. I'll try to continue with the next record"); + log::warn!("{why}") + } + Ok(record) => match Process::try_from(record) { + Err(why) => log::error!("{why}"), + Ok(Some(process)) => { + if has_username(&process) { + let pid = UniquePid::from(&process); + unique_pids + .entry(process.new_process_id) + .or_insert_with(HashSet::new) + .insert(pid.clone()); + events.insert(pid, Rc::new(RefCell::new(process))); + } + handled_records += 1; + } + Ok(None) => handled_records += 1, + }, + } + } + + log::info!("finished reading all records"); + + if handled_records < expected_records { + log::warn!("I expected {expected_records}, but only {handled_records} could be handled.") + } log::warn!("found {} process creations", events.len()); @@ -110,7 +133,16 @@ pub(crate) fn display_pstree(cli: &Cli) -> anyhow::Result<()> { println!("{}", serde_json::to_string_pretty(&procs_as_json)?); } - Format::Csv => unimplemented!(), + Format::Csv => { + let mut wtr = csv::Writer::from_writer(stdout().lock()); + let processes: HashSet<_> = events + .values() + .map(|p| ProcessTableEntry::from(p.as_ref().borrow().deref())) + .collect(); + for process in processes { + wtr.serialize(process)?; + } + } Format::Markdown => { let root_processes: BTreeMap<_, _> = events diff --git a/src/bin/evtxanalyze/pstree/process.rs b/src/bin/evtxanalyze/pstree/process.rs index 4c696c1..d61e821 100644 --- a/src/bin/evtxanalyze/pstree/process.rs +++ b/src/bin/evtxanalyze/pstree/process.rs @@ -1,31 +1,93 @@ -use std::{collections::{BTreeMap, HashMap}, rc::Weak, cell::RefCell, fmt::Display}; +use std::{ + cell::RefCell, + collections::{BTreeMap, HashMap}, + fmt::Display, + hash::Hash, + rc::Weak, +}; use anyhow::bail; use chrono::{DateTime, Utc}; use evtx::SerializedEvtxRecord; -use serde_json::{Value, json}; - - -pub (crate) struct Process { - pub (crate) timestamp: DateTime, - pub (crate) event_record_id: u64, - pub (crate) subject_user_sid: String, - pub (crate) subject_user_name: String, - pub (crate) subject_domain_name: String, - pub (crate) subject_logon_id: String, - pub (crate) new_process_id: u64, - pub (crate) new_process_name: String, - pub (crate) token_elevation_type: String, - pub (crate) process_id: u64, - pub (crate) command_line: String, - pub (crate) target_user_sid: String, - pub (crate) target_user_name: String, - pub (crate) target_domain_name: String, - pub (crate) target_logon_id: String, - pub (crate) parent_process_name: Option, - pub (crate) mandatory_label: Option, - pub (crate) children: BTreeMap, Weak>>, - pub (crate) is_root: bool, +use serde::Serialize; +use serde_json::{json, Value}; + +pub(crate) struct Process { + pub(crate) timestamp: DateTime, + pub(crate) event_record_id: u64, + pub(crate) subject_user_sid: String, + pub(crate) subject_user_name: String, + pub(crate) subject_domain_name: String, + pub(crate) subject_logon_id: String, + pub(crate) new_process_id: u64, + pub(crate) new_process_name: String, + pub(crate) token_elevation_type: String, + pub(crate) process_id: u64, + pub(crate) command_line: String, + pub(crate) target_user_sid: String, + pub(crate) target_user_name: String, + pub(crate) target_domain_name: String, + pub(crate) target_logon_id: String, + pub(crate) parent_process_name: Option, + pub(crate) mandatory_label: Option, + pub(crate) children: BTreeMap, Weak>>, + pub(crate) is_root: bool, +} + +#[derive(Serialize, Clone)] +pub(crate) struct ProcessTableEntry { + pub(crate) subject_user_sid: String, + pub(crate) subject_user_name: String, + pub(crate) subject_domain_name: String, + pub(crate) subject_logon_id: String, + pub(crate) new_process_id: u64, + pub(crate) new_process_name: String, + pub(crate) token_elevation_type: String, + pub(crate) process_id: u64, + pub(crate) command_line: String, + pub(crate) target_user_sid: String, + pub(crate) target_user_name: String, + pub(crate) target_domain_name: String, + pub(crate) target_logon_id: String, + pub(crate) parent_process_name: Option, +} + +impl From<&Process> for ProcessTableEntry { + fn from(value: &Process) -> Self { + Self { + subject_user_sid: value.subject_user_sid.clone(), + subject_user_name: value.subject_user_name.clone(), + subject_domain_name: value.subject_domain_name.clone(), + subject_logon_id: value.subject_logon_id.clone(), + new_process_id: value.new_process_id, + new_process_name: value.new_process_name.clone(), + token_elevation_type: value.token_elevation_type.clone(), + process_id: value.process_id, + command_line: value.command_line.clone(), + target_user_sid: value.target_user_sid.clone(), + target_user_name: value.target_user_name.clone(), + target_domain_name: value.target_domain_name.clone(), + target_logon_id: value.target_logon_id.clone(), + parent_process_name: value.parent_process_name.clone(), + } + } +} + +impl Hash for ProcessTableEntry { + fn hash(&self, state: &mut H) { + self.process_id.hash(state); + self.command_line.hash(state); + self.parent_process_name.hash(state); + } +} + +impl Eq for ProcessTableEntry {} +impl PartialEq for ProcessTableEntry { + fn eq(&self, other: &Self) -> bool { + self.process_id == other.process_id + && self.command_line == other.command_line + && self.parent_process_name == other.parent_process_name + } } impl From<&Process> for Value { @@ -207,10 +269,10 @@ impl Process { .into(); let parent_process_name = from_json_or_null!(event_data, "ParentProcessName") .as_str() - .map(|s|s.to_owned()); + .map(|s| s.to_owned()); let mandatory_label = from_json_or_null!(event_data, "MandatoryLabel") .as_str() - .map(|s|s.to_owned()); + .map(|s| s.to_owned()); Ok(Some(Self { timestamp: record.timestamp, diff --git a/src/bin/evtxanalyze/sessions/session_store.rs b/src/bin/evtxanalyze/sessions/session_store.rs index 865acf7..0791345 100644 --- a/src/bin/evtxanalyze/sessions/session_store.rs +++ b/src/bin/evtxanalyze/sessions/session_store.rs @@ -25,8 +25,14 @@ impl SessionStore { for filename in KNOWN_FILES { let path = evtx_files_dir.join(filename); - if !(path.exists() && path.is_file()) { - log::warn!("unable to read file {}", path.display()); + + if ! path.exists() { + log::error!("expected file '{}', but it does not exist. Omitting it...", path.display()); + continue; + } + + if ! path.is_file() { + log::error!("tried to read '{}', but it is not a file. Omiting it...", path.display()); continue; } diff --git a/src/bin/evtxls/main.rs b/src/bin/evtxls/main.rs index 9dfab95..cff5a7d 100644 --- a/src/bin/evtxls/main.rs +++ b/src/bin/evtxls/main.rs @@ -76,11 +76,18 @@ impl EvtxLs { }; let mut records = Vec::new(); + let mut handled_records = 0; + let mut expected_records: usize = 0; for result in parser.records_json_value() { + expected_records += 1; match result { - Err(_) => (), + Err(why) => { + log::error!("error while parsing a record; read {handled_records} until now. I'll try to continue with the next record"); + log::warn!("{why}") + } Ok(record) => { + handled_records += 1; if let Some(not_before) = self.cli.not_before.as_ref() { if &record.timestamp < not_before { continue; @@ -116,6 +123,10 @@ impl EvtxLs { } } + if handled_records < expected_records { + log::warn!("I expected {expected_records}, but only {handled_records} could be handled.") + } + Ok(records) } diff --git a/src/bin/evtxscan/main.rs b/src/bin/evtxscan/main.rs index 95d9c9a..ef2836a 100644 --- a/src/bin/evtxscan/main.rs +++ b/src/bin/evtxscan/main.rs @@ -3,11 +3,15 @@ use std::{collections::HashMap, path::PathBuf}; use anyhow::Result; use chrono::Duration; use cli::Cli; -use dfir_toolkit::common::FancyParser; use colored_json::to_colored_json_auto; +use dfir_toolkit::common::FancyParser; use dfir_toolkit::evtx::{EventId, Range}; use evtx::{EvtxParser, SerializedEvtxRecord}; -use term_table::{row::Row, table_cell::TableCell}; +use term_table::row; +use term_table::{ + row::Row, + table_cell::{Alignment, TableCell}, +}; mod cli; @@ -66,23 +70,19 @@ fn print_ranges( let mut table = term_table::Table::new(); if let Some(size) = termsize::get() { table.set_max_column_widths(vec![ - (0, (size.cols / 2).into()), - (1, (size.cols / 2).into()), + (0, ((size.cols-3) / 2).into()), + (1, ((size.cols-3) / 2).into()), ]) } - table.add_row(Row::new(vec![ - TableCell::new_with_alignment( - range.begin().timestamp().format("%FT%T"), - 1, - term_table::table_cell::Alignment::Center, - ), - TableCell::new_with_alignment( - range.end().timestamp().format("%FT%T"), - 1, - term_table::table_cell::Alignment::Center, - ), - ])); + table.add_row(row!( + TableCell::builder(range.begin().timestamp().format("%FT%T")) + .alignment(Alignment::Center) + .col_span(1), + TableCell::builder(range.end().timestamp().format("%FT%T")) + .alignment(Alignment::Center) + .col_span(1) + )); let first_record = &records[range.begin()]; let last_record = &records[range.end()]; diff --git a/src/common/forensics_timestamp.rs b/src/common/forensics_timestamp.rs index f9c1c45..4c88285 100644 --- a/src/common/forensics_timestamp.rs +++ b/src/common/forensics_timestamp.rs @@ -90,6 +90,14 @@ impl Display for ForensicsTimestamp { } } +impl Serialize for ForensicsTimestamp { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + serializer.serialize_str(&format!("{self}")) + } +} + #[cfg(test)] mod tests { use chrono_tz::{Europe, UTC}; @@ -105,11 +113,3 @@ mod tests { assert_eq!(ts.to_string(), "2024-05-16T07:45:46+00:00"); } } - -impl Serialize for ForensicsTimestamp { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer { - serializer.serialize_str(&format!("{self}")) - } -}