|
// +build windows |
|
|
|
package main |
|
|
|
import ( |
|
"fmt" |
|
"math" |
|
"os" |
|
"os/exec" |
|
"syscall" |
|
"unsafe" |
|
|
|
"golang.org/x/sys/windows" |
|
"golang.org/x/sys/windows/svc/eventlog" |
|
) |
|
|
|
func main() { |
|
if len(os.Args) > 1 { |
|
// Running as child process, print environment variables |
|
fmt.Println("Environment variables: ", os.Environ()) |
|
return |
|
} |
|
|
|
log, err := eventlog.Open("BugTestService") |
|
if err != nil { |
|
return |
|
} |
|
|
|
execPath, err := os.Executable() |
|
if err != nil { |
|
return |
|
} |
|
|
|
output, err := withoutEnv(execPath) |
|
if err != nil { |
|
return |
|
} |
|
_ = log.Info(1, "Without Env Set: " + output) |
|
|
|
output, err = withEnv(execPath) |
|
if err != nil { |
|
return |
|
} |
|
_ = log.Info(1, "With Env Set: " + output) |
|
} |
|
|
|
func withoutEnv(path string) (string, error) { |
|
attr, _, err := getChildProcessSysProcAttr() |
|
if err != nil { |
|
return "", err |
|
} |
|
defer windows.Close(windows.Handle(attr.Token)) |
|
|
|
cmd := exec.Command(path, "child") |
|
cmd.SysProcAttr = attr |
|
defer windows.Close(windows.Handle(attr.Token)) |
|
|
|
output, err := cmd.Output() |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
return string(output), nil |
|
} |
|
|
|
func withEnv(path string) (string, error) { |
|
attr, env, err := getChildProcessSysProcAttr() |
|
if err != nil { |
|
return "", err |
|
} |
|
defer windows.Close(windows.Handle(attr.Token)) |
|
|
|
cmd := exec.Command(path, "child") |
|
cmd.SysProcAttr = attr |
|
cmd.Env = env |
|
defer windows.Close(windows.Handle(attr.Token)) |
|
|
|
output, err := cmd.Output() |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
return string(output), nil |
|
} |
|
|
|
// wtsEnumerateSessions queries the Windows kernel |
|
// to obtain a list of active user sessions attached |
|
// to the current Window Terminal Server |
|
func wtsEnumerateSessions() ([]*windows.WTS_SESSION_INFO, error) { |
|
var sessionPointer uintptr |
|
var sessionCount uint32 |
|
|
|
err := windows.WTSEnumerateSessions(0, 0, 1, (**windows.WTS_SESSION_INFO)(unsafe.Pointer(&sessionPointer)), &sessionCount) |
|
if err != nil { |
|
return nil, fmt.Errorf("enumerate terminal server sessions: %w", err) |
|
} |
|
defer windows.WTSFreeMemory(sessionPointer) |
|
|
|
sessions := make([]*windows.WTS_SESSION_INFO, sessionCount) |
|
size := unsafe.Sizeof(windows.WTS_SESSION_INFO{}) |
|
for i := range sessions { |
|
sessions[i] = (*windows.WTS_SESSION_INFO)(unsafe.Pointer(sessionPointer + (size * uintptr(i)))) |
|
} |
|
|
|
return sessions, nil |
|
} |
|
|
|
// getCurrentUserSessionID enumerates the active |
|
// terminal server sessions to find the active |
|
// terminal session and returns the session ID. |
|
// |
|
// If no active session can be found it will return |
|
// a value of MaxUint32. |
|
func getCurrentUserSessionId() (uint32, error) { |
|
sessionList, err := wtsEnumerateSessions() |
|
if err != nil { |
|
return 0, fmt.Errorf("obtain terminal server session list: %w", err) |
|
} |
|
|
|
for i := range sessionList { |
|
if sessionList[i].State == windows.WTSActive { |
|
return sessionList[i].SessionID, nil |
|
} |
|
} |
|
|
|
return math.MaxUint32, nil |
|
} |
|
|
|
// duplicateUserTokenFromSession will obtain the token |
|
// for the session ID provided, it will then duplicate |
|
// the token with permissions to launch a process as the |
|
// related user |
|
func duplicateUserTokenFromSessionID(sessionID uint32) (syscall.Token, error) { |
|
var impersonationToken, userToken windows.Token |
|
|
|
if err := windows.WTSQueryUserToken(sessionID, &impersonationToken); err != nil { |
|
return 0, fmt.Errorf("query user token for session ID: %w", err) |
|
} |
|
|
|
if err := windows.DuplicateTokenEx(impersonationToken, 0, nil, windows.SecurityImpersonation, windows.TokenPrimary, &userToken); err != nil { |
|
return 0, fmt.Errorf("duplicate user token with desired security: %w", err) |
|
} |
|
|
|
if err := windows.CloseHandle(windows.Handle(impersonationToken)); err != nil { |
|
return 0, fmt.Errorf("close handle for original user token: %w", err) |
|
} |
|
|
|
return syscall.Token(userToken), nil |
|
} |
|
|
|
//go:linkname environForSysProcAttr os.environForSysProcAttr |
|
func environForSysProcAttr(sys *syscall.SysProcAttr) (env []string, err error) |
|
|
|
// getChildProcessSysProcAttr will first obtain the |
|
// current active session ID and will duplicate a user |
|
// token from that session. |
|
// |
|
// With the user token it will construct process attributes |
|
// to allow the process to be started in the user's execution |
|
// context. |
|
func getChildProcessSysProcAttr() (attr *syscall.SysProcAttr, env []string, err error) { |
|
attr = &syscall.SysProcAttr{ |
|
HideWindow: true, |
|
} |
|
|
|
sessionID, err := getCurrentUserSessionId() |
|
if err != nil { |
|
return nil, nil, fmt.Errorf("get current user session ID: %w", err) |
|
} |
|
|
|
attr.Token, err = duplicateUserTokenFromSessionID(sessionID) |
|
if err != nil { |
|
return nil, nil, fmt.Errorf("duplicate user token from session ID: %w", err) |
|
} |
|
|
|
// ========== |
|
// Makes it all work |
|
// =========== |
|
env, err = environForSysProcAttr(attr) |
|
if err != nil { |
|
return nil, nil, fmt.Errorf("load environment variables for user: %w", err) |
|
} |
|
|
|
return |
|
} |