From 06c80289b7b4a3bc469c563c61471b306c4a1a5c Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 13 May 2019 11:32:51 +0200 Subject: service: use more standard naming scheme for syscalls --- attacksurface.md | 2 +- service/securityapi.go | 51 ++++++++++++++----------- service/service_manager.go | 40 +++++++++---------- service/tunneltracker.go | 93 +++++++++++++++++++-------------------------- service/zsyscall_windows.go | 10 +++-- 5 files changed, 96 insertions(+), 100 deletions(-) diff --git a/attacksurface.md b/attacksurface.md index 8010d7b0..7b960dba 100644 --- a/attacksurface.md +++ b/attacksurface.md @@ -29,7 +29,7 @@ The manager service is a userspace service running as Local System, responsible - A writable `CreateFileMapping` handle to a binary ringlog shared by all services, inherited by the unprivileged UI process. It's unclear if this brings with it surprising hidden attack surface in the mm system. - 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 `WTSSESSION_NOTIFICATION` to walk through each available session. It then uses `wtfQueryUserToken`, 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 descried above. + - 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 descried above. ### UI diff --git a/service/securityapi.go b/service/securityapi.go index d89d9404..2c1db5db 100644 --- a/service/securityapi.go +++ b/service/securityapi.go @@ -13,38 +13,45 @@ import ( ) const ( - wtsSessionLogon uint32 = 5 - wtsSessionLogoff uint32 = 6 + 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 ) -type wtsState int - const ( - wtsActive wtsState = iota - wtsConnected - wtsConnectQuery - wtsShadow - wtsDisconnected - wtsIdle - wtsListen - wtsReset - wtsDown - wtsInit + WTSActive = 0 + WTSConnected = 1 + WTSConnectQuery = 2 + WTSShadow = 3 + WTSDisconnected = 4 + WTSIdle = 5 + WTSListen = 6 + WTSReset = 7 + WTSDown = 8 + WTSInit = 9 ) -type wtsSessionNotification struct { - size uint32 - sessionID uint32 +type WTS_SESSION_NOTIFICATION struct { + Size uint32 + SessionID uint32 } -type wtsSessionInfo struct { - sessionID uint32 - windowStationName *uint16 - state wtsState +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 **wtsSessionInfo, count *uint32) (err error) = wtsapi32.WTSEnumerateSessionsW +//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 const ( diff --git a/service/service_manager.go b/service/service_manager.go index 92508215..a3642712 100644 --- a/service/service_manager.go +++ b/service/service_manager.go @@ -219,27 +219,27 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest go checkForUpdates() - var sessionsPointer *wtsSessionInfo + var sessionsPointer *WTS_SESSION_INFO var count uint32 err = wtsEnumerateSessions(0, 0, 1, &sessionsPointer, &count) if err != nil { serviceError = ErrorEnumerateSessions return } - sessions := *(*[]wtsSessionInfo)(unsafe.Pointer(&struct { - addr *wtsSessionInfo + sessions := *(*[]WTS_SESSION_INFO)(unsafe.Pointer(&struct { + addr *WTS_SESSION_INFO len int cap int }{sessionsPointer, int(count), int(count)})) for _, session := range sessions { - if session.state != wtsActive { + if session.State != WTSActive { continue } procsLock.Lock() - if alive := aliveSessions[session.sessionID]; !alive { - aliveSessions[session.sessionID] = true - if _, ok := procs[session.sessionID]; !ok { - go startProcess(session.sessionID) + if alive := aliveSessions[session.SessionID]; !alive { + aliveSessions[session.SessionID] = true + if _, ok := procs[session.SessionID]; !ok { + go startProcess(session.SessionID) } } procsLock.Unlock() @@ -262,27 +262,27 @@ loop: case svc.Interrogate: changes <- c.CurrentStatus case svc.SessionChange: - if c.EventType != wtsSessionLogon && c.EventType != wtsSessionLogoff { + if c.EventType != WTS_SESSION_LOGON && c.EventType != WTS_SESSION_LOGOFF { continue } - sessionNotification := (*wtsSessionNotification)(unsafe.Pointer(c.EventData)) - if uintptr(sessionNotification.size) != unsafe.Sizeof(*sessionNotification) { - log.Printf("Unexpected size of WTSSESSION_NOTIFICATION: %d", sessionNotification.size) + sessionNotification := (*WTS_SESSION_NOTIFICATION)(unsafe.Pointer(c.EventData)) + if uintptr(sessionNotification.Size) != unsafe.Sizeof(*sessionNotification) { + log.Printf("Unexpected size of WTS_SESSION_NOTIFICATION: %d", sessionNotification.Size) continue } - if c.EventType == wtsSessionLogoff { + if c.EventType == WTS_SESSION_LOGOFF { procsLock.Lock() - delete(aliveSessions, sessionNotification.sessionID) - if proc, ok := procs[sessionNotification.sessionID]; ok { + delete(aliveSessions, sessionNotification.SessionID) + if proc, ok := procs[sessionNotification.SessionID]; ok { proc.Kill() } procsLock.Unlock() - } else if c.EventType == wtsSessionLogon { + } else if c.EventType == WTS_SESSION_LOGON { procsLock.Lock() - if alive := aliveSessions[sessionNotification.sessionID]; !alive { - aliveSessions[sessionNotification.sessionID] = true - if _, ok := procs[sessionNotification.sessionID]; !ok { - go startProcess(sessionNotification.sessionID) + if alive := aliveSessions[sessionNotification.SessionID]; !alive { + aliveSessions[sessionNotification.SessionID] = true + if _, ok := procs[sessionNotification.SessionID]; !ok { + go startProcess(sessionNotification.SessionID) } } procsLock.Unlock() diff --git a/service/tunneltracker.go b/service/tunneltracker.go index 4403e558..1c368584 100644 --- a/service/tunneltracker.go +++ b/service/tunneltracker.go @@ -16,49 +16,36 @@ import ( "sync" "syscall" "time" - "unsafe" ) -//sys notifyServiceStatusChange(service windows.Handle, notifyMask uint32, notifyBuffer uintptr) (status uint32) = advapi32.NotifyServiceStatusChangeW +//sys notifyServiceStatusChange(service windows.Handle, notifyMask uint32, notifier *SERVICE_NOTIFY) (ret error) = advapi32.NotifyServiceStatusChangeW //sys sleepEx(milliseconds uint32, alertable bool) (ret uint32) = kernel32.SleepEx const ( - serviceNotify_CREATED uint32 = 0x00000080 - serviceNotify_CONTINUE_PENDING = 0x00000010 - serviceNotify_DELETE_PENDING = 0x00000200 - serviceNotify_DELETED = 0x00000100 - serviceNotify_PAUSE_PENDING = 0x00000020 - serviceNotify_PAUSED = 0x00000040 - serviceNotify_RUNNING = 0x00000008 - serviceNotify_START_PENDING = 0x00000002 - serviceNotify_STOP_PENDING = 0x00000004 - serviceNotify_STOPPED = 0x00000001 + SERVICE_NOTIFY_STATUS_CHANGE = 2 + SERVICE_NOTIFY_STOPPED = 0x00000001 + SERVICE_NOTIFY_START_PENDING = 0x00000002 + SERVICE_NOTIFY_STOP_PENDING = 0x00000004 + SERVICE_NOTIFY_RUNNING = 0x00000008 + SERVICE_NOTIFY_CONTINUE_PENDING = 0x00000010 + SERVICE_NOTIFY_PAUSE_PENDING = 0x00000020 + SERVICE_NOTIFY_PAUSED = 0x00000040 + SERVICE_NOTIFY_CREATED = 0x00000080 + SERVICE_NOTIFY_DELETED = 0x00000100 + SERVICE_NOTIFY_DELETE_PENDING = 0x00000200 + + STATUS_USER_APC = 0x000000C0 + WAIT_IO_COMPLETION = STATUS_USER_APC ) -const serviceNotify_STATUS_CHANGE uint32 = 2 -const errorServiceMARKED_FOR_DELETE uint32 = 1072 -const errorServiceNOTIFY_CLIENT_LAGGING uint32 = 1294 -const sleepWAIT_IO_COMPLETION uint32 = 0x000000C0 - -type serviceStatus struct { - serviceType uint32 - currentState uint32 - controlsAccepted uint32 - win32ExitCode uint32 - serviceSpecificExitCode uint32 - checkPoint uint32 - waitHint uint32 - processId uint32 - serviceFlags uint32 -} -type serviceNotify struct { - version uint32 - notifyCallback uintptr - context uintptr - notificationStatus uint32 - serviceStatus serviceStatus - notificationTriggered uint32 - serviceNames *uint16 +type SERVICE_NOTIFY struct { + Version uint32 + NotifyCallback uintptr + Context uintptr + NotificationStatus uint32 + ServiceStatus windows.SERVICE_STATUS_PROCESS + NotificationTriggered uint32 + ServiceNames *uint16 } func trackExistingTunnels() error { @@ -84,7 +71,7 @@ func trackExistingTunnels() error { return nil } -var serviceTrackerCallbackPtr = windows.NewCallback(func(notifier *serviceNotify) uintptr { +var serviceTrackerCallbackPtr = windows.NewCallback(func(notifier *SERVICE_NOTIFY) uintptr { return 0 }) @@ -141,10 +128,10 @@ func trackTunnelService(tunnelName string, service *mgr.Service) { trackedTunnelsLock.Unlock() }() - const serviceNotifications = serviceNotify_RUNNING | serviceNotify_START_PENDING | serviceNotify_STOP_PENDING | serviceNotify_STOPPED | serviceNotify_DELETE_PENDING - notifier := &serviceNotify{ - version: serviceNotify_STATUS_CHANGE, - notifyCallback: serviceTrackerCallbackPtr, + const serviceNotifications = SERVICE_NOTIFY_RUNNING | SERVICE_NOTIFY_START_PENDING | SERVICE_NOTIFY_STOP_PENDING | SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_DELETE_PENDING + notifier := &SERVICE_NOTIFY{ + Version: SERVICE_NOTIFY_STATUS_CHANGE, + NotifyCallback: serviceTrackerCallbackPtr, } checkForDisabled := func() (shouldReturn bool) { @@ -168,46 +155,46 @@ func trackTunnelService(tunnelName string, service *mgr.Service) { defer runtime.UnlockOSThread() lastState := TunnelUnknown for { - ret := notifyServiceStatusChange(service.Handle, serviceNotifications, uintptr(unsafe.Pointer(notifier))) - switch ret { - case 0: + err := notifyServiceStatusChange(service.Handle, serviceNotifications, notifier) + switch err { + case nil: for { - if sleepEx(uint32(time.Second*3/time.Millisecond), true) == sleepWAIT_IO_COMPLETION { + if sleepEx(uint32(time.Second*3/time.Millisecond), true) == WAIT_IO_COMPLETION { break } else if checkForDisabled() { return } } - case errorServiceMARKED_FOR_DELETE: + case windows.ERROR_SERVICE_MARKED_FOR_DELETE: trackedTunnelsLock.Lock() trackedTunnels[tunnelName] = TunnelStopped trackedTunnelsLock.Unlock() IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, nil) return - case errorServiceNOTIFY_CLIENT_LAGGING: + case windows.ERROR_SERVICE_NOTIFY_CLIENT_LAGGING: continue default: trackedTunnelsLock.Lock() trackedTunnels[tunnelName] = TunnelStopped trackedTunnelsLock.Unlock() - IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, fmt.Errorf("Unable to continue monitoring service, so stopping: %v", syscall.Errno(ret))) + IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, fmt.Errorf("Unable to continue monitoring service, so stopping: %v", err)) service.Control(svc.Stop) return } - state := svcStateToTunState(svc.State(notifier.serviceStatus.currentState)) + state := svcStateToTunState(svc.State(notifier.ServiceStatus.CurrentState)) var tunnelError error if state == TunnelStopped { - if notifier.serviceStatus.win32ExitCode == uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) { - maybeErr := Error(notifier.serviceStatus.serviceSpecificExitCode) + if notifier.ServiceStatus.Win32ExitCode == uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) { + maybeErr := Error(notifier.ServiceStatus.ServiceSpecificExitCode) if maybeErr != ErrorSuccess { tunnelError = maybeErr } } else { - switch notifier.serviceStatus.win32ExitCode { + switch notifier.ServiceStatus.Win32ExitCode { case uint32(windows.NO_ERROR), serviceNEVER_STARTED: default: - tunnelError = syscall.Errno(notifier.serviceStatus.win32ExitCode) + tunnelError = syscall.Errno(notifier.ServiceStatus.Win32ExitCode) } } } diff --git a/service/zsyscall_windows.go b/service/zsyscall_windows.go index bb8d89d4..cc40ddea 100644 --- a/service/zsyscall_windows.go +++ b/service/zsyscall_windows.go @@ -60,7 +60,7 @@ func wtsQueryUserToken(session uint32, token *windows.Token) (err error) { return } -func wtsEnumerateSessions(handle windows.Handle, reserved uint32, version uint32, sessions **wtsSessionInfo, count *uint32) (err error) { +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 { @@ -77,9 +77,11 @@ func wtsFreeMemory(ptr uintptr) { return } -func notifyServiceStatusChange(service windows.Handle, notifyMask uint32, notifyBuffer uintptr) (status uint32) { - r0, _, _ := syscall.Syscall(procNotifyServiceStatusChangeW.Addr(), 3, uintptr(service), uintptr(notifyMask), uintptr(notifyBuffer)) - status = uint32(r0) +func notifyServiceStatusChange(service windows.Handle, notifyMask uint32, notifier *SERVICE_NOTIFY) (ret error) { + r0, _, _ := syscall.Syscall(procNotifyServiceStatusChangeW.Addr(), 3, uintptr(service), uintptr(notifyMask), uintptr(unsafe.Pointer(notifier))) + if r0 != 0 { + ret = syscall.Errno(r0) + } return } -- cgit v1.2.3-59-g8ed1b