Last active
August 9, 2024 02:08
-
-
Save KatelynHaworth/1ac37f7fb6018293fc43f86993db24fc to your computer and use it in GitHub Desktop.
Example of run an interactive process on the current user from system service on windows (Golang)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
@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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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?