-
-
Save mholt/f52b56653de12dd9075db1b2ce914bd8 to your computer and use it in GitHub Desktop.
package main | |
import ( | |
"github.com/kardianos/service" | |
"log" | |
"flag" | |
) | |
type Service struct {} | |
var logger service.Logger | |
func (*Service) Start(_ service.Service) error { | |
if err := StartProcessAsCurrentUser("notepad.exe", "", ""); err != nil { | |
return err | |
} | |
return nil | |
} | |
func (*Service) Stop(_ service.Service) error { | |
return nil | |
} | |
var serviceFlag = flag.String("service", "", "Control the service") | |
func main() { | |
svcConfig := &service.Config{ | |
Name: "RunAsUserTest", | |
DisplayName: "Run As User Test", | |
Description: "Service to test launching programs as user from service", | |
} | |
svc := &Service{} | |
s, err := service.New(svc, svcConfig) | |
if err != nil { | |
log.Fatal(err) | |
} | |
flag.Parse() | |
if *serviceFlag != "" { | |
if err := service.Control(s, *serviceFlag); err != nil { | |
log.Fatal(err) | |
} | |
return | |
} | |
logger, err = s.Logger(nil) | |
if err != nil { | |
log.Fatal(err) | |
} | |
err = s.Run() | |
if err != nil { | |
logger.Error(err) | |
} | |
} |
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), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(appPath))), commandLine, 0, 0, 0, | |
uintptr(creationFlags), uintptr(envInfo), workingDir, uintptr(unsafe.Pointer(&startupInfo)), uintptr(unsafe.Pointer(&processInfo)), | |
); returnCode == 0 { | |
return fmt.Errorf("create process as user: %s", err) | |
} | |
return nil | |
} |
@BelodedAleksey Might be a bit late to the party, but if it can help someone, your service needs to be running as SYSTEM for this to work. Works for me.
To support that above statement, calling WTSQueryUserToken() requires your service process to have SE_TCB_NAME privilege enabled which is available to an NT USER(SYSTEM) which is part of the trusted computer base.
After register this RunAsUserTest as a windows service by command : main.exe -service install
and main.exe -service start
, it won't be error like "WTSQueryUserToken: A required privilege is not held by the client", works fine, good job.
Hey!
I used your code in my project and modified it to start the process for all active users.
I noticed a memory leak when I repeatedly call WTSEnumerateSessionsW.
I looked at the MS documentation I suggested that I need to free the WTS_SESSION_INFO memory in the WTSEnumerateSessionsW function. I tried to free the WTS_SESSION_INFO memory area
by calling:
modwtsapi32a = syscall.NewLazyDLL ("wtsapi32.dll")
procWTSFreeMemory = modwtsapi32a.NewProc ("WTSFreeMemory")
syscall.Syscall (procWTSFreeMemory.Addr (), 1, uintptr (unsafe.Pointer (& sessionInformation)), 0, 0)
But the memory still leaks away. What else could be causing the leak?
Excellent gits!!!
But when I want to hide the console window using const CREATE_NO_WINDOW
, it failed to compile:
And I searched MSDN:
Then I changed uint16
to uint32
, and it worked!
const (
CREATE_UNICODE_ENVIRONMENT uint32 = 0x00000400 // changed from uint16 to uint32
CREATE_NO_WINDOW = 0x08000000
CREATE_NEW_CONSOLE = 0x00000010
)
I got error "WTSQueryUserToken: A required privilege is not held by the client". Can you please tell me what can i do to get code working?
I tried to execute RevertToSelf and OpenThreadToken before WTSQuery, but results are the same with error "WTSQueryUserToken: A required privilege is not held by the client"