-
-
Save KatelynHaworth/1ac37f7fb6018293fc43f86993db24fc 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 | |
} |
thank you for sharing this! native.go is very useful and you spared me from dealing with windows api!
@LiamHaworth Hello, I'm getting get duplicate user token for current user session: call native WTSQueryUserToken: A required privilege is not held by the client.
error when I try to run StartProcessAsCurrentUser
. I searched but I couldn't find anything. Do you have any idea?
@LiamHaworth Hello, I'm getting
get duplicate user token for current user session: call native WTSQueryUserToken: A required privilege is not held by the client.
error when I try to runStartProcessAsCurrentUser
. I searched but I couldn't find anything. Do you have any idea?
It needs "SeTcbPrivilege".
I think your program must be run with LocalSystem(windows service).
@lv254165754 Thank you. I will try it again.
thank you for sharing this! native.go is very useful and you spared me from dealing with windows api!