winreg_artifacts/
com_hijacking.rs1use std::io::Cursor;
9
10use winreg_core::hive::Hive;
11
12#[derive(Debug, Clone, serde::Serialize)]
16pub struct ComHijackInfo {
17 pub clsid: String,
19 pub hkcu_server: String,
21 pub hkcr_server: String,
23 pub is_suspicious: bool,
25 pub suspicious_reason: Option<String>,
27 pub last_written: Option<chrono::DateTime<chrono::Utc>>,
30}
31
32#[allow(clippy::similar_names)]
43pub fn classify_com_hijack(hkcr_server: &str, hkcu_server: &str) -> (bool, Option<String>) {
44 if hkcu_server.is_empty() {
45 return (false, None);
46 }
47 let lower = hkcu_server.to_ascii_lowercase();
48
49 if lower.contains("\\temp\\") {
50 return (true, Some("DLL in \\temp\\".to_string()));
51 }
52 if lower.contains("\\appdata\\") {
53 return (true, Some("DLL in \\appdata\\".to_string()));
54 }
55 if lower.contains("\\downloads\\") {
56 return (true, Some("DLL in \\downloads\\".to_string()));
57 }
58 if lower.contains("\\public\\") {
59 return (true, Some("DLL in \\public\\".to_string()));
60 }
61 if lower.contains("\\programdata\\") {
62 return (true, Some("DLL in \\programdata\\".to_string()));
63 }
64 if !hkcr_server.is_empty() && !hkcu_server.eq_ignore_ascii_case(hkcr_server) {
65 return (true, Some(format!("HKCU overrides HKCR ({hkcr_server})")));
66 }
67 (false, None)
68}
69
70#[allow(clippy::similar_names)]
78pub fn parse_pair(
79 hku_hive: &Hive<Cursor<Vec<u8>>>,
80 hkcr_hive: &Hive<Cursor<Vec<u8>>>,
81) -> Vec<ComHijackInfo> {
82 let mut results = Vec::new();
83
84 let Some(clsid_key) = open_user_clsid_key(hku_hive) else {
85 return results;
86 };
87
88 let Ok(guids) = clsid_key.subkeys() else {
89 return results;
90 };
91
92 for guid_key in guids {
93 let clsid = guid_key.name();
94
95 let Ok(Some(inproc)) = guid_key.subkey("InprocServer32") else {
97 continue;
98 };
99
100 let hkcu_server = read_default_value(&inproc);
101 if hkcu_server.is_empty() {
102 continue;
103 }
104
105 let hkcr_server = read_hkcr_server(hkcr_hive, &clsid);
107
108 let (is_suspicious, suspicious_reason) = classify_com_hijack(&hkcr_server, &hkcu_server);
109
110 results.push(ComHijackInfo {
111 clsid,
112 hkcu_server,
113 hkcr_server,
114 is_suspicious,
115 suspicious_reason,
116 last_written: guid_key.last_written(),
117 });
118 }
119
120 results
121}
122
123pub fn parse_hkcu_only(hku_hive: &Hive<Cursor<Vec<u8>>>) -> Vec<ComHijackInfo> {
127 let mut results = Vec::new();
128
129 let Some(clsid_key) = open_user_clsid_key(hku_hive) else {
130 return results;
131 };
132
133 let Ok(guids) = clsid_key.subkeys() else {
134 return results;
135 };
136
137 for guid_key in guids {
138 let clsid = guid_key.name();
139
140 let Ok(Some(inproc)) = guid_key.subkey("InprocServer32") else {
141 continue;
142 };
143
144 let hkcu_server = read_default_value(&inproc);
145 if hkcu_server.is_empty() {
146 continue;
147 }
148
149 let (is_suspicious, suspicious_reason) = classify_com_hijack("", &hkcu_server);
150
151 results.push(ComHijackInfo {
152 clsid,
153 hkcu_server,
154 hkcr_server: String::new(),
155 is_suspicious,
156 suspicious_reason,
157 last_written: guid_key.last_written(),
158 });
159 }
160
161 results
162}
163
164fn open_user_clsid_key(hive: &Hive<Cursor<Vec<u8>>>) -> Option<winreg_core::key::Key<'_>> {
170 ["Software\\Classes\\CLSID", "Classes\\CLSID", "CLSID"]
171 .iter()
172 .find_map(|path| hive.open_key(path).ok().flatten())
173}
174
175fn read_default_value(key: &winreg_core::key::Key<'_>) -> String {
177 let Ok(vals) = key.values() else {
178 return String::new();
179 };
180 for val in vals {
181 if val.name().is_empty() {
182 return val.as_string().unwrap_or_default();
183 }
184 }
185 String::new()
186}
187
188fn read_hkcr_server(hkcr_hive: &Hive<Cursor<Vec<u8>>>, clsid: &str) -> String {
192 let paths = [
193 format!("SOFTWARE\\Classes\\CLSID\\{clsid}\\InprocServer32"),
194 format!("Classes\\CLSID\\{clsid}\\InprocServer32"),
195 format!("CLSID\\{clsid}\\InprocServer32"),
196 ];
197 for path in &paths {
198 if let Ok(Some(k)) = hkcr_hive.open_key(path) {
199 let s = read_default_value(&k);
200 if !s.is_empty() {
201 return s;
202 }
203 }
204 }
205 String::new()
206}