Skip to content

Commit

Permalink
chore(alertsenders): Adapt to use shared eventlog code
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rabbitstack committed Nov 23, 2024
1 parent 2f66468 commit 74a755b
Show file tree
Hide file tree
Showing 9 changed files with 44 additions and 80 deletions.
2 changes: 1 addition & 1 deletion make.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
13 changes: 9 additions & 4 deletions pkg/alertsender/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "" {
Expand Down
4 changes: 2 additions & 2 deletions pkg/alertsender/alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{{
Expand All @@ -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",
},
}

Expand Down
45 changes: 11 additions & 34 deletions pkg/alertsender/eventlog/eventlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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.
Expand Down
5 changes: 0 additions & 5 deletions pkg/outputs/eventlog/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/outputs/eventlog/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
40 changes: 11 additions & 29 deletions pkg/outputs/eventlog/eventlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
)
Expand All @@ -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)
}
}
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions pkg/outputs/eventlog/eventlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand Down
8 changes: 6 additions & 2 deletions pkg/outputs/eventlog/mc/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package main

import (
"bytes"
"errors"
"fmt"
"github.com/Masterminds/sprig/v3"
"github.com/rabbitstack/fibratus/pkg/kevent/ktypes"
"io"
Expand Down Expand Up @@ -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
}
Expand All @@ -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 }}
Expand Down

0 comments on commit 74a755b

Please sign in to comment.