1use std::ffi::c_void;
2use std::os::windows::ffi::OsStrExt;
3use std::ptr::{null, null_mut};
4use windows_sys::core::{GUID, HRESULT};
5use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, S_FALSE, S_OK};
6use windows_sys::Win32::Security::{
7 GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY,
8};
9use windows_sys::Win32::System::Com::{
10 CoGetObject, CoInitializeEx, CoUninitialize, BIND_OPTS3, CLSCTX_LOCAL_SERVER,
11 COINIT_APARTMENTTHREADED,
12};
13use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress};
14use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
15use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOW;
16
17const RPC_E_CHANGED_MODE: HRESULT = 0x80010106u32 as i32;
18
19const IID_ICMLUAUTIL: GUID = GUID {
20 data1: 0x6EDD6D74,
21 data2: 0xC007,
22 data3: 0x4E75,
23 data4: [0xB7, 0x6A, 0xE5, 0x74, 0x09, 0x95, 0xE2, 0x4C],
24};
25
26#[repr(C)]
27struct ICMLuaUtilVtbl {
28 _query_interface: *const c_void,
29 _add_ref: *const c_void,
30 release: unsafe extern "system" fn(this: *mut ICMLuaUtil) -> u32,
31 _reserved: [*const c_void; 6],
32 shell_exec: unsafe extern "system" fn(
33 this: *mut ICMLuaUtil,
34 lp_file: *const u16,
35 lp_parameters: *const u16,
36 lp_directory: *const u16,
37 f_mask: u32,
38 n_show: u32,
39 ) -> HRESULT,
40}
41
42#[repr(C)]
43struct ICMLuaUtil {
44 lp_vtbl: *const ICMLuaUtilVtbl,
45}
46
47#[repr(C)]
48#[derive(Clone, Copy)]
49struct UnicodeString {
50 length: u16,
51 maximum_length: u16,
52 buffer: *mut u16,
53}
54
55type RtlInitUnicodeStringFn = unsafe extern "system" fn(*mut UnicodeString, *const u16);
56type RtlAcquirePebLockFn = unsafe extern "system" fn();
57type RtlReleasePebLockFn = unsafe extern "system" fn();
58
59struct Ntdll {
60 rtl_init_unicode_string: RtlInitUnicodeStringFn,
61 rtl_acquire_peb_lock: RtlAcquirePebLockFn,
62 rtl_release_peb_lock: RtlReleasePebLockFn,
63}
64
65impl Ntdll {
66 unsafe fn load() -> Result<Self, Error> {
67 let ntdll = GetModuleHandleW(windows_sys::core::w!("ntdll.dll"));
68 if ntdll.is_null() {
69 return Err(Error::Masquerade("ntdll.dll module handle not found"));
70 }
71
72 let p_init_unicode = GetProcAddress(ntdll, c"RtlInitUnicodeString".as_ptr() as *const u8);
73 let p_acquire_lock = GetProcAddress(ntdll, c"RtlAcquirePebLock".as_ptr() as *const u8);
74 let p_release_lock = GetProcAddress(ntdll, c"RtlReleasePebLock".as_ptr() as *const u8);
75
76 let rtl_init_unicode_string: RtlInitUnicodeStringFn = p_init_unicode
77 .ok_or(Error::Masquerade("RtlInitUnicodeString export not found"))
78 .map(|f| std::mem::transmute(f))?;
79
80 let rtl_acquire_peb_lock: RtlAcquirePebLockFn = p_acquire_lock
81 .ok_or(Error::Masquerade("RtlAcquirePebLock export not found"))
82 .map(|f| std::mem::transmute(f))?;
83
84 let rtl_release_peb_lock: RtlReleasePebLockFn = p_release_lock
85 .ok_or(Error::Masquerade("RtlReleasePebLock export not found"))
86 .map(|f| std::mem::transmute(f))?;
87
88 Ok(Self {
89 rtl_init_unicode_string,
90 rtl_acquire_peb_lock,
91 rtl_release_peb_lock,
92 })
93 }
94}
95
96#[derive(Debug)]
98pub enum Error {
99 ComInit(HRESULT),
101 Masquerade(&'static str),
103 Activation(HRESULT),
105 ShellExec(HRESULT),
107 TokenQuery(u32),
109 CurrentExePath(std::io::Error),
111}
112
113impl std::fmt::Display for Error {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 match self {
116 Self::ComInit(hr) => write!(f, "COM initialization failed with HRESULT: {hr:#010X}"),
117 Self::Masquerade(msg) => write!(f, "Process masquerade failed: {msg}"),
118 Self::Activation(hr) => {
119 write!(f, "Elevation moniker activation failed with HRESULT: {hr:#010X}")
120 }
121 Self::ShellExec(hr) => write!(f, "ShellExec failed with HRESULT: {hr:#010X}"),
122 Self::TokenQuery(code) => write!(f, "Token query failed with OS error {code}"),
123 Self::CurrentExePath(err) => {
124 write!(f, "Failed to retrieve current executable path: {err}")
125 }
126 }
127 }
128}
129
130impl std::error::Error for Error {}
131
132fn to_wide(s: &str) -> Vec<u16> {
133 std::ffi::OsStr::new(s)
134 .encode_wide()
135 .chain(std::iter::once(0))
136 .collect()
137}
138
139fn encode_arg(arg: &str) -> String {
140 if arg.is_empty() {
141 return "\"\"".to_string();
142 }
143 if !arg.contains([' ', '\t', '\n', '\x0b', '\"']) {
144 return arg.to_string();
145 }
146
147 let mut result = String::with_capacity(arg.len() + 2);
148 result.push('"');
149
150 let mut backslashes = 0;
151 for c in arg.chars() {
152 if c == '\\' {
153 backslashes += 1;
154 } else if c == '"' {
155 for _ in 0..backslashes * 2 + 1 {
156 result.push('\\');
157 }
158 backslashes = 0;
159 result.push('"');
160 } else {
161 for _ in 0..backslashes {
162 result.push('\\');
163 }
164 backslashes = 0;
165 result.push(c);
166 }
167 }
168
169 for _ in 0..backslashes * 2 {
170 result.push('\\');
171 }
172 result.push('"');
173
174 result
175}
176
177struct TokenGuard(HANDLE);
178
179impl Drop for TokenGuard {
180 fn drop(&mut self) {
181 unsafe { CloseHandle(self.0) };
182 }
183}
184
185pub fn is_elevated() -> Result<bool, Error> {
187 unsafe {
188 let mut token: HANDLE = null_mut();
189 if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
190 return Err(Error::TokenQuery(GetLastError()));
191 }
192
193 let _guard = TokenGuard(token);
194
195 let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 };
196 let mut return_length = 0u32;
197
198 let result = GetTokenInformation(
199 token,
200 TokenElevation,
201 &mut elevation as *mut _ as *mut c_void,
202 std::mem::size_of::<TOKEN_ELEVATION>() as u32,
203 &mut return_length,
204 );
205
206 if result == 0 {
207 return Err(Error::TokenQuery(GetLastError()));
208 }
209
210 Ok(elevation.TokenIsElevated != 0)
211 }
212}
213
214struct PebOffsets {
215 ldr: usize,
216 params: usize,
217 in_load_order_list: usize,
218 full_dll_name: usize,
219 base_dll_name: usize,
220 image_path: usize,
221 command_line: usize,
222}
223
224#[cfg(target_arch = "x86_64")]
225const OFFSETS: PebOffsets = PebOffsets {
226 ldr: 0x18,
227 params: 0x20,
228 in_load_order_list: 0x10,
229 full_dll_name: 0x48,
230 base_dll_name: 0x58,
231 image_path: 0x60,
232 command_line: 0x70,
233};
234
235#[cfg(target_arch = "x86")]
236const OFFSETS: PebOffsets = PebOffsets {
237 ldr: 0x0C,
238 params: 0x10,
239 in_load_order_list: 0x0C,
240 full_dll_name: 0x24,
241 base_dll_name: 0x2C,
242 image_path: 0x38,
243 command_line: 0x40,
244};
245
246#[cfg(target_arch = "x86_64")]
247#[inline]
248unsafe fn get_peb() -> *mut u8 {
249 let teb: *mut u8;
250 std::arch::asm!("mov {}, gs:[0x30]", out(reg) teb, options(pure, nomem, nostack));
251 if teb.is_null() {
252 return null_mut();
253 }
254 *(teb.add(0x60) as *mut *mut u8)
255}
256
257#[cfg(target_arch = "x86")]
258#[inline]
259unsafe fn get_peb() -> *mut u8 {
260 let teb: *mut u8;
261 std::arch::asm!("mov {}, fs:[0x18]", out(reg) teb, options(pure, nomem, nostack));
262 if teb.is_null() {
263 return null_mut();
264 }
265 *(teb.add(0x30) as *mut *mut u8)
266}
267
268#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
269compile_error!("buac only supports x86 and x86_64 Windows architectures");
270
271struct MasqueradeGuard {
272 image_path: *mut UnicodeString,
273 orig_image_path: UnicodeString,
274 command_line: *mut UnicodeString,
275 orig_command_line: UnicodeString,
276 full_dll_name: *mut UnicodeString,
277 orig_full_dll_name: UnicodeString,
278 base_dll_name: *mut UnicodeString,
279 orig_base_dll_name: UnicodeString,
280 acquire_lock: RtlAcquirePebLockFn,
281 release_lock: RtlReleasePebLockFn,
282}
283
284impl Drop for MasqueradeGuard {
285 fn drop(&mut self) {
286 unsafe {
287 (self.acquire_lock)();
288 *self.image_path = self.orig_image_path;
289 *self.command_line = self.orig_command_line;
290 *self.full_dll_name = self.orig_full_dll_name;
291 *self.base_dll_name = self.orig_base_dll_name;
292 (self.release_lock)();
293 }
294 }
295}
296
297unsafe fn masquerade_process() -> Result<MasqueradeGuard, Error> {
298 let ntdll = Ntdll::load()?;
299
300 let peb = get_peb();
301 if peb.is_null() {
302 return Err(Error::Masquerade("PEB address is null"));
303 }
304
305 let ldr = *(peb.add(OFFSETS.ldr) as *mut *mut u8);
306 let process_parameters = *(peb.add(OFFSETS.params) as *mut *mut u8);
307
308 if ldr.is_null() {
309 return Err(Error::Masquerade("PEB Ldr pointer is null"));
310 }
311 if process_parameters.is_null() {
312 return Err(Error::Masquerade("PEB ProcessParameters pointer is null"));
313 }
314
315 let in_load_order_list = *(ldr.add(OFFSETS.in_load_order_list) as *mut *mut u8);
316 if in_load_order_list.is_null() {
317 return Err(Error::Masquerade("Ldr InLoadOrderModuleList pointer is null"));
318 }
319
320 let image_path = process_parameters.add(OFFSETS.image_path) as *mut UnicodeString;
321 let command_line = process_parameters.add(OFFSETS.command_line) as *mut UnicodeString;
322 let full_dll_name = in_load_order_list.add(OFFSETS.full_dll_name) as *mut UnicodeString;
323 let base_dll_name = in_load_order_list.add(OFFSETS.base_dll_name) as *mut UnicodeString;
324
325 let orig_image_path = *image_path;
326 let orig_command_line = *command_line;
327 let orig_full_dll_name = *full_dll_name;
328 let orig_base_dll_name = *base_dll_name;
329
330 (ntdll.rtl_acquire_peb_lock)();
331
332 let fake_full_path = windows_sys::core::w!("C:\\Windows\\explorer.exe");
333 let fake_base_name = windows_sys::core::w!("explorer.exe");
334
335 (ntdll.rtl_init_unicode_string)(image_path, fake_full_path);
336 (ntdll.rtl_init_unicode_string)(command_line, fake_base_name);
337 (ntdll.rtl_init_unicode_string)(full_dll_name, fake_full_path);
338 (ntdll.rtl_init_unicode_string)(base_dll_name, fake_base_name);
339
340 (ntdll.rtl_release_peb_lock)();
341
342 Ok(MasqueradeGuard {
343 image_path,
344 orig_image_path,
345 command_line,
346 orig_command_line,
347 full_dll_name,
348 orig_full_dll_name,
349 base_dll_name,
350 orig_base_dll_name,
351 acquire_lock: ntdll.rtl_acquire_peb_lock,
352 release_lock: ntdll.rtl_release_peb_lock,
353 })
354}
355
356struct ComGuard {
357 need_uninit: bool,
358}
359
360impl Drop for ComGuard {
361 fn drop(&mut self) {
362 if self.need_uninit {
363 unsafe { CoUninitialize() };
364 }
365 }
366}
367
368struct LuaUtilGuard(*mut ICMLuaUtil);
369
370impl Drop for LuaUtilGuard {
371 fn drop(&mut self) {
372 if !self.0.is_null() {
373 unsafe {
374 let vtbl = (*self.0).lp_vtbl;
375 ((*vtbl).release)(self.0);
376 }
377 }
378 }
379}
380
381pub fn execute(target_path: &str, arguments: Option<&str>) -> Result<(), Error> {
385 unsafe {
386 let hr_init = CoInitializeEx(null(), COINIT_APARTMENTTHREADED as u32);
387 let _com_guard = ComGuard {
388 need_uninit: hr_init == S_OK || hr_init == S_FALSE,
389 };
390
391 if hr_init < 0 && hr_init != RPC_E_CHANGED_MODE {
392 return Err(Error::ComInit(hr_init));
393 }
394
395 let mut lua_util: *mut ICMLuaUtil = null_mut();
396 {
397 let _masquerade_guard = masquerade_process()?;
398
399 let mut bind_opts: BIND_OPTS3 = std::mem::zeroed();
400 bind_opts.Base.Base.cbStruct = std::mem::size_of::<BIND_OPTS3>() as u32;
401 bind_opts.Base.dwClassContext = CLSCTX_LOCAL_SERVER;
402
403 let hr_get = CoGetObject(
404 windows_sys::core::w!(
405 "Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}"
406 ),
407 &bind_opts as *const _ as *const _,
408 &IID_ICMLUAUTIL,
409 &mut lua_util as *mut _ as *mut *mut c_void,
410 );
411
412 if hr_get != S_OK || lua_util.is_null() {
413 return Err(Error::Activation(hr_get));
414 }
415 }
416
417 let _lua_guard = LuaUtilGuard(lua_util);
418
419 let file_w = to_wide(target_path);
420 let params_w = arguments.map(to_wide);
421 let params_ptr = params_w.as_ref().map_or(null(), |p| p.as_ptr());
422
423 let cwd = std::env::current_dir()
424 .ok()
425 .map(|p| to_wide(&p.to_string_lossy()));
426 let cwd_ptr = cwd.as_ref().map_or(null(), |p| p.as_ptr());
427
428 let hr_exec = ((*(*lua_util).lp_vtbl).shell_exec)(
429 lua_util,
430 file_w.as_ptr(),
431 params_ptr,
432 cwd_ptr,
433 0,
434 SW_SHOW as u32,
435 );
436
437 if hr_exec != S_OK {
438 return Err(Error::ShellExec(hr_exec));
439 }
440
441 Ok(())
442 }
443}
444
445pub fn spawn_elevated() -> Result<bool, Error> {
450 if is_elevated()? {
451 return Ok(false);
452 }
453
454 let current_exe = std::env::current_exe().map_err(Error::CurrentExePath)?;
455 let current_exe_str = current_exe.to_string_lossy();
456
457 let args: Vec<String> = std::env::args_os()
458 .skip(1)
459 .map(|a| a.to_string_lossy().into_owned())
460 .collect();
461
462 let args_str = if args.is_empty() {
463 None
464 } else {
465 Some(
466 args.iter()
467 .map(|a| encode_arg(a))
468 .collect::<Vec<String>>()
469 .join(" "),
470 )
471 };
472
473 execute(¤t_exe_str, args_str.as_deref())?;
474 Ok(true)
475}
476
477pub fn elevate() -> Result<(), Error> {
482 if spawn_elevated()? {
483 std::process::exit(0);
484 }
485 Ok(())
486}