#[must_use]
pub fn is_valid_windows_pid(pid: u32) -> bool {
pid != 0 && pid % 4 == 0
}
#[must_use]
pub fn is_child_born_before_parent(child_create_ns: i64, parent_create_ns: i64) -> bool {
child_create_ns < parent_create_ns
}
pub const LOGON_INTERACTIVE: u32 = 2;
pub const LOGON_NETWORK: u32 = 3;
pub const LOGON_BATCH: u32 = 4;
pub const LOGON_SERVICE: u32 = 5;
pub const LOGON_NETWORK_CLEARTEXT: u32 = 8;
pub const LOGON_NEW_CREDENTIALS: u32 = 9; pub const LOGON_REMOTE_INTERACTIVE: u32 = 10;
#[must_use]
pub fn is_remote_logon(logon_type: u32) -> bool {
matches!(
logon_type,
LOGON_NETWORK | LOGON_REMOTE_INTERACTIVE | LOGON_NETWORK_CLEARTEXT
)
}
#[must_use]
pub fn is_lateral_movement_logon(logon_type: u32) -> bool {
matches!(
logon_type,
LOGON_NEW_CREDENTIALS | LOGON_NETWORK | LOGON_NETWORK_CLEARTEXT
)
}
#[must_use]
pub fn is_system_session(session_id: u32) -> bool {
session_id == 0
}
pub const TOKEN_ELEVATION_FULL: u32 = 3;
#[must_use]
pub fn is_elevated_token(elevation_type: u32) -> bool {
elevation_type == TOKEN_ELEVATION_FULL
}
pub const OPENSSH_VIRTUAL_USERS_DOMAIN: &str = "VIRTUAL USERS";
#[must_use]
pub fn is_openssh_virtual_users_domain(domain: &str) -> bool {
domain.eq_ignore_ascii_case(OPENSSH_VIRTUAL_USERS_DOMAIN)
}
#[must_use]
pub fn is_winscp_ssh_service_logon(logon_type: u32, domain: &str, process_name: &str) -> bool {
logon_type == LOGON_SERVICE
&& is_openssh_virtual_users_domain(domain)
&& process_name.eq_ignore_ascii_case("sshd.exe")
}
pub const SUSPICIOUS_PID_GAP: u32 = 50;
#[must_use]
pub fn has_pid_gap(sorted_pids: &[u32], max_gap: u32) -> bool {
sorted_pids
.windows(2)
.any(|w| w[1].saturating_sub(w[0]) > max_gap)
}
pub const CAP_DAC_OVERRIDE: u32 = 1; pub const CAP_NET_RAW: u32 = 13; pub const CAP_SYS_PTRACE: u32 = 19; pub const CAP_SYS_ADMIN: u32 = 21;
pub const DANGEROUS_CAPS: &[u32] = &[CAP_DAC_OVERRIDE, CAP_NET_RAW, CAP_SYS_PTRACE, CAP_SYS_ADMIN];
#[must_use]
pub fn is_dangerous_capability(cap: u32) -> bool {
DANGEROUS_CAPS.contains(&cap)
}
#[must_use]
pub fn is_run_key_powershell_stager(cmd: &str) -> bool {
let lower = cmd.to_lowercase();
if !lower.contains("powershell") {
return false;
}
if lower.contains("get-itemproperty") || lower.contains("(gp ") || lower.contains("(gp\t") {
return true;
}
if lower.contains(" -enc ") || lower.contains(" -enc\t") || lower.contains("-encodedcommand") {
return true;
}
false
}
#[must_use]
pub fn is_comspec_in_registry_value(value: &str) -> bool {
value.to_lowercase().contains("%comspec%")
}
#[must_use]
pub fn is_service_binpath_compression_obfuscated(binpath: &str) -> bool {
let lower = binpath.to_lowercase();
lower.contains("gzipstream") || lower.contains("[io.compression.")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_windows_pid_4() {
assert!(is_valid_windows_pid(4));
}
#[test]
fn valid_windows_pid_1000() {
assert!(is_valid_windows_pid(1000));
}
#[test]
fn invalid_windows_pid_zero() {
assert!(!is_valid_windows_pid(0));
}
#[test]
fn invalid_windows_pid_odd() {
assert!(!is_valid_windows_pid(1001));
}
#[test]
fn child_born_before_parent_returns_true() {
assert!(is_child_born_before_parent(100, 200));
}
#[test]
fn child_born_after_parent_returns_false() {
assert!(!is_child_born_before_parent(200, 100));
}
#[test]
fn child_same_time_as_parent_returns_false() {
assert!(!is_child_born_before_parent(100, 100));
}
#[test]
fn remote_logon_network() {
assert!(is_remote_logon(LOGON_NETWORK));
}
#[test]
fn remote_logon_rdp() {
assert!(is_remote_logon(LOGON_REMOTE_INTERACTIVE));
}
#[test]
fn remote_logon_cleartext() {
assert!(is_remote_logon(LOGON_NETWORK_CLEARTEXT));
}
#[test]
fn interactive_logon_is_not_remote() {
assert!(!is_remote_logon(LOGON_INTERACTIVE));
}
#[test]
fn lateral_movement_new_credentials() {
assert!(is_lateral_movement_logon(LOGON_NEW_CREDENTIALS));
}
#[test]
fn lateral_movement_network() {
assert!(is_lateral_movement_logon(LOGON_NETWORK));
}
#[test]
fn service_logon_is_not_lateral() {
assert!(!is_lateral_movement_logon(LOGON_SERVICE));
}
#[test]
fn system_session_zero() {
assert!(is_system_session(0));
}
#[test]
fn user_session_one() {
assert!(!is_system_session(1));
}
#[test]
fn elevated_token_type_3() {
assert!(is_elevated_token(TOKEN_ELEVATION_FULL));
}
#[test]
fn non_elevated_token_type_1() {
assert!(!is_elevated_token(1));
}
#[test]
fn pid_gap_detected() {
assert!(has_pid_gap(&[1, 2, 100, 101], 50));
}
#[test]
fn pid_gap_not_detected_small_gaps() {
assert!(!has_pid_gap(&[1, 2, 3, 4], 50));
}
#[test]
fn pid_gap_empty_slice() {
assert!(!has_pid_gap(&[], 50));
}
#[test]
fn dangerous_cap_sys_admin() {
assert!(is_dangerous_capability(CAP_SYS_ADMIN));
}
#[test]
fn dangerous_cap_net_raw() {
assert!(is_dangerous_capability(CAP_NET_RAW));
}
#[test]
fn non_dangerous_cap_chown() {
assert!(!is_dangerous_capability(0));
}
#[test]
fn virtual_users_domain_matches() {
assert!(is_openssh_virtual_users_domain("VIRTUAL USERS"));
}
#[test]
fn virtual_users_domain_case_insensitive() {
assert!(is_openssh_virtual_users_domain("virtual users"));
assert!(is_openssh_virtual_users_domain("Virtual Users"));
}
#[test]
fn normal_domain_not_virtual_users() {
assert!(!is_openssh_virtual_users_domain("WORKGROUP"));
assert!(!is_openssh_virtual_users_domain("CORP"));
assert!(!is_openssh_virtual_users_domain("NT AUTHORITY"));
}
#[test]
fn empty_domain_not_virtual_users() {
assert!(!is_openssh_virtual_users_domain(""));
}
#[test]
fn winscp_ssh_logon_type5_virtual_users_sshd() {
assert!(is_winscp_ssh_service_logon(5, "VIRTUAL USERS", "sshd.exe"));
}
#[test]
fn winscp_ssh_logon_process_case_insensitive() {
assert!(is_winscp_ssh_service_logon(5, "VIRTUAL USERS", "SSHD.EXE"));
assert!(is_winscp_ssh_service_logon(5, "VIRTUAL USERS", "Sshd.Exe"));
}
#[test]
fn winscp_ssh_logon_wrong_type_not_flagged() {
assert!(!is_winscp_ssh_service_logon(3, "VIRTUAL USERS", "sshd.exe"));
}
#[test]
fn winscp_ssh_logon_wrong_domain_not_flagged() {
assert!(!is_winscp_ssh_service_logon(5, "CORP", "sshd.exe"));
}
#[test]
fn winscp_ssh_logon_wrong_process_not_flagged() {
assert!(!is_winscp_ssh_service_logon(
5,
"VIRTUAL USERS",
"lsass.exe"
));
}
#[test]
fn run_key_get_item_property_stager_detected() {
let cmd = r"powershell.exe -WindowStyle Hidden -NoProfile -NonInteractive -enc (Get-ItemProperty HKLM:\Software\hztGpoWa).hztGpoWa";
assert!(is_run_key_powershell_stager(cmd));
}
#[test]
fn run_key_gp_shorthand_stager_detected() {
let cmd = r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -w hidden -enc (gp HKLM:\software\payload).data";
assert!(is_run_key_powershell_stager(cmd));
}
#[test]
fn run_key_powershell_encoded_command_detected() {
let cmd = "powershell -EncodedCommand JABjAG0AZAAgAD0AIAB7AHsAaQBuAHYAbwBrAGUALQBtAGkAbQBpAGsAYQB0AHoAfQB9";
assert!(is_run_key_powershell_stager(cmd));
}
#[test]
fn run_key_powershell_enc_short_flag_detected() {
let cmd = "powershell.exe -nop -w hidden -enc SQBFAFgA";
assert!(is_run_key_powershell_stager(cmd));
}
#[test]
fn run_key_normal_powershell_not_stager() {
let cmd = r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\scripts\backup.ps1";
assert!(!is_run_key_powershell_stager(cmd));
}
#[test]
fn run_key_non_powershell_not_stager() {
let cmd = r"C:\Program Files\SomeApp\app.exe --start";
assert!(!is_run_key_powershell_stager(cmd));
}
#[test]
fn run_key_empty_value_not_stager() {
assert!(!is_run_key_powershell_stager(""));
}
#[test]
fn comspec_uppercase_detected() {
let value = r"%COMSPEC% /b /c start /b /min powershell.exe -nop -w hidden -enc SQBFAFgA";
assert!(is_comspec_in_registry_value(value));
}
#[test]
fn comspec_lowercase_detected() {
let value = r"%comspec% /c powershell -enc SQBFAFgA";
assert!(is_comspec_in_registry_value(value));
}
#[test]
fn comspec_mixed_case_detected() {
let value = r"%ComSpec% /c cmd /c start";
assert!(is_comspec_in_registry_value(value));
}
#[test]
fn value_without_comspec_not_flagged() {
let value = r"C:\Windows\system32\cmd.exe /c start";
assert!(!is_comspec_in_registry_value(value));
}
#[test]
fn empty_value_not_flagged_comspec() {
assert!(!is_comspec_in_registry_value(""));
}
#[test]
fn binpath_gzipstream_detected() {
let binpath = r#"%COMSPEC% /b /c start /b /min powershell -nop -w hidden -c "$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String('...')); IEX(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress)|%{$_.Read}|Out-String)""#;
assert!(is_service_binpath_compression_obfuscated(binpath));
}
#[test]
fn binpath_gzipstream_case_insensitive() {
let binpath = "%COMSPEC% /c powershell -enc ... GZIPStream ...";
assert!(is_service_binpath_compression_obfuscated(binpath));
}
#[test]
fn binpath_io_compression_compressionmode_detected() {
let binpath = r"%COMSPEC% /c powershell -nop [IO.Compression.CompressionMode]::Decompress";
assert!(is_service_binpath_compression_obfuscated(binpath));
}
#[test]
fn binpath_io_compression_gzipstream_class_detected() {
let binpath = r"%COMSPEC% /c powershell -nop [IO.Compression.GzipStream]";
assert!(is_service_binpath_compression_obfuscated(binpath));
}
#[test]
fn binpath_plain_base64_only_not_compression_flagged() {
let binpath = "%COMSPEC% /b /c start /b /min powershell.exe -nop -w hidden -enc SQBFAFgA";
assert!(!is_service_binpath_compression_obfuscated(binpath));
}
#[test]
fn binpath_legitimate_service_not_flagged() {
let binpath = r"C:\Windows\System32\svchost.exe -k netsvcs";
assert!(!is_service_binpath_compression_obfuscated(binpath));
}
#[test]
fn binpath_empty_not_flagged_compression() {
assert!(!is_service_binpath_compression_obfuscated(""));
}
}