Created
November 2, 2019 16:48
Revisions
-
KatelynHaworth created this gist
Nov 2, 2019 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,37 @@ Reproduction code for golang/go#35314 ===================================== This gist contains example code to reproduce golang/go#35314, due to the privilege requirements of `DuplicateTokenEx` the code is designed to run as a service which then spawns a child process and logs the output of the child process to the Windows event log. Written for go 1.13.4 ### Running the code First compile the code to a binary ```bash env GOOS=windows go build -o bugtest.exe service.go ``` Then copy the binary to a Windows machine and create a new service that uses the binary ```powershell New-Service -Name BugTestService -BinaryPathName <bugtest.exe location> -StartupType Manual ``` Finally, run the service and get the logs (The service start will error but will complete its job) ```powershell Start-Service -Name BugTestService Get-EventLog -LogName Application -Source BugTestService | Select -ExpandProperty Message ``` ### Cleaning up To remove the service run the following ```powershell Remove-Service -Name BugTestService ``` This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,185 @@ // +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 }