ABOUT ME

-

Total
-
  • Rust: 스타크래프트1 멀티 실행기 만들기 (windows api)
    컴퓨터/Rust 2024. 4. 22. 14:25
    728x90
    반응형
     

    GitHub - somersby10ml/sc_multiloader

    Contribute to somersby10ml/sc_multiloader development by creating an account on GitHub.

    github.com

     

    위 프로젝트를 보고, Rust로 windows-rs API는 개발이 잘되어가는지 궁금해서

    위 앱을 Rust로 만들어보았다. (CLI + GUI)

     

    windows-rs

    API 문서 따위 아직 없다. 그냥 알아서 필요한거 찾아서 쓰고, 없는 건 만들어 써야 한다.

    docs.rs의 검색 기능 때문에 원하는 구조체, 함수 등 찾기는 쉽다.

     

    https://microsoft.github.io/windows-docs-rs/

     

    microsoft.github.io

     

    우선 "핸들" 이라는 것을 닫는 것이 목표이다.

    핸들이란 파일, 레지스트리 키, 이벤트 또는 시스템 객체와 같은 시스템 리소스에 대한 추상적 참조이다.

    ProcessHacker: flux.exe 핸들

    프로세스 핸들을 작업하기 위해서는 관리자 권한이 필요하다.

     

    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 랑 포인터 섞어서 써야 해서 힘들었다.

    C++ 코드

     

    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

     

    GitHub - Alfex4936/SC1-Multi-Launcher: StarCraft I multi launcher in Rust. using windows-rs

    StarCraft I multi launcher in Rust. using windows-rs - Alfex4936/SC1-Multi-Launcher

    github.com

     

    728x90

    댓글