feat(chrome): scaffold module, types, base64 + epoch utils, --chrome flag

This commit is contained in:
NK
2026-06-07 14:49:20 +02:00
parent c3d1e264ce
commit 9c45399d81
8 changed files with 338 additions and 0 deletions
Generated
+140
View File
@@ -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"
+3
View File
@@ -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 }
+16
View File
@@ -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()
}
+91
View File
@@ -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()
}
}
+73
View File
@@ -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);
}
}
+2
View File
@@ -23,3 +23,5 @@ pub mod vbox;
#[cfg(feature = "vmware")]
pub mod vmware;
pub mod windows;
#[cfg(feature = "chrome")]
pub mod chrome;
+4
View File
@@ -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")]
+9
View File
@@ -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());
}