mirror of
https://github.com/nikaiw/VMkatz
synced 2026-09-25 07:51:06 +00:00
LSASS credential extraction: - Minidump (.dmp) support: full MSV/Kerberos/DPAPI/WDigest/TsPkg/SSP/ LiveSSP/CredMan/CloudAP extraction from LSASS minidumps - Pre-Vista (WinXP/Win2003): 32-bit EPROCESS, PAE paging, DES-X-CBC/RC4 - Win11 24H2 (26100+): correct EPROCESS offsets, MSV LIST_64/LIST_65, Kerberos variant with shifted offsets - All MSV/SSP/LiveSSP/CloudAP/CredMan/WDigest offsets verified against mimikatz C structs and pypykatz templates - EPROCESS offsets verified against Vergilius Project (13 variants) - AES-CFB-128 cipher for non-8-aligned LSASS blobs - DPAPI extraction from dpapisrv.dll (Win10 19041+ moved g_MasterKeyCacheList) - Adaptive MSV offset discovery with build-number-aware variant ordering - MSV NT hash fix: validated variant tracking + DPAPI cross-check for human accounts (SHA1 validation only works for machine accounts) - Kerberos: AES/DES/RC4 key extraction, kirbi/ccache export, ticket quality validation, false positive filtering - CloudAP: PRT blob extraction, 7 patterns covering Win10 1507–Win11 24H2 - CredMan: correct 2-level navigation (SET_LIST→STARTER→entry) - Garbage filtering: repeating pattern detection, structural score validation, unknown etype rejection Architecture: - Carve mode: two-level degraded extraction for truncated memory files - sam/mod.rs split into 4 submodules (partition, ntfs_reader, disk_fallbacks, vmdk_scan) - ProviderStatus enum replacing string-based status tracking - Safe read helpers (utils.rs) replacing 86 try_into().unwrap() calls - GovmemError renamed to VmkatzError across all 35 source files - Named paging constants (PAGE_PHYS_MASK, LARGE_*_MASK) Performance: - TLB cache for page table translation (256-entry direct-mapped) - QCOW2 L2 table caching (64 tables, amortized I/O) - VMware region binary search (partition_point) - Stack-allocated ASN.1 length encoding, IV entropy histogram - Single-pass System process + EPT scanning - memchr::memmem for pattern matching Robustness: - Minidump parser hardening (bounds checks, overflow protection) - PE32 validation (machine type, section count, optional header size) - Multiple pagefile support (PTE pagefile_number routing) - VMware embedded memory support (.vmss/.vmsn without .vmem) - EPT false positive prevention (reserved bits, PDPT validation) - VMEM truncation detection with user warning Testing: - 8 automated tests (4 unit + 4 integration) - Non-regression framework: compare.py + esxi_test.sh - All credentials verified against pypykatz on 10+ minidumps CLI: - --all/-a: show empty sessions (hidden by default) - --no-ept: skip EPT scanning - --kirbi/--ccache: Kerberos ticket export - Silent output modes: ntlm, hashcat, text summary - Hex display for non-printable machine account passwords
83 lines
2.6 KiB
Rust
83 lines
2.6 KiB
Rust
#![cfg(feature = "sam")]
|
|
|
|
use std::io::{Read, Seek, SeekFrom};
|
|
use std::path::Path;
|
|
use vmkatz::disk::DiskImage;
|
|
use vmkatz::disk::qcow2::QcowDisk;
|
|
use vmkatz::disk::vdi::VdiDisk;
|
|
|
|
#[test]
|
|
fn test_open_qcow2() {
|
|
let path = Path::new("/tmp/test.qcow2");
|
|
if !path.exists() {
|
|
return;
|
|
}
|
|
let mut disk = QcowDisk::open(path).expect("failed to open QCOW2");
|
|
assert_eq!(disk.disk_size(), 85899345920); // 80 GB
|
|
|
|
// Read MBR and check signature
|
|
let mut mbr = [0u8; 512];
|
|
disk.read_exact(&mut mbr).expect("failed to read MBR");
|
|
assert_eq!(mbr[510], 0x55);
|
|
assert_eq!(mbr[511], 0xAA);
|
|
|
|
// Check NTFS signature at LBA 2048 (byte offset 0x100000)
|
|
disk.seek(SeekFrom::Start(2048 * 512)).unwrap();
|
|
let mut ntfs_hdr = [0u8; 8];
|
|
disk.read_exact(&mut ntfs_hdr).unwrap();
|
|
assert_eq!(&ntfs_hdr[3..8], b"NTFS ");
|
|
}
|
|
|
|
#[test]
|
|
fn test_qcow2_sam_extraction() {
|
|
let path = Path::new("/tmp/test.qcow2");
|
|
if !path.exists() {
|
|
return;
|
|
}
|
|
let secrets = vmkatz::sam::extract_disk_secrets(path).expect("SAM extraction failed");
|
|
assert!(!secrets.sam_entries.is_empty(), "should find SAM entries");
|
|
// At minimum, Administrator (RID 500) and Guest (RID 501) should exist
|
|
let admin = secrets.sam_entries.iter().find(|e| e.rid == 500);
|
|
assert!(admin.is_some(), "Administrator account not found");
|
|
}
|
|
|
|
#[test]
|
|
fn test_open_base_vdi() {
|
|
let path = Path::new("/home/user/vm/windows10-clean/windows10-clean.vdi");
|
|
if !path.exists() {
|
|
return;
|
|
}
|
|
let mut disk = VdiDisk::open(path).expect("failed to open base VDI");
|
|
assert_eq!(disk.disk_size(), 85899345920); // 80 GB
|
|
|
|
// Read MBR and check signature
|
|
let mut mbr = [0u8; 512];
|
|
disk.read_exact(&mut mbr).expect("failed to read MBR");
|
|
assert_eq!(mbr[510], 0x55);
|
|
assert_eq!(mbr[511], 0xAA);
|
|
|
|
// Check NTFS signature at LBA 2048 (byte offset 0x100000)
|
|
disk.seek(SeekFrom::Start(2048 * 512)).unwrap();
|
|
let mut ntfs_hdr = [0u8; 8];
|
|
disk.read_exact(&mut ntfs_hdr).unwrap();
|
|
assert_eq!(&ntfs_hdr[3..8], b"NTFS ");
|
|
}
|
|
|
|
#[test]
|
|
fn test_open_diff_vdi() {
|
|
let path = Path::new(
|
|
"/home/user/vm/windows10-clean/Snapshots/{29fc354e-2d14-424f-95be-d4f79d10e922}.vdi",
|
|
);
|
|
if !path.exists() {
|
|
return;
|
|
}
|
|
let mut disk = VdiDisk::open(path).expect("failed to open diff VDI");
|
|
assert_eq!(disk.disk_size(), 85899345920);
|
|
|
|
// MBR should be readable (from parent via fallthrough)
|
|
let mut mbr = [0u8; 512];
|
|
disk.read_exact(&mut mbr).expect("failed to read MBR");
|
|
assert_eq!(mbr[510], 0x55);
|
|
assert_eq!(mbr[511], 0xAA);
|
|
}
|