Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 1 addition & 19 deletions build/msi/fibratus.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@

<UI Id="UI">
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLDIR" />
<Publish Dialog="ExitDialog"
Control="Finish"
Event="DoAction"
Value="LaunchSystray"
Condition="NOT Installed"/>
</UI>

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

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

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

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

<InstallExecuteSequence>
<Custom Action="ConfigureServiceRecovery" After="InstallServices" Condition="NOT REMOVE" />
Expand Down
57 changes: 23 additions & 34 deletions cmd/systray/main_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"io"
"net"
"os"
"os/user"
"path/filepath"
"unsafe"
)
Expand Down Expand Up @@ -181,28 +180,26 @@ func (s *Systray) loadIconFromResource(mod windows.Handle) (windows.Handle, erro
}

func (s *Systray) handlePipeClient(conn net.Conn) {
buf := make([]byte, 1024)
defer conn.Close()
for {
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
logrus.Errorf("pipe read: %v", err)
}
break
}
var m Msg
err = json.Unmarshal(buf[:n], &m)
if err != nil {
logrus.Error(err)
break
}
err = s.handleMessage(m)
if err != nil {
logrus.Error(err)
break
buf, err := io.ReadAll(conn)
if err != nil {
if err != io.EOF {
logrus.Errorf("pipe read: %v", err)
}
}
if len(buf) == 0 {
return
}
var m Msg
err = json.Unmarshal(buf, &m)
if err != nil {
logrus.Error(err)
return
}
err = s.handleMessage(m)
if err != nil {
logrus.Error(err)
}
}

func (s *Systray) handleMessage(m Msg) error {
Expand All @@ -211,13 +208,15 @@ func (s *Systray) handleMessage(m Msg) error {
var c systray.Config
err := m.decode(&c)
if err != nil {
logrus.Errorf("unable to decode systray server config: %v", err)
return err
}
s.config = c
case Balloon:
var alert alertsender.Alert
err := m.decode(&alert)
if err != nil {
logrus.Errorf("unable to decode alert: %v", err)
return err
}
text := alert.Text
Expand All @@ -232,36 +231,26 @@ func (s *Systray) handleMessage(m Msg) error {
}

func main() {
err := log.InitFromConfig(log.Config{Level: "info", LogStdout: true}, "fibratus-systray.log")
err := log.InitFromConfig(log.Config{Level: "info", LogStdout: true, Formatter: "text"}, "fibratus-systray.log")
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
logrus.Info("starting systray server...")
usr, err := user.Current()
if err != nil {
logrus.Fatalf("failed to retrieve the current user: %v", err)
}
// Named pipe security and access rights.
// Give generic read/write access to the
// current user SID
descriptor := "D:P(A;;GA;;;" + usr.Uid + ")"
// spin up named-pipe server
l, err := winio.ListenPipe(systrayPipe, &winio.PipeConfig{SecurityDescriptor: descriptor})
// spin up the named-pipe server
l, err := winio.ListenPipe(systrayPipe, nil)
if err != nil {
logrus.Fatalf("unable to listen on named pipe: %s: %v", systrayPipe, err)
}

// detach console
sys.FreeConsole()

tray, err := newSystray()
if err != nil {
logrus.Fatalf("unable to create systray: %v", err)
}

go func() {
<-tray.quit
logrus.Info("shutting down...")
l.Close()
err := tray.shutdown()
if err != nil {
Expand Down
100 changes: 96 additions & 4 deletions pkg/alertsender/systray/systray.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ import (
"github.com/Microsoft/go-winio"
"github.com/cenkalti/backoff/v4"
"github.com/rabbitstack/fibratus/pkg/alertsender"
"github.com/rabbitstack/fibratus/pkg/kevent"
"github.com/rabbitstack/fibratus/pkg/sys"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/windows"
"math"
"os"
"path/filepath"
"time"
)

Expand All @@ -44,6 +49,7 @@ const systrayPipe = `\\.\pipe\fibratus-systray`
// by the systray sender.
type systray struct {
config Config
proc windows.Handle // systray server process handle
}

// MsgType determines the type of the message sent
Expand Down Expand Up @@ -84,6 +90,78 @@ func makeSender(config alertsender.Config) (alertsender.Sender, error) {
return &systray{}, nil
}

// spin up systray server process
var si windows.StartupInfo
var pi windows.ProcessInformation

exe, err := os.Executable()
if err != nil {
return nil, err
}
cmdline := filepath.Join(filepath.Dir(exe), "fibratus-systray.exe")
argv, err := windows.UTF16PtrFromString(cmdline)
if err != nil {
return nil, err
}

log.Infof("starting systray server process %q", cmdline)

if sys.IsWindowsService() {
// if we're running inside Windows Service, the systray server
// process must be created in the console session and with the
// currently logged-in user access token
var token windows.Token

// enable TCB privilege to obtain the console session user token
sys.SetTcbPrivilege()

consoleSessionID := sys.WTSGetActiveConsoleSessionID()
if consoleSessionID == math.MaxUint32 && sys.IsInSandbox() {
// if we failed to obtain the console session ID and
// are running inside Windows Sandbox, use session 1
consoleSessionID = 1
}

if sys.WTSQueryUserToken(consoleSessionID, &token) {
log.Infof("obtained user token for console session ID %d", consoleSessionID)
err = windows.CreateProcessAsUser(
token,
nil,
argv,
nil,
nil,
false,
windows.CREATE_NO_WINDOW,
nil,
nil,
&si,
&pi,
)
} else {
err = fmt.Errorf("unable to obtain user token for console session ID %d", consoleSessionID)
}

// drop TCB privilege and close the token handle
sys.RemoveTcbPrivilege()
_ = windows.CloseHandle(windows.Handle(token))
} else {
err = windows.CreateProcess(
nil,
argv,
nil,
nil,
false,
windows.CREATE_NO_WINDOW,
nil,
nil,
&si,
&pi)
}

if err != nil {
log.Warnf("unable to start systray server process: %v", err)
}

s := &systray{config: c}
b := &backoff.ExponentialBackOff{
// first backoff timeout will be somewhere in the 100 - 300 ms range given the default multiplier
Expand Down Expand Up @@ -111,26 +189,39 @@ func makeSender(config alertsender.Config) (alertsender.Sender, error) {
break
}

return s, s.writePipe(&Msg{Type: Conf, Data: c})
log.Info("established connection to systray server")

s.proc = pi.Process

return s, s.send(&Msg{Type: Conf, Data: c})
}

func (s *systray) Send(alert alertsender.Alert) error {
return s.writePipe(&Msg{Type: Balloon, Data: alert})
// remove all events to avoid decoding errors on systray server end
alert.Events = make([]*kevent.Kevent, 0)
return s.send(&Msg{Type: Balloon, Data: alert})
}

func (*systray) Type() alertsender.Type { return alertsender.Systray }
func (*systray) SupportsMarkdown() bool { return false }

func (s *systray) Shutdown() error { return nil }
func (s *systray) Shutdown() error {
if s.proc != 0 && sys.IsProcessRunning(s.proc) {
return windows.TerminateProcess(s.proc, 1)
}
return nil
}

func (s *systray) writePipe(m *Msg) error {
func (s *systray) send(m *Msg) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

conn, err := winio.DialPipeContext(ctx, systrayPipe)
if err != nil {
return fmt.Errorf("unable to dial %s pipe: %v", systrayPipe, err)
}
defer conn.Close()

b, err := m.encode()
if err != nil {
return err
Expand All @@ -141,6 +232,7 @@ func (s *systray) writePipe(m *Msg) error {
if _, err = conn.Write(b); err != nil {
return fmt.Errorf("unable to write systray pipe: %v", err)
}

return nil
}

Expand Down
34 changes: 29 additions & 5 deletions pkg/sys/privilege.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (
const (
// SeDebugPrivilege is the name of the privilege used to debug programs.
SeDebugPrivilege = "SeDebugPrivilege"
// SeTcbPrivilege privilege identifies its holder as part of the trusted computer base.
SeTcbPrivilege = "SeTcbPrivilege"
)

// Errors returned by AdjustTokenPrivileges.
Expand All @@ -41,6 +43,8 @@ const (
const (
// PrivilegedEnabled enables the privilege.
PrivilegedEnabled uint32 = 0x00000002
// PrivilegeRemoved removes the privilege.
PrivilegeRemoved uint32 = 0x00000004
)

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

// EnableTokenPrivileges enables the specified privileges in the given
// adjustTokenPrivileges enables/disables the specified privileges in the given
// Token. The token must have TOKEN_ADJUST_PRIVILEGES access. If the token
// does not already contain the privilege it cannot be enabled.
func EnableTokenPrivileges(token windows.Token, privileges ...string) error {
func adjustTokenPrivileges(token windows.Token, state uint32, privileges ...string) error {
privValues, err := mapPrivileges(privileges)
if err != nil {
return err
Expand All @@ -74,7 +78,7 @@ func EnableTokenPrivileges(token windows.Token, privileges ...string) error {
if err := binary.Write(&b, binary.LittleEndian, p); err != nil {
continue
}
if err := binary.Write(&b, binary.LittleEndian, PrivilegedEnabled); err != nil {
if err := binary.Write(&b, binary.LittleEndian, state); err != nil {
continue
}
}
Expand All @@ -90,9 +94,29 @@ func EnableTokenPrivileges(token windows.Token, privileges ...string) error {
return nil
}

// SetDebugPrivilege sets the debug privilege in the current running process.
// SetDebugPrivilege sets the debug privilege in the current process token.
func SetDebugPrivilege() {
enablePrivileges(SeDebugPrivilege)
}

// SetTcbPrivilege sets the TCB privilege in the current process token.
func SetTcbPrivilege() {
enablePrivileges(SeTcbPrivilege)
}

// RemoveTcbPrivilege removes the TCB privilege from the access token of the current process.
func RemoveTcbPrivilege() {
removePrivileges(SeTcbPrivilege)
}

func enablePrivileges(privs ...string) {
var token windows.Token
_ = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
_ = adjustTokenPrivileges(token, PrivilegedEnabled, privs...)
}

func removePrivileges(privs ...string) {
var token windows.Token
_ = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
_ = EnableTokenPrivileges(token, SeDebugPrivilege)
_ = adjustTokenPrivileges(token, PrivilegeRemoved, privs...)
}
2 changes: 2 additions & 0 deletions pkg/sys/syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ package sys

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

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