package main import ( "fmt" "unsafe" "golang.org/x/sys/windows" ) var ( modwtsapi32 *windows.LazyDLL = windows.NewLazySystemDLL("wtsapi32.dll") modkernel32 *windows.LazyDLL = windows.NewLazySystemDLL("kernel32.dll") modadvapi32 *windows.LazyDLL = windows.NewLazySystemDLL("advapi32.dll") moduserenv *windows.LazyDLL = windows.NewLazySystemDLL("userenv.dll") procWTSEnumerateSessionsW *windows.LazyProc = modwtsapi32.NewProc("WTSEnumerateSessionsW") procWTSGetActiveConsoleSessionId *windows.LazyProc = modkernel32.NewProc("WTSGetActiveConsoleSessionId") procWTSQueryUserToken *windows.LazyProc = modwtsapi32.NewProc("WTSQueryUserToken") procDuplicateTokenEx *windows.LazyProc = modadvapi32.NewProc("DuplicateTokenEx") procCreateEnvironmentBlock *windows.LazyProc = moduserenv.NewProc("CreateEnvironmentBlock") procCreateProcessAsUser *windows.LazyProc = modadvapi32.NewProc("CreateProcessAsUserW") ) const ( WTS_CURRENT_SERVER_HANDLE uintptr = 0 ) type WTS_CONNECTSTATE_CLASS int const ( WTSActive WTS_CONNECTSTATE_CLASS = iota WTSConnected WTSConnectQuery WTSShadow WTSDisconnected WTSIdle WTSListen WTSReset WTSDown WTSInit ) type SECURITY_IMPERSONATION_LEVEL int const ( SecurityAnonymous SECURITY_IMPERSONATION_LEVEL = iota SecurityIdentification SecurityImpersonation SecurityDelegation ) type TOKEN_TYPE int const ( TokenPrimary TOKEN_TYPE = iota + 1 TokenImpersonazion ) type SW int const ( SW_HIDE SW = 0 SW_SHOWNORMAL = 1 SW_NORMAL = 1 SW_SHOWMINIMIZED = 2 SW_SHOWMAXIMIZED = 3 SW_MAXIMIZE = 3 SW_SHOWNOACTIVATE = 4 SW_SHOW = 5 SW_MINIMIZE = 6 SW_SHOWMINNOACTIVE = 7 SW_SHOWNA = 8 SW_RESTORE = 9 SW_SHOWDEFAULT = 10 SW_MAX = 1 ) type WTS_SESSION_INFO struct { SessionID windows.Handle WinStationName *uint16 State WTS_CONNECTSTATE_CLASS } const ( CREATE_UNICODE_ENVIRONMENT uint16 = 0x00000400 CREATE_NO_WINDOW = 0x08000000 CREATE_NEW_CONSOLE = 0x00000010 ) // GetCurrentUserSessionId will attempt to resolve // the session ID of the user currently active on // the system. func GetCurrentUserSessionId() (windows.Handle, error) { sessionList, err := WTSEnumerateSessions() if err != nil { return 0xFFFFFFFF, fmt.Errorf("get current user session token: %s", err) } for i := range sessionList { if sessionList[i].State == WTSActive { return sessionList[i].SessionID, nil } } if sessionId, _, err := procWTSGetActiveConsoleSessionId.Call(); sessionId == 0xFFFFFFFF { return 0xFFFFFFFF, fmt.Errorf("get current user session token: call native WTSGetActiveConsoleSessionId: %s", err) } else { return windows.Handle(sessionId), nil } } // WTSEnumerateSession will call the native // version for Windows and parse the result // to a Golang friendly version func WTSEnumerateSessions() ([]*WTS_SESSION_INFO, error) { var ( sessionInformation windows.Handle = windows.Handle(0) sessionCount int = 0 sessionList []*WTS_SESSION_INFO = make([]*WTS_SESSION_INFO, 0) ) if returnCode, _, err := procWTSEnumerateSessionsW.Call(WTS_CURRENT_SERVER_HANDLE, 0, 1, uintptr(unsafe.Pointer(&sessionInformation)), uintptr(unsafe.Pointer(&sessionCount))); returnCode == 0 { return nil, fmt.Errorf("call native WTSEnumerateSessionsW: %s", err) } structSize := unsafe.Sizeof(WTS_SESSION_INFO{}) current := uintptr(sessionInformation) for i := 0; i < sessionCount; i++ { sessionList = append(sessionList, (*WTS_SESSION_INFO)(unsafe.Pointer(current))) current += structSize } return sessionList, nil } // DuplicateUserTokenFromSessionID will attempt // to duplicate the user token for the user logged // into the provided session ID func DuplicateUserTokenFromSessionID(sessionId windows.Handle) (windows.Token, error) { var ( impersonationToken windows.Handle = 0 userToken windows.Token = 0 ) if returnCode, _, err := procWTSQueryUserToken.Call(uintptr(sessionId), uintptr(unsafe.Pointer(&impersonationToken))); returnCode == 0 { return 0xFFFFFFFF, fmt.Errorf("call native WTSQueryUserToken: %s", err) } if returnCode, _, err := procDuplicateTokenEx.Call(uintptr(impersonationToken), 0, 0, uintptr(SecurityImpersonation), uintptr(TokenPrimary), uintptr(unsafe.Pointer(&userToken))); returnCode == 0 { return 0xFFFFFFFF, fmt.Errorf("call native DuplicateTokenEx: %s", err) } if err := windows.CloseHandle(impersonationToken); err != nil { return 0xFFFFFFFF, fmt.Errorf("close windows handle used for token duplication: %s", err) } return userToken, nil } func StartProcessAsCurrentUser(appPath, cmdLine, workDir string) error { var ( sessionId windows.Handle userToken windows.Token envInfo windows.Handle startupInfo windows.StartupInfo processInfo windows.ProcessInformation commandLine uintptr = 0 workingDir uintptr = 0 err error ) if sessionId, err = GetCurrentUserSessionId(); err != nil { return err } if userToken, err = DuplicateUserTokenFromSessionID(sessionId); err != nil { return fmt.Errorf("get duplicate user token for current user session: %s", err) } if returnCode, _, err := procCreateEnvironmentBlock.Call(uintptr(unsafe.Pointer(&envInfo)), uintptr(userToken), 0); returnCode == 0 { return fmt.Errorf("create environment details for process: %s", err) } creationFlags := CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE startupInfo.ShowWindow = SW_SHOW startupInfo.Desktop = windows.StringToUTF16Ptr("winsta0\\default") if len(cmdLine) > 0 { commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cmdLine))) } if len(workDir) > 0 { workingDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(workDir))) } if returnCode, _, err := procCreateProcessAsUser.Call( uintptr(userToken), // hToken //uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(appPath))), // lpApplicationName uintptr(unsafe.Pointer(nil)), // lpApplicationName commandLine, // lpCommandLine 0, // lpProcessAttributes 0, // lpThreadAttributes 0, // lpInheritHandles uintptr(creationFlags), // dwCreationFlags uintptr(envInfo), // lpEnvironment workingDir, // lpCurrentDirectory uintptr(unsafe.Pointer(&startupInfo)), // lpStartupInfo uintptr(unsafe.Pointer(&processInfo)), // lpProcessInformation ); returnCode == 0 { return fmt.Errorf("create process as user: %s", err) } return nil }