From 4bbe16811e03d6a2299add58202448d1e0c0d0f1 Mon Sep 17 00:00:00 2001 From: NK Date: Tue, 10 Feb 2026 15:26:18 +0100 Subject: [PATCH] Fix MSV credential merge: handle empty domain and prefer Interactive sessions - When physical scan credential has empty domain (local logons), match by username only instead of requiring exact domain match - Prefer Interactive (logon_type=2) sessions when multiple sessions match the same username, ensuring credentials are shown under the correct session entry - Fixes VBox output showing \User instead of DESKTOP-HMI10SP\User --- src/lsass/finder.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/lsass/finder.rs b/src/lsass/finder.rs index 7419828..9315f67 100644 --- a/src/lsass/finder.rs +++ b/src/lsass/finder.rs @@ -375,21 +375,43 @@ pub fn extract_all_credentials( } } - // Merge MSV credentials with unknown LUID (0) into matching credentials by username+domain + // Merge MSV credentials with unknown LUID (0) into matching credentials by username+domain. + // When the credential blob has an empty domain (common for local logons), match by username only. + // Prefer Interactive (logon_type=2) sessions over other types. if let Some(orphan) = all_creds.remove(&0) { if let Some(msv_cred) = orphan.msv { - let key = (msv_cred.username.to_lowercase(), msv_cred.domain.to_lowercase()); + let cred_user = msv_cred.username.to_lowercase(); + let cred_domain = msv_cred.domain.to_lowercase(); let mut merged = false; - for cred in all_creds.values_mut() { - let cred_key = (cred.username.to_lowercase(), cred.domain.to_lowercase()); - if cred_key == key { + // Find best matching session: prefer Interactive (logon_type=2) + let mut best_luid: Option = None; + let mut best_logon_type: u32 = 0; + for cred in all_creds.values() { + let sess_user = cred.username.to_lowercase(); + let sess_domain = cred.domain.to_lowercase(); + let matches = if cred_domain.is_empty() { + sess_user == cred_user + } else { + sess_user == cred_user && sess_domain == cred_domain + }; + if matches { + // Prefer Interactive (2), then any non-zero logon type, then any + let priority = if cred.logon_type == 2 { 3 } else if cred.logon_type != 0 { 2 } else { 1 }; + let best_priority = if best_logon_type == 2 { 3 } else if best_logon_type != 0 { 2 } else { 1 }; + if best_luid.is_none() || priority > best_priority { + best_luid = Some(cred.luid); + best_logon_type = cred.logon_type; + } + } + } + if let Some(target_luid) = best_luid { + if let Some(cred) = all_creds.get_mut(&target_luid) { cred.msv = Some(msv_cred.clone()); merged = true; log::debug!( "Merged MSV credential into LUID 0x{:x} (user='{}' domain='{}')", cred.luid, cred.username, cred.domain ); - break; } } if !merged {