diff --git a/Cargo.lock b/Cargo.lock index 75ebced..03bd81c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -702,7 +702,7 @@ dependencies = [ [[package]] name = "dfir-toolkit" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anyhow", "assert-json-diff", @@ -732,6 +732,7 @@ dependencies = [ "getset", "indicatif", "lazy-regex", + "lazy_static", "lnk", "log", "matches", diff --git a/Cargo.toml b/Cargo.toml index 29b93cb..51086e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dfir-toolkit" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = ["Jan Starke ", "Deborah Mahn "] description = "CLI tools for digital forensics and incident response" @@ -113,6 +113,7 @@ log = {version = "0.4", features = [ "release_max_level_info" ]} serde = { version = "1.0", features = ["derive"] } simplelog = "0.12" winstructs = "0.3.0" +lazy_static = "1.4" regex = {version = "1", optional=true} clap-markdown = "0.1.3" diff --git a/README.md b/README.md index d142116..d607b49 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,39 @@ mactime2 --autocomplete bash | sudo tee /etc/bash_completion.d/mactime2 would install a autocompletion script in `/etc/bash_completion.d/mactime2`. +# Usage + +## Configuring the global timestamp format + +Per default, the DFIR toolkit uses an RFC3339-compliant data format. If you want to, you can change the data format +being used by setting the `DFIR_DATE` environment variable. Let's look at an example: + +```shell +$ mac2time2 -b tests/data/mactime2/sample.bodyfile -d | head +1970-01-01T00:00:00+00:00,0,macb,V/V---------,0,0,62447617,"/$OrphanFiles" +2022-04-18T10:28:59+00:00,4096,m...,d/drwxr-xr-x,0,0,42729473,"/proc" +2022-04-18T10:28:59+00:00,4096,m...,d/drwxr-xr-x,0,0,36306945,"/sys" +2022-04-21T00:57:50+00:00,7,m...,l/lrwxrwxrwx,0,0,12,"/bin -> usr/bin" +2022-04-21T00:57:50+00:00,7,m...,l/lrwxrwxrwx,0,0,13,"/lib -> usr/lib" +2022-04-21T00:57:50+00:00,9,m...,l/lrwxrwxrwx,0,0,14,"/lib32 -> usr/lib32" +2022-04-21T00:57:50+00:00,9,m...,l/lrwxrwxrwx,0,0,15,"/lib64 -> usr/lib64" +2022-04-21T00:57:50+00:00,10,m...,l/lrwxrwxrwx,0,0,16,"/libx32 -> usr/libx32" +2022-04-21T00:57:50+00:00,8,m...,l/lrwxrwxrwx,0,0,17,"/sbin -> usr/sbin" +2022-04-21T00:57:51+00:00,4096,m...,d/drwxr-xr-x,0,0,38010881,"/srv" +``` + +```shell +$ DFIR_DATE="%F %T (%Z)" mac2time2 -b tests/data/mactime2/sample.bodyfile -d | head +1970-01-01 00:00:00 (UTC),0,macb,V/V---------,0,0,62447617,"/$OrphanFiles" +2022-04-18 10:28:59 (UTC),4096,m...,d/drwxr-xr-x,0,0,42729473,"/proc" +2022-04-18 10:28:59 (UTC),4096,m...,d/drwxr-xr-x,0,0,36306945,"/sys" +2022-04-21 00:57:50 (UTC),7,m...,l/lrwxrwxrwx,0,0,12,"/bin -> usr/bin" +2022-04-21 00:57:50 (UTC),7,m...,l/lrwxrwxrwx,0,0,13,"/lib -> usr/lib" +2022-04-21 00:57:50 (UTC),9,m...,l/lrwxrwxrwx,0,0,14,"/lib32 -> usr/lib32" +2022-04-21 00:57:50 (UTC),9,m...,l/lrwxrwxrwx,0,0,15,"/lib64 -> usr/lib64" +2022-04-21 00:57:50 (UTC),10,m...,l/lrwxrwxrwx,0,0,16,"/libx32 -> usr/libx32" +2022-04-21 00:57:50 (UTC),8,m...,l/lrwxrwxrwx,0,0,17,"/sbin -> usr/sbin" +2022-04-21 00:57:51 (UTC),4096,m...,d/drwxr-xr-x,0,0,38010881,"/srv" +``` + +The value of `DFIR_DATE` can be any format string which can also be used in `DateTime::strftime` () \ No newline at end of file diff --git a/scripts/update-md.sh b/scripts/update-md.sh index e4be5a4..8bdb537 100755 --- a/scripts/update-md.sh +++ b/scripts/update-md.sh @@ -21,7 +21,7 @@ cat >README.md <<'EOF' - [Tools](#tools) - [x] [`cleanhive`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/cleanhive.md) - [x] [`evtx2bodyfile`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/evtx2bodyfile.md) - - [x] [`evtxanalyze`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/avtxanalyze.md) + - [x] [`evtxanalyze`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/evtxanalyze.md) - [x] [`evtxscan`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/evtxscan.md) - [x] [`evtxcat`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/evtxcat.md) - [x] [`evtxls`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/evtxls.md) @@ -58,8 +58,46 @@ mactime2 --autocomplete bash | sudo tee /etc/bash_completion.d/mactime2 would install a autocompletion script in `/etc/bash_completion.d/mactime2`. +# Usage + +## Configuring the global timestamp format + +Per default, the DFIR toolkit uses an RFC3339-compliant data format. If you want to, you can change the data format +being used by setting the `DFIR_DATE` environment variable. Let's look at an example: + +```shell +$ mac2time2 -b tests/data/mactime2/sample.bodyfile -d | head +1970-01-01T00:00:00+00:00,0,macb,V/V---------,0,0,62447617,"/$OrphanFiles" +2022-04-18T10:28:59+00:00,4096,m...,d/drwxr-xr-x,0,0,42729473,"/proc" +2022-04-18T10:28:59+00:00,4096,m...,d/drwxr-xr-x,0,0,36306945,"/sys" +2022-04-21T00:57:50+00:00,7,m...,l/lrwxrwxrwx,0,0,12,"/bin -> usr/bin" +2022-04-21T00:57:50+00:00,7,m...,l/lrwxrwxrwx,0,0,13,"/lib -> usr/lib" +2022-04-21T00:57:50+00:00,9,m...,l/lrwxrwxrwx,0,0,14,"/lib32 -> usr/lib32" +2022-04-21T00:57:50+00:00,9,m...,l/lrwxrwxrwx,0,0,15,"/lib64 -> usr/lib64" +2022-04-21T00:57:50+00:00,10,m...,l/lrwxrwxrwx,0,0,16,"/libx32 -> usr/libx32" +2022-04-21T00:57:50+00:00,8,m...,l/lrwxrwxrwx,0,0,17,"/sbin -> usr/sbin" +2022-04-21T00:57:51+00:00,4096,m...,d/drwxr-xr-x,0,0,38010881,"/srv" +``` + +```shell +$ DFIR_DATE="%F %T (%Z)" mac2time2 -b tests/data/mactime2/sample.bodyfile -d | head +1970-01-01 00:00:00 (UTC),0,macb,V/V---------,0,0,62447617,"/$OrphanFiles" +2022-04-18 10:28:59 (UTC),4096,m...,d/drwxr-xr-x,0,0,42729473,"/proc" +2022-04-18 10:28:59 (UTC),4096,m...,d/drwxr-xr-x,0,0,36306945,"/sys" +2022-04-21 00:57:50 (UTC),7,m...,l/lrwxrwxrwx,0,0,12,"/bin -> usr/bin" +2022-04-21 00:57:50 (UTC),7,m...,l/lrwxrwxrwx,0,0,13,"/lib -> usr/lib" +2022-04-21 00:57:50 (UTC),9,m...,l/lrwxrwxrwx,0,0,14,"/lib32 -> usr/lib32" +2022-04-21 00:57:50 (UTC),9,m...,l/lrwxrwxrwx,0,0,15,"/lib64 -> usr/lib64" +2022-04-21 00:57:50 (UTC),10,m...,l/lrwxrwxrwx,0,0,16,"/libx32 -> usr/libx32" +2022-04-21 00:57:50 (UTC),8,m...,l/lrwxrwxrwx,0,0,17,"/sbin -> usr/sbin" +2022-04-21 00:57:51 (UTC),4096,m...,d/drwxr-xr-x,0,0,38010881,"/srv" +``` + +The value of `DFIR_DATE` can be any format string which can also be used in `DateTime::strftime` () + + EOF for B in $(cd src/bin; echo *); do cargo run --bin $B -- --markdown-help >>doc/$B.md -done \ No newline at end of file +done diff --git a/src/bin/evtxanalyze/pstree/unique_pid.rs b/src/bin/evtxanalyze/pstree/unique_pid.rs index 8d29d30..f03794d 100644 --- a/src/bin/evtxanalyze/pstree/unique_pid.rs +++ b/src/bin/evtxanalyze/pstree/unique_pid.rs @@ -37,7 +37,7 @@ impl Ord for UniquePid { } } -#[allow(clippy::incorrect_partial_ord_impl_on_ord_type)] +#[allow(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for UniquePid { fn partial_cmp(&self, other: &Self) -> Option { if self.pid != other.pid { diff --git a/src/bin/evtxanalyze/sessions/mod.rs b/src/bin/evtxanalyze/sessions/mod.rs index c65b6bb..1ba0f87 100644 --- a/src/bin/evtxanalyze/sessions/mod.rs +++ b/src/bin/evtxanalyze/sessions/mod.rs @@ -10,7 +10,6 @@ mod active_directory_domain_name; pub use session::*; pub use session_store::*; pub use session_event::*; -pub use session_event_templates::*; pub use session_event_error::*; pub use session_as_json::*; pub use session_as_csv::*; diff --git a/src/bin/evtxcat/main.rs b/src/bin/evtxcat/main.rs index 4a2b510..46e44b9 100644 --- a/src/bin/evtxcat/main.rs +++ b/src/bin/evtxcat/main.rs @@ -19,7 +19,7 @@ use record_list_formatter::RecordListFormatter; fn main() -> Result<()> { let cli = Cli::parse_cli(); - let path = PathBuf::try_from(&cli.evtx_file)?; + let path = PathBuf::from(&cli.evtx_file); let parser = EvtxParser::from_path(path)?; diff --git a/src/bin/evtxls/main.rs b/src/bin/evtxls/main.rs index a278074..6f52691 100644 --- a/src/bin/evtxls/main.rs +++ b/src/bin/evtxls/main.rs @@ -16,7 +16,7 @@ use evtx::{EvtxParser, ParserSettings, SerializedEvtxRecord}; use highlighted_string::HighlightedStringBuilder; use serde_json::Value; -use dfir_toolkit::common::FancyParser; +use dfir_toolkit::common::{FancyParser, FormattableDatetime}; use crate::system_field::FilterBySystemField; @@ -37,7 +37,7 @@ impl EvtxLs { let mut records = Vec::new(); for f_name in self.cli.evtx_files.iter() { - let path = PathBuf::try_from(&f_name)?; + let path = PathBuf::from(&f_name); let settings = ParserSettings::default().num_threads(0); let parser = EvtxParser::from_path(path)?.with_configuration(settings); @@ -145,18 +145,10 @@ impl EvtxLs { .unwrap_or_else(|| "".to_owned()) .replace("\\u001b", "\u{001b}"); - let output = match self.cli.delimiter { - None => format!( - "{} {system_fields}{event_data}", - record.timestamp.format("%FT%T%.3f") - ), - Some(d) => format!( - "{}{d}{system_fields}{event_data}", - record.timestamp.to_rfc3339() - ), - } - .normal(); + let timestamp = FormattableDatetime::from(&record.timestamp); + let delimiter = self.cli.delimiter.unwrap_or(' '); + let output=format!("{timestamp}{delimiter}{system_fields}{event_data}").normal(); println!("{output}"); Ok(()) diff --git a/src/bin/evtxscan/main.rs b/src/bin/evtxscan/main.rs index 23d72e7..95d9c9a 100644 --- a/src/bin/evtxscan/main.rs +++ b/src/bin/evtxscan/main.rs @@ -16,7 +16,7 @@ fn main() -> Result<()> { let mut record_ids: Vec = Vec::new(); let mut records: HashMap> = HashMap::new(); - let path = PathBuf::try_from(&cli.evtx_file)?; + let path = PathBuf::from(&cli.evtx_file); let mut parser = EvtxParser::from_path(path)?; for record in parser.records_json_value() { diff --git a/src/bin/regdump/main.rs b/src/bin/regdump/main.rs index 8c19f2d..5948cde 100644 --- a/src/bin/regdump/main.rs +++ b/src/bin/regdump/main.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Result}; use dfir_toolkit::common::bodyfile::Bodyfile3Line; -use dfir_toolkit::common::FancyParser; +use dfir_toolkit::common::{FancyParser, FormattableDatetime}; use nt_hive2::*; use simplelog::{Config, SimpleLogger}; use std::fs::File; @@ -80,7 +80,7 @@ where if cli.hide_timestamps { println!("\n[{}]", ¤t_path); } else { - println!("\n[{}]; {}", ¤t_path, keynode.timestamp()); + println!("\n[{}]; {}", ¤t_path, FormattableDatetime::from(keynode.timestamp())); } print_values(keynode); diff --git a/src/common/forensics_timestamp.rs b/src/common/forensics_timestamp.rs index da85748..94f9d04 100644 --- a/src/common/forensics_timestamp.rs +++ b/src/common/forensics_timestamp.rs @@ -1,8 +1,35 @@ use std::fmt::Display; -use chrono::{NaiveDateTime, LocalResult}; -use chrono_tz::Tz; +use chrono::format::StrftimeItems; use chrono::offset::TimeZone; +use chrono::{DateTime, FixedOffset, LocalResult, NaiveDateTime}; +use chrono_tz::Tz; +use lazy_static::lazy_static; + +lazy_static! { + static ref TIMESTAMP_FORMAT: Option = { + if let Ok(format) = std::env::var("DFIR_DATE") { + if StrftimeItems::new(&format).any(|i| i == chrono::format::Item::Error) { + eprintln!(); + eprintln!("ERROR: invalid date format: '{format}' stored in environment variable $DFIR_DATE!"); + eprintln!(); + eprintln!("Please take a look at"); + eprintln!(); + eprintln!(" "); + eprintln!(); + eprintln!("to see which format strings are accepted."); + eprintln!(); + std::process::exit(-1); + } else { + Some(format) + } + } else { + None + } + }; + static ref ZERO: DateTime = + DateTime::::parse_from_rfc3339("0000-00-00T00:00:00+00:00").unwrap(); +} pub struct ForensicsTimestamp { unix_ts: i64, @@ -11,10 +38,24 @@ pub struct ForensicsTimestamp { } impl ForensicsTimestamp { - pub fn new(unix_ts: i64, src_zone: Tz, dst_zone: Tz) -> Self { Self { - unix_ts, src_zone, dst_zone + unix_ts, + src_zone, + dst_zone, + } + } + + fn display_datetime( + dt: &DateTime, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result + where + ::Offset: std::fmt::Display, + { + match &*TIMESTAMP_FORMAT { + Some(format) => dt.format(format).fmt(f), + None => dt.to_rfc3339().fmt(f), } } } @@ -22,7 +63,8 @@ impl ForensicsTimestamp { impl Display for ForensicsTimestamp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.unix_ts >= 0 { - let src_timestamp = match self.src_zone + let src_timestamp = match self + .src_zone .from_local_datetime(&NaiveDateTime::from_timestamp_opt(self.unix_ts, 0).unwrap()) { LocalResult::None => { @@ -31,10 +73,10 @@ impl Display for ForensicsTimestamp { LocalResult::Single(t) => t, LocalResult::Ambiguous(t1, _t2) => t1, }; - let dst_timestamp = src_timestamp.with_timezone(&self.dst_zone); - write!(f, "{}", dst_timestamp.to_rfc3339()) + + Self::display_datetime(&src_timestamp.with_timezone(&self.dst_zone), f) } else { - write!(f, "0000-00-00T00:00:00+00:00") + Self::display_datetime(&*ZERO, f) } } -} \ No newline at end of file +} diff --git a/src/common/formattable_datetime.rs b/src/common/formattable_datetime.rs new file mode 100644 index 0000000..4634e45 --- /dev/null +++ b/src/common/formattable_datetime.rs @@ -0,0 +1,50 @@ +use std::fmt::Display; + +use chrono::{DateTime, FixedOffset, TimeZone}; +use lazy_static::lazy_static; + +lazy_static! { + static ref TIMESTAMP_FORMAT: Option = std::env::var("DFIR_DATE").ok(); + static ref ZERO: DateTime = + DateTime::::parse_from_rfc3339("0000-00-00T00:00:00+00:00").unwrap(); +} + +/// Wrapper around [`DateTime`] to allow customization of the timestamp output +/// using the `DFIR_DATE` environment variable +/// +pub struct FormattableDatetime(DateTime) +where + ::Offset: std::fmt::Display; + +impl From> for FormattableDatetime +where + TZ: TimeZone, + ::Offset: std::fmt::Display, +{ + fn from(value: DateTime) -> Self { + Self(value) + } +} + +impl From<&DateTime> for FormattableDatetime +where + TZ: TimeZone, + ::Offset: std::fmt::Display, +{ + fn from(value: &DateTime) -> Self { + Self(value.clone()) + } +} + +impl Display for FormattableDatetime +where + TZ: TimeZone, + ::Offset: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &*TIMESTAMP_FORMAT { + Some(format) => self.0.format(format).fmt(f), + None => self.0.to_rfc3339().fmt(f), + } + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index bc7ae23..ba026d6 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -4,10 +4,12 @@ mod parse_cli; mod rfc3339_datetime; mod tzargument; mod file_input; +mod formattable_datetime; pub use forensics_timestamp::*; pub use parse_cli::*; pub use rfc3339_datetime::*; pub use tzargument::*; +pub use formattable_datetime::*; pub use file_input::*; \ No newline at end of file