win_text_inject/
target.rs1use std::path::Path;
13
14use windows::Win32::Foundation::{CloseHandle, HANDLE, HWND};
15use windows::Win32::Security::{
16 GetTokenInformation, TokenIntegrityLevel, TOKEN_MANDATORY_LABEL, TOKEN_QUERY,
17};
18use windows::Win32::System::Threading::{
19 GetCurrentProcess, OpenProcess, OpenProcessToken, QueryFullProcessImageNameW,
20 PROCESS_NAME_FORMAT, PROCESS_QUERY_LIMITED_INFORMATION,
21};
22use windows::Win32::UI::WindowsAndMessaging::{
23 GetForegroundWindow, GetWindowThreadProcessId, RealGetWindowClassW,
24};
25
26use crate::Error;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
30pub enum Integrity {
31 Untrusted,
33 Low,
35 Medium,
37 MediumPlus,
39 High,
41 System,
43 Protected,
45}
46
47impl Integrity {
48 fn from_rid(rid: u32) -> Self {
53 match rid {
54 0..=0x0FFF => Integrity::Untrusted,
55 0x1000..=0x1FFF => Integrity::Low,
56 0x2000..=0x2FFF => Integrity::Medium,
57 0x3000..=0x3FFF => Integrity::MediumPlus,
58 0x4000..=0x4FFF => Integrity::High,
59 0x5000..=0x5FFF => Integrity::System,
60 _ => Integrity::Protected,
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
71pub struct Target {
72 pub hwnd: isize,
74 pub pid: u32,
76 pub exe: String,
78 pub class: String,
80 pub integrity: Integrity,
82}
83
84impl Target {
85 pub fn foreground() -> Result<Self, Error> {
87 let hwnd = unsafe { GetForegroundWindow() };
88 if hwnd.0.is_null() {
89 return Err(Error::NoForegroundWindow);
90 }
91
92 let mut pid: u32 = 0;
93 unsafe { GetWindowThreadProcessId(hwnd, Some(&mut pid)) };
94 if pid == 0 {
95 return Err(Error::NoForegroundWindow);
96 }
97
98 Ok(Self {
99 hwnd: hwnd.0 as isize,
100 pid,
101 exe: process_exe_name(pid).unwrap_or_default(),
102 class: window_class(hwnd),
103 integrity: process_integrity(pid).unwrap_or(Integrity::Medium),
104 })
105 }
106
107 pub fn still_foreground(&self) -> bool {
109 let current = unsafe { GetForegroundWindow() };
110 current.0 as isize == self.hwnd
111 }
112
113 pub fn accepts_injection(&self) -> bool {
117 match our_integrity() {
118 Some(ours) => self.integrity <= ours,
119 None => true,
122 }
123 }
124}
125
126fn window_class(hwnd: HWND) -> String {
127 let mut buf = [0u16; 256];
128 let len = unsafe { RealGetWindowClassW(hwnd, &mut buf) };
129 String::from_utf16_lossy(&buf[..len as usize])
130}
131
132fn process_exe_name(pid: u32) -> Option<String> {
133 unsafe {
134 let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid).ok()?;
135 let mut buf = [0u16; 512];
136 let mut len = buf.len() as u32;
137 let result = QueryFullProcessImageNameW(
138 handle,
139 PROCESS_NAME_FORMAT(0),
140 windows::core::PWSTR(buf.as_mut_ptr()),
141 &mut len,
142 );
143 let _ = CloseHandle(handle);
144 result.ok()?;
145
146 let full = String::from_utf16_lossy(&buf[..len as usize]);
147 Some(
148 Path::new(&full)
149 .file_name()?
150 .to_string_lossy()
151 .to_lowercase(),
152 )
153 }
154}
155
156fn process_integrity(pid: u32) -> Option<Integrity> {
157 unsafe {
158 let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid).ok()?;
159 let level = token_integrity(handle);
160 let _ = CloseHandle(handle);
161 level
162 }
163}
164
165fn our_integrity() -> Option<Integrity> {
166 unsafe { token_integrity(GetCurrentProcess()) }
167}
168
169unsafe fn token_integrity(process: HANDLE) -> Option<Integrity> {
170 let mut token = HANDLE::default();
171 OpenProcessToken(process, TOKEN_QUERY, &mut token).ok()?;
172
173 let mut needed: u32 = 0;
174 let _ = GetTokenInformation(token, TokenIntegrityLevel, None, 0, &mut needed);
176 if needed == 0 {
177 let _ = CloseHandle(token);
178 return None;
179 }
180
181 let mut buf = vec![0u8; needed as usize];
182 let result = GetTokenInformation(
183 token,
184 TokenIntegrityLevel,
185 Some(buf.as_mut_ptr() as *mut _),
186 needed,
187 &mut needed,
188 );
189 let _ = CloseHandle(token);
190 result.ok()?;
191
192 let label = &*(buf.as_ptr() as *const TOKEN_MANDATORY_LABEL);
193 let sid = label.Label.Sid;
194 if sid.is_invalid() {
195 return None;
196 }
197
198 let count_ptr = windows::Win32::Security::GetSidSubAuthorityCount(sid);
199 if count_ptr.is_null() {
200 return None;
201 }
202 let last = (*count_ptr).saturating_sub(1) as u32;
203 let rid_ptr = windows::Win32::Security::GetSidSubAuthority(sid, last);
204 if rid_ptr.is_null() {
205 return None;
206 }
207 Some(Integrity::from_rid(*rid_ptr))
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn integrity_is_ordered() {
216 assert!(Integrity::Low < Integrity::Medium);
217 assert!(Integrity::Medium < Integrity::High);
218 assert!(Integrity::High < Integrity::System);
219 }
220
221 #[test]
222 fn rid_ranges_map_to_named_levels() {
223 assert_eq!(Integrity::from_rid(0x0000), Integrity::Untrusted);
224 assert_eq!(Integrity::from_rid(0x1000), Integrity::Low);
225 assert_eq!(Integrity::from_rid(0x2000), Integrity::Medium);
226 assert_eq!(Integrity::from_rid(0x3000), Integrity::MediumPlus);
227 assert_eq!(Integrity::from_rid(0x4000), Integrity::High);
228 assert_eq!(Integrity::from_rid(0x5000), Integrity::System);
229 }
230
231 #[test]
232 fn in_between_rids_fall_to_the_lower_named_level() {
233 assert_eq!(Integrity::from_rid(0x2100), Integrity::Medium);
235 assert_eq!(Integrity::from_rid(0x4FFF), Integrity::High);
236 }
237
238 #[test]
239 fn our_own_integrity_is_readable() {
240 assert!(our_integrity().is_some());
242 }
243
244 #[test]
245 fn a_normal_test_process_runs_at_medium_or_high() {
246 let level = our_integrity().unwrap();
247 assert!(level >= Integrity::Medium, "unexpected level {level:?}");
248 }
249}