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}
28
29pub fn classify_com_hijack(hkcr_server: &str, hkcu_server: &str) -> (bool, Option<String>) {
38 if hkcu_server.is_empty() {
39 return (false, None);
40 }
41 let lower = hkcu_server.to_ascii_lowercase();
42
43 if lower.contains("\\temp\\") {
44 return (true, Some("DLL in \\temp\\".to_string()));
45 }
46 if lower.contains("\\appdata\\") {
47 return (true, Some("DLL in \\appdata\\".to_string()));
48 }
49 if lower.contains("\\downloads\\") {
50 return (true, Some("DLL in \\downloads\\".to_string()));
51 }
52 if lower.contains("\\public\\") {
53 return (true, Some("DLL in \\public\\".to_string()));
54 }
55 if lower.contains("\\programdata\\") {
56 return (true, Some("DLL in \\programdata\\".to_string()));
57 }
58 if !hkcr_server.is_empty() && !hkcu_server.eq_ignore_ascii_case(hkcr_server) {
59 return (true, Some(format!("HKCU overrides HKCR ({hkcr_server})")));
60 }
61 (false, None)
62}
63
64pub fn parse_pair(
71 hku_hive: &Hive<Cursor<Vec<u8>>>,
72 hkcr_hive: &Hive<Cursor<Vec<u8>>>,
73) -> Vec<ComHijackInfo> {
74 let mut results = Vec::new();
75
76 let clsid_key = match hku_hive.open_key("Software\\Classes\\CLSID") {
77 Ok(Some(k)) => k,
78 _ => return results,
79 };
80
81 let guids = match clsid_key.subkeys() {
82 Ok(v) => v,
83 Err(_) => return results,
84 };
85
86 for guid_key in guids {
87 let clsid = guid_key.name();
88
89 let inproc = match guid_key.subkey("InprocServer32") {
91 Ok(Some(k)) => k,
92 _ => continue,
93 };
94
95 let hkcu_server = read_default_value(&inproc);
96 if hkcu_server.is_empty() {
97 continue;
98 }
99
100 let hkcr_server = read_hkcr_server(hkcr_hive, &clsid);
102
103 let (is_suspicious, suspicious_reason) = classify_com_hijack(&hkcr_server, &hkcu_server);
104
105 results.push(ComHijackInfo {
106 clsid,
107 hkcu_server,
108 hkcr_server,
109 is_suspicious,
110 suspicious_reason,
111 });
112 }
113
114 results
115}
116
117pub fn parse_hkcu_only(hku_hive: &Hive<Cursor<Vec<u8>>>) -> Vec<ComHijackInfo> {
121 let mut results = Vec::new();
122
123 let clsid_key = match hku_hive.open_key("Software\\Classes\\CLSID") {
124 Ok(Some(k)) => k,
125 _ => return results,
126 };
127
128 let guids = match clsid_key.subkeys() {
129 Ok(v) => v,
130 Err(_) => return results,
131 };
132
133 for guid_key in guids {
134 let clsid = guid_key.name();
135
136 let inproc = match guid_key.subkey("InprocServer32") {
137 Ok(Some(k)) => k,
138 _ => continue,
139 };
140
141 let hkcu_server = read_default_value(&inproc);
142 if hkcu_server.is_empty() {
143 continue;
144 }
145
146 let (is_suspicious, suspicious_reason) = classify_com_hijack("", &hkcu_server);
147
148 results.push(ComHijackInfo {
149 clsid,
150 hkcu_server,
151 hkcr_server: String::new(),
152 is_suspicious,
153 suspicious_reason,
154 });
155 }
156
157 results
158}
159
160fn read_default_value(key: &winreg_core::key::Key<'_>) -> String {
164 let vals = match key.values() {
165 Ok(v) => v,
166 Err(_) => return String::new(),
167 };
168 for val in vals {
169 if val.name().is_empty() {
170 return val.as_string().unwrap_or_default();
171 }
172 }
173 String::new()
174}
175
176fn read_hkcr_server(hkcr_hive: &Hive<Cursor<Vec<u8>>>, clsid: &str) -> String {
180 let paths = [
181 format!("SOFTWARE\\Classes\\CLSID\\{clsid}\\InprocServer32"),
182 format!("Classes\\CLSID\\{clsid}\\InprocServer32"),
183 format!("CLSID\\{clsid}\\InprocServer32"),
184 ];
185 for path in &paths {
186 if let Ok(Some(k)) = hkcr_hive.open_key(path) {
187 let s = read_default_value(&k);
188 if !s.is_empty() {
189 return s;
190 }
191 }
192 }
193 String::new()
194}