-
Rust: 스타크래프트1 멀티 실행기 만들기 (windows api)컴퓨터/Rust 2024. 4. 22. 14:25728x90반응형
위 프로젝트를 보고, Rust로 windows-rs API는 개발이 잘되어가는지 궁금해서
위 앱을 Rust로 만들어보았다. (CLI + GUI)
windows-rs
API 문서 따위 아직 없다. 그냥 알아서 필요한거 찾아서 쓰고, 없는 건 만들어 써야 한다.
docs.rs의 검색 기능 때문에 원하는 구조체, 함수 등 찾기는 쉽다.
우선 "핸들" 이라는 것을 닫는 것이 목표이다.
핸들이란 파일, 레지스트리 키, 이벤트 또는 시스템 객체와 같은 시스템 리소스에 대한 추상적 참조이다.
프로세스 핸들을 작업하기 위해서는 관리자 권한이 필요하다.
Rust로 조금 digging 을 하다 보면 그렇게 어려운 작업은 아니었다.
use std::ptr; use windows::core::PWSTR; use windows::Win32::Foundation::{BOOL, HANDLE, HWND, PSID}; use windows::Win32::Security::{ AllocateAndInitializeSid, CheckTokenMembership, FreeSid, SID_IDENTIFIER_AUTHORITY, }; use windows::Win32::System::SystemServices::{ DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID, }; use windows::Win32::UI::Shell::ShellExecuteW; pub fn is_admin() -> bool { unsafe { // pub struct PSID(pub *mut core::ffi::c_void); let mut sid: PSID = PSID(ptr::null_mut()); let nt_authority: SID_IDENTIFIER_AUTHORITY = SID_IDENTIFIER_AUTHORITY { Value: [0, 0, 0, 0, 0, 5], // SECURITY_NT_AUTHORITY }; if AllocateAndInitializeSid( &nt_authority, 2, SECURITY_BUILTIN_DOMAIN_RID as u32, DOMAIN_ALIAS_RID_ADMINS as u32, 0, 0, 0, 0, 0, 0, &mut sid, ) .is_ok() { let mut is_member = BOOL(0); let result = CheckTokenMembership(HANDLE(0), sid, &mut is_member); FreeSid(sid); return result.is_ok() && is_member.as_bool(); } } false } pub fn run_as_admin() -> bool { unsafe { let result = ShellExecuteW( HWND(0), PWSTR("runas".encode_utf16().collect::<Vec<u16>>().as_ptr() as _), PWSTR( std::env::current_exe() .unwrap() .to_str() .unwrap() .encode_utf16() .collect::<Vec<u16>>() .as_ptr() as _, ), PWSTR(ptr::null_mut()), PWSTR(ptr::null_mut()), windows::Win32::UI::WindowsAndMessaging::SW_SHOW, ); !result.is_invalid() } } #[tokio::main] async fn main() { if !is_admin() { println!("관리자 실행 안했네요..."); if run_as_admin() { // 관리자 권한 요청하기 println!("실패, 다시 관리자 권한으로 실행해주세요..."); return; } else { println!("실패, 종료."); return; } } ... }
그러나 버퍼들은 C++에서는 쉽지만, Rust에서는 unsafe 랑 포인터 섞어서 써야 해서 힘들었다.
ZwQueryInformationProcess는 아직 windows-rs에 없어서 만들어야 했다.
검색해서 파라미터마다 그냥 windows-rs에서 타입 불러서 만들면 된다.
#[link(name = "ntdll.dll", kind = "raw-dylib", modifiers = "+verbatim")] extern "system" { pub fn ZwQueryInformationProcess( ProcessHandle: HANDLE, ProcessInformationClass: PROCESSINFOCLASS, ProcessInformation: *mut std::ffi::c_void, ProcessInformationLength: u32, ReturnLength: *mut u32, ) -> NTSTATUS; } // let mut buffer: Vec<u8> = Vec::new(); let mut dw_length: u32 = 0; let mut status: NTSTATUS = custom_windows::ZwQueryInformationProcess( process_handle, ProcessHandleInformation, std::ptr::null_mut(), // initially pass a null pointer 0, // and a length of 0 &mut dw_length, ); while status == STATUS_INFO_LENGTH_MISMATCH { buffer.resize(dw_length as usize, 0); status = custom_windows::ZwQueryInformationProcess( process_handle, ProcessHandleInformation, buffer.as_mut_ptr() as *mut c_void, buffer.len() as u32, &mut dw_length, ); }
PROCESSENTRY32W 을 쓰면 wide string이라 편하지만, PROCESSENTRY32를 쓴다면
ASCII 문자열이기 때문에 변환해서 프로세스 이름을 찾을 수 있다.
let starcraft_name = b"StarCraft.exe\0"; // ANSI string with null termination if Process32First(h_snapshot.get_handle(), &mut entry as *mut PROCESSENTRY32).is_ok() { ... if unsafe { // 이름 체크 std::ffi::CStr::from_ptr(entry.szExeFile.as_ptr()).to_bytes_with_nul() == starcraft_name } ... }
핸들을 찾고 닫을 때는
// 원하는 이름이 들어가 있는 핸들 닫기 if name .to_string_lossy() .contains("YOOOO") { let status = DuplicateHandle( process_handle, handle, GetCurrentProcess(), &mut copy_handle, MAXIMUM_ALLOWED, false, DUPLICATE_CLOSE_SOURCE, ); // 닫아버리기 if status.is_ok() { println!("Closed"); let _ = CloseHandle(copy_handle); } }
자세한 건 아래 Github에 올려놓은 코드를 확인하면 좋다.
https://github.com/Alfex4936/SC1-Multi-Launcher
728x90'컴퓨터 > Rust' 카테고리의 다른 글
Meilisearch: Rust로 작성된 ElasticSearch (0) 2024.07.12 Rust: 비동기 리팩토링 tokio::task::JoinSet (0) 2024.04.06 Rust: OAuth2 구글, Github, 카카오, 네이버 로그인 (1) 2023.08.06