From 41987a9a58b93c92e3be3dec58f3cfe05e3042b4 Mon Sep 17 00:00:00 2001 From: BTMuli Date: Mon, 29 Dec 2025 21:28:49 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20=E4=BD=BF=E7=94=A8=20widestring?= =?UTF-8?q?=20=E6=9B=BF=E4=BB=A3=E7=8E=B0=E6=9C=89=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/watchdog.rs | 43 +++++++++++++-------------- src-tauri/src/yae/inject.rs | 58 ++++++++++++++++++------------------- 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/src-tauri/src/watchdog.rs b/src-tauri/src/watchdog.rs index 36598854..02dd5cac 100644 --- a/src-tauri/src/watchdog.rs +++ b/src-tauri/src/watchdog.rs @@ -1,35 +1,37 @@ //! 重启提权相关处理 -//! @since Beta v0.8.7 +//! @since Beta v0.9.1 #![cfg(target_os = "windows")] -use std::ffi::OsStr; -use std::iter::once; -use std::os::windows::ffi::OsStrExt; use std::ptr::null_mut; use std::time::Duration; use tauri::AppHandle; +use widestring::U16CString; use windows_sys::Win32::Foundation::{HANDLE, HWND}; use windows_sys::Win32::Storage::FileSystem::SYNCHRONIZE; use windows_sys::Win32::System::Threading::{OpenProcess, WaitForSingleObject, INFINITE}; use windows_sys::Win32::UI::Shell::ShellExecuteW; use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL; -// 带参数启动 +/// 带参数启动(使用 ShellExecuteW + runas) fn shell_runas_with_args(args: &str) -> Result<(), String> { - fn to_wide(s: &OsStr) -> Vec { - s.encode_wide().chain(once(0)).collect() - } - let exe_path = std::env::current_exe().map_err(|e| e.to_string())?; - let exe_wide = to_wide(exe_path.as_os_str()); - let args_wide = to_wide(OsStr::new(args)); - let cwd_wide = - exe_path.parent().map(|p| to_wide(p.as_os_str())).unwrap_or_else(|| to_wide(OsStr::new(""))); + + let exe_wide = + U16CString::from_os_str(exe_path.as_os_str()).map_err(|e| format!("路径转换失败: {e}"))?; + let args_wide = U16CString::from_str(args).map_err(|e| format!("参数转换失败: {e}"))?; + let cwd_wide = exe_path + .parent() + .map(|p| U16CString::from_os_str(p.as_os_str())) + .transpose() + .map_err(|e| format!("路径转换失败: {e}"))? + .unwrap_or_else(|| U16CString::from_str("").unwrap()); + + let verb = U16CString::from_str("runas").unwrap(); unsafe { let result = ShellExecuteW( 0 as HWND, - to_wide(OsStr::new("runas")).as_ptr(), + verb.as_ptr(), exe_wide.as_ptr(), args_wide.as_ptr(), if cwd_wide.len() > 1 { cwd_wide.as_ptr() } else { null_mut() }, @@ -38,17 +40,15 @@ fn shell_runas_with_args(args: &str) -> Result<(), String> { if (result as usize) > 32 { Ok(()) } else { - Err("Failed to ShellExecuteW runas".into()) + Err("ShellExecuteW runas 启动失败".into()) } } } -// 等待父进程退出(释放单例锁)后,再以管理员身份启动新实例 +/// 等待父进程退出(释放单例锁)后,再以管理员身份启动新实例 pub fn run_watchdog(parent_pid: u32, args_to_pass: &str) -> Result<(), String> { - // 打开父进程句柄用于等待 let handle: HANDLE = unsafe { OpenProcess(SYNCHRONIZE, 0, parent_pid) }; - if handle == std::ptr::null_mut() { - // 如果拿不到句柄,可能父进程已退出,稍作等待后继续 + if handle.is_null() { std::thread::sleep(Duration::from_millis(300)); } else { unsafe { @@ -56,11 +56,10 @@ pub fn run_watchdog(parent_pid: u32, args_to_pass: &str) -> Result<(), String> { } } - // 父进程已退出 → 触发 UAC 提权启动新实例 shell_runas_with_args(args_to_pass) } -// 以管理员权限重启应用 +/// 以管理员权限重启应用 #[tauri::command] pub fn run_with_admin(app_handle: AppHandle) -> Result<(), String> { let parent_pid = std::process::id(); @@ -69,11 +68,9 @@ pub fn run_with_admin(app_handle: AppHandle) -> Result<(), String> { cmd .arg("--watchdog") .arg(format!("--ppid={}", parent_pid)) - // 看门狗不加载单例插件(通过参数决定 main 的初始化) .spawn() .map_err(|e| format!("spawn watchdog failed: {e}"))?; - // 立即退出:单例锁释放 app_handle.exit(0); Ok(()) } diff --git a/src-tauri/src/yae/inject.rs b/src-tauri/src/yae/inject.rs index e75df41e..6362e7df 100644 --- a/src-tauri/src/yae/inject.rs +++ b/src-tauri/src/yae/inject.rs @@ -2,10 +2,8 @@ //! @since Beta v0.9.1 #![cfg(target_os = "windows")] -use std::ffi::OsStr; -use std::iter::once; -use std::os::windows::ffi::OsStrExt; use std::ptr; +use widestring::U16CString; use windows_sys::Win32::Foundation::{CloseHandle, FreeLibrary, HANDLE, INVALID_HANDLE_VALUE}; use windows_sys::Win32::Storage::FileSystem::PIPE_ACCESS_DUPLEX; use windows_sys::Win32::System::Diagnostics::Debug::WriteProcessMemory; @@ -24,15 +22,10 @@ use windows_sys::Win32::System::Threading::{ STARTUPINFOW, }; -/// 转为宽字符串 -pub fn to_wide_string(s: &str) -> Vec { - OsStr::new(s).encode_wide().chain(once(0)).collect() -} - /// 创建命名管道 pub fn create_named_pipe(pipe_name: &str) -> HANDLE { let full_pipe_name = format!(r"\\.\pipe\{}", pipe_name); - let wide: Vec = to_wide_string(&full_pipe_name); + let wide = U16CString::from_str(&full_pipe_name).expect("invalid pipe name"); unsafe { let handle = CreateNamedPipeW( @@ -52,46 +45,52 @@ pub fn create_named_pipe(pipe_name: &str) -> HANDLE { } } -/// 启动目标进程,附加cwd +/// 启动目标进程,附加 cwd 或 ticket pub fn spawn_process(path: &str, ticket: Option) -> PROCESS_INFORMATION { - let wide_path: Vec = to_wide_string(path); // 如果有 ticket,构造命令行 - let mut wide_cmd: Option> = None; - if let Some(ref t) = ticket { + let wide_path = U16CString::from_str(path).expect("invalid path"); + + let wide_cmd = ticket.as_ref().map(|t| { let cmdline = format!("{} login_auth_ticket={}", path, t); - wide_cmd = Some(to_wide_string(&cmdline)); - } // 如果没有 ticket,使用 cwd - let wide_cwd: Option> = if ticket.is_none() { - let cwd = std::path::Path::new(path).parent().unwrap().to_str().unwrap(); - Some(to_wide_string(cwd)) + U16CString::from_str(&cmdline).expect("invalid cmdline") + }); + + let wide_cwd: Option = if ticket.is_none() { + std::path::Path::new(path) + .parent() + .and_then(|p| Some(U16CString::from_os_str(p.as_os_str()).unwrap())) } else { None }; + unsafe { let mut si: STARTUPINFOW = std::mem::zeroed(); si.cb = std::mem::size_of::() as u32; let mut pi: PROCESS_INFORMATION = std::mem::zeroed(); + let success = CreateProcessW( wide_path.as_ptr(), - wide_cmd.as_mut().map(|v| v.as_mut_ptr()).unwrap_or(ptr::null_mut()), // 有 ticket 时传命令行 + wide_cmd.as_ref().map(|s| s.as_ptr() as *mut u16).unwrap_or(ptr::null_mut()), ptr::null_mut(), ptr::null_mut(), 0, 0, ptr::null_mut(), - wide_cwd.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null()), // 无 ticket 时传 cwd + wide_cwd.as_ref().map(|s: &widestring::U16CString| s.as_ptr()).unwrap_or(ptr::null()), &mut si, &mut pi, ); + if success == 0 { panic!("CreateProcessW failed"); } + pi } } /// 注入 DLL pub fn inject_dll(pi: &PROCESS_INFORMATION, dll_path: &str) { - let dll_utf16: Vec = to_wide_string(dll_path); + let dll_utf16 = U16CString::from_str(dll_path).expect("invalid dll path"); let size = dll_utf16.len() * 2; unsafe { @@ -107,7 +106,7 @@ pub fn inject_dll(pi: &PROCESS_INFORMATION, dll_path: &str) { } let k32 = GetModuleHandleA(b"kernel32.dll\0".as_ptr()); - if k32 == std::ptr::null_mut() { + if k32.is_null() { panic!("GetModuleHandleA failed"); } @@ -164,33 +163,32 @@ pub fn find_module_base(pid: u32, dll_name: &str) -> Option { /// 执行 YaeMain pub fn call_yaemain(pi: &PROCESS_INFORMATION, base: usize, dll_path: &str) { - let dll_path_wide: Vec = to_wide_string(dll_path); + let dll_path_wide = U16CString::from_str(dll_path).expect("invalid dll path"); + unsafe { let local = - LoadLibraryExW(dll_path_wide.as_ptr(), std::ptr::null_mut(), DONT_RESOLVE_DLL_REFERENCES); - if local == std::ptr::null_mut() { + LoadLibraryExW(dll_path_wide.as_ptr(), ptr::null_mut(), DONT_RESOLVE_DLL_REFERENCES); + if local.is_null() { panic!("LoadLibraryExW failed"); } let proc = GetProcAddress(local, b"YaeMain\0".as_ptr()).expect("无法找到 YaeMain"); - // Option isize> - let proc_addr = proc as *const () as usize; + let proc_addr = proc as usize; let rva = proc_addr - local as usize; println!("YaeMain RVA: {:#x}", rva); FreeLibrary(local); let remote_yaemain = (base + rva) as *mut std::ffi::c_void; - // 在远程进程里调用 YaeMain(hModule) CreateRemoteThread( pi.hProcess, - std::ptr::null_mut(), + ptr::null_mut(), 0, Some(std::mem::transmute(remote_yaemain)), base as *mut _, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); } }