Windowsでsuしたい
この記事はhydrogen Advent Calender 2024の19日目の記事です。
Windows API
Windowsでsuしたい。単に管理者権限で実行するのではなく、ユーザーを切り替えて実行したい。runas
コマンドがあるだろとは言われるが、runas
は管理者権限が失われる上に、管理者権限で起動してもパスワードの入力が必須となる。
だが、条件付きではあるもののパスワードの入力なしに別のユーザーでプロセスを起動する方法がある。既存のプロセスからログイン情報を拝借すればいいのである。
OpenProcessTokenとDuplicateTokenEx
OpenProcessToken
関数を使うことで、プロセスから実行しているユーザーの情報が入ったトークンを取得することができる。
但し、普通にやると利用可能な形で得られないのでSeDebugPrivilege
特権を使って、複製可能な形で得る必要がある。DuplicateTokenEx
関数を使うことで、そのトークンを複製し、プロセスを起動できる形にすることができる。
なお、プロセスを起動できるようにするためにPrimaryToken
に変換し、ImpersonationLevel
はSecurityAnonymous
を指定する。
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さんにコードを少し修正していただきました。ありがとうございます。