Windowsでsuしたい

投稿日:
最終更新日:

この記事はhydrogen Advent Calender 2024の19日目の記事です。

Windows API

Windowsでsuしたい。単に管理者権限で実行するのではなく、ユーザーを切り替えて実行したい。runasコマンドがあるだろとは言われるが、runasは管理者権限が失われる上に、管理者権限で起動してもパスワードの入力が必須となる。

だが、条件付きではあるもののパスワードの入力なしに別のユーザーでプロセスを起動する方法がある。既存のプロセスからログイン情報を拝借すればいいのである。

OpenProcessTokenとDuplicateTokenEx

OpenProcessToken関数を使うことで、プロセスから実行しているユーザーの情報が入ったトークンを取得することができる。

但し、普通にやると利用可能な形で得られないのでSeDebugPrivilege特権を使って、複製可能な形で得る必要がある。DuplicateTokenEx関数を使うことで、そのトークンを複製し、プロセスを起動できる形にすることができる。

なお、プロセスを起動できるようにするためにPrimaryTokenに変換し、ImpersonationLevelSecurityAnonymousを指定する。

CreateProcessWithTokenW

CreateProcessWithTokenW関数を使うことで、複製したトークンを使ってプロセスを起動することができる。

但し、この操作にはSeAssignPrimaryTokenPrivilege特権が必要である。

以上から分かる通りこの操作は管理者権限を要する。

実装例

Rustで実装したものがこちらである。

    let handle = unsafe {
        let pid: u32 = arg[1].parse::<u32>()?;
        OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid)?
    };
    let token = unsafe {
        let mut token = HANDLE::default();
        OpenProcessToken(
            handle,
            TOKEN_DUPLICATE,
            &mut token
        )?;
        token
    };
    ...
    let prim_token = unsafe {
        let mut mtoken = HANDLE::default();
        DuplicateTokenEx(
            token,
            TOKEN_ALL_ACCESS,
            None,
            SecurityAnonymous,
            TokenPrimary,
            &mut mtoken
        )?;
        mtoken
    };
    ...
    let lpstartupinfo = STARTUPINFOW {
        cb: size_of::<STARTUPINFOW>() as u32,
        lpReserved: PWSTR::null(),
        lpDesktop: PWSTR::null(),
        lpTitle: PWSTR::null(),
        dwX: 0,
        dwY: 0,
        dwXSize: 0,
        dwYSize: 0,
        dwXCountChars: 0,
        dwYCountChars: 0,
        dwFillAttribute: 0,
        dwFlags: STARTUPINFOW_FLAGS::default(),
        wShowWindow: 0,
        cbReserved2: 0,
        lpReserved2: null_mut(),
        hStdInput: stdin,
        hStdOutput: stdout,
        hStdError: stderr,
    };
    ...
    unsafe {
        CreateProcessWithTokenW(
            prim_token,
            LOGON_WITH_PROFILE,
            None,
            PWSTR::from_raw(cmd),
            CREATE_NEW_CONSOLE,
            None,
            None,
            &lpstartupinfo,
            &mut PROCESS_INFORMATION::default(),
        )?
    };
    ...

このプログラムは引数としてプロセスIDを受け取り、そのプロセスのユーザーでcmd.exeを対話可能な状態で起動する。

実際に起動してみよう。管理者権限で起動したコマンドプロンプトから、winlogon.exeのプロセスIDを指定してこのプログラムを実行する。

token_steal.exe 25800

すると別ウィンドウが立ち上がり、このウィンドウでコマンドを実行してみよう。

> whoami
nt authority\system

無事成功した。

条件について

このプログラムで切り替えられるユーザーは、実行中のプロセスが存在するものに限られる。それゆえにまだログインしていないユーザーに切り替えることはできない。

Windowsにおいては、ログインしていないユーザーに切り替えることは通常の手段では不可能である。

謝辞

ryota2357さんにコードを少し修正していただきました。ありがとうございます。