use std::io::Cursor;
use winreg_core::detect::HiveType;
use winreg_core::hive::Hive;
const SOFTWARE_RUN_PATHS: &[&str] = &[
"Microsoft\\Windows\\CurrentVersion\\Run",
"Microsoft\\Windows\\CurrentVersion\\RunOnce",
"Microsoft\\Windows\\CurrentVersion\\RunServices",
"Microsoft\\Windows\\CurrentVersion\\RunServicesOnce",
];
const NTUSER_RUN_PATHS: &[&str] = &[
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
"Software\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce",
];
const WINLOGON_PATH_SOFTWARE: &str = "Microsoft\\Windows NT\\CurrentVersion\\Winlogon";
const WINLOGON_PATH_NTUSER: &str = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon";
const WINLOGON_VALUES: &[&str] = &["Userinit", "Shell"];
#[derive(Debug, Clone, serde::Serialize)]
pub struct RunKeyEntry {
pub hive: String,
pub key_path: String,
pub value_name: String,
pub command: String,
pub is_suspicious: bool,
pub suspicious_reason: Option<String>,
}
pub fn classify_run_entry(command: &str) -> Option<String> {
if command.is_empty() {
return None;
}
let lower = command.to_ascii_lowercase();
if lower.contains("powershell") && (lower.contains("-enc") || lower.contains("-encodedcommand"))
{
return Some("powershell encoded command (-enc / -encodedcommand)".to_string());
}
if lower.contains("cmd") && lower.contains("/c") {
if lower.contains("http") || lower.contains("ftp") || lower.contains("\\\\") {
return Some("cmd /c with remote resource (http/ftp/UNC)".to_string());
}
}
if lower.contains("mshta") {
return Some("mshta execution (HTML Application host abuse)".to_string());
}
if lower.contains("regsvr32") && (lower.contains("/s") && lower.contains("/n"))
|| (lower.contains("regsvr32") && lower.contains("/u") && lower.contains("/s"))
{
return Some("regsvr32 /s /n or /u /s (AppLocker bypass / squiblydoo)".to_string());
}
if lower.contains("certutil") && (lower.contains("-decode") || lower.contains("-urlcache")) {
return Some("certutil -decode or -urlcache (download cradle / obfuscation)".to_string());
}
if lower.contains("bitsadmin") && lower.contains("/transfer") {
return Some("bitsadmin /transfer (BITS download abuse)".to_string());
}
if (lower.contains("wscript") || lower.contains("cscript"))
&& (lower.contains("\\temp\\") || lower.contains("\\appdata\\"))
{
return Some("wscript/cscript launched from \\temp\\ or \\appdata\\ path".to_string());
}
if lower.contains("rundll32") && (lower.contains("\\temp\\") || lower.contains("\\appdata\\")) {
return Some("rundll32 with DLL in \\temp\\ or \\appdata\\ path".to_string());
}
if lower.contains("\\appdata\\local\\temp\\") || lower.starts_with("\\temp\\") {
return Some("executable path is in \\temp\\ or \\appdata\\local\\temp\\".to_string());
}
if lower.contains("msiexec") && lower.contains("/q") && lower.contains("http") {
return Some("msiexec /q with HTTP URL (silent remote install)".to_string());
}
None
}
pub fn parse(hive: &Hive<Cursor<Vec<u8>>>) -> Vec<RunKeyEntry> {
let hive_type = hive.detect_hive_type();
let (hive_label, run_paths, winlogon_path) = match hive_type {
HiveType::Software => ("HKLM", SOFTWARE_RUN_PATHS, WINLOGON_PATH_SOFTWARE),
HiveType::NtUser => ("HKCU", NTUSER_RUN_PATHS, WINLOGON_PATH_NTUSER),
_ => ("UNKNOWN", SOFTWARE_RUN_PATHS, WINLOGON_PATH_SOFTWARE),
};
let mut entries: Vec<RunKeyEntry> = Vec::new();
for &key_path in run_paths {
let key = match hive.open_key(key_path) {
Ok(Some(k)) => k,
_ => continue,
};
let values = match key.values() {
Ok(v) => v,
Err(_) => continue,
};
for val in values {
let command = val.as_string().unwrap_or_default();
let suspicious_reason = classify_run_entry(&command);
let is_suspicious = suspicious_reason.is_some();
entries.push(RunKeyEntry {
hive: hive_label.to_string(),
key_path: key_path.to_string(),
value_name: val.name(),
command,
is_suspicious,
suspicious_reason,
});
}
}
if let Ok(Some(winlogon)) = hive.open_key(winlogon_path) {
for &vname in WINLOGON_VALUES {
let val = match winlogon.value(vname) {
Ok(Some(v)) => v,
_ => continue,
};
let command = val.as_string().unwrap_or_default();
let suspicious_reason = classify_run_entry(&command);
let is_suspicious = suspicious_reason.is_some();
entries.push(RunKeyEntry {
hive: hive_label.to_string(),
key_path: winlogon_path.to_string(),
value_name: vname.to_string(),
command,
is_suspicious,
suspicious_reason,
});
}
}
entries
}