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 451afaea4..3b83a1444 100644 --- a/pkg/alertsender/eventlog/eventlog.go +++ b/pkg/alertsender/eventlog/eventlog.go @@ -19,16 +19,15 @@ package eventlog import ( + "errors" "fmt" "github.com/rabbitstack/fibratus/pkg/alertsender" + 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 minIDChars = 12 type eventlog struct { @@ -45,11 +44,18 @@ 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(evlog.Levels) + 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) @@ -59,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 @@ -87,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) @@ -101,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/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..4b10baf88 100644 --- a/pkg/outputs/eventlog/api.go +++ b/pkg/outputs/eventlog/api.go @@ -25,21 +25,12 @@ 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 +65,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 +89,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..045b6befe 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. @@ -81,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 cab99fc07..6912a1b6e 100644 --- a/pkg/outputs/eventlog/eventlog.go +++ b/pkg/outputs/eventlog/eventlog.go @@ -22,6 +22,8 @@ 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" @@ -31,13 +33,6 @@ import ( ) const ( - // source under which eventlog events are reported - 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" - // categoryOffset specifies the start of the event id number space categoryOffset = 25 ) @@ -63,10 +58,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(eventlog.Levels) 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) } } @@ -88,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 @@ -120,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 levelFromString(e.config.Level) { - case Info: - return e.evtlog.Info(eventID, categoryID, buf) - case Warn: - return e.evtlog.Warning(eventID, categoryID, buf) - case 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 { @@ -154,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 }} 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/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 new file mode 100644 index 000000000..21082a6ca --- /dev/null +++ b/pkg/util/eventlog/eventlog.go @@ -0,0 +1,99 @@ +/* + * 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/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 + +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 +) + +// 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 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 key.Close() + + sk, exists, err := registry.CreateKey(key, Source, registry.SET_VALUE) + if err != nil { + return err + } + defer sk.Close() + if exists { + return ErrKeyExists + } + + err = sk.SetDWordValue("CustomSource", 1) + if err != nil { + return err + } + err = sk.SetExpandStringValue("EventMessageFile", msgFile) + if err != nil { + return err + } + err = sk.SetExpandStringValue("CategoryMessageFile", msgFile) + if err != nil { + return err + } + err = sk.SetDWordValue("TypesSupported", eventsSupported) + if err != nil { + return err + } + err = sk.SetDWordValue("CategoryCount", categoryCount) + if err != nil { + return err + } + return nil +}