meta data for this page
Windows Disk Image
- Assumes basic setup from setup
- All command below should be
- copied to a “IR log” text/md file
- edited as needed
- executed with output copied back to “IR log”
- recommendation: move commands/output from “useless commands” to second “IR dump” text/md file
##is a placeholder for a number
setup
echo 'source $HOME/.case.env' >> ~/.bashrc
Env variables
rm ~/.case.env pwd WD=/case/working/folder && echo "WD=$WD" >> ~/.case.env E=/case/working/folder/evidence/evidence.E01 && echo "E=$E" >> ~/.case.env
Disk Image basics
mmls -r $E sudo -E ~/.local/bin/imount -p -o ## -md /mnt/imount $E # -p has 'pretty' & predictable folder name, fails if in use # new tab or crtl-z DISK_C=<imount_dir> && echo "DISK_C=$DISK_C" >> ~/.case.env
Hayabusa
target-fs "$E" cp 'c:/Windows/System32/winevt/Logs' --output ./logs docker run -it --rm -v "$PWD":/work --entrypoint /opt/hayabusa/hayabusa tabledevil/hayabusa:3.8.1 csv-timeline -d /work/logs -o /work/output/hayabusa.csv -p super-verbose docker run -it --rm -v "$PWD":/work --entrypoint /opt/hayabusa/hayabusa tabledevil/hayabusa:3.8.1 json-timeline -d /work/logs -o /work/output/hayabusa.jsonl -p super-verbose
Plaso
docker run -it --rm --entrypoint=/bin/bash -v ./:/work log2timeline/plaso # docker run -it --rm --user :$(id -g) --entrypoint=/bin/bash -v ./:/work log2timeline/plaso # one step psteal.py --source /work/evidence/<filename> -o dynamic,json_line -w /work/data/plaso_#.json # two step log2timeline.py --storage-file /work/data/timeline.plaso /work/evidence/<filename> psort.py -o json_line -w /work/output/plaso_out.json /work/data/timeline.plaso #psort.py -o json_line -w /work/output/plaso_out.json /work/data/timeline.plaso cp output/plaso_out.json tools/splunk/import/ cd tools/splunk docker compose up -d # -> localhost:8000, admin:password, settings->add->monitor->files->index_once->source_type=plaso
Splunk
mkdir -p etc/system/local/ vim props.conf cp props.conf etc/system/local
Browser
Users\...\AppData\Local\Google\Chrome\User Data\Default\History Users\...\AppData\Local\Microsoft\Edge\User Data\Default\History Users\...\AppData\Roaming\Mozilla\Firefox\Profiles\<ProfileName>.default\places.sqlite C_DR= EUSER= mkdir -p evidence/chrome mkdir evidence/edge mkdir evidence/firefox cp -r $C_DR/Users/$EUSER/AppData/Local/Google/Chrome/User\ Data/Default/History evidence/chrome cp -r $C_DR/Users/$EUSER/AppData/Local/Microsoft/Edge/User\ Data/Default/History evidence/edge cp -r $C_DR/Users/$EUSER/AppData/Roaming/Mozilla/Firefox/Profiles/*-release/places.sqlite evidence/firefox/ sqlitebrowser evidence/firefox/places.sqlite sqlitebrowser evidence/chrome/History
PE
https://github.com/sethenoka/Install_EZTools.git
Prefetch on Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.cargo/env rustup toolchain install 1.85.0 rustup default 1.85.0
use chrono::{DateTime, Utc}; use std::{env, fs}; fn filetime_to_datetime(ft: i64) -> DateTime<Utc> { // FILETIME epoch (1601-01-01) -> Unix epoch (1970-01-01) const EPOCH_DIFF_100NS: i64 = 116_444_736_000_000_000; let unix_100ns = ft - EPOCH_DIFF_100NS; let secs = unix_100ns / 10_000_000; let nanos = ((unix_100ns % 10_000_000) * 100) as u32; DateTime::from_timestamp(secs, nanos) .expect("invalid timestamp") } fn main() { let path = env::args() .nth(1) .expect("usage: pf_info <file.pf>"); let raw = fs::read(&path).expect("read failed"); let info = prefetch_core::parse(&raw) .unwrap_or_else(|e| panic!("parse error: {:?}", e)); println!("Executable : {}", info.executable); println!("Run count : {}", info.run_count); println!("\nLast run times:"); for ft in &info.last_run_times { println!( " {} ({})", filetime_to_datetime(*ft).to_rfc3339(), ft ); } println!("\nVolumes:"); for v in &info.volumes { println!(" Device: {}", v.device_path); let created = filetime_to_datetime(v.creation_time); println!(" Created: {}", created.to_rfc3339()); println!(" Serial : {:08X}", v.serial); println!(); } }
#![allow(clippy::unwrap_used, clippy::expect_used)] use std::path::PathBuf; use std::fs; use std::path::Path; use chrono::{DateTime, Utc}; /// based on https://docs.rs/crate/prefetch-core/0.1.0/source/examples/pf_dump.rs fn filetime_to_datetime(ft: i64) -> DateTime<Utc> { // FILETIME epoch (1601-01-01) -> Unix epoch (1970-01-01) const EPOCH_DIFF_100NS: i64 = 116_444_736_000_000_000; let unix_100ns = ft - EPOCH_DIFF_100NS; let secs = unix_100ns / 10_000_000; let nanos = ((unix_100ns % 10_000_000) * 100) as u32; DateTime::from_timestamp(secs, nanos) .expect("invalid timestamp") } fn csv_escape(s: &str) -> String { let mut out = String::new(); out.push('"'); for c in s.chars() { match c { '"' => out.push_str("\"\""), _ => out.push(c), } } out.push('"'); out } fn main() { let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); root.pop(); let out_dir = std::env::args() .nth(1) .unwrap_or_else(|| "/tmp/pf_scca".to_string()); std::fs::create_dir_all(&out_dir).expect("mkdir out_dir"); let dir = Path::new("/media/data/IR/case260618/prefetch"); let files: Vec<String> = fs::read_dir(dir) .unwrap() .filter_map(Result::ok) .map(|e| e.path()) .filter(|p| { p.extension() .and_then(|ext| ext.to_str()) .map(|ext| ext == "pf") .unwrap_or(false) }) .map(|p| p.to_string_lossy().to_string()) .collect(); //.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pf")) println!( "file,scca_len,version,executable,run_count,last_run_times,filenames_count,filenames,volumes" ); for name in files { let p = PathBuf::from(&name); let raw = std::fs::read(&p).expect("read fixture"); let scca = prefetch_core::decompress(&raw).expect("decompress"); std::fs::write(PathBuf::from(&out_dir).join(format!("{name}.scca")), &scca) .expect("write scca"); let info = prefetch_core::parse(&raw).expect("parse"); let filenames = info .filenames .iter() .map(|f| escape(f)) .collect::<Vec<_>>() .join(";"); let volumes = info .volumes .iter() .map(|v| { format!( "{{serial:{},device_path:{},creation_time:{}}}", v.serial, escape(&v.device_path), filetime_to_datetime(v.creation_time) ) }) .collect::<Vec<_>>() .join(";"); let last_runs = info .last_run_times .iter() .map(|t| filetime_to_datetime(*t).to_string()) .collect::<Vec<_>>() .join(";"); println!( "{},{},{},{},{},{},{},{},{}", csv_escape(&name), scca.len(), info.version, csv_escape(&info.executable), info.run_count, csv_escape(&last_runs), info.filenames.len(), csv_escape(&filenames), csv_escape(&volumes) ); } } fn escape(s: &str) -> String { s.replace('\\', "\\\\") }
Extended disk image anaylsis
fsstat -o <offset> $E istat -o <offset> $E 5 # root node istat -o <offset> $E <inode from fsstat> fls -o <offset> -m C: -r $E > data/bodyfile mactime -b data/bodyfile -d -z UTC yyyy-mm-ddThh:mm:ss > output/disk_timeline.csv mactime -b data/bodyfile -d -z UTC yyyy-mm-ddThh:mm:ss..yyyy-mm-dd > output/disk_timeline.csv # dissect target-query -f hostname,domain,version,ips,install_date,timezone $E # much more useful for queries on multiple disks at once target-query -j -f services $E | jq -r '.name' # JSON output → jq target-query --list | grep -iE 'userassist|shimcache|amcache|services|powershell_history|browser.history'