UWSC UAC環境下で昇格ダイアログ起動と降格起動

UWSC公式掲示板に、管理者権限で起動したUWSCを一時的に降格する方法はないか、という質問があった。
CreateProcessWithTokenWでやれば良いとのことだったので、面白そうだからやってみた。



スクリプト

OPTION EXPLICIT

IFB GET_UWSC_NAME = "UserToken.uws" THEN
	IFB UT.IsUserAnAdmin() THEN
		IFB MSGBOX("管理者権限持ってます。<#CR>一般権限で再起動しますか?", BTN_YES OR BTN_NO) = BTN_YES THEN
			MSGBOX(UT.RunWithToken(GET_UWSC_DIR + "\uwsc.exe", GET_UWSC_NAME))
		ENDIF
		IFB MSGBOX("一時的に一般権限にしますか?", BTN_YES OR BTN_NO) = BTN_YES THEN
			DIM hToken = UT.GetToken()
			IFB VARTYPE(hToken) < VAR_BSTR THEN     // 正常は数値。エラーは文字列
				IF !UT.ImpersonateLoggedOnUser(hToken) THEN MSGBOX("権限偽装に失敗しました")
				UT.CloseHandle(hToken)  // トークンが不要になったらCloseHandleする
			ELSE
				MSGBOX(hToken)
			ENDIF
			IFB UT.IsUserAnAdmin() THEN
				MSGBOX("管理者権限あるままです")        // 指定トークンに管理者権限があるとこっち
			ELSE
				MSGBOX("管理者権限ありません<#CR>、、、けどプロセスはありで作られます、、、")
			ENDIF
			UT.RevertToSelf()       // 偽装はここまで
			IFB UT.IsUserAnAdmin() THEN
				MSGBOX("管理者権限もどりました")
			ELSE
				MSGBOX("管理者権限もどりません???")  // こっちにはならないはず
			ENDIF
		ENDIF
	ELSE
		IFB MSGBOX("管理者権限持ってません。<#CR>管理者権限で再起動しますか?", BTN_YES OR BTN_NO) = BTN_YES THEN
			UT.RunAs(GET_UWSC_DIR + "\uwsc.exe", GET_UWSC_NAME)
		ENDIF
	ENDIF
ENDIF


MODULE UT

	// 昇格実行用
	PROCEDURE RunAs(path, para=EMPTY, cd=EMPTY)
		IF cd = EMPTY THEN cd = GET_CUR_DIR
		WITH CREATEOLEOBJ("Shell.Application")
			.ShellExecute(path, para, cd, "runas", 1)
		ENDWITH
	FEND

	// 指定プロセスIDのトークンを取得する
	// プロセスID省略時は、シェルのトークンを取得する
	// 正常は数値が返る。使い終わったらCloseHandleすること
	FUNCTION GetToken(pid=-1)
		IF pid = -1 THEN GetWindowThreadProcessId(GetShellWindow(), pid)
		DIM hProc = OpenProcess($02000000, FALSE, pid)  // MAXIMUM_ALLOWED
		IFB hProc THEN
			DIM hToken
			IFB OpenProcessToken(hProc, $F01FF, hToken) THEN        // TOKEN_ALL_ACCESS
				DIM hTokenDup
				IFB DuplicateTokenEx(hToken, $02000000, 0, 3, 1, hTokenDup) THEN
					RESULT = hTokenDup
				ELSE
					RESULT = "DuplicateTokenEx error."
				ENDIF
				CloseHandle(hToken)
			ELSE
				RESULT = "OpenProcessToken error."
			ENDIF
			CloseHandle(hProc)
		ELSE
			RESULT = "OpenProcess error."
		ENDIF
	FEND

	// 指定したトークンでプロセスを起動する
	// 要管理者権限
	// 正常はプロセスID
	FUNCTION RunWithToken(path, para=EMPTY, cd=EMPTY, hToken=NULL)
		IF cd = EMPTY THEN cd = GET_CUR_DIR
		DIM sui[16], pi[3], res, bClose = (hToken = NULL)
		IF bClose THEN hToken = GetToken()
		SETCLEAR(sui, 0)        // いらんけどなんとなく初期化
		sui[0] = 4 * 17         // これはいる
		// コマンドラインの指定が、、、何か変で小細工
		IFB LENGTH(para) THEN
			IF LENGTH(path) THEN para = path + " " + para
			res = CreateProcessWithTokenW(hToken, 0, NULL, para, 0, NULL, cd, sui, pi)
		ELSE
			res = CreateProcessWithTokenW(hToken, 0, path, para, 0, NULL, cd, sui, pi)
		ENDIF
		IFB res THEN
			// スレッドハンドル・プロセスハンドルは閉じる。終了待ちするなら使うけど
			CloseHandle(pi[1])
			CloseHandle(pi[0])
			RESULT = pi[2]
		ELSE
			RESULT = "CreateProcessWithTokenW error."
		ENDIF
		IF bClose THEN CloseHandle(hToken)
	FEND

	// 管理者権限有無確認用
	DEF_DLL IsUserAnAdmin():BOOL:shell32

	// スレッド権限偽装用。Imp...からRevert...までが指定トークンで偽装される
	DEF_DLL ImpersonateLoggedOnUser(DWORD):BOOL:advapi32
	DEF_DLL RevertToSelf():BOOL:advapi32

	// その他必要Win32API
	DEF_DLL GetShellWindow():HWND:user32
	DEF_DLL GetWindowThreadProcessId(HWND,var DWORD):DWORD:user32
	DEF_DLL OpenProcess(DWORD,BOOL,DWORD):DWORD:kernel32
	DEF_DLL OpenProcessToken(DWORD,DWORD,var DWORD):BOOL:advapi32
	DEF_DLL CloseHandle(DWORD):BOOL:kernel32
	DEF_DLL DuplicateTokenEx(DWORD,DWORD,DWORD,int,int,var DWORD):BOOL:advapi32
	DEF_DLL CreateProcessWithTokenW(DWORD,DWORD,wstring,var wstring,DWORD,pWchar,wstring,DWORD[],DWORD[]):BOOL:advapi32

	TEXTBLOCK struct_STARTUPINFO
		DWORD cb;
		LPTSTR lpReserved;
		LPTSTR lpDesktop;
		LPTSTR lpTitle;
		DWORD dwX;
		DWORD dwY;
		DWORD dwXSize;
		DWORD dwYSize;
		DWORD dwXCountChars;
		DWORD dwYCountChars;
		DWORD dwFillAttribute;
		DWORD dwFlags;
		WORD wShowWindow;
		WORD cbReserved2;
		LPBYTE lpReserved2;
		HANDLE hStdInput;
		HANDLE hStdOutput;
		HANDLE hStdError;
	ENDTEXTBLOCK    // STARTUPINFO,  *LPSTARTUPINFO;
	TEXTBLOCK struct_PROCESS_INFORMATION
		HANDLE hProcess;
		HANDLE hThread;
		DWORD dwProcessId;
		DWORD dwThreadId;
	ENDTEXTBLOCK    // PROCESS_INFORMATION,  *LPPROCESS_INFORMATION;

ENDMODULE

はまったポイント

  • トークンリンクで取得したトークンでは、スレッド偽装&CreateProcessもCreateProcessWithTokenWもエラーとなった(権限が不足?)
  • SaferCreateLevelでは、特権のまま
  • ということで降格トークンの取得は、シェルのトークンのコピーとした
  • スレッド偽装&EXECではダメだった(特権のまま)
  • CreateProcessWithTokenWにコマンドラインを上手く渡せなかった

備考

Powershellで特権Powershellの起動は「start powershell -Verb runas」