Skip to content

Commit 7847552

Browse files
committed
refactor(alertsenders): Launch systray server manually
The systray server is automatically launched when the systray alert sender is enabled. The process is created either via the regular CreateProcess API or CreateProcessAsUser when Fibratus is running inside Windows Service. This requires querying the user token of the currently logged-in console session user. The TCB privilege must be present in the user token.
1 parent 4e7f1d9 commit 7847552

File tree

7 files changed

+188
-48
lines changed

7 files changed

+188
-48
lines changed

build/msi/fibratus.wxs

+1-19
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@
1818

1919
<UI Id="UI">
2020
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLDIR" />
21-
<Publish Dialog="ExitDialog"
22-
Control="Finish"
23-
Event="DoAction"
24-
Value="LaunchSystray"
25-
Condition="NOT Installed"/>
2621
</UI>
2722

2823
<!-- Custom banners -->
@@ -39,8 +34,6 @@
3934
<Files Include="!(bindpath.dir)**">
4035
<!-- Exclude from harvesting as its need fine-grained authoring in Windows Service -->
4136
<Exclude Files="!(bindpath.dir)Bin\fibratus.exe" />
42-
<!-- Exclude from harvesting as it is used in custom action -->
43-
<Exclude Files="!(bindpath.dir)Bin\fibratus-systray.exe" />
4437
</Files>
4538

4639
<!-- Windows Service -->
@@ -57,14 +50,6 @@
5750
</ServiceInstall>
5851
<ServiceControl Id="Fibratus" Name="Fibratus" Start="install" Stop="both" Remove="uninstall" Wait="yes" />
5952
</Component>
60-
<Component Directory="BINDIR" Id="Systray" Guid="F3C06EDD-C830-4FCD-BAFA-0D15C697EE76">
61-
<File Id="Systray" Source="!(bindpath.dir)Bin\fibratus-systray.exe" KeyPath="yes" Checksum="yes"/>
62-
<RegistryValue Root="HKMU" Action="write"
63-
Key="Software\Microsoft\Windows\CurrentVersion\Run"
64-
Name="Fibratus Systray"
65-
Value="[BINDIR]fibratus-systray.exe"
66-
Type="string" />
67-
</Component>
6853
</ComponentGroup>
6954

7055
<StandardDirectory Id="ProgramFiles64Folder">
@@ -85,10 +70,7 @@
8570
<Property Id="ConfigureServiceRecovery" Value="&quot;SC.EXE&quot; failureflag fibratus 1" />
8671
<CustomAction Id="ConfigureServiceRecovery" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixQuietExec" Impersonate="no" Execute="deferred" Return="ignore" />
8772

88-
<!-- Systray -->
89-
<Property Id="WixShellExecTarget" Value="[#Systray]"/>
90-
<CustomAction Id="LaunchSystray" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixShellExec" Impersonate="yes" />
91-
<util:CloseApplication Id="CloseSystray" CloseMessage="yes" Target="fibratus-systray" RebootPrompt="no" />
73+
<util:CloseApplication Id="CloseSystray" CloseMessage="yes" Target="fibratus-systray.exe" RebootPrompt="no" />
9274

9375
<InstallExecuteSequence>
9476
<Custom Action="ConfigureServiceRecovery" After="InstallServices" Condition="NOT REMOVE" />

cmd/systray/main_windows.go

+19-22
Original file line numberDiff line numberDiff line change
@@ -181,26 +181,25 @@ func (s *Systray) loadIconFromResource(mod windows.Handle) (windows.Handle, erro
181181

182182
func (s *Systray) handlePipeClient(conn net.Conn) {
183183
defer conn.Close()
184-
for {
185-
buf, err := io.ReadAll(conn)
186-
if err != nil {
187-
if err != io.EOF {
188-
logrus.Errorf("pipe read: %v", err)
189-
}
190-
break
191-
}
192-
var m Msg
193-
err = json.Unmarshal(buf, &m)
194-
if err != nil {
195-
logrus.Error(err)
196-
break
197-
}
198-
err = s.handleMessage(m)
199-
if err != nil {
200-
logrus.Error(err)
201-
break
184+
buf, err := io.ReadAll(conn)
185+
if err != nil {
186+
if err != io.EOF {
187+
logrus.Errorf("pipe read: %v", err)
202188
}
203189
}
190+
if len(buf) == 0 {
191+
return
192+
}
193+
var m Msg
194+
err = json.Unmarshal(buf, &m)
195+
if err != nil {
196+
logrus.Error(err)
197+
return
198+
}
199+
err = s.handleMessage(m)
200+
if err != nil {
201+
logrus.Error(err)
202+
}
204203
}
205204

206205
func (s *Systray) handleMessage(m Msg) error {
@@ -209,6 +208,7 @@ func (s *Systray) handleMessage(m Msg) error {
209208
var c systray.Config
210209
err := m.decode(&c)
211210
if err != nil {
211+
logrus.Errorf("unable to decode systray server config: %v", err)
212212
return err
213213
}
214214
s.config = c
@@ -231,7 +231,7 @@ func (s *Systray) handleMessage(m Msg) error {
231231
}
232232

233233
func main() {
234-
err := log.InitFromConfig(log.Config{Level: "info", LogStdout: true}, "fibratus-systray.log")
234+
err := log.InitFromConfig(log.Config{Level: "info", LogStdout: true, Formatter: "text"}, "fibratus-systray.log")
235235
if err != nil {
236236
fmt.Printf("%v", err)
237237
os.Exit(1)
@@ -258,9 +258,6 @@ func main() {
258258
}
259259
}()
260260

261-
// detach console
262-
sys.FreeConsole()
263-
264261
// server loop
265262
for {
266263
conn, err := l.Accept()

pkg/alertsender/systray/systray.go

+88-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@ import (
2626
"github.com/cenkalti/backoff/v4"
2727
"github.com/rabbitstack/fibratus/pkg/alertsender"
2828
"github.com/rabbitstack/fibratus/pkg/kevent"
29+
"github.com/rabbitstack/fibratus/pkg/sys"
2930
log "github.com/sirupsen/logrus"
31+
"golang.org/x/sys/windows"
32+
"math"
3033
"os"
34+
"path/filepath"
3135
"time"
3236
)
3337

@@ -45,6 +49,7 @@ const systrayPipe = `\\.\pipe\fibratus-systray`
4549
// by the systray sender.
4650
type systray struct {
4751
config Config
52+
proc windows.Handle // systray server process handle
4853
}
4954

5055
// MsgType determines the type of the message sent
@@ -85,6 +90,78 @@ func makeSender(config alertsender.Config) (alertsender.Sender, error) {
8590
return &systray{}, nil
8691
}
8792

93+
// spin up systray server process
94+
var si windows.StartupInfo
95+
var pi windows.ProcessInformation
96+
97+
exe, err := os.Executable()
98+
if err != nil {
99+
return nil, err
100+
}
101+
cmdline := filepath.Join(filepath.Dir(exe), "fibratus-systray.exe")
102+
argv, err := windows.UTF16PtrFromString(cmdline)
103+
if err != nil {
104+
return nil, err
105+
}
106+
107+
log.Infof("starting systray server process %q", cmdline)
108+
109+
if sys.IsWindowsService() {
110+
// if we're running inside Windows Service, the systray server
111+
// process must be created in the console session and with the
112+
// currently logged-in user access token
113+
var token windows.Token
114+
115+
// enable TCB privilege to obtain the console session user token
116+
sys.SetTcbPrivilege()
117+
118+
consoleSessionID := sys.WTSGetActiveConsoleSessionID()
119+
if consoleSessionID == math.MaxUint32 && sys.IsInSandbox() {
120+
// if we failed to obtain the console session ID and
121+
// are running inside Windows Sandbox, use session 1
122+
consoleSessionID = 1
123+
}
124+
125+
if sys.WTSQueryUserToken(consoleSessionID, &token) {
126+
log.Infof("obtained user token for console session ID %d", consoleSessionID)
127+
err = windows.CreateProcessAsUser(
128+
token,
129+
nil,
130+
argv,
131+
nil,
132+
nil,
133+
false,
134+
windows.CREATE_NO_WINDOW,
135+
nil,
136+
nil,
137+
&si,
138+
&pi,
139+
)
140+
} else {
141+
err = fmt.Errorf("unable to obtain user token for console session ID %d", consoleSessionID)
142+
}
143+
144+
// drop TCB privilege and close the token handle
145+
sys.RemoveTcbPrivilege()
146+
_ = windows.CloseHandle(windows.Handle(token))
147+
} else {
148+
err = windows.CreateProcess(
149+
nil,
150+
argv,
151+
nil,
152+
nil,
153+
false,
154+
windows.CREATE_NO_WINDOW,
155+
nil,
156+
nil,
157+
&si,
158+
&pi)
159+
}
160+
161+
if err != nil {
162+
log.Warnf("unable to start systray server process: %v", err)
163+
}
164+
88165
s := &systray{config: c}
89166
b := &backoff.ExponentialBackOff{
90167
// first backoff timeout will be somewhere in the 100 - 300 ms range given the default multiplier
@@ -112,6 +189,10 @@ func makeSender(config alertsender.Config) (alertsender.Sender, error) {
112189
break
113190
}
114191

192+
log.Info("established connection to systray server")
193+
194+
s.proc = pi.Process
195+
115196
return s, s.send(&Msg{Type: Conf, Data: c})
116197
}
117198

@@ -124,11 +205,17 @@ func (s *systray) Send(alert alertsender.Alert) error {
124205
func (*systray) Type() alertsender.Type { return alertsender.Systray }
125206
func (*systray) SupportsMarkdown() bool { return false }
126207

127-
func (s *systray) Shutdown() error { return nil }
208+
func (s *systray) Shutdown() error {
209+
if s.proc != 0 && sys.IsProcessRunning(s.proc) {
210+
return windows.TerminateProcess(s.proc, 1)
211+
}
212+
return nil
213+
}
128214

129215
func (s *systray) send(m *Msg) error {
130216
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
131217
defer cancel()
218+
132219
conn, err := winio.DialPipeContext(ctx, systrayPipe)
133220
if err != nil {
134221
return fmt.Errorf("unable to dial %s pipe: %v", systrayPipe, err)
@@ -151,6 +238,5 @@ func (s *systray) send(m *Msg) error {
151238

152239
func pipeExists() bool {
153240
_, err := os.Stat(systrayPipe)
154-
log.Warnf("pipe not found: %v", err)
155241
return err == nil
156242
}

pkg/sys/privilege.go

+29-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import (
2929
const (
3030
// SeDebugPrivilege is the name of the privilege used to debug programs.
3131
SeDebugPrivilege = "SeDebugPrivilege"
32+
// SeTcbPrivilege privilege identifies its holder as part of the trusted computer base.
33+
SeTcbPrivilege = "SeTcbPrivilege"
3234
)
3335

3436
// Errors returned by AdjustTokenPrivileges.
@@ -41,6 +43,8 @@ const (
4143
const (
4244
// PrivilegedEnabled enables the privilege.
4345
PrivilegedEnabled uint32 = 0x00000002
46+
// PrivilegeRemoved removes the privilege.
47+
PrivilegeRemoved uint32 = 0x00000004
4448
)
4549

4650
// mapPrivileges maps privilege names to LUID values.
@@ -57,10 +61,10 @@ func mapPrivileges(names []string) ([]windows.LUID, error) {
5761
return privileges, nil
5862
}
5963

60-
// EnableTokenPrivileges enables the specified privileges in the given
64+
// adjustTokenPrivileges enables/disables the specified privileges in the given
6165
// Token. The token must have TOKEN_ADJUST_PRIVILEGES access. If the token
6266
// does not already contain the privilege it cannot be enabled.
63-
func EnableTokenPrivileges(token windows.Token, privileges ...string) error {
67+
func adjustTokenPrivileges(token windows.Token, state uint32, privileges ...string) error {
6468
privValues, err := mapPrivileges(privileges)
6569
if err != nil {
6670
return err
@@ -74,7 +78,7 @@ func EnableTokenPrivileges(token windows.Token, privileges ...string) error {
7478
if err := binary.Write(&b, binary.LittleEndian, p); err != nil {
7579
continue
7680
}
77-
if err := binary.Write(&b, binary.LittleEndian, PrivilegedEnabled); err != nil {
81+
if err := binary.Write(&b, binary.LittleEndian, state); err != nil {
7882
continue
7983
}
8084
}
@@ -90,9 +94,29 @@ func EnableTokenPrivileges(token windows.Token, privileges ...string) error {
9094
return nil
9195
}
9296

93-
// SetDebugPrivilege sets the debug privilege in the current running process.
97+
// SetDebugPrivilege sets the debug privilege in the current process token.
9498
func SetDebugPrivilege() {
99+
enablePrivileges(SeDebugPrivilege)
100+
}
101+
102+
// SetTcbPrivilege sets the TCB privilege in the current process token.
103+
func SetTcbPrivilege() {
104+
enablePrivileges(SeTcbPrivilege)
105+
}
106+
107+
// RemoveTcbPrivilege removes the TCB privilege from the access token of the current process.
108+
func RemoveTcbPrivilege() {
109+
removePrivileges(SeTcbPrivilege)
110+
}
111+
112+
func enablePrivileges(privs ...string) {
113+
var token windows.Token
114+
_ = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
115+
_ = adjustTokenPrivileges(token, PrivilegedEnabled, privs...)
116+
}
117+
118+
func removePrivileges(privs ...string) {
95119
var token windows.Token
96120
_ = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
97-
_ = EnableTokenPrivileges(token, SeDebugPrivilege)
121+
_ = adjustTokenPrivileges(token, PrivilegeRemoved, privs...)
98122
}

pkg/sys/syscall.go

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ package sys
4444

4545
// Windows Terminal Server Functions
4646
//sys WTSQuerySessionInformationA(handle windows.Handle, sessionID uint32, klass uint8, buf **uint16, size *uint32) (err error) = wtsapi32.WTSQuerySessionInformationW
47+
//sys WTSGetActiveConsoleSessionID() (n uint32) = kernel32.WTSGetActiveConsoleSessionId
48+
//sys WTSQueryUserToken(sessionID uint32, token *windows.Token) (ok bool) = wtsapi32.WTSQueryUserToken
4749

4850
// Windows Trust Functions
4951
//sys WinVerifyTrust(handle windows.Handle, action *windows.GUID, data *WintrustData) (ret uint32, err error) [failretval!=0] = wintrust.WinVerifyTrust

pkg/sys/util.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2021-2022 by Nedim Sabic Sabic
3+
* https://www.fibratus.io
4+
* All Rights Reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package sys
20+
21+
import "golang.org/x/sys/windows/registry"
22+
23+
// IsInSandbox indicates if the process is running inside an
24+
// isolated environment such as Windows Containers or Windows
25+
// Sandbox.
26+
func IsInSandbox() bool {
27+
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.QUERY_VALUE)
28+
if err != nil {
29+
return false
30+
}
31+
defer key.Close()
32+
v, _, err := key.GetIntegerValue("ContainerType")
33+
34+
return err == nil && v > 0
35+
}

0 commit comments

Comments
 (0)