From 8674333b5186453ea3d5b665abace0b3bc9c1788 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Sat, 2 Nov 2024 19:50:27 +0100 Subject: [PATCH 1/3] chore(alertsenders): Expose StringShort methods for process/event and create a common eventlog package --- pkg/alertsender/eventlog/eventlog.go | 22 +++++- pkg/kevent/kevent.go | 63 +++++++++++++-- pkg/outputs/eventlog/api.go | 65 +-------------- pkg/outputs/eventlog/config.go | 26 ------ pkg/outputs/eventlog/eventlog.go | 15 ++-- pkg/ps/types/types_windows.go | 66 ++++++++++++++++ pkg/util/eventlog/eventlog.go | 113 +++++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 103 deletions(-) create mode 100644 pkg/util/eventlog/eventlog.go diff --git a/pkg/alertsender/eventlog/eventlog.go b/pkg/alertsender/eventlog/eventlog.go index 451afaea4..614f3a884 100644 --- a/pkg/alertsender/eventlog/eventlog.go +++ b/pkg/alertsender/eventlog/eventlog.go @@ -19,15 +19,26 @@ package eventlog import ( + "errors" "fmt" "github.com/rabbitstack/fibratus/pkg/alertsender" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" + evlog "github.com/rabbitstack/fibratus/pkg/util/eventlog" "golang.org/x/sys/windows" "hash/crc32" "strings" ) -// source represents the event source that generates the alerts -const source = "Fibratus" +const ( + // source represents the event source that generates the alerts + source = "Fibratus" + // levels designates the supported eventlog levels + levels = uint32(evlog.Info | evlog.Warn | evlog.Erro) + // msgFile specifies the location of the eventlog message DLL + msgFile = "%ProgramFiles%\\Fibratus\\fibratus.dll" + // keyName represents the registry key under which the eventlog source is registered + keyName = `SYSTEM\CurrentControlSet\Services\EventLog` +) const minIDChars = 12 @@ -50,6 +61,13 @@ func makeSender(config alertsender.Config) (alertsender.Sender, error) { return nil, fmt.Errorf("could not convert source name: %v", err) } + err = evlog.Install(source, msgFile, keyName, false, levels, uint32(len(ktypes.Categories()))) + if err != nil { + if !errors.Is(err, evlog.ErrKeyExists{}) { + return nil, err + } + } + h, err := windows.RegisterEventSource(nil, sourceName) if err != nil { return nil, fmt.Errorf("could not register event source: %v", err) diff --git a/pkg/kevent/kevent.go b/pkg/kevent/kevent.go index e7044bef7..2f7efe7e4 100644 --- a/pkg/kevent/kevent.go +++ b/pkg/kevent/kevent.go @@ -120,10 +120,10 @@ func (e *Kevent) String() string { Name: %s Category: %s Description: %s - Host: %s, - Timestamp: %s, - Kparams: %s, - Metadata: %s, + Host: %s + Timestamp: %s + Kparams: %s + Metadata: %s %s `, e.Seq, @@ -150,9 +150,9 @@ func (e *Kevent) String() string { Name: %s Category: %s Description: %s - Host: %s, - Timestamp: %s, - Kparams: %s, + Host: %s + Timestamp: %s + Kparams: %s Metadata: %s `, e.Seq, @@ -170,6 +170,55 @@ func (e *Kevent) String() string { ) } +// StringShort returns event's string representation +// by removing some irrelevant event/process fields. +func (e *Kevent) StringShort() string { + e.mmux.RLock() + defer e.mmux.RUnlock() + if e.PS != nil { + return fmt.Sprintf(` + Seq: %d + Pid: %d + Tid: %d + Name: %s + Category: %s + Host: %s + Timestamp: %s + Parameters: %s + %s + `, + e.Seq, + e.PID, + e.Tid, + e.Name, + e.Category, + e.Host, + e.Timestamp, + e.Kparams, + e.PS.StringShort(), + ) + } + return fmt.Sprintf(` + Seq: %d + Pid: %d + Tid: %d + Name: %s + Category: %s + Host: %s + Timestamp: %s + Parameters: %s + `, + e.Seq, + e.PID, + e.Tid, + e.Name, + e.Category, + e.Host, + e.Timestamp, + e.Kparams, + ) +} + // Empty return a pristine event instance. func Empty() *Kevent { return &Kevent{ diff --git a/pkg/outputs/eventlog/api.go b/pkg/outputs/eventlog/api.go index 4b571ba25..a86b924b6 100644 --- a/pkg/outputs/eventlog/api.go +++ b/pkg/outputs/eventlog/api.go @@ -25,21 +25,17 @@ package eventlog import ( "bytes" "errors" - "fmt" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" + "github.com/rabbitstack/fibratus/pkg/util/eventlog" "syscall" "golang.org/x/sys/windows" - "golang.org/x/sys/windows/registry" ) const addKeyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application` var categoryCount = len(ktypes.Categories()) -// ErrKeyExists signals that the registry key already exists -var ErrKeyExists = fmt.Errorf("%s\\%s already exists", addKeyName, source) - // Eventlog provides access to the system log. type Eventlog struct { Handle windows.Handle @@ -74,59 +70,6 @@ func OpenRemote(host, source string) (*Eventlog, error) { return &Eventlog{Handle: h}, nil } -// Install modifies PC registry to allow logging with an event source src. -// It adds all required keys and values to the event log registry key. -// Install uses msgFile as the event message file. If useExpandKey is true, -// the event message file is installed as REG_EXPAND_SZ value, -// otherwise as REG_SZ. Use bitwise of log.Error, log.Warning and -// log.Info to specify events supported by the new event source. -func Install(src, msgFile string, useExpandKey bool, eventsSupported uint32) error { - appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.CREATE_SUB_KEY) - if err != nil { - return err - } - defer appkey.Close() - - sk, alreadyExist, err := registry.CreateKey(appkey, src, registry.SET_VALUE) - if err != nil { - return err - } - defer sk.Close() - if alreadyExist { - return ErrKeyExists - } - - err = sk.SetDWordValue("CustomSource", 1) - if err != nil { - return err - } - if useExpandKey { - err = sk.SetExpandStringValue("EventMessageFile", msgFile) - } else { - err = sk.SetStringValue("EventMessageFile", msgFile) - } - if err != nil { - return err - } - if useExpandKey { - err = sk.SetExpandStringValue("CategoryMessageFile", msgFile) - } else { - err = sk.SetStringValue("CategoryMessageFile", msgFile) - } - if err != nil { - return err - } - err = sk.SetDWordValue("TypesSupported", eventsSupported) - if err != nil { - return err - } - err = sk.SetDWordValue("CategoryCount", uint32(categoryCount)) - if err != nil { - return err - } - return nil -} - // Close closes event log. func (l *Eventlog) Close() error { return windows.DeregisterEventSource(l.Handle) @@ -151,15 +94,15 @@ func (l *Eventlog) report(etype uint16, eid uint32, category uint16, msg []byte) // Info writes an information event msg with event id eid to the end of event log. func (l *Eventlog) Info(eid uint32, category uint16, msg []byte) error { - return l.report(uint16(Info), eid, category, msg) + return l.report(uint16(eventlog.Info), eid, category, msg) } // Warning writes a warning event msg with event id eid to the end of event log. func (l *Eventlog) Warning(eid uint32, category uint16, msg []byte) error { - return l.report(uint16(Warn), eid, category, msg) + return l.report(uint16(eventlog.Warn), eid, category, msg) } // Error writes an error event msg with event id eid to the end of event log. func (l *Eventlog) Error(eid uint32, category uint16, msg []byte) error { - return l.report(uint16(Erro), eid, category, msg) + return l.report(uint16(eventlog.Erro), eid, category, msg) } diff --git a/pkg/outputs/eventlog/config.go b/pkg/outputs/eventlog/config.go index 418e088fb..65c5f0bf6 100644 --- a/pkg/outputs/eventlog/config.go +++ b/pkg/outputs/eventlog/config.go @@ -19,7 +19,6 @@ package eventlog import ( - "fmt" "github.com/rabbitstack/fibratus/pkg/kevent" "text/template" @@ -33,31 +32,6 @@ const ( tmpl = "output.eventlog.template" ) -// Level is the type definition for the eventlog log level -type Level uint16 - -const ( - // Info represents the info log level - Info Level = 4 - // Warn represents the warning info level - Warn Level = 2 - // Erro represents the error log level - Erro Level = 1 -) - -func levelFromString(s string) Level { - switch s { - case "info", "INFO": - return Info - case "warn", "warning", "WARN", "WARNING": - return Warn - case "erro", "error", "ERRO", "ERROR": - return Erro - default: - panic(fmt.Sprintf("unrecognized evtlog level: %s", s)) - } -} - // Config contains configuration properties for fine-tuning the eventlog output. type Config struct { // Enabled determines whether the eventlog output is enabled. diff --git a/pkg/outputs/eventlog/eventlog.go b/pkg/outputs/eventlog/eventlog.go index cab99fc07..f4fc1398c 100644 --- a/pkg/outputs/eventlog/eventlog.go +++ b/pkg/outputs/eventlog/eventlog.go @@ -22,6 +22,7 @@ package eventlog import ( "errors" + "github.com/rabbitstack/fibratus/pkg/util/eventlog" "text/template" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" @@ -34,7 +35,7 @@ const ( // source under which eventlog events are reported source = "Fibratus" // levels designates the supported eventlog levels - levels = uint32(Info | Warn | Erro) + levels = uint32(eventlog.Info | eventlog.Warn | eventlog.Erro) // msgFile specifies the location of the eventlog message DLL msgFile = "%ProgramFiles%\\Fibratus\\fibratus.dll" @@ -63,10 +64,10 @@ func initEventlog(config outputs.Config) (outputs.OutputGroup, error) { if !ok { return outputs.Fail(outputs.ErrInvalidConfig(outputs.Eventlog, config.Output)) } - err := Install(source, msgFile, false, levels) + err := eventlog.Install(source, msgFile, addKeyName, false, levels, uint32(categoryCount)) if err != nil { // ignore error if the key already exists - if !errors.Is(err, ErrKeyExists) { + if !errors.Is(err, eventlog.ErrKeyExists{}) { return outputs.Fail(err) } } @@ -132,12 +133,12 @@ func (e *evtlog) publish(kevt *kevent.Kevent) error { } func (e *evtlog) log(eventID uint32, categoryID uint16, buf []byte) error { - switch levelFromString(e.config.Level) { - case Info: + switch eventlog.LevelFromString(e.config.Level) { + case eventlog.Info: return e.evtlog.Info(eventID, categoryID, buf) - case Warn: + case eventlog.Warn: return e.evtlog.Warning(eventID, categoryID, buf) - case Erro: + case eventlog.Erro: return e.evtlog.Error(eventID, categoryID, buf) default: panic("unknown eventlog level") diff --git a/pkg/ps/types/types_windows.go b/pkg/ps/types/types_windows.go index ae2bb9f76..85c35f3fb 100644 --- a/pkg/ps/types/types_windows.go +++ b/pkg/ps/types/types_windows.go @@ -201,6 +201,72 @@ func (ps *PS) String() string { ) } +// StringShort returns a string representation of the process' state +// by removing some verbose attributes such as the env variables block. +func (ps *PS) StringShort() string { + parent := ps.Parent + if parent != nil { + return fmt.Sprintf(` + Pid: %d + Ppid: %d + Name: %s + Parent name: %s + Cmdline: %s + Parent cmdline: %s + Exe: %s + Cwd: %s + SID: %s + Username: %s + Domain: %s + Args: %s + Session ID: %d + Ancestors: %s + `, + ps.PID, + ps.Ppid, + ps.Name, + parent.Name, + ps.Cmdline, + parent.Cmdline, + ps.Exe, + ps.Cwd, + ps.SID, + ps.Username, + ps.Domain, + ps.Args, + ps.SessionID, + strings.Join(ps.Ancestors(), " > "), + ) + } + return fmt.Sprintf(` + Pid: %d + Ppid: %d + Name: %s + Cmdline: %s + Exe: %s + Cwd: %s + SID: %s + Username: %s + Domain: %s + Args: %s + Session ID: %d + Ancestors: %s + `, + ps.PID, + ps.Ppid, + ps.Name, + ps.Cmdline, + ps.Exe, + ps.Cwd, + ps.SID, + ps.Username, + ps.Domain, + ps.Args, + ps.SessionID, + strings.Join(ps.Ancestors(), " > "), + ) +} + // Ancestors returns all ancestors of this process. The string slice contains // the process image name followed by the process id. func (ps *PS) Ancestors() []string { diff --git a/pkg/util/eventlog/eventlog.go b/pkg/util/eventlog/eventlog.go new file mode 100644 index 000000000..2cb8fa1bc --- /dev/null +++ b/pkg/util/eventlog/eventlog.go @@ -0,0 +1,113 @@ +/* + * Copyright 2021-2022 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eventlog + +import ( + "fmt" + "golang.org/x/sys/windows/registry" +) + +// Level is the type definition for the eventlog log level +type Level uint16 + +const ( + // Info represents the info log level + Info Level = 4 + // Warn represents the warning info level + Warn Level = 2 + // Erro represents the error log level + Erro Level = 1 +) + +// LevelFromString resolves the eventlog levle from string +func LevelFromString(s string) Level { + switch s { + case "info", "INFO": + return Info + case "warn", "warning", "WARN", "WARNING": + return Warn + case "erro", "error", "ERRO", "ERROR": + return Erro + default: + panic(fmt.Sprintf("unrecognized evtlog level: %s", s)) + } +} + +// ErrKeyExists signals that the registry key already exists +type ErrKeyExists struct { + src string + key string +} + +func (e ErrKeyExists) Error() string { + return fmt.Sprintf("%s\\%s already exists", e.key, e.src) +} + +// Install modifies PC registry to allow logging with an event source src. +// It adds all required keys and values to the event log registry key. +// Install uses msgFile as the event message file. If useExpandKey is true, +// the event message file is installed as REG_EXPAND_SZ value, +// otherwise as REG_SZ. Use bitwise of log.Error, log.Warning and +// log.Info to specify events supported by the new event source. +func Install(src, f, key string, useExpandKey bool, eventsSupported, cats uint32) error { + appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.CREATE_SUB_KEY) + if err != nil { + return err + } + defer appkey.Close() + + sk, alreadyExist, err := registry.CreateKey(appkey, src, registry.SET_VALUE) + if err != nil { + return err + } + defer sk.Close() + if alreadyExist { + return ErrKeyExists{src, key} + } + + err = sk.SetDWordValue("CustomSource", 1) + if err != nil { + return err + } + if useExpandKey { + err = sk.SetExpandStringValue("EventMessageFile", f) + } else { + err = sk.SetStringValue("EventMessageFile", f) + } + if err != nil { + return err + } + if useExpandKey { + err = sk.SetExpandStringValue("CategoryMessageFile", f) + } else { + err = sk.SetStringValue("CategoryMessageFile", f) + } + if err != nil { + return err + } + err = sk.SetDWordValue("TypesSupported", eventsSupported) + if err != nil { + return err + } + err = sk.SetDWordValue("CategoryCount", cats) + if err != nil { + return err + } + return nil +} From 80d6fa76a2bbf4416766b49877a4a0e1d7bd59ee Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Fri, 8 Nov 2024 21:28:49 +0100 Subject: [PATCH 2/3] chore(alertsenders): Create a common eventlog package This package is a foundation for the future consolidation of the shared eventlog functionality. Additionally, it offers the function for construction of the event id from multiple components. --- pkg/util/eventlog/eventid.go | 57 ++++++++++++++++++++++++ pkg/util/eventlog/eventid_test.go | 52 ++++++++++++++++++++++ pkg/util/eventlog/eventlog.go | 72 +++++++++++++------------------ 3 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 pkg/util/eventlog/eventid.go create mode 100644 pkg/util/eventlog/eventid_test.go diff --git a/pkg/util/eventlog/eventid.go b/pkg/util/eventlog/eventid.go new file mode 100644 index 000000000..f65617e88 --- /dev/null +++ b/pkg/util/eventlog/eventid.go @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2022 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eventlog + +import "golang.org/x/sys/windows" + +// EventID produces the eventlog event identifier from the given +// severity and event code. The format of the event id +// integer is described by the next layout: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +func EventID(etype, code uint16) uint32 { + var id uint32 + + // severity + switch etype { + case windows.EVENTLOG_INFORMATION_TYPE: + id = uint32(0)<<30 | uint32(1)<<29 + case windows.EVENTLOG_WARNING_TYPE: + id = uint32(1)<<30 | uint32(0)<<29 + case windows.EVENTLOG_ERROR_TYPE: + id = uint32(1)<<30 | uint32(1)<<29 + default: + id = uint32(0)<<30 | uint32(1)<<29 + } + // customer bit + id |= uint32(0) << 28 + // reserved bit + id |= uint32(0) << 27 + // facility + id |= uint32(0) << 15 + // code + id |= uint32(code) + + return id +} diff --git a/pkg/util/eventlog/eventid_test.go b/pkg/util/eventlog/eventid_test.go new file mode 100644 index 000000000..9797676ae --- /dev/null +++ b/pkg/util/eventlog/eventid_test.go @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2022 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eventlog + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "golang.org/x/sys/windows" + "testing" +) + +func TestEventID(t *testing.T) { + var tests = []struct { + eid uint32 + expected uint32 + }{ + { + EventID(windows.EVENTLOG_INFORMATION_TYPE, 3191), + 0x20000c77, + }, + { + EventID(windows.EVENTLOG_WARNING_TYPE, 3191), + 0x40000c77, + }, + { + EventID(windows.EVENTLOG_ERROR_TYPE, 3191), + 0x60000c77, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%d", tt.expected), func(t *testing.T) { + assert.Equal(t, tt.expected, tt.eid) + }) + } +} diff --git a/pkg/util/eventlog/eventlog.go b/pkg/util/eventlog/eventlog.go index 2cb8fa1bc..21082a6ca 100644 --- a/pkg/util/eventlog/eventlog.go +++ b/pkg/util/eventlog/eventlog.go @@ -20,9 +20,27 @@ package eventlog import ( "fmt" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" "golang.org/x/sys/windows/registry" ) +const ( + // Source represents the event source that generates the alerts + Source = "Fibratus" + // Levels designates the supported eventlog levels + Levels = uint32(Info | Warn | Erro) + // msgFile specifies the location of the eventlog message DLL + msgFile = "%ProgramFiles%\\Fibratus\\fibratus.dll" + // keyName represents the registry key under which the eventlog source is registered + keyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application` +) + +// ErrKeyExists signals that the registry key already exists +var ErrKeyExists = fmt.Errorf("%s\\%s already exists", keyName, Source) + +// categoryCount indicates the number of current event categories +var categoryCount = uint32(len(ktypes.Categories())) + // Level is the type definition for the eventlog log level type Level uint16 @@ -35,69 +53,37 @@ const ( Erro Level = 1 ) -// LevelFromString resolves the eventlog levle from string -func LevelFromString(s string) Level { - switch s { - case "info", "INFO": - return Info - case "warn", "warning", "WARN", "WARNING": - return Warn - case "erro", "error", "ERRO", "ERROR": - return Erro - default: - panic(fmt.Sprintf("unrecognized evtlog level: %s", s)) - } -} - -// ErrKeyExists signals that the registry key already exists -type ErrKeyExists struct { - src string - key string -} - -func (e ErrKeyExists) Error() string { - return fmt.Sprintf("%s\\%s already exists", e.key, e.src) -} - // Install modifies PC registry to allow logging with an event source src. // It adds all required keys and values to the event log registry key. // Install uses msgFile as the event message file. If useExpandKey is true, // the event message file is installed as REG_EXPAND_SZ value, -// otherwise as REG_SZ. Use bitwise of log.Error, log.Warning and -// log.Info to specify events supported by the new event source. -func Install(src, f, key string, useExpandKey bool, eventsSupported, cats uint32) error { - appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.CREATE_SUB_KEY) +// otherwise as REG_SZ. Use bitwise of Errr, Warn, and Info to specify events +// supported by the new event source. +func Install(eventsSupported uint32) error { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, keyName, registry.CREATE_SUB_KEY) if err != nil { return err } - defer appkey.Close() + defer key.Close() - sk, alreadyExist, err := registry.CreateKey(appkey, src, registry.SET_VALUE) + sk, exists, err := registry.CreateKey(key, Source, registry.SET_VALUE) if err != nil { return err } defer sk.Close() - if alreadyExist { - return ErrKeyExists{src, key} + if exists { + return ErrKeyExists } err = sk.SetDWordValue("CustomSource", 1) if err != nil { return err } - if useExpandKey { - err = sk.SetExpandStringValue("EventMessageFile", f) - } else { - err = sk.SetStringValue("EventMessageFile", f) - } + err = sk.SetExpandStringValue("EventMessageFile", msgFile) if err != nil { return err } - if useExpandKey { - err = sk.SetExpandStringValue("CategoryMessageFile", f) - } else { - err = sk.SetStringValue("CategoryMessageFile", f) - } + err = sk.SetExpandStringValue("CategoryMessageFile", msgFile) if err != nil { return err } @@ -105,7 +91,7 @@ func Install(src, f, key string, useExpandKey bool, eventsSupported, cats uint32 if err != nil { return err } - err = sk.SetDWordValue("CategoryCount", cats) + err = sk.SetDWordValue("CategoryCount", categoryCount) if err != nil { return err } From d3a585f95e05e9041ce3667042db180e85722ced Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Fri, 8 Nov 2024 21:30:28 +0100 Subject: [PATCH 3/3] chore(alertsenders): Adapt to use shared eventlog code Presently, both eventlog output and eventlog sender profit from the common codebase. In the future, we'll modularize the rest of the eventlog operations and make all conumsers use the shared code. --- make.bat | 2 +- pkg/alertsender/alert.go | 13 +++++--- pkg/alertsender/alert_test.go | 4 +-- pkg/alertsender/eventlog/eventlog.go | 45 +++++++-------------------- pkg/outputs/eventlog/api.go | 5 --- pkg/outputs/eventlog/config.go | 2 +- pkg/outputs/eventlog/eventlog.go | 40 +++++++----------------- pkg/outputs/eventlog/eventlog_test.go | 5 +-- pkg/outputs/eventlog/mc/gen.go | 8 +++-- 9 files changed, 44 insertions(+), 80 deletions(-) diff --git a/make.bat b/make.bat index a20aefd87..a2cc24ae8 100644 --- a/make.bat +++ b/make.bat @@ -86,7 +86,7 @@ goto :EOF :mc :: Generate eventlog message compiler input file go generate github.com/rabbitstack/fibratus/pkg/outputs/eventlog -windmc -r pkg/outputs/eventlog/mc pkg/outputs/eventlog/mc/fibratus.mc +windmc -c -r pkg/outputs/eventlog/mc pkg/outputs/eventlog/mc/fibratus.mc windres -O coff -r -fo pkg/outputs/eventlog/mc/fibratus.res pkg/outputs/eventlog/mc/fibratus.rc :: Link the resulting resource object gcc pkg/outputs/eventlog/mc/fibratus.res -o pkg/outputs/eventlog/mc/fibratus.dll -s -shared "-Wl,--subsystem,windows" diff --git a/pkg/alertsender/alert.go b/pkg/alertsender/alert.go index 6d1f09091..6f07284d3 100644 --- a/pkg/alertsender/alert.go +++ b/pkg/alertsender/alert.go @@ -104,14 +104,19 @@ type Alert struct { func (a Alert) String(verbose bool) string { if verbose { var b strings.Builder + if len(a.Events) > 1 { + b.WriteString("System events involved in this alert:\n\n") + } else { + b.WriteString("System event involved in this alert:\n\n") + } for n, evt := range a.Events { - b.WriteString(fmt.Sprintf("Event #%d:\n", n+1)) - b.WriteString(evt.String()) + b.WriteString(fmt.Sprintf("\tEvent #%d:\n", n+1)) + b.WriteString(strings.TrimSuffix(evt.StringShort(), "\t")) } if a.Text == "" { - return fmt.Sprintf("%s\n\n%s", a.Title, b.String()) + return fmt.Sprintf("%s\n\nSeverity: %s\n\n%s", a.Title, a.Severity, b.String()) } - return fmt.Sprintf("%s\n\n%s\n\n%s", a.Title, a.Text, b.String()) + return fmt.Sprintf("%s\n\n%s\n\nSeverity: %s\n\n%s", a.Title, a.Text, a.Severity, b.String()) } if a.Text == "" { diff --git a/pkg/alertsender/alert_test.go b/pkg/alertsender/alert_test.go index 425622b64..21d04b16e 100644 --- a/pkg/alertsender/alert_test.go +++ b/pkg/alertsender/alert_test.go @@ -62,7 +62,7 @@ func TestAlertString(t *testing.T) { }, }}), true, - "Credential discovery via VaultCmd.exe\n\nSuspicious vault enumeration via VaultCmd tool\n\nEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tType: CreateProcess\n\t\tCPU: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tDescription: \n\t\tHost: ,\n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC,\n\t\tKparams: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe,\n\t\tMetadata: ,\n\t \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tEnvs: map[]\n\t\t\n\t", + "Credential discovery via VaultCmd.exe\n\nSuspicious vault enumeration via VaultCmd tool\n\nSeverity: low\n\nSystem event involved in this alert:\n\n\tEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tHost: \n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC\n\t\tParameters: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe\n \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tAncestors: \n\t\n", }, { NewAlertWithEvents("Credential discovery via VaultCmd.exe", "", nil, Normal, []*kevent.Kevent{{ @@ -83,7 +83,7 @@ func TestAlertString(t *testing.T) { }, }}), true, - "Credential discovery via VaultCmd.exe\n\nEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tType: CreateProcess\n\t\tCPU: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tDescription: \n\t\tHost: ,\n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC,\n\t\tKparams: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe,\n\t\tMetadata: ,\n\t \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tEnvs: map[]\n\t\t\n\t", + "Credential discovery via VaultCmd.exe\n\nSeverity: low\n\nSystem event involved in this alert:\n\n\tEvent #1:\n\n\t\tSeq: 0\n\t\tPid: 1023\n\t\tTid: 0\n\t\tName: CreateProcess\n\t\tCategory: process\n\t\tHost: \n\t\tTimestamp: 0001-01-01 00:00:00 +0000 UTC\n\t\tParameters: cmdline➜ C:\\Windows\\system32\\svchost-fake.exe -k RPCSS, name➜ svchost-fake.exe\n \n\t\tPid: 0\n\t\tPpid: 345\n\t\tName: svchost.exe\n\t\tCmdline: C:\\Windows\\System32\\svchost.exe\n\t\tExe: \n\t\tCwd: \n\t\tSID: S-1-5-18\n\t\tUsername: SYSTEM\n\t\tDomain: NT AUTHORITY\n\t\tArgs: []\n\t\tSession ID: 0\n\t\tAncestors: \n\t\n", }, } diff --git a/pkg/alertsender/eventlog/eventlog.go b/pkg/alertsender/eventlog/eventlog.go index 614f3a884..3b83a1444 100644 --- a/pkg/alertsender/eventlog/eventlog.go +++ b/pkg/alertsender/eventlog/eventlog.go @@ -22,24 +22,12 @@ import ( "errors" "fmt" "github.com/rabbitstack/fibratus/pkg/alertsender" - "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" evlog "github.com/rabbitstack/fibratus/pkg/util/eventlog" "golang.org/x/sys/windows" "hash/crc32" "strings" ) -const ( - // source represents the event source that generates the alerts - source = "Fibratus" - // levels designates the supported eventlog levels - levels = uint32(evlog.Info | evlog.Warn | evlog.Erro) - // msgFile specifies the location of the eventlog message DLL - msgFile = "%ProgramFiles%\\Fibratus\\fibratus.dll" - // keyName represents the registry key under which the eventlog source is registered - keyName = `SYSTEM\CurrentControlSet\Services\EventLog` -) - const minIDChars = 12 type eventlog struct { @@ -56,14 +44,14 @@ func makeSender(config alertsender.Config) (alertsender.Sender, error) { if !ok { return nil, alertsender.ErrInvalidConfig(alertsender.Eventlog) } - sourceName, err := windows.UTF16PtrFromString(source) + sourceName, err := windows.UTF16PtrFromString(evlog.Source) if err != nil { return nil, fmt.Errorf("could not convert source name: %v", err) } - err = evlog.Install(source, msgFile, keyName, false, levels, uint32(len(ktypes.Categories()))) + err = evlog.Install(evlog.Levels) if err != nil { - if !errors.Is(err, evlog.ErrKeyExists{}) { + if !errors.Is(err, evlog.ErrKeyExists) { return nil, err } } @@ -77,24 +65,11 @@ func makeSender(config alertsender.Config) (alertsender.Sender, error) { // Send logs the alert to the eventlog. func (s *eventlog) Send(alert alertsender.Alert) error { - var etype uint16 - switch alert.Severity { - case alertsender.Normal: - etype = windows.EVENTLOG_INFORMATION_TYPE - case alertsender.Medium: - etype = windows.EVENTLOG_WARNING_TYPE - case alertsender.High, alertsender.Critical: - etype = windows.EVENTLOG_ERROR_TYPE - default: - etype = windows.EVENTLOG_INFORMATION_TYPE - } - - var eventID uint32 - + var code uint16 // despite the event id is 4-byte long // we can only use 2 bytes to store the - // event identifier. Calculate the hash - // of the event id from alert identifier + // event code. Calculate the hash + // of the event code from alert identifier // but keeping in mind collisions are // possible since we're mapping a larger // space to a smaller one @@ -105,8 +80,7 @@ func (s *eventlog) Send(alert alertsender.Alert) error { id := strings.Replace(alert.ID, "-", "", -1) h := crc32.ChecksumIEEE([]byte(id[:minIDChars])) // take the lower 16 bits of the CRC32 hash - eid := uint16(h & 0xFFFF) - eventID = uint32(eid) + code = uint16(h & 0xFFFF) } msg := alert.String(s.config.Verbose) @@ -119,7 +93,10 @@ func (s *eventlog) Send(alert alertsender.Alert) error { return fmt.Errorf("could not convert eventlog message to UTF16: %v: %s", err, msg) } - return windows.ReportEvent(s.log, etype, 0, eventID, uintptr(0), 1, 0, &m, nil) + return windows.ReportEvent(s.log, windows.EVENTLOG_INFORMATION_TYPE, 0, + evlog.EventID(windows.EVENTLOG_INFORMATION_TYPE, code), + uintptr(0), + 1, 0, &m, nil) } // Shutdown deregisters the event source. diff --git a/pkg/outputs/eventlog/api.go b/pkg/outputs/eventlog/api.go index a86b924b6..4b10baf88 100644 --- a/pkg/outputs/eventlog/api.go +++ b/pkg/outputs/eventlog/api.go @@ -25,17 +25,12 @@ package eventlog import ( "bytes" "errors" - "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" "github.com/rabbitstack/fibratus/pkg/util/eventlog" "syscall" "golang.org/x/sys/windows" ) -const addKeyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application` - -var categoryCount = len(ktypes.Categories()) - // Eventlog provides access to the system log. type Eventlog struct { Handle windows.Handle diff --git a/pkg/outputs/eventlog/config.go b/pkg/outputs/eventlog/config.go index 65c5f0bf6..045b6befe 100644 --- a/pkg/outputs/eventlog/config.go +++ b/pkg/outputs/eventlog/config.go @@ -55,7 +55,7 @@ func (c Config) parseTemplate() (*template.Template, error) { // AddFlags registers persistent flags. func AddFlags(flags *pflag.FlagSet) { flags.String(tmpl, "", "Go template for rendering the eventlog message") - flags.String(level, "info", "Specifies the eventlog level") + flags.String(level, "info", "Specifies the eventlog level. Deprecated") flags.String(remoteHost, "", "Address of the remote eventlog intake") flags.Bool(enabled, false, "Indicates if the eventlog output is enabled") } diff --git a/pkg/outputs/eventlog/eventlog.go b/pkg/outputs/eventlog/eventlog.go index f4fc1398c..6912a1b6e 100644 --- a/pkg/outputs/eventlog/eventlog.go +++ b/pkg/outputs/eventlog/eventlog.go @@ -23,6 +23,7 @@ package eventlog import ( "errors" "github.com/rabbitstack/fibratus/pkg/util/eventlog" + "golang.org/x/sys/windows" "text/template" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" @@ -32,13 +33,6 @@ import ( ) const ( - // source under which eventlog events are reported - source = "Fibratus" - // levels designates the supported eventlog levels - levels = uint32(eventlog.Info | eventlog.Warn | eventlog.Erro) - // msgFile specifies the location of the eventlog message DLL - msgFile = "%ProgramFiles%\\Fibratus\\fibratus.dll" - // categoryOffset specifies the start of the event id number space categoryOffset = 25 ) @@ -64,10 +58,10 @@ func initEventlog(config outputs.Config) (outputs.OutputGroup, error) { if !ok { return outputs.Fail(outputs.ErrInvalidConfig(outputs.Eventlog, config.Output)) } - err := eventlog.Install(source, msgFile, addKeyName, false, levels, uint32(categoryCount)) + err := eventlog.Install(eventlog.Levels) if err != nil { // ignore error if the key already exists - if !errors.Is(err, eventlog.ErrKeyExists{}) { + if !errors.Is(err, eventlog.ErrKeyExists) { return outputs.Fail(err) } } @@ -89,9 +83,9 @@ func (e *evtlog) Connect() error { err error ) if e.config.RemoteHost != "" { - evl, err = OpenRemote(e.config.RemoteHost, source) + evl, err = OpenRemote(e.config.RemoteHost, eventlog.Source) } else { - evl, err = Open(source) + evl, err = Open(eventlog.Source) } if err != nil { return err @@ -121,30 +115,18 @@ func (e *evtlog) publish(kevt *kevent.Kevent) error { if err != nil { return err } - eid := e.eventID(kevt) - if eid == 0 { + categoryID := e.categoryID(kevt) + eventID := eventlog.EventID(windows.EVENTLOG_INFORMATION_TYPE, uint16(e.eventCode(kevt))) + if eventID == 0 { return ErrUnknownEventID } - err = e.log(eid, e.categoryID(kevt), buf) + err = e.evtlog.Info(eventID, categoryID, buf) if err != nil { return err } return nil } -func (e *evtlog) log(eventID uint32, categoryID uint16, buf []byte) error { - switch eventlog.LevelFromString(e.config.Level) { - case eventlog.Info: - return e.evtlog.Info(eventID, categoryID, buf) - case eventlog.Warn: - return e.evtlog.Warning(eventID, categoryID, buf) - case eventlog.Erro: - return e.evtlog.Error(eventID, categoryID, buf) - default: - panic("unknown eventlog level") - } -} - // categoryID maps category name to eventlog identifier. func (e *evtlog) categoryID(kevt *kevent.Kevent) uint16 { for i, cat := range e.cats { @@ -155,8 +137,8 @@ func (e *evtlog) categoryID(kevt *kevent.Kevent) uint16 { return 0 } -// eventID returns the event ID from the event type. -func (e *evtlog) eventID(kevt *kevent.Kevent) uint32 { +// eventCode returns the event ID from the event type. +func (e *evtlog) eventCode(kevt *kevent.Kevent) uint32 { for i, evt := range e.events { if evt.Name == kevt.Name { return uint32(i + categoryOffset) diff --git a/pkg/outputs/eventlog/eventlog_test.go b/pkg/outputs/eventlog/eventlog_test.go index a0c97d1b4..879ef8922 100644 --- a/pkg/outputs/eventlog/eventlog_test.go +++ b/pkg/outputs/eventlog/eventlog_test.go @@ -20,6 +20,7 @@ package eventlog import ( "errors" + "github.com/rabbitstack/fibratus/pkg/util/eventlog" "github.com/rabbitstack/fibratus/pkg/util/va" "golang.org/x/sys/windows" "testing" @@ -46,8 +47,8 @@ func TestEvtlogPublish(t *testing.T) { events: ktypes.GetKtypesMetaIndexed(), cats: ktypes.Categories(), } - err = Install(source, msgFile, false, levels) - if err != nil && !errors.Is(err, ErrKeyExists) { + err = eventlog.Install(eventlog.Levels) + if err != nil && !errors.Is(err, eventlog.ErrKeyExists) { require.NoError(t, err) } require.NoError(t, el.Connect()) diff --git a/pkg/outputs/eventlog/mc/gen.go b/pkg/outputs/eventlog/mc/gen.go index ba3417831..4c142a075 100644 --- a/pkg/outputs/eventlog/mc/gen.go +++ b/pkg/outputs/eventlog/mc/gen.go @@ -20,7 +20,7 @@ package main import ( "bytes" - "errors" + "fmt" "github.com/Masterminds/sprig/v3" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" "io" @@ -55,7 +55,7 @@ func (s *Source) Generate(w io.Writer) error { t := template.Must(template.New("main").Funcs(funcmap).Parse(srcTemplate)) err := t.Execute(w, s) if err != nil { - return errors.New("failed to execute template: " + err.Error()) + return fmt.Errorf("failed to execute template: %v", err) } return nil } @@ -79,6 +79,10 @@ func main() { const srcTemplate = ` ;// Code generated by 'go generate'; DO NOT EDIT. +MessageIdTypedef=DWORD + +LanguageNames=(English=0x409:MSG00409) + ;//************** Event categories ************ {{- range $i, $cat := .Categories }} MessageId={{ add1 $i }}