CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

Add DOWN security scanner v0.2 with quarantine and browser fix #3984

Merged⚡ AI-generatedXSccantynz wants to mergeclaude/windows-antivirus-tool-xG3zYmainopened Mar 30, 2026
2 changed files+290−40
Modifieddown-scanner/src/scanner/network.rs+197−29View fileUnifiedSplit
1use crate::signatures::ip_blocklist::{KNOWN_BAD_DOMAINS, KNOWN_BAD_DNS, KNOWN_BAD_IP_PREFIXES, LEGITIMATE_HOSTS_ENTRIES};
1use crate::signatures::ip_blocklist::{
2 KNOWN_BAD_DOMAINS, KNOWN_BAD_DNS, KNOWN_BAD_IP_PREFIXES, LEGITIMATE_HOSTS_ENTRIES,
3};
24use crate::threat::{Severity, Threat, ThreatAction, ThreatCategory};
35use std::fs;
46use std::path::Path;
57
8/// Scan network configuration for threats
69pub fn scan() -> Vec<Threat> {
710 let mut threats = Vec::new();
11
12 // Check hosts file for tampering
813 check_hosts_file(&mut threats);
9 #[cfg(windows)] check_network_connections(&mut threats);
10 #[cfg(windows)] check_dns_settings(&mut threats);
11 #[cfg(not(windows))] check_resolv_conf(&mut threats);
14
15 // Check for suspicious network connections (Windows)
16 #[cfg(windows)]
17 check_network_connections(&mut threats);
18
19 // Check DNS configuration (Windows)
20 #[cfg(windows)]
21 check_dns_settings(&mut threats);
22
23 // Check proxy hijacking (Windows)
24 #[cfg(windows)]
25 check_proxy_hijack(&mut threats);
26
27 // On non-Windows, do basic hosts file check and resolv.conf
28 #[cfg(not(windows))]
29 check_resolv_conf(&mut threats);
30
1231 threats
1332}
1433
34/// Check if system proxy has been hijacked by malware
35#[cfg(windows)]
36fn check_proxy_hijack(threats: &mut Vec<Threat>) {
37 use winreg::enums::*;
38 use winreg::RegKey;
39
40 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
41 let key_path = r"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
42 let key = match hkcu.open_subkey(key_path) {
43 Ok(k) => k,
44 Err(_) => return,
45 };
46
47 let proxy_enable: u32 = key.get_value("ProxyEnable").unwrap_or(0);
48 if proxy_enable == 0 {
49 return; // Proxy not enabled \u{2014} clean
50 }
51
52 let proxy_server: String = key.get_value("ProxyServer").unwrap_or_default();
53 let auto_config_url: String = key.get_value("AutoConfigURL").unwrap_or_default();
54
55 if !proxy_server.is_empty() {
56 threats.push(Threat {
57 name: format!("Proxy server configured: {}", proxy_server),
58 severity: Severity::High,
59 category: ThreatCategory::ProxyHijack,
60 location: format!("{}\\ProxyServer", key_path),
61 description: format!(
62 "Your internet traffic is being routed through proxy server '{}'. \
63 If you didn't set this yourself, malware may be intercepting your traffic.",
64 proxy_server
65 ),
66 action: ThreatAction::ResetProxy,
67 });
68 }
69
70 if !auto_config_url.is_empty() {
71 threats.push(Threat {
72 name: format!("Proxy auto-config URL: {}", auto_config_url),
73 severity: Severity::High,
74 category: ThreatCategory::ProxyHijack,
75 location: format!("{}\\AutoConfigURL", key_path),
76 description: format!(
77 "A proxy auto-config (PAC) file is set to '{}'. \
78 This can redirect your traffic through a malicious proxy.",
79 auto_config_url
80 ),
81 action: ThreatAction::ResetProxy,
82 });
83 }
84}
85
1586fn check_hosts_file(threats: &mut Vec<Threat>) {
16 let hosts_path = if cfg!(windows) { Path::new(r"C:\Windows\System32\drivers\etc\hosts") } else { Path::new("/etc/hosts") };
17 let content = match fs::read_to_string(hosts_path) { Ok(c) => c, Err(_) => return };
87 let hosts_path = if cfg!(windows) {
88 Path::new(r"C:\Windows\System32\drivers\etc\hosts")
89 } else {
90 Path::new("/etc/hosts")
91 };
92
93 let content = match fs::read_to_string(hosts_path) {
94 Ok(c) => c,
95 Err(_) => return, // Can't read hosts file \u{2014} might need admin
96 };
97
1898 let mut suspicious_entries = Vec::new();
99
19100 for line in content.lines() {
20101 let trimmed = line.trim();
21 if trimmed.is_empty() || trimmed.starts_with('#') { continue; }
102
103 // Skip comments and empty lines
104 if trimmed.is_empty() || trimmed.starts_with('#') {
105 continue;
106 }
107
108 // Parse the line: IP hostname [hostname2 ...]
22109 let parts: Vec<&str> = trimmed.split_whitespace().collect();
23 if parts.len() < 2 { continue; }
110 if parts.len() < 2 {
111 continue;
112 }
113
24114 let ip = parts[0];
25115 let hostnames: Vec<&str> = parts[1..].to_vec();
116
117 // Check if hostnames are legitimate
26118 for hostname in &hostnames {
27 let is_legitimate = LEGITIMATE_HOSTS_ENTRIES.iter().any(|legit| hostname.eq_ignore_ascii_case(legit));
119 let is_legitimate = LEGITIMATE_HOSTS_ENTRIES
120 .iter()
121 .any(|legit| hostname.eq_ignore_ascii_case(legit));
122
28123 if !is_legitimate {
124 // Check if the entry is redirecting known good sites (hijacking)
29125 let is_redirect = ip != "127.0.0.1" && ip != "::1" && ip != "0.0.0.0";
30126 let is_blocking = ip == "127.0.0.1" || ip == "0.0.0.0";
127
31128 if is_redirect {
129 // Redirecting to a non-local IP \u{2014} very suspicious
32130 suspicious_entries.push(format!("{} -> {} (REDIRECT)", hostname, ip));
33131 } else if is_blocking {
34 let important_domains = ["windowsupdate", "microsoft.com", "google.com", "chrome.google.com", "update.googleapis.com"];
35 if important_domains.iter().any(|d| hostname.to_lowercase().contains(d)) {
36 suspicious_entries.push(format!("{} BLOCKED by hosts file (could prevent updates)", hostname));
132 // Blocking entries could be ad-blockers (legitimate) or malware
133 // Only flag if it's blocking important domains
134 let important_domains = [
135 "windowsupdate", "microsoft.com", "google.com",
136 "chrome.google.com", "update.googleapis.com",
137 ];
138 if important_domains
139 .iter()
140 .any(|d| hostname.to_lowercase().contains(d))
141 {
142 suspicious_entries
143 .push(format!("{} BLOCKED by hosts file (could prevent updates)", hostname));
37144 }
38145 }
39146 }
40147 }
148
149 // Check if the IP points to known bad addresses
41150 for (bad_prefix, description) in KNOWN_BAD_IP_PREFIXES {
42151 if ip.starts_with(bad_prefix) {
43152 threats.push(Threat {
44153 name: "Hosts file points to malicious IP".to_string(),
45 severity: Severity::Critical, category: ThreatCategory::HostsTampering,
154 severity: Severity::Critical,
155 category: ThreatCategory::HostsTampering,
46156 location: hosts_path.to_string_lossy().to_string(),
47 description: format!("Hosts entry '{}' redirects to suspicious IP {} \u{2014} {}", hostnames.join(", "), ip, description),
157 description: format!(
158 "Hosts entry '{}' redirects to suspicious IP {} \u{2014} {}",
159 hostnames.join(", "),
160 ip,
161 description
162 ),
48163 action: ThreatAction::ManualReview,
49164 });
50165 }
51166 }
167
168 // Check for known bad domains in hosts file
52169 for (bad_domain, description) in KNOWN_BAD_DOMAINS {
53170 for hostname in &hostnames {
54171 if hostname.to_lowercase().contains(bad_domain) {
55172 threats.push(Threat {
56173 name: format!("Known bad domain in hosts: {}", hostname),
57 severity: Severity::High, category: ThreatCategory::HostsTampering,
174 severity: Severity::High,
175 category: ThreatCategory::HostsTampering,
58176 location: hosts_path.to_string_lossy().to_string(),
59 description: format!("Hosts file references known malicious domain pattern '{}' \u{2014} {}", bad_domain, description),
177 description: format!(
178 "Hosts file references known malicious domain pattern '{}' \u{2014} {}",
179 bad_domain, description
180 ),
60181 action: ThreatAction::ManualReview,
61182 });
62183 }
63184 }
64185 }
65186 }
187
66188 if !suspicious_entries.is_empty() {
67189 threats.push(Threat {
68190 name: "Hosts file modifications detected".to_string(),
69 severity: Severity::High, category: ThreatCategory::HostsTampering,
191 severity: Severity::High,
192 category: ThreatCategory::HostsTampering,
70193 location: hosts_path.to_string_lossy().to_string(),
71 description: format!("Found {} suspicious entries in hosts file:\n {}", suspicious_entries.len(), suspicious_entries.join("\n ")),
194 description: format!(
195 "Found {} suspicious entries in hosts file:\n {}",
196 suspicious_entries.len(),
197 suspicious_entries.join("\n ")
198 ),
72199 action: ThreatAction::ManualReview,
73200 });
74201 }
76203
77204#[cfg(windows)]
78205fn check_network_connections(threats: &mut Vec<Threat>) {
79 let output = match std::process::Command::new("netstat").args(["-an"]).output() { Ok(o) => o, Err(_) => return };
206 // Use netstat via command to list connections
207 let output = match std::process::Command::new("netstat")
208 .args(["-an"])
209 .output()
210 {
211 Ok(o) => o,
212 Err(_) => return,
213 };
214
80215 let stdout = String::from_utf8_lossy(&output.stdout);
216
81217 for line in stdout.lines() {
82218 let parts: Vec<&str> = line.split_whitespace().collect();
83 if parts.len() < 3 { continue; }
219 if parts.len() < 3 {
220 continue;
221 }
222
223 // Check for connections to known bad IPs
84224 let remote = parts.get(2).unwrap_or(&"");
85225 for (bad_prefix, description) in KNOWN_BAD_IP_PREFIXES {
86226 if remote.starts_with(bad_prefix) {
87227 threats.push(Threat {
88228 name: format!("Connection to suspicious IP: {}", remote),
89 severity: Severity::Critical, category: ThreatCategory::SuspiciousNetwork,
229 severity: Severity::Critical,
230 category: ThreatCategory::SuspiciousNetwork,
90231 location: format!("Active connection: {} -> {}", parts.get(1).unwrap_or(&"?"), remote),
91 description: format!("Active network connection to known suspicious IP range \u{2014} {}", description),
232 description: format!(
233 "Active network connection to known suspicious IP range \u{2014} {}",
234 description
235 ),
92236 action: ThreatAction::ManualReview,
93237 });
94238 }
98242
99243#[cfg(windows)]
100244fn check_dns_settings(threats: &mut Vec<Threat>) {
101 let output = match std::process::Command::new("ipconfig").args(["/all"]).output() { Ok(o) => o, Err(_) => return };
245 // Check DNS via ipconfig
246 let output = match std::process::Command::new("ipconfig")
247 .args(["/all"])
248 .output()
249 {
250 Ok(o) => o,
251 Err(_) => return,
252 };
253
102254 let stdout = String::from_utf8_lossy(&output.stdout);
255
103256 for line in stdout.lines() {
104257 let trimmed = line.trim();
105258 if trimmed.contains("DNS Servers") || trimmed.contains("DNS-Server") {
259 // Extract IP from the line
106260 if let Some(ip_part) = trimmed.split(':').nth(1) {
107261 let ip = ip_part.trim();
108262 for (bad_dns, description) in KNOWN_BAD_DNS {
109263 if ip.starts_with(bad_dns) {
110264 threats.push(Threat {
111265 name: format!("Malicious DNS server: {}", ip),
112 severity: Severity::Critical, category: ThreatCategory::DnsTampering,
266 severity: Severity::Critical,
267 category: ThreatCategory::DnsTampering,
113268 location: "Network adapter DNS settings".to_string(),
114 description: format!("DNS server {} is known malicious \u{2014} {}. Your DNS queries may be intercepted.", ip, description),
269 description: format!(
270 "DNS server {} is known malicious \u{2014} {}. \
271 Your DNS queries may be intercepted.",
272 ip, description
273 ),
115274 action: ThreatAction::ManualReview,
116275 });
117276 }
123282
124283#[cfg(not(windows))]
125284fn check_resolv_conf(threats: &mut Vec<Threat>) {
126 let content = match fs::read_to_string("/etc/resolv.conf") { Ok(c) => c, Err(_) => return };
285 let resolv_path = "/etc/resolv.conf";
286 let content = match fs::read_to_string(resolv_path) {
287 Ok(c) => c,
288 Err(_) => return,
289 };
290
127291 for line in content.lines() {
128292 let trimmed = line.trim();
129293 if trimmed.starts_with("nameserver") {
132296 if ip.starts_with(bad_dns) {
133297 threats.push(Threat {
134298 name: format!("Malicious DNS server: {}", ip),
135 severity: Severity::Critical, category: ThreatCategory::DnsTampering,
136 location: "/etc/resolv.conf".to_string(),
137 description: format!("DNS server {} is known malicious \u{2014} {}", ip, description),
299 severity: Severity::Critical,
300 category: ThreatCategory::DnsTampering,
301 location: resolv_path.to_string(),
302 description: format!(
303 "DNS server {} is known malicious \u{2014} {}",
304 ip, description
305 ),
138306 action: ThreatAction::ManualReview,
139307 });
140308 }
Modifieddown-scanner/src/scanner/scareware.rs+93−11View fileUnifiedSplit
11use crate::signatures::process_names::KNOWN_BAD_PROCESSES;
2#[cfg(windows)]
3use crate::signatures::safe_tasks::SAFE_TASK_PATTERNS;
24use crate::threat::{Severity, Threat, ThreatAction, ThreatCategory};
35use std::fs;
46use std::path::PathBuf;
2729pub fn scan() -> Vec<Threat> {
2830 let mut threats = Vec::new();
2931 scan_program_dirs(&mut threats);
30 #[cfg(windows)] scan_installed_programs_registry(&mut threats);
31 #[cfg(windows)] scan_scheduled_tasks(&mut threats);
32 #[cfg(windows)]
33 scan_installed_programs_registry(&mut threats);
34 #[cfg(windows)]
35 scan_scheduled_tasks(&mut threats);
36 #[cfg(windows)]
37 check_defender_tampering(&mut threats);
3238 threats
3339}
3440
4753 name: format!("Scareware installed: {}", folder_name),
4854 severity: Severity::High, category: ThreatCategory::Scareware,
4955 location: entry.path().to_string_lossy().to_string(),
50 description: format!("Program folder '{}' matches known scareware '{}'. These programs often show fake scan results to trick you into paying.", folder_name, scareware_name),
56 description: format!("Program folder '{}' matches known scareware '{}'. These programs show fake scan results to trick you into paying.", folder_name, scareware_name),
5157 action: ThreatAction::QuarantineFile(entry.path().to_string_lossy().to_string()),
5258 });
5359 break;
9399 if display_lower.contains(&scareware_name.to_lowercase()) {
94100 let install_location: String = subkey.get_value("InstallLocation").unwrap_or_default();
95101 let uninstall_string: String = subkey.get_value("UninstallString").unwrap_or_default();
102 let quiet_uninstall: String = subkey.get_value("QuietUninstallString").unwrap_or_default();
103 let best_uninstall = if !quiet_uninstall.is_empty() { quiet_uninstall } else { uninstall_string.clone() };
104
96105 threats.push(Threat {
97106 name: format!("Installed scareware: {}", display_name),
98107 severity: Severity::High, category: ThreatCategory::Scareware,
99108 location: if install_location.is_empty() { format!("Registry: {}\\{}", path, subkey_name) } else { install_location },
100109 description: format!("Installed program '{}' matches known scareware '{}'. Uninstall command: {}", display_name, scareware_name, if uninstall_string.is_empty() { "Not available".to_string() } else { uninstall_string }),
101 action: ThreatAction::ManualReview,
110 action: ThreatAction::UninstallProgram {
111 uninstall_string: best_uninstall,
112 name: display_name.clone(),
113 },
102114 });
103115 break;
104116 }
113125 let stdout = String::from_utf8_lossy(&output.stdout);
114126 for line in stdout.lines().skip(1) {
115127 let lower = line.to_lowercase();
128
129 // Skip known safe tasks
130 if SAFE_TASK_PATTERNS.iter().any(|safe| lower.contains(safe)) {
131 continue;
132 }
133
134 // Extract task name from CSV (first field)
135 let task_name = line.split(',').next().unwrap_or("").trim_matches('"').to_string();
136 if task_name.is_empty() || task_name == "TaskName" { continue; }
137
116138 for bad_name in KNOWN_BAD_PROCESSES {
117139 if lower.contains(bad_name) {
118140 threats.push(Threat {
119 name: format!("Suspicious scheduled task matching '{}'", bad_name),
141 name: format!("Malicious scheduled task: {}", task_name),
120142 severity: Severity::High, category: ThreatCategory::SuspiciousStartup,
121143 location: "Windows Task Scheduler".to_string(),
122 description: format!("Scheduled task matches known threat '{}'. Task details: {}", bad_name, line.chars().take(200).collect::<String>()),
123 action: ThreatAction::ManualReview,
144 description: format!("Scheduled task '{}' matches known threat '{}'.", task_name, bad_name),
145 action: ThreatAction::DeleteScheduledTask { task_name: task_name.clone() },
124146 });
125147 break;
126148 }
127149 }
128150 for scareware_name in SCAREWARE_DISPLAY_NAMES {
129151 if lower.contains(&scareware_name.to_lowercase()) {
130 let already = threats.iter().any(|t| t.location == "Windows Task Scheduler" && t.description.contains(scareware_name));
152 let already = threats.iter().any(|t| matches!(&t.action, ThreatAction::DeleteScheduledTask { task_name: tn } if *tn == task_name));
131153 if !already {
132154 threats.push(Threat {
133 name: format!("Scareware scheduled task: {}", scareware_name),
155 name: format!("Scareware scheduled task: {}", task_name),
134156 severity: Severity::High, category: ThreatCategory::Scareware,
135157 location: "Windows Task Scheduler".to_string(),
136 description: format!("Scheduled task for known scareware '{}' found. This keeps the scareware running.", scareware_name),
137 action: ThreatAction::ManualReview,
158 description: format!("Scheduled task '{}' belongs to known scareware '{}'.", task_name, scareware_name),
159 action: ThreatAction::DeleteScheduledTask { task_name: task_name.clone() },
138160 });
139161 }
140162 break;
143165 }
144166}
145167
168/// Check if Windows Defender has been tampered with by malware
169#[cfg(windows)]
170fn check_defender_tampering(threats: &mut Vec<Threat>) {
171 use winreg::enums::*;
172 use winreg::RegKey;
173
174 // Check DisableAntiSpyware policy
175 let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
176 if let Ok(key) = hklm.open_subkey(r"SOFTWARE\Policies\Microsoft\Windows Defender") {
177 let disabled: u32 = key.get_value("DisableAntiSpyware").unwrap_or(0);
178 if disabled != 0 {
179 threats.push(Threat {
180 name: "Windows Defender DISABLED by policy".to_string(),
181 severity: Severity::Critical,
182 category: ThreatCategory::DefenderTampering,
183 location: r"HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\DisableAntiSpyware".to_string(),
184 description: "Windows Defender has been disabled via Group Policy. This is a common technique used by malware to prevent detection. Your PC has NO active antivirus protection.".to_string(),
185 action: ThreatAction::RestoreDefender,
186 });
187 }
188 }
189
190 // Check if Defender service is running
191 let output = std::process::Command::new("sc").args(["query", "WinDefend"]).output();
192 if let Ok(o) = output {
193 let stdout = String::from_utf8_lossy(&o.stdout);
194 if stdout.contains("STOPPED") {
195 let already = threats.iter().any(|t| t.category == ThreatCategory::DefenderTampering);
196 if !already {
197 threats.push(Threat {
198 name: "Windows Defender service STOPPED".to_string(),
199 severity: Severity::Critical,
200 category: ThreatCategory::DefenderTampering,
201 location: "Service: WinDefend".to_string(),
202 description: "The Windows Defender service is not running. Malware may have stopped it to avoid detection.".to_string(),
203 action: ThreatAction::RestoreDefender,
204 });
205 }
206 }
207 }
208
209 // Check if real-time protection is disabled
210 if let Ok(key) = hklm.open_subkey(r"SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection") {
211 let disabled: u32 = key.get_value("DisableRealtimeMonitoring").unwrap_or(0);
212 if disabled != 0 {
213 let already = threats.iter().any(|t| t.category == ThreatCategory::DefenderTampering);
214 if !already {
215 threats.push(Threat {
216 name: "Defender real-time protection DISABLED".to_string(),
217 severity: Severity::Critical,
218 category: ThreatCategory::DefenderTampering,
219 location: r"HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection".to_string(),
220 description: "Real-time protection has been disabled via policy. Malware can run freely without being caught.".to_string(),
221 action: ThreatAction::RestoreDefender,
222 });
223 }
224 }
225 }
226}
227
146228fn get_program_directories() -> Vec<PathBuf> {
147229 let mut dirs = Vec::new();
148230 if cfg!(windows) {
149231
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts