diff --git a/Cargo.lock b/Cargo.lock index 5520df2..6320020 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -852,6 +852,7 @@ dependencies = [ "time", "tokio", "tokio-async-drop", + "walkdir", "winstructs", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index 4e5b4b3..4d35620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", "exitcode"] +evtxanalyze = ["evtx", "dfirtk-sessionevent-derive", "dfirtk-eventdata", "exitcode", "walkdir"] evtx2bodyfile = ["evtx", "getset", "ouroboros", "indicatif"] ipgrep = [] ts2date = ["regex"] @@ -162,6 +162,7 @@ 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} +walkdir = {version="2.5.0", optional=true} # evtx2bodyfile indicatif = {version="0.17", optional=true} diff --git a/src/bin/evtxanalyze/cli.rs b/src/bin/evtxanalyze/cli.rs index 2cc7a5c..fccd342 100644 --- a/src/bin/evtxanalyze/cli.rs +++ b/src/bin/evtxanalyze/cli.rs @@ -1,6 +1,5 @@ use std::{io::stdout, path::PathBuf}; -use anyhow::bail; use clap::{Parser, Subcommand, ValueEnum, ValueHint}; use dfir_toolkit::common::HasVerboseFlag; use log::LevelFilter; @@ -83,20 +82,6 @@ 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}"), @@ -120,20 +105,6 @@ 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()); diff --git a/src/bin/evtxanalyze/sessions/session_store.rs b/src/bin/evtxanalyze/sessions/session_store.rs index 0791345..db137c6 100644 --- a/src/bin/evtxanalyze/sessions/session_store.rs +++ b/src/bin/evtxanalyze/sessions/session_store.rs @@ -1,15 +1,17 @@ use std::{collections::HashMap, path::Path}; +use anyhow::bail; use dfirtk_eventdata::SessionId; use evtx::EvtxParser; +use walkdir::WalkDir; use super::{Session, SessionEvent}; -static KNOWN_FILES: & [&str] = &[ +static KNOWN_FILES: &[&str] = &[ "Security.evtx", "Microsoft-Windows-TerminalServices-RDPClient%4Operational.evtx", "Microsoft-Windows-TerminalServices-RemoteConnectionManager%4Operational.evtx", - "Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx" + "Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx", ]; pub struct SessionStore { @@ -18,21 +20,59 @@ pub struct SessionStore { impl SessionStore { pub fn import(evtx_files_dir: &Path, include_anonymous: bool) -> Result { + 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 mut sessions = Self { sessions: HashMap::::new(), }; - for filename in KNOWN_FILES { - let path = evtx_files_dir.join(filename); - - if ! path.exists() { - log::error!("expected file '{}', but it does not exist. Omitting it...", path.display()); - continue; + let mut path = evtx_files_dir.join(filename); + + // maybe we have troubles with case sensitivity + // Let's try this: + if !path.exists() { + let mut files = WalkDir::new(evtx_files_dir) + .max_depth(1) + .into_iter() + .filter_map(Result::ok) + .filter(|f| f.file_name().to_string_lossy().to_lowercase() == filename.to_lowercase()); + + if let Some(first_entry) = files.next() { + path = first_entry.into_path(); + + // there should be no more entry, otherwise + // the filename is unambigious + if let Some(next_entry) = files.next() { + log::error!( + "expected file '{filename}', but there exist \ + multiple variants of this name. I found at least \ + '{}' and '{}'. Omitting those files...", + path.file_name().unwrap().to_string_lossy(), + next_entry.file_name().to_string_lossy() + ); + continue; + } + } } - if ! path.is_file() { - log::error!("tried to read '{}', but it is not a file. Omiting it...", path.display()); + if !path.is_file() { + log::error!( + "tried to read '{}', but it is not a file. Omiting it...", + path.display() + ); continue; } @@ -76,19 +116,16 @@ impl SessionStore { } pub fn find_session(&self, index: &str) -> Option<&Session> { - self.sessions.iter().find(|(k, _)| { - match k { - SessionId::ActivityId(id)| - SessionId::SessionName(id)| - SessionId::LogonId(id) | - SessionId::SessionId(id) => { - index == id - } - SessionId::None(id) => { - index == id.to_string() - }, - } - }).map(|(_, v)| v) + self.sessions + .iter() + .find(|(k, _)| match k { + SessionId::ActivityId(id) + | SessionId::SessionName(id) + | SessionId::LogonId(id) + | SessionId::SessionId(id) => index == id, + SessionId::None(id) => index == id.to_string(), + }) + .map(|(_, v)| v) } } @@ -102,4 +139,4 @@ impl IntoIterator for SessionStore { v.sort(); v.into_iter() } -} \ No newline at end of file +}