Skip to content

Commit

Permalink
add new evtxcat files
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Starke committed Sep 8, 2023
1 parent 2eb1b30 commit fb2055b
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 0 deletions.
42 changes: 42 additions & 0 deletions src/bin/evtxcat/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use clap::Parser;
use dfir_toolkit::common::HasVerboseFlag;
use log::LevelFilter;

use crate::output_format::OutputFormat;

/// Display one or more events from an evtx file
#[derive(Parser)]
#[clap(name=env!("CARGO_BIN_NAME"),author,version,about)]
pub (crate) struct Cli {
/// Name of the evtx file to read from
pub (crate) evtx_file: String,

/// filter: minimal event record identifier
#[clap(long)]
pub (crate) min: Option<u64>,

/// filter: maximal event record identifier
#[clap(long)]
pub (crate) max: Option<u64>,

/// show only the one event with this record identifier
#[clap(short, long)]
pub (crate) id: Option<u64>,

/// don't display the records in a table format
#[clap(short('T'), long("display-table"))]
pub (crate) show_table: bool,

/// output format
#[clap(value_enum, short('F'), long("format"), default_value_t = OutputFormat::Xml)]
pub (crate) format: OutputFormat,

#[clap(flatten)]
verbose: clap_verbosity_flag::Verbosity,
}

impl HasVerboseFlag for Cli {
fn log_level_filter(&self)-> LevelFilter {
self.verbose.log_level_filter()
}
}
6 changes: 6 additions & 0 deletions src/bin/evtxcat/output_format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

#[derive(clap::ValueEnum, Clone)]
pub (crate) enum OutputFormat {
Json,
Xml,
}
75 changes: 75 additions & 0 deletions src/bin/evtxcat/record_filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use std::{io::{Read, Seek}, collections::HashMap, fs::File};

use evtx::{EvtxParser, SerializedEvtxRecord};

use crate::unfiltered::Unfiltered;


pub (crate) trait RecordFilter: Sized {
type ReaderType: Read + Seek;

fn unfiltered(parser: &mut EvtxParser<Self::ReaderType>) -> Unfiltered<Self>;

fn filter_by_id(
mut parser: EvtxParser<Self::ReaderType>,
filter_id: u64,
) -> (Vec<u64>, HashMap<u64, SerializedEvtxRecord<Self>>) {
let mut record_ids: Vec<u64> = Vec::new();
let mut records: HashMap<u64, SerializedEvtxRecord<Self>> = HashMap::new();
if let Some(result) = Self::unfiltered(&mut parser).find(|record| match record {
Ok(evt) => evt.event_record_id == filter_id,
_ => false,
}) {
let evt = result.unwrap();
record_ids.push(evt.event_record_id);
records.insert(evt.event_record_id, evt);
}
(record_ids, records)
}

fn filter_by_range(
mut parser: EvtxParser<Self::ReaderType>,
min: u64,
max: u64,
) -> (Vec<u64>, HashMap<u64, SerializedEvtxRecord<Self>>) {
let mut record_ids: Vec<u64> = Vec::new();
let mut records: HashMap<u64, SerializedEvtxRecord<Self>> = HashMap::new();

for record in Self::unfiltered(&mut parser) {
match record {
Err(_) => (),
Ok(evt) => {
let id = evt.event_record_id;

if id >= min && id <= max {
record_ids.push(id);
records.insert(id, evt);
}
}
}
}

record_ids.sort_unstable();
(record_ids, records)
}
}

impl RecordFilter for serde_json::Value {
type ReaderType = File;

fn unfiltered(parser: &mut EvtxParser<Self::ReaderType>) -> Unfiltered<Self> {
Unfiltered {
inner: Box::new(parser.records_json_value()),
}
}
}

impl RecordFilter for String {
type ReaderType = File;

fn unfiltered(parser: &mut EvtxParser<Self::ReaderType>) -> Unfiltered<Self> {
Unfiltered {
inner: Box::new(parser.records()),
}
}
}
50 changes: 50 additions & 0 deletions src/bin/evtxcat/record_list_formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::collections::HashMap;

use colored_json::to_colored_json_auto;
use evtx::SerializedEvtxRecord;
use term_table::{row::Row, table_cell::TableCell};

use crate::cli::Cli;

pub (crate) trait RecordListFormatter: Sized {
fn format(record: &SerializedEvtxRecord<Self>) -> String;

fn display_results(
record_ids: Vec<u64>,
records: HashMap<u64, SerializedEvtxRecord<Self>>,
cli: &Cli,
) {
if !cli.show_table {
for id in record_ids.into_iter() {
let record = &records[&id];
println!("{}", Self::format(record));
}
} else {
let mut table = term_table::Table::new();
if let Some(size) = termsize::get() {
table.set_max_column_widths(vec![(0, 12), (1, (size.cols - 16).into())])
}

for id in record_ids.into_iter() {
let record = &records[&id];
table.add_row(Row::new(vec![
TableCell::new(id),
TableCell::new(Self::format(record)),
]));
}
println!("{}", table.render());
}
}
}

impl RecordListFormatter for String {
fn format(record: &SerializedEvtxRecord<Self>) -> String {
record.data.clone()
}
}

impl RecordListFormatter for serde_json::Value {
fn format(record: &SerializedEvtxRecord<Self>) -> String {
to_colored_json_auto(&record.data).unwrap()
}
}
13 changes: 13 additions & 0 deletions src/bin/evtxcat/unfiltered.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use evtx::SerializedEvtxRecord;

pub (crate) struct Unfiltered<'a, V> {
pub (crate) inner: Box<dyn Iterator<Item = evtx::err::Result<SerializedEvtxRecord<V>>> + 'a>,
}

impl<'a, V> Iterator for Unfiltered<'a, V> {
type Item = evtx::err::Result<SerializedEvtxRecord<V>>;

fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}

0 comments on commit fb2055b

Please sign in to comment.