Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

53 stack trace error in evtxanalyze pstree function #56

Merged
merged 9 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 55 additions & 69 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dfir-toolkit"
version = "0.11.1"
version = "0.11.2"
edition = "2021"
authors = ["Jan Starke <jan.starke@posteo.de>", "Deborah Mahn <deborah.mahn@dfir-dd.de>"]
description = "CLI tools for digital forensics and incident response"
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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}
Expand Down
34 changes: 31 additions & 3 deletions src/bin/evtxanalyze/cli.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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}"),
Expand All @@ -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());
Expand All @@ -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()
}
}
}
20 changes: 18 additions & 2 deletions src/bin/evtxanalyze/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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(())
}
68 changes: 50 additions & 18 deletions src/bin/evtxanalyze/pstree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pub mod unique_pid;
use std::{
cell::RefCell,
collections::{BTreeMap, HashMap, HashSet},
io::stdout,
ops::Deref,
rc::{Rc, Weak},
};

Expand All @@ -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;

Expand All @@ -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()
Expand All @@ -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());

Expand Down Expand Up @@ -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
Expand Down
114 changes: 88 additions & 26 deletions src/bin/evtxanalyze/pstree/process.rs
Original file line number Diff line number Diff line change
@@ -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<Utc>,
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<String>,
pub (crate) mandatory_label: Option<String>,
pub (crate) children: BTreeMap<DateTime<Utc>, Weak<RefCell<Self>>>,
pub (crate) is_root: bool,
use serde::Serialize;
use serde_json::{json, Value};

pub(crate) struct Process {
pub(crate) timestamp: DateTime<Utc>,
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<String>,
pub(crate) mandatory_label: Option<String>,
pub(crate) children: BTreeMap<DateTime<Utc>, Weak<RefCell<Self>>>,
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<String>,
}

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<H: std::hash::Hasher>(&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 {
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 8 additions & 2 deletions src/bin/evtxanalyze/sessions/session_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading
Loading