mirror of
https://github.com/nikaiw/VMkatz
synced 2026-09-25 07:51:06 +00:00
feat(chrome): scaffold module, types, base64 + epoch utils, --chrome flag
This commit is contained in:
Generated
+140
@@ -2,6 +2,16 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
@@ -13,6 +23,20 @@ dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
@@ -239,9 +263,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.5"
|
||||
@@ -321,6 +355,27 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -355,6 +410,12 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
@@ -442,6 +503,24 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -466,6 +545,15 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@@ -487,6 +575,15 @@ version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
@@ -507,6 +604,19 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
@@ -537,6 +647,12 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -621,6 +737,16 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
@@ -638,6 +764,7 @@ name = "vmkatz"
|
||||
version = "1.4.1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"cbc",
|
||||
"clap",
|
||||
@@ -648,10 +775,17 @@ dependencies = [
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"ntfs",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.2.1"
|
||||
@@ -672,3 +806,9 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
@@ -15,6 +15,7 @@ sam = ["dep:ntfs", "dep:md-5", "dep:sha2"]
|
||||
carve = []
|
||||
dump = []
|
||||
vmfs = ["sam"]
|
||||
chrome = ["sam", "dep:aes-gcm", "dep:serde_json"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
@@ -37,3 +38,5 @@ anyhow = "1"
|
||||
hex = "0.4"
|
||||
log = "0.4"
|
||||
memchr = "2"
|
||||
aes-gcm = { version = "0.10", optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//! Chrome / Chromium / Firefox secrets extraction.
|
||||
//!
|
||||
//! See `docs/plans/2026-06-05-chrome-module-design.md` for full spec.
|
||||
|
||||
pub mod types;
|
||||
pub mod util;
|
||||
|
||||
pub use types::{
|
||||
AutofillEntry, AutofillKind, Browser, BrowserProfile, ChromeFindings, ChromeSource, Cookie,
|
||||
SavedPassword,
|
||||
};
|
||||
|
||||
/// Top-level entrypoint. Wired into CLI in a later task.
|
||||
pub fn run_placeholder() -> ChromeFindings {
|
||||
ChromeFindings::default()
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Browser {
|
||||
Chrome,
|
||||
Edge,
|
||||
Brave,
|
||||
Opera,
|
||||
Vivaldi,
|
||||
Firefox,
|
||||
}
|
||||
|
||||
impl fmt::Display for Browser {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
Browser::Chrome => "Chrome",
|
||||
Browser::Edge => "Edge",
|
||||
Browser::Brave => "Brave",
|
||||
Browser::Opera => "Opera",
|
||||
Browser::Vivaldi => "Vivaldi",
|
||||
Browser::Firefox => "Firefox",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ChromeSource {
|
||||
Memory { pid: u32, process: String },
|
||||
DiskDpapi,
|
||||
DiskAbe,
|
||||
HybridMemKey { mk_guid: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BrowserProfile {
|
||||
pub browser: Browser,
|
||||
pub user: String,
|
||||
pub profile_name: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SavedPassword {
|
||||
pub profile: BrowserProfile,
|
||||
pub url: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub source: ChromeSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cookie {
|
||||
pub profile: BrowserProfile,
|
||||
pub host: String,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub path: String,
|
||||
pub expires: Option<i64>,
|
||||
pub http_only: bool,
|
||||
pub secure: bool,
|
||||
pub source: ChromeSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AutofillKind {
|
||||
FormField,
|
||||
CreditCard,
|
||||
Address,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AutofillEntry {
|
||||
pub profile: BrowserProfile,
|
||||
pub kind: AutofillKind,
|
||||
pub fields: Vec<(String, String)>,
|
||||
pub source: ChromeSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ChromeFindings {
|
||||
pub passwords: Vec<SavedPassword>,
|
||||
pub cookies: Vec<Cookie>,
|
||||
pub autofill: Vec<AutofillEntry>,
|
||||
}
|
||||
|
||||
impl ChromeFindings {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.passwords.is_empty() && self.cookies.is_empty() && self.autofill.is_empty()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/// RFC 4648 standard base64 decode. Returns None on invalid input.
|
||||
pub fn b64_decode(input: &str) -> Option<Vec<u8>> {
|
||||
const TBL: [i8; 256] = {
|
||||
let mut t = [-1i8; 256];
|
||||
let mut i = 0u8;
|
||||
while i < 26 { t[(b'A' + i) as usize] = i as i8; i += 1; }
|
||||
let mut i = 0u8;
|
||||
while i < 26 { t[(b'a' + i) as usize] = (26 + i) as i8; i += 1; }
|
||||
let mut i = 0u8;
|
||||
while i < 10 { t[(b'0' + i) as usize] = (52 + i) as i8; i += 1; }
|
||||
t[b'+' as usize] = 62;
|
||||
t[b'/' as usize] = 63;
|
||||
t
|
||||
};
|
||||
let bytes = input.as_bytes();
|
||||
let mut out = Vec::with_capacity(input.len() * 3 / 4);
|
||||
let mut buf = 0u32;
|
||||
let mut bits = 0u32;
|
||||
for &b in bytes {
|
||||
if b == b'=' || b == b'\n' || b == b'\r' || b == b' ' {
|
||||
if b == b'=' { break; }
|
||||
continue;
|
||||
}
|
||||
let v = TBL[b as usize];
|
||||
if v < 0 { return None; }
|
||||
buf = (buf << 6) | (v as u32);
|
||||
bits += 6;
|
||||
if bits >= 8 {
|
||||
bits -= 8;
|
||||
out.push((buf >> bits) as u8);
|
||||
buf &= (1 << bits) - 1;
|
||||
}
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// Chrome time epoch (1601-01-01) to Unix epoch (1970-01-01), in seconds.
|
||||
/// Returns None for the sentinel value 0 (no expiry).
|
||||
pub fn chrome_time_to_unix(chrome_us: i64) -> Option<i64> {
|
||||
if chrome_us == 0 { return None; }
|
||||
// Chrome stores microseconds since 1601-01-01 UTC.
|
||||
const EPOCH_DELTA_SECS: i64 = 11_644_473_600;
|
||||
Some(chrome_us / 1_000_000 - EPOCH_DELTA_SECS)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn b64_basic() {
|
||||
assert_eq!(b64_decode("aGVsbG8="), Some(b"hello".to_vec()));
|
||||
assert_eq!(b64_decode("aGVsbG8"), Some(b"hello".to_vec())); // no padding
|
||||
assert_eq!(b64_decode("YWJjZGVm"), Some(b"abcdef".to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b64_invalid() {
|
||||
assert_eq!(b64_decode("!!!!"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chrome_epoch() {
|
||||
// 13_000_000_000_000_000 microseconds = ~2012-something
|
||||
let unix = chrome_time_to_unix(13_000_000_000_000_000).unwrap();
|
||||
assert!(unix > 1_300_000_000 && unix < 1_400_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chrome_epoch_zero_is_none() {
|
||||
assert_eq!(chrome_time_to_unix(0), None);
|
||||
}
|
||||
}
|
||||
@@ -23,3 +23,5 @@ pub mod vbox;
|
||||
#[cfg(feature = "vmware")]
|
||||
pub mod vmware;
|
||||
pub mod windows;
|
||||
#[cfg(feature = "chrome")]
|
||||
pub mod chrome;
|
||||
|
||||
@@ -185,6 +185,10 @@ struct Args {
|
||||
#[arg(long, default_value_t = false)]
|
||||
carve: bool,
|
||||
|
||||
/// Extract browser secrets (Chromium + Firefox). Requires --features chrome at build time.
|
||||
#[arg(long, default_value_t = false)]
|
||||
chrome: bool,
|
||||
|
||||
/// VMFS-6 raw SCSI device for reading flat VMDKs through VMFS locks
|
||||
#[cfg(feature = "vmfs")]
|
||||
#[arg(long, value_name = "DEVICE")]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#![cfg(feature = "chrome")]
|
||||
|
||||
use vmkatz::chrome::{run_placeholder, ChromeFindings};
|
||||
|
||||
#[test]
|
||||
fn run_placeholder_returns_empty() {
|
||||
let f: ChromeFindings = run_placeholder();
|
||||
assert!(f.is_empty());
|
||||
}
|
||||
Reference in New Issue
Block a user