From a464a86c6d8368df784a365c88fbc279a737c87c Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Wed, 15 May 2019 13:00:56 +0200 Subject: service: move WTS upstream Signed-off-by: Jason A. Donenfeld --- attacksurface.md | 2 +- service/mksyscall.go | 8 --- service/securityapi.go | 139 -------------------------------------------- service/service_manager.go | 24 ++++---- service/tokens.go | 97 +++++++++++++++++++++++++++++++ service/zsyscall_windows.go | 74 ----------------------- 6 files changed, 110 insertions(+), 234 deletions(-) delete mode 100644 service/mksyscall.go delete mode 100644 service/securityapi.go create mode 100644 service/tokens.go delete mode 100644 service/zsyscall_windows.go diff --git a/attacksurface.md b/attacksurface.md index d9b32f5a..f843cc75 100644 --- a/attacksurface.md +++ b/attacksurface.md @@ -29,7 +29,7 @@ The manager service is a userspace service running as Local System, responsible - A readable `CreateFileMapping` handle to a binary ringlog shared by all services, inherited by the UI process. - It listens for service changes in tunnel services according to the string prefix "WireGuardTunnel$". - It manages DPAPI-encrypted configuration files in Local System's local appdata directory, and makes some effort to enforce good configuration filenames. - - It uses `WTSEnumerateSessions` and `WTS_SESSION_NOTIFICATION` to walk through each available session. It then uses `WTSQueryUserToken`, and then calls `GetTokenInformation(TokenGroups)` on it. If one of the returned group's SIDs matches `CreateWellKnownSid(WinBuiltinAdministratorsSid)`, and has attributes of either `SE_GROUP_ENABLED` or `SE_GROUP_USE_FOR_DENY_ONLY` and calling `GetTokenInformation(TokenElevation)` on it or its `TokenLinkedToken` indicates that either is elevated, then it spawns the UI process as that the elevated user token, passing it three unnamed pipe handles for IPC and the log mapping handle, as described above. + - It uses `WTSEnumerateSessions` and `WTSSESSION_NOTIFICATION` to walk through each available session. It then uses `WTSQueryUserToken`, and then calls `GetTokenInformation(TokenGroups)` on it. If one of the returned group's SIDs matches `CreateWellKnownSid(WinBuiltinAdministratorsSid)`, and has attributes of either `SE_GROUP_ENABLED` or `SE_GROUP_USE_FOR_DENY_ONLY` and calling `GetTokenInformation(TokenElevation)` on it or its `TokenLinkedToken` indicates that either is elevated, then it spawns the UI process as that the elevated user token, passing it three unnamed pipe handles for IPC and the log mapping handle, as described above. ### UI diff --git a/service/mksyscall.go b/service/mksyscall.go deleted file mode 100644 index d15628ae..00000000 --- a/service/mksyscall.go +++ /dev/null @@ -1,8 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. - */ - -package service - -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go securityapi.go diff --git a/service/securityapi.go b/service/securityapi.go deleted file mode 100644 index a7e6072c..00000000 --- a/service/securityapi.go +++ /dev/null @@ -1,139 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. - */ - -package service - -import ( - "errors" - "runtime" - "unsafe" - - "golang.org/x/sys/windows" -) - -const ( - WTS_CONSOLE_CONNECT = 0x1 - WTS_CONSOLE_DISCONNECT = 0x2 - WTS_REMOTE_CONNECT = 0x3 - WTS_REMOTE_DISCONNECT = 0x4 - WTS_SESSION_LOGON = 0x5 - WTS_SESSION_LOGOFF = 0x6 - WTS_SESSION_LOCK = 0x7 - WTS_SESSION_UNLOCK = 0x8 - WTS_SESSION_REMOTE_CONTROL = 0x9 - WTS_SESSION_CREATE = 0xa - WTS_SESSION_TERMINATE = 0xb -) - -const ( - WTSActive = 0 - WTSConnected = 1 - WTSConnectQuery = 2 - WTSShadow = 3 - WTSDisconnected = 4 - WTSIdle = 5 - WTSListen = 6 - WTSReset = 7 - WTSDown = 8 - WTSInit = 9 -) - -type WTS_SESSION_NOTIFICATION struct { - Size uint32 - SessionID uint32 -} - -type WTS_SESSION_INFO struct { - SessionID uint32 - WindowStationName *uint16 - State uint32 -} - -//sys wtsQueryUserToken(session uint32, token *windows.Token) (err error) = wtsapi32.WTSQueryUserToken -//sys wtsEnumerateSessions(handle windows.Handle, reserved uint32, version uint32, sessions **WTS_SESSION_INFO, count *uint32) (err error) = wtsapi32.WTSEnumerateSessionsW -//sys wtsFreeMemory(ptr uintptr) = wtsapi32.WTSFreeMemory - -func tokenIsElevated(token windows.Token) bool { - var isElevated uint32 - var outLen uint32 - err := windows.GetTokenInformation(token, windows.TokenElevation, (*byte)(unsafe.Pointer(&isElevated)), uint32(unsafe.Sizeof(isElevated)), &outLen) - if err != nil { - return false - } - return outLen == uint32(unsafe.Sizeof(isElevated)) && isElevated != 0 -} - -func getElevatedToken(token windows.Token) (windows.Token, error) { - if tokenIsElevated(token) { - return token, nil - } - var linkedToken windows.Token - var outLen uint32 - err := windows.GetTokenInformation(token, windows.TokenLinkedToken, (*byte)(unsafe.Pointer(&linkedToken)), uint32(unsafe.Sizeof(linkedToken)), &outLen) - if err != nil { - return windows.Token(0), err - } - if tokenIsElevated(linkedToken) { - return linkedToken, nil - } - linkedToken.Close() - return windows.Token(0), errors.New("the linked token is not elevated") -} - -func TokenIsMemberOfBuiltInAdministrator(token windows.Token) bool { - adminSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) - if err != nil { - return false - } - gs, err := token.GetTokenGroups() - if err != nil { - return false - } - groups := (*[(1 << 28) - 1]windows.SIDAndAttributes)(unsafe.Pointer(&gs.Groups[0]))[:gs.GroupCount] - isAdmin := false - for _, g := range groups { - if (g.Attributes&windows.SE_GROUP_USE_FOR_DENY_ONLY != 0 || g.Attributes&windows.SE_GROUP_ENABLED != 0) && windows.EqualSid(g.Sid, adminSid) { - isAdmin = true - break - } - } - runtime.KeepAlive(gs) - return isAdmin -} - -func dropAllPrivileges() error { - processHandle, err := windows.GetCurrentProcess() - if err != nil { - return err - } - var processToken windows.Token - err = windows.OpenProcessToken(processHandle, windows.TOKEN_READ|windows.TOKEN_WRITE, &processToken) - if err != nil { - return err - } - defer processToken.Close() - - var bufferSizeRequired uint32 - windows.GetTokenInformation(processToken, windows.TokenPrivileges, nil, 0, &bufferSizeRequired) - if bufferSizeRequired == 0 || bufferSizeRequired < uint32(unsafe.Sizeof(windows.Tokenprivileges{}.PrivilegeCount)) { - return errors.New("GetTokenInformation failed to provide a buffer size") - } - buffer := make([]byte, bufferSizeRequired) - var bytesWritten uint32 - err = windows.GetTokenInformation(processToken, windows.TokenPrivileges, &buffer[0], uint32(len(buffer)), &bytesWritten) - if err != nil { - return err - } - if bytesWritten != bufferSizeRequired { - return errors.New("GetTokenInformation returned incomplete data") - } - tokenPrivileges := (*windows.Tokenprivileges)(unsafe.Pointer(&buffer[0])) - for i := uint32(0); i < tokenPrivileges.PrivilegeCount; i++ { - (*windows.LUIDAndAttributes)(unsafe.Pointer(uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0])) + unsafe.Sizeof(tokenPrivileges.Privileges[0])*uintptr(i))).Attributes = windows.SE_PRIVILEGE_REMOVED - } - err = windows.AdjustTokenPrivileges(processToken, false, tokenPrivileges, 0, nil, nil) - runtime.KeepAlive(buffer) - return err -} diff --git a/service/service_manager.go b/service/service_manager.go index b2d93385..dcc8a908 100644 --- a/service/service_manager.go +++ b/service/service_manager.go @@ -87,7 +87,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest }() var userToken windows.Token - err := wtsQueryUserToken(session, &userToken) + err := windows.WTSQueryUserToken(session, &userToken) if err != nil { return } @@ -220,20 +220,20 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest go checkForUpdates() - var sessionsPointer *WTS_SESSION_INFO + var sessionsPointer *windows.WTS_SESSION_INFO var count uint32 - err = wtsEnumerateSessions(0, 0, 1, &sessionsPointer, &count) + err = windows.WTSEnumerateSessions(0, 0, 1, &sessionsPointer, &count) if err != nil { serviceError = ErrorEnumerateSessions return } - sessions := *(*[]WTS_SESSION_INFO)(unsafe.Pointer(&struct { - addr *WTS_SESSION_INFO + sessions := *(*[]windows.WTS_SESSION_INFO)(unsafe.Pointer(&struct { + addr *windows.WTS_SESSION_INFO len int cap int }{sessionsPointer, int(count), int(count)})) for _, session := range sessions { - if session.State != WTSActive && session.State != WTSDisconnected { + if session.State != windows.WTSActive && session.State != windows.WTSDisconnected { continue } procsLock.Lock() @@ -245,7 +245,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest } procsLock.Unlock() } - wtsFreeMemory(uintptr(unsafe.Pointer(sessionsPointer))) + windows.WTSFreeMemory(uintptr(unsafe.Pointer(sessionsPointer))) changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptSessionChange} @@ -263,22 +263,22 @@ loop: case svc.Interrogate: changes <- c.CurrentStatus case svc.SessionChange: - if c.EventType != WTS_SESSION_LOGON && c.EventType != WTS_SESSION_LOGOFF { + if c.EventType != windows.WTS_SESSION_LOGON && c.EventType != windows.WTS_SESSION_LOGOFF { continue } - sessionNotification := (*WTS_SESSION_NOTIFICATION)(unsafe.Pointer(c.EventData)) + sessionNotification := (*windows.WTSSESSION_NOTIFICATION)(unsafe.Pointer(c.EventData)) if uintptr(sessionNotification.Size) != unsafe.Sizeof(*sessionNotification) { - log.Printf("Unexpected size of WTS_SESSION_NOTIFICATION: %d", sessionNotification.Size) + log.Printf("Unexpected size of WTSSESSION_NOTIFICATION: %d", sessionNotification.Size) continue } - if c.EventType == WTS_SESSION_LOGOFF { + if c.EventType == windows.WTS_SESSION_LOGOFF { procsLock.Lock() delete(aliveSessions, sessionNotification.SessionID) if proc, ok := procs[sessionNotification.SessionID]; ok { proc.Kill() } procsLock.Unlock() - } else if c.EventType == WTS_SESSION_LOGON { + } else if c.EventType == windows.WTS_SESSION_LOGON { procsLock.Lock() if alive := aliveSessions[sessionNotification.SessionID]; !alive { aliveSessions[sessionNotification.SessionID] = true diff --git a/service/tokens.go b/service/tokens.go new file mode 100644 index 00000000..dba4cd62 --- /dev/null +++ b/service/tokens.go @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package service + +import ( + "errors" + "runtime" + "unsafe" + + "golang.org/x/sys/windows" +) + +func tokenIsElevated(token windows.Token) bool { + var isElevated uint32 + var outLen uint32 + err := windows.GetTokenInformation(token, windows.TokenElevation, (*byte)(unsafe.Pointer(&isElevated)), uint32(unsafe.Sizeof(isElevated)), &outLen) + if err != nil { + return false + } + return outLen == uint32(unsafe.Sizeof(isElevated)) && isElevated != 0 +} + +func getElevatedToken(token windows.Token) (windows.Token, error) { + if tokenIsElevated(token) { + return token, nil + } + var linkedToken windows.Token + var outLen uint32 + err := windows.GetTokenInformation(token, windows.TokenLinkedToken, (*byte)(unsafe.Pointer(&linkedToken)), uint32(unsafe.Sizeof(linkedToken)), &outLen) + if err != nil { + return windows.Token(0), err + } + if tokenIsElevated(linkedToken) { + return linkedToken, nil + } + linkedToken.Close() + return windows.Token(0), errors.New("the linked token is not elevated") +} + +func TokenIsMemberOfBuiltInAdministrator(token windows.Token) bool { + adminSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) + if err != nil { + return false + } + gs, err := token.GetTokenGroups() + if err != nil { + return false + } + groups := (*[(1 << 28) - 1]windows.SIDAndAttributes)(unsafe.Pointer(&gs.Groups[0]))[:gs.GroupCount] + isAdmin := false + for _, g := range groups { + if (g.Attributes&windows.SE_GROUP_USE_FOR_DENY_ONLY != 0 || g.Attributes&windows.SE_GROUP_ENABLED != 0) && windows.EqualSid(g.Sid, adminSid) { + isAdmin = true + break + } + } + runtime.KeepAlive(gs) + return isAdmin +} + +func dropAllPrivileges() error { + processHandle, err := windows.GetCurrentProcess() + if err != nil { + return err + } + var processToken windows.Token + err = windows.OpenProcessToken(processHandle, windows.TOKEN_READ|windows.TOKEN_WRITE, &processToken) + if err != nil { + return err + } + defer processToken.Close() + + var bufferSizeRequired uint32 + windows.GetTokenInformation(processToken, windows.TokenPrivileges, nil, 0, &bufferSizeRequired) + if bufferSizeRequired == 0 || bufferSizeRequired < uint32(unsafe.Sizeof(windows.Tokenprivileges{}.PrivilegeCount)) { + return errors.New("GetTokenInformation failed to provide a buffer size") + } + buffer := make([]byte, bufferSizeRequired) + var bytesWritten uint32 + err = windows.GetTokenInformation(processToken, windows.TokenPrivileges, &buffer[0], uint32(len(buffer)), &bytesWritten) + if err != nil { + return err + } + if bytesWritten != bufferSizeRequired { + return errors.New("GetTokenInformation returned incomplete data") + } + tokenPrivileges := (*windows.Tokenprivileges)(unsafe.Pointer(&buffer[0])) + for i := uint32(0); i < tokenPrivileges.PrivilegeCount; i++ { + (*windows.LUIDAndAttributes)(unsafe.Pointer(uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0])) + unsafe.Sizeof(tokenPrivileges.Privileges[0])*uintptr(i))).Attributes = windows.SE_PRIVILEGE_REMOVED + } + err = windows.AdjustTokenPrivileges(processToken, false, tokenPrivileges, 0, nil, nil) + runtime.KeepAlive(buffer) + return err +} diff --git a/service/zsyscall_windows.go b/service/zsyscall_windows.go deleted file mode 100644 index bb1eced3..00000000 --- a/service/zsyscall_windows.go +++ /dev/null @@ -1,74 +0,0 @@ -// Code generated by 'go generate'; DO NOT EDIT. - -package service - -import ( - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -var _ unsafe.Pointer - -// Do the interface allocations only once for common -// Errno values. -const ( - errnoERROR_IO_PENDING = 997 -) - -var ( - errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) -) - -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. -func errnoErr(e syscall.Errno) error { - switch e { - case 0: - return nil - case errnoERROR_IO_PENDING: - return errERROR_IO_PENDING - } - // TODO: add more here, after collecting data on the common - // error values see on Windows. (perhaps when running - // all.bat?) - return e -} - -var ( - modwtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll") - - procWTSQueryUserToken = modwtsapi32.NewProc("WTSQueryUserToken") - procWTSEnumerateSessionsW = modwtsapi32.NewProc("WTSEnumerateSessionsW") - procWTSFreeMemory = modwtsapi32.NewProc("WTSFreeMemory") -) - -func wtsQueryUserToken(session uint32, token *windows.Token) (err error) { - r1, _, e1 := syscall.Syscall(procWTSQueryUserToken.Addr(), 2, uintptr(session), uintptr(unsafe.Pointer(token)), 0) - if r1 == 0 { - if e1 != 0 { - err = errnoErr(e1) - } else { - err = syscall.EINVAL - } - } - return -} - -func wtsEnumerateSessions(handle windows.Handle, reserved uint32, version uint32, sessions **WTS_SESSION_INFO, count *uint32) (err error) { - r1, _, e1 := syscall.Syscall6(procWTSEnumerateSessionsW.Addr(), 5, uintptr(handle), uintptr(reserved), uintptr(version), uintptr(unsafe.Pointer(sessions)), uintptr(unsafe.Pointer(count)), 0) - if r1 == 0 { - if e1 != 0 { - err = errnoErr(e1) - } else { - err = syscall.EINVAL - } - } - return -} - -func wtsFreeMemory(ptr uintptr) { - syscall.Syscall(procWTSFreeMemory.Addr(), 1, uintptr(ptr), 0, 0) - return -} -- cgit v1.2.3-59-g8ed1b