I am trying to get the UPN of a logged user while running as a service. I was able to get the Username
of the logged in users in format DOMAIN\username
, along with the SessionID
of the user and a lot of other things.
To get the UPN, I tried to impersonate the user by getting an access token and running the whoami /upn
command with this target token. It works pretty well when the service does this directly, but it doesn't work when a sub-process of this service do it.
Here's the go example that fetches the UPN of the logged in users. As a matter of fact, I can't simply get the current user's UPN, because the application is running as a service
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
"unsafe"
winapi "github.com/kumako/go-win64api"
"golang.org/x/sys/windows"
"qohash.com/qostodian-agent/internal/logging"
)
var (
modwtsapi32 *windows.LazyDLL = windows.NewLazySystemDLL("wtsapi32.dll")
modadvapi32 *windows.LazyDLL = windows.NewLazySystemDLL("advapi32.dll")
procWTSQueryUserToken *windows.LazyProc = modwtsapi32.NewProc("WTSQueryUserToken")
procDuplicateTokenEx *windows.LazyProc = modadvapi32.NewProc("DuplicateTokenEx")
)
func GetUserSessions() ([]User, error) {
var u []User
users, err := winapi.ListLoggedInUsers()
if err != nil {
return u, err
}
for _, user := range users {
var upn string
var err error
// UPN Translation doesn't work -> try to impersonate the session and run whoami command
upn, err= getUpnForLoggedUser(user.SessionID)
if err!= nil {
logging.Debugf("could not get UPN for user %v: \n\t - First attempt: %v \n\t ", user.FullUser(), err)
} else {
logging.Debugf("User UPN: %v", upn)
}
u = append(u, User{
Username: user.Username,
Domain: user.Domain,
Upn: upn,
})
}
return u, nil
}
func getUpnForLoggedUser(sessionID uint32) (string, error) {
userToken, err := duplicateUserTokenFromSessionID(windows.Handle(sessionID))
if err != nil {
return "", fmt.Errorf("get duplicate user token for current user session: %s", err)
}
winPath, ok := os.LookupEnv("SystemRoot")
if !ok {
return "", errors.New("Cannot obtain SystemRoot environment variable")
}
cmd := exec.Command(fmt.Sprintf("%s\\system32\\whoami.exe", winPath), "/upn")
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
Token: userToken,
}
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
userToken.Close()
return strings.TrimSpace(string(out)), nil
}
func duplicateUserTokenFromSessionID(sessionId windows.Handle) (syscall.Token, error) {
var (
impersonationToken windows.Handle = 0
userToken syscall.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(windows.SecurityImpersonation), uintptr(windows.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
}
When my main service calls GetUserSessions
it works and return the users with the logged in UPNs.
If this main service spawn a subprocess, the UPN is always empty. When calling the whoami
, the application gets fork/exec C:\WINDOWS\system32\whoami.exe: Access is denied
.
My questions are:
whoami /upn
command as another service ?Thank you!
User contributions licensed under CC BY-SA 3.0