From 990ca77ef18dc962ce380ffc8c12748254c38651 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Thu, 17 Sep 2026 17:53:17 +0200 Subject: [PATCH 1/3] refactor(event): Implement tagged event table Keeping a byte array for event identification came with a lot of hassle. Firstly, there is a performance impact in switch statements as Go can't jump-table GUID structures. The conversion from native ETW to our own event type depended on either the opcode or event ID, which have different types, uint8 and uint16 respectively. As an upcoming initiative, we're going to tackle a tracelogging provider that doesn't have either opcode or event ID. That would exacerbate the array-based event type alias. The aim of this refactor is to create a dense, integer-based type for the event identification and build an indexed event table to decouple the event metadata such as the name, description, source, or category, out of the main event. This paves the road for future optimizations, such as event pooling or array-based event parameters. --- cmd/fibratus/app/list/list.go | 9 +- internal/etw/consumer.go | 8 +- internal/etw/processors/fs_windows.go | 12 +- internal/etw/processors/fs_windows_test.go | 25 +- .../{mem_windows.go => memory_windows.go} | 2 +- ...windows_test.go => memory_windows_test.go} | 11 +- internal/etw/processors/net_windows.go | 10 +- internal/etw/processors/net_windows_test.go | 30 +- internal/etw/processors/registry_windows.go | 2 +- .../etw/processors/registry_windows_test.go | 27 +- internal/etw/source.go | 19 +- internal/etw/source_test.go | 20 +- internal/etw/stackext.go | 14 +- internal/etw/stackext_test.go | 19 +- internal/evasion/direct_syscall_test.go | 15 +- internal/evasion/indirect_syscall_test.go | 8 +- internal/evasion/scanner_test.go | 2 - pkg/aggregator/aggregator_test.go | 11 +- .../transformers/remove/remove_test.go | 7 +- .../transformers/rename/rename_test.go | 7 +- pkg/aggregator/transformers/tags/tags_test.go | 2 +- pkg/aggregator/transformers/trim/trim_test.go | 20 +- pkg/alertsender/alert.go | 4 +- pkg/alertsender/alert_test.go | 18 +- pkg/alertsender/eventlog/eventlog_test.go | 34 +- pkg/alertsender/mail/renderer_test.go | 41 +- pkg/cap/header.go | 2 +- pkg/cap/version/version_windows.go | 2 + pkg/cap/writer_windows.go | 6 +- pkg/cap/writer_windows_test.go | 17 +- pkg/config/eventsource.go | 51 +- pkg/config/eventsource_test.go | 2 +- pkg/event/batch_test.go | 56 +- pkg/event/bitset.go | 103 --- pkg/event/bitset_test.go | 129 --- pkg/event/category.go | 127 +-- pkg/event/event.go | 48 +- pkg/event/event_windows.go | 103 +-- pkg/event/event_windows_test.go | 36 +- pkg/event/formatter.go | 20 +- pkg/event/formatter_test.go | 139 +-- pkg/event/formatter_windows.go | 6 +- pkg/event/marshaller_test.go | 150 ++-- pkg/event/marshaller_windows.go | 166 ++-- pkg/event/metainfo_windows.go | 316 +++---- pkg/event/metainfo_windows_test.go | 33 +- pkg/event/param_decoder_windows.go | 25 +- pkg/event/param_decoder_windows_test.go | 58 +- pkg/event/param_windows.go | 26 +- pkg/event/queue.go | 2 +- pkg/event/queue_test.go | 8 - pkg/event/stackwalk.go | 2 +- pkg/event/types_windows.go | 809 ++++++------------ pkg/event/types_windows_test.go | 518 ++++++++--- pkg/filament/dict.go | 6 +- pkg/filament/dict_test.go | 42 +- pkg/filament/filament_test.go | 7 +- pkg/filter/accessor.go | 6 +- pkg/filter/accessor_windows.go | 18 +- pkg/filter/accessor_windows_test.go | 26 +- pkg/filter/filter_test.go | 150 ++-- pkg/filter/ql/literal.go | 44 +- pkg/filter/ql/literal_test.go | 67 +- pkg/outputs/amqp/amqp_test.go | 56 +- .../elasticsearch/elasticsearch_test.go | 56 +- pkg/outputs/http/http_test.go | 58 +- .../correct_category_name_field.yml | 2 +- .../incorrect_category_name_field.yml | 2 +- .../_fixtures/merged_filters/filter3.yml | 2 +- pkg/rules/compiler.go | 82 +- pkg/rules/compiler_test.go | 5 +- pkg/rules/engine.go | 19 +- pkg/rules/engine_test.go | 67 +- pkg/rules/sequence_test.go | 59 +- pkg/symbolize/symbolizer_test.go | 29 +- pkg/sys/etw/types.go | 34 +- pkg/yara/config/config.go | 2 +- pkg/yara/config/config_test.go | 16 +- pkg/yara/scanner.go | 2 +- pkg/yara/scanner_test.go | 87 +- 80 files changed, 1833 insertions(+), 2448 deletions(-) rename internal/etw/processors/{mem_windows.go => memory_windows.go} (98%) rename internal/etw/processors/{mem_windows_test.go => memory_windows_test.go} (97%) delete mode 100644 pkg/event/bitset.go delete mode 100644 pkg/event/bitset_test.go diff --git a/cmd/fibratus/app/list/list.go b/cmd/fibratus/app/list/list.go index 92ab1eff6..055795d7a 100644 --- a/cmd/fibratus/app/list/list.go +++ b/cmd/fibratus/app/list/list.go @@ -21,15 +21,16 @@ package list import ( "bufio" "fmt" + "os" + "path/filepath" + "strings" + "github.com/jedib0t/go-pretty/v6/table" "github.com/rabbitstack/fibratus/internal/bootstrap" "github.com/rabbitstack/fibratus/pkg/config" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/filter/fields" "github.com/spf13/cobra" - "os" - "path/filepath" - "strings" ) var Command = &cobra.Command{ @@ -130,7 +131,7 @@ func listEvents(cmd *cobra.Command, args []string) { t.AppendHeader(table.Row{"Name", "Category", "Description"}) t.SetStyle(table.StyleLight) - for _, ev := range event.GetTypesMeta() { + for _, ev := range event.GetTypesInfo() { t.AppendRow(table.Row{ev.Name, ev.Category, ev.Description}) } diff --git a/internal/etw/consumer.go b/internal/etw/consumer.go index b63634970..0044309c6 100644 --- a/internal/etw/consumer.go +++ b/internal/etw/consumer.go @@ -78,8 +78,8 @@ func (c *Consumer) ProcessEvent(r *etw.EventRecord) error { return nil } - if !c.config.EventSource.EventExists(r.ID()) { - eventsUnknown.Add(1) + etype := event.NewTypeFromEventRecord(r) + if etype == event.Unknown { return nil } if event.IsCurrentProcDropped(r.Header.ProcessID) && r.Header.ProviderID != etw.WindowsKernelProcessGUID { @@ -92,13 +92,13 @@ func (c *Consumer) ProcessEvent(r *etw.EventRecord) error { } defer c.approvers.Cleanup(rec) - if c.config.EventSource.ExcludeEvent(rec.ID()) { + if c.config.EventSource.ExcludeEvent(etype) { eventsExcluded.Add(1) return nil } eventsProcessed.Add(1) - evt := event.New(c.sequencer.Get(), rec) + evt := event.New(c.sequencer.Get(), rec, etype) // Dispatch each event to the processor chain. // Processors may further augment the event with diff --git a/internal/etw/processors/fs_windows.go b/internal/etw/processors/fs_windows.go index 0e9a0df19..2027db94e 100644 --- a/internal/etw/processors/fs_windows.go +++ b/internal/etw/processors/fs_windows.go @@ -71,7 +71,7 @@ func newFsProcessor( } func (f *fsProcessor) ProcessEvent(e *event.Event) (*event.Event, bool, error) { - if e.Category == event.File { + if e.Category() == event.File { evt, err := f.processEvent(e) return evt, false, err } @@ -100,7 +100,7 @@ func (f *fsProcessor) processEvent(e *event.Event) (*event.Event, error) { totalRundownFiles.Add(1) f.files[fileObject] = &FileInfo{Name: filepath, Type: fs.GetFileType(filepath, 0)} } - case event.MapFileRundown: + case event.MapViewOfSection: fileKey := e.Params.MustGetUint64(params.FileKey) fileinfo := f.files[fileKey] @@ -144,7 +144,7 @@ func (f *fsProcessor) processEvent(e *event.Event) (*event.Event, error) { // delete file metadata by file object address fileObject := e.Params.MustGetUint64(params.FileObject) delete(f.files, fileObject) - case event.UnmapViewFile: + case event.UnmapViewOfSection: ok, proc := f.psnap.Find(e.PID) addr := e.Params.TryGetAddress(params.FileViewBase) if ok { @@ -157,11 +157,13 @@ func (f *fsProcessor) processEvent(e *event.Event) (*event.Event, error) { totalMapRundownFiles.Add(-1) return e, f.psnap.RemoveMmap(e.PID, addr) + case event.FileOpEnd: + return e, nil default: var fileObject uint64 fileKey := e.Params.MustGetUint64(params.FileKey) - if !e.IsMapViewFile() { + if !e.IsMapViewOfSection() { fileObject = e.Params.MustGetUint64(params.FileObject) } @@ -202,7 +204,7 @@ func (f *fsProcessor) processEvent(e *event.Event) (*event.Event, error) { e.AppendParam(params.FilePath, params.Path, fileinfo.Name) } - if e.IsMapViewFile() { + if e.IsMapViewOfSection() { return e, f.psnap.AddMmap(e) } } diff --git a/internal/etw/processors/fs_windows_test.go b/internal/etw/processors/fs_windows_test.go index 70b841ccd..7e593d2e4 100644 --- a/internal/etw/processors/fs_windows_test.go +++ b/internal/etw/processors/fs_windows_test.go @@ -46,8 +46,7 @@ func TestFsProcessor(t *testing.T) { { "process file rundown", &event.Event{ - Type: event.FileRundown, - Category: event.File, + Type: event.FileRundown, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(124567380264)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -69,9 +68,8 @@ func TestFsProcessor(t *testing.T) { { "process mapped file rundown", &event.Event{ - PID: 10233, - Type: event.MapFileRundown, - Category: event.File, + PID: 10233, + Type: event.MapViewSectionRundown, Params: event.Params{ params.FileKey: {Name: params.FileKey, Type: params.Uint64, Value: uint64(124567380264)}, params.FileViewSize: {Name: params.FileViewSize, Type: params.Uint64, Value: uint64(3098)}, @@ -99,8 +97,7 @@ func TestFsProcessor(t *testing.T) { { "release file and remove file info", &event.Event{ - Type: event.ReleaseFile, - Category: event.File, + Type: event.ReleaseFile, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(18446738026482168384)}, params.FileKey: {Name: params.FileKey, Type: params.Uint64, Value: uint64(14446538026482168384)}, @@ -122,9 +119,8 @@ func TestFsProcessor(t *testing.T) { { "unmap view file", &event.Event{ - PID: 10233, - Type: event.UnmapViewFile, - Category: event.File, + PID: 10233, + Type: event.UnmapViewOfSection, Params: event.Params{ params.FileKey: {Name: params.FileKey, Type: params.Uint64, Value: uint64(124567380264)}, params.FileViewSize: {Name: params.FileViewSize, Type: params.Uint64, Value: uint64(3098)}, @@ -147,8 +143,7 @@ func TestFsProcessor(t *testing.T) { { "process write file", &event.Event{ - Type: event.WriteFile, - Category: event.File, + Type: event.WriteFile, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(18446738026482168384)}, params.FileKey: {Name: params.FileKey, Type: params.Uint64, Value: uint64(14446538026482168384)}, @@ -173,8 +168,7 @@ func TestFsProcessor(t *testing.T) { { "process write file consult handle snapshotter", &event.Event{ - Type: event.WriteFile, - Category: event.File, + Type: event.WriteFile, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(18446738026482168384)}, params.FileKey: {Name: params.FileKey, Type: params.Uint64, Value: uint64(14446538026482168384)}, @@ -198,8 +192,7 @@ func TestFsProcessor(t *testing.T) { { "process enum directory", &event.Event{ - Type: event.EnumDirectory, - Category: event.File, + Type: event.EnumDirectory, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(18446738026482168384)}, params.FileKey: {Name: params.FileKey, Type: params.Uint64, Value: uint64(14446538026482168384)}, diff --git a/internal/etw/processors/mem_windows.go b/internal/etw/processors/memory_windows.go similarity index 98% rename from internal/etw/processors/mem_windows.go rename to internal/etw/processors/memory_windows.go index 2e51c4c49..4149a9947 100644 --- a/internal/etw/processors/mem_windows.go +++ b/internal/etw/processors/memory_windows.go @@ -48,7 +48,7 @@ func (m memProcessor) Close() { } func (m memProcessor) ProcessEvent(e *event.Event) (*event.Event, bool, error) { - if e.Category == event.Mem { + if e.Category() == event.Memory { pid := e.Params.MustGetPid() if e.IsVirtualAlloc() { // retrieve info about the range of pages and enrich the event diff --git a/internal/etw/processors/mem_windows_test.go b/internal/etw/processors/memory_windows_test.go similarity index 97% rename from internal/etw/processors/mem_windows_test.go rename to internal/etw/processors/memory_windows_test.go index dcf666b3e..ef671833e 100644 --- a/internal/etw/processors/mem_windows_test.go +++ b/internal/etw/processors/memory_windows_test.go @@ -19,6 +19,9 @@ package processors import ( + "os" + "testing" + "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/ps" @@ -28,8 +31,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "golang.org/x/sys/windows" - "os" - "testing" ) func TestMemProcessor(t *testing.T) { @@ -47,8 +48,7 @@ func TestMemProcessor(t *testing.T) { { "virtual alloc", &event.Event{ - Type: event.VirtualAlloc, - Category: event.Mem, + Type: event.VirtualAlloc, Params: event.Params{ params.MemRegionSize: {Name: params.MemRegionSize, Type: params.Uint64, Value: uint64(1024)}, params.MemBaseAddress: {Name: params.MemBaseAddress, Type: params.Address, Value: uint64(base)}, @@ -73,8 +73,7 @@ func TestMemProcessor(t *testing.T) { { "virtual free", &event.Event{ - Type: event.VirtualFree, - Category: event.Mem, + Type: event.VirtualFree, Params: event.Params{ params.MemRegionSize: {Name: params.MemRegionSize, Type: params.Uint64, Value: uint64(1024)}, params.MemBaseAddress: {Name: params.MemBaseAddress, Type: params.Address, Value: uint64(base)}, diff --git a/internal/etw/processors/net_windows.go b/internal/etw/processors/net_windows.go index a09001f2b..4c32f955e 100644 --- a/internal/etw/processors/net_windows.go +++ b/internal/etw/processors/net_windows.go @@ -21,7 +21,6 @@ package processors import ( "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" - "github.com/rabbitstack/fibratus/pkg/network" "github.com/rabbitstack/fibratus/pkg/util/ports" ) @@ -38,14 +37,7 @@ func (netProcessor) Name() ProcessorType { return Net } func (n netProcessor) Close() {} func (n *netProcessor) ProcessEvent(e *event.Event) (*event.Event, bool, error) { - if e.Category == event.Net { - if e.IsNetworkTCP() && !e.IsDNS() { - e.AppendEnum(params.NetL4Proto, uint32(network.TCP), network.ProtoNames) - } - if e.IsNetworkUDP() && !e.IsDNS() { - e.AppendEnum(params.NetL4Proto, uint32(network.UDP), network.ProtoNames) - } - + if e.Category() == event.Network { if e.IsDNS() { return e, false, nil } diff --git a/internal/etw/processors/net_windows_test.go b/internal/etw/processors/net_windows_test.go index 6f6e36bc0..94247b8df 100644 --- a/internal/etw/processors/net_windows_test.go +++ b/internal/etw/processors/net_windows_test.go @@ -19,12 +19,14 @@ package processors import ( + "net" + "testing" + "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" + "github.com/rabbitstack/fibratus/pkg/network" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "net" - "testing" ) func TestNetworkProcessor(t *testing.T) { @@ -36,13 +38,13 @@ func TestNetworkProcessor(t *testing.T) { { "send tcpv4", &event.Event{ - Type: event.SendTCPv4, - Category: event.Net, + Type: event.Send, Params: event.Params{ - params.NetDport: {Name: params.NetDport, Type: params.Uint16, Value: uint16(443)}, - params.NetSport: {Name: params.NetSport, Type: params.Uint16, Value: uint16(43123)}, - params.NetSIP: {Name: params.NetSIP, Type: params.IPv4, Value: net.ParseIP("127.0.0.1")}, - params.NetDIP: {Name: params.NetDIP, Type: params.IPv4, Value: net.ParseIP("8.8.8.8")}, + params.NetDport: {Name: params.NetDport, Type: params.Uint16, Value: uint16(443)}, + params.NetSport: {Name: params.NetSport, Type: params.Uint16, Value: uint16(43123)}, + params.NetSIP: {Name: params.NetSIP, Type: params.IPv4, Value: net.ParseIP("127.0.0.1")}, + params.NetDIP: {Name: params.NetDIP, Type: params.IPv4, Value: net.ParseIP("8.8.8.8")}, + params.NetL4Proto: {Name: params.NetL4Proto, Type: params.Enum, Value: uint32(network.TCP), Enum: network.ProtoNames}, }, }, func(e *event.Event, t *testing.T) { @@ -58,13 +60,13 @@ func TestNetworkProcessor(t *testing.T) { { "recv udp6", &event.Event{ - Type: event.RecvUDPv6, - Category: event.Net, + Type: event.Recv, Params: event.Params{ - params.NetDport: {Name: params.NetDport, Type: params.Uint16, Value: uint16(53)}, - params.NetSport: {Name: params.NetSport, Type: params.Uint16, Value: uint16(43123)}, - params.NetSIP: {Name: params.NetSIP, Type: params.IPv4, Value: net.ParseIP("127.0.0.1")}, - params.NetDIP: {Name: params.NetDIP, Type: params.IPv4, Value: net.ParseIP("8.8.8.8")}, + params.NetDport: {Name: params.NetDport, Type: params.Uint16, Value: uint16(53)}, + params.NetSport: {Name: params.NetSport, Type: params.Uint16, Value: uint16(43123)}, + params.NetSIP: {Name: params.NetSIP, Type: params.IPv4, Value: net.ParseIP("127.0.0.1")}, + params.NetDIP: {Name: params.NetDIP, Type: params.IPv4, Value: net.ParseIP("8.8.8.8")}, + params.NetL4Proto: {Name: params.NetL4Proto, Type: params.Enum, Value: uint32(network.UDP), Enum: network.ProtoNames}, }, }, func(e *event.Event, t *testing.T) { diff --git a/internal/etw/processors/registry_windows.go b/internal/etw/processors/registry_windows.go index d3979493f..87b25a579 100644 --- a/internal/etw/processors/registry_windows.go +++ b/internal/etw/processors/registry_windows.go @@ -102,7 +102,7 @@ func newRegistryProcessor(hsnap handle.Snapshotter) Processor { } func (r *registryProcessor) ProcessEvent(e *event.Event) (*event.Event, bool, error) { - if e.Category == event.Registry { + if e.Category() == event.Registry { evt, err := r.processEvent(e) return evt, false, err } diff --git a/internal/etw/processors/registry_windows_test.go b/internal/etw/processors/registry_windows_test.go index 25fd6aa7e..9b75d879c 100644 --- a/internal/etw/processors/registry_windows_test.go +++ b/internal/etw/processors/registry_windows_test.go @@ -47,8 +47,7 @@ func TestRegistryProcessor(t *testing.T) { { "process KCB rundown", &event.Event{ - Type: event.RegKCBRundown, - Category: event.Registry, + Type: event.RegKCBRundown, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.UnicodeString, Value: `\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\bthserv\Parameters`}, params.RegKCB: {Name: params.RegKCB, Type: params.Uint64, Value: uint64(18446666033549154696)}, @@ -68,8 +67,7 @@ func TestRegistryProcessor(t *testing.T) { { "process delete KCB", &event.Event{ - Type: event.RegDeleteKCB, - Category: event.Registry, + Type: event.RegDeleteKCB, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.UnicodeString, Value: `\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\bthserv\Parameters`}, params.RegKCB: {Name: params.RegKCB, Type: params.Uint64, Value: uint64(18446666033549154696)}, @@ -90,8 +88,7 @@ func TestRegistryProcessor(t *testing.T) { { "full key name", &event.Event{ - Type: event.RegOpenKey, - Category: event.Registry, + Type: event.RegOpenKey, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\bthserv\Parameters`}, params.RegKCB: {Name: params.RegKCB, Type: params.Uint64, Value: uint64(0)}, @@ -109,8 +106,7 @@ func TestRegistryProcessor(t *testing.T) { { "incomplete key name", &event.Event{ - Type: event.RegOpenKey, - Category: event.Registry, + Type: event.RegOpenKey, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `Pid`}, params.RegKCB: {Name: params.RegKCB, Type: params.Uint64, Value: uint64(18446666033549154696)}, @@ -130,9 +126,8 @@ func TestRegistryProcessor(t *testing.T) { { "incomplete key name consult handle snapshotter", &event.Event{ - Type: event.RegOpenKey, - Category: event.Registry, - PID: 23234, + Type: event.RegOpenKey, + PID: 23234, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `Pid`}, params.RegKCB: {Name: params.RegKCB, Type: params.Uint64, Value: uint64(18446666033549154696)}, @@ -153,9 +148,8 @@ func TestRegistryProcessor(t *testing.T) { { "process registry set value", &event.Event{ - Type: event.RegSetValue, - Category: event.Registry, - PID: 23234, + Type: event.RegSetValue, + PID: 23234, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`}, params.RegKCB: {Name: params.RegKCB, Type: params.Uint64, Value: uint64(0)}, @@ -175,9 +169,8 @@ func TestRegistryProcessor(t *testing.T) { { "process registry set value from internal event", &event.Event{ - Type: event.RegSetValue, - Category: event.Registry, - PID: 23234, + Type: event.RegSetValue, + PID: 23234, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`}, params.RegKCB: {Name: params.RegKCB, Type: params.Uint64, Value: uint64(0)}, diff --git a/internal/etw/source.go b/internal/etw/source.go index 65376cac4..db0924c0a 100644 --- a/internal/etw/source.go +++ b/internal/etw/source.go @@ -60,8 +60,6 @@ var ( eventsFailed = expvar.NewMap("eventsource.events.failed") // eventsProcessed counts the number of total processed events eventsProcessed = expvar.NewInt("eventsource.events.processed") - // eventsUnknown counts the number of published events which types are not present in the internal catalog - eventsUnknown = expvar.NewInt("eventsource.events.unknown") // eventsExcluded counts the number of excluded events eventsExcluded = expvar.NewInt("eventsource.events.excluded") // buffersRead amount of buffers fetched from the ETW session @@ -138,7 +136,11 @@ func (e *EventSource) Open(config *config.Config) error { config.EventSource.EnableMemEvents = config.EventSource.EnableMemEvents && (e.r.HasMemEvents || (config.Yara.Enabled && !config.Yara.SkipAllocs)) config.EventSource.EnableDNSEvents = config.EventSource.EnableDNSEvents && e.r.HasDNSEvents config.EventSource.EnableAuditAPIEvents = config.EventSource.EnableAuditAPIEvents && e.r.HasAuditAPIEvents - for _, typ := range event.All() { + + for _, typ := range event.AllTypes() { + if typ.OnlyState() || typ.StateSnapshot() { + continue + } if typ == event.CreateProcess || typ == event.TerminateProcess || typ == event.LoadModule || typ == event.UnloadModule { // always allow fundamental events @@ -146,7 +148,7 @@ func (e *EventSource) Open(config *config.Config) error { } // allow events required for memory/file scanning - if typ == event.MapViewFile && config.Yara.Enabled && !config.Yara.SkipMmaps { + if typ == event.MapViewOfSection && config.Yara.Enabled && !config.Yara.SkipMmaps { continue } if typ == event.VirtualAlloc && config.Yara.Enabled && !config.Yara.SkipAllocs { @@ -179,13 +181,10 @@ func (e *EventSource) Open(config *config.Config) error { // modified value. This data is used to attach various parameters // to the RegSetValue event published by the NT Kernel Logger if config.EventSource.EnableRegistryEvents { - // undocumented ETW feature to enable captured data in RegSetValue events - val := 0x2 - eventFilterDescriptor := etw.EventFilterDescriptor{ - Ptr: uintptr(unsafe.Pointer(&val)), + trace.AddProvider(etw.WindowsKernelRegistryGUID, false, WithKeywords(etw.SetValueKeyword), WithEventFilterDescriptors(etw.EventFilterDescriptor{ + Ptr: uintptr(unsafe.Pointer(&etw.CaptureRegistryValue)), Size: 4, - } - trace.AddProvider(etw.WindowsKernelRegistryGUID, false, WithKeywords(etw.SetValueKeyword), WithEventFilterDescriptors(eventFilterDescriptor)) + })) } if config.EventSource.EnableDNSEvents { diff --git a/internal/etw/source_test.go b/internal/etw/source_test.go index 8582d0479..319b6230e 100644 --- a/internal/etw/source_test.go +++ b/internal/etw/source_test.go @@ -180,9 +180,9 @@ func TestEventSourceEnableFlagsDynamically(t *testing.T) { event.RegSetValue, event.CreateFile, event.RenameFile, - event.MapViewFile, + event.MapViewOfSection, event.OpenProcess, - event.ConnectTCPv4, + event.Connect, }, } cfg := &config.Config{ @@ -222,7 +222,7 @@ func TestEventSourceEnableFlagsDynamically(t *testing.T) { require.False(t, cfg.EventSource.TestDropMask(event.UnloadModule)) require.True(t, cfg.EventSource.TestDropMask(event.WriteFile)) - require.True(t, cfg.EventSource.TestDropMask(event.UnmapViewFile)) + require.True(t, cfg.EventSource.TestDropMask(event.UnmapViewOfSection)) require.False(t, cfg.EventSource.TestDropMask(event.OpenProcess)) } @@ -259,7 +259,7 @@ func TestEventSourceEnableFlagsDynamicallyWithYaraEnabled(t *testing.T) { event.RegSetValue, event.RenameFile, event.OpenProcess, - event.ConnectTCPv4, + event.Connect, }, } cfg := &config.Config{ @@ -300,7 +300,7 @@ func TestEventSourceEnableFlagsDynamicallyWithYaraEnabled(t *testing.T) { require.True(t, flags&etw.VirtualAlloc != 0) require.False(t, cfg.EventSource.TestDropMask(event.CreateFile)) - require.True(t, cfg.EventSource.TestDropMask(event.MapViewFile)) + require.True(t, cfg.EventSource.TestDropMask(event.MapViewOfSection)) require.False(t, cfg.EventSource.TestDropMask(event.VirtualAlloc)) } @@ -481,7 +481,7 @@ func TestEventSourceAllEvents(t *testing.T) { return nil }, func(e *event.Event) bool { - return e.CurrentPid() && (e.Type == event.ConnectTCPv4 || e.Type == event.ConnectTCPv6) + return e.CurrentPid() && e.Type == event.Connect }, false, }, @@ -534,7 +534,7 @@ func TestEventSourceAllEvents(t *testing.T) { return nil }, func(e *event.Event) bool { - return e.CurrentPid() && e.Type == event.MapViewFile && + return e.CurrentPid() && e.Type == event.MapViewOfSection && e.GetParamAsString(params.MemProtect) == "EXECUTE_READWRITE|READONLY" && e.GetParamAsString(params.FileViewSectionType) == "IMAGE" }, @@ -581,7 +581,7 @@ func TestEventSourceAllEvents(t *testing.T) { return sys.NtUnmapViewOfSection(windows.CurrentProcess(), viewBase) }, func(e *event.Event) bool { - return e.CurrentPid() && e.Type == event.UnmapViewFile && + return e.CurrentPid() && e.Type == event.UnmapViewOfSection && e.GetParamAsString(params.MemProtect) == "READONLY" && e.Params.MustGetUint64(params.FileViewBase) == uint64(viewBase) }, @@ -629,7 +629,7 @@ func TestEventSourceAllEvents(t *testing.T) { }, func(e *event.Event) bool { return e.CurrentPid() && e.Type == event.QueryDNS && e.IsDNS() && - e.Type.Subcategory() == event.DNS && + e.Subcategory() == event.DNS && e.GetParamAsString(params.DNSName) == "dns.google" && e.GetParamAsString(params.DNSRR) == "A" }, @@ -643,7 +643,7 @@ func TestEventSourceAllEvents(t *testing.T) { }, func(e *event.Event) bool { return e.CurrentPid() && e.Type == event.ReplyDNS && e.IsDNS() && - e.Type.Subcategory() == event.DNS && + e.Subcategory() == event.DNS && e.GetParamAsString(params.DNSName) == "dns.google" && e.GetParamAsString(params.DNSRR) == "AAAA" && e.GetParamAsString(params.DNSRcode) == "NOERROR" && diff --git a/internal/etw/stackext.go b/internal/etw/stackext.go index a210e3770..8bbde214b 100644 --- a/internal/etw/stackext.go +++ b/internal/etw/stackext.go @@ -22,7 +22,6 @@ import ( "github.com/rabbitstack/fibratus/pkg/config" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/sys/etw" - "golang.org/x/sys/windows" ) // StackExtensions manages stack tracing enablement @@ -39,15 +38,8 @@ func NewStackExtensions(config config.EventSourceConfig) *StackExtensions { // AddStackTracing enables stack tracing for the specified event type. func (s *StackExtensions) AddStackTracing(typ event.Type) { - if !s.config.TestDropMask(typ) { - s.ids = append(s.ids, etw.NewClassicEventID(typ.GUID(), typ.HookID())) - } -} - -// AddStackTracingWith enables stack tracing for the specified provider GUID and event hook id. -func (s *StackExtensions) AddStackTracingWith(guid windows.GUID, hookID uint16) { - if !s.config.TestDropMask(event.TypeFromParts(guid, hookID)) { - s.ids = append(s.ids, etw.NewClassicEventID(guid, hookID)) + if !s.config.TestDropMask(typ) && !typ.EventID().IsEmpty() { + s.ids = append(s.ids, typ.EventID()) } } @@ -69,7 +61,7 @@ func (s *StackExtensions) EnableProcessCallstack() { s.AddStackTracing(event.TerminateThread) } if s.config.EnableModuleEvents { - s.AddStackTracingWith(event.ProcessEventGUID, event.LoadModule.HookID()) + s.AddStackTracing(event.LoadModule) } } diff --git a/internal/etw/stackext_test.go b/internal/etw/stackext_test.go index 48b92b2b1..0833f55ec 100644 --- a/internal/etw/stackext_test.go +++ b/internal/etw/stackext_test.go @@ -19,12 +19,13 @@ package etw import ( + "testing" + "time" + "github.com/rabbitstack/fibratus/pkg/config" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/sys/etw" "github.com/stretchr/testify/assert" - "testing" - "time" ) func TestStackExtensions(t *testing.T) { @@ -50,11 +51,11 @@ func TestStackExtensions(t *testing.T) { exts.EnableMemoryCallstack() assert.Len(t, exts.EventIds(), 7) - assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.ProcessEventGUID, Type: uint8(event.CreateProcess.HookID())}) - assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.ThreadEventGUID, Type: uint8(event.CreateThread.HookID())}) - assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.ThreadEventGUID, Type: uint8(event.TerminateThread.HookID())}) - assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.FileEventGUID, Type: uint8(event.CreateFile.HookID())}) - assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.FileEventGUID, Type: uint8(event.RenameFile.HookID())}) - assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.FileEventGUID, Type: uint8(event.DeleteFile.HookID())}) - assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.MemEventGUID, Type: uint8(event.VirtualAlloc.HookID())}) + assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.ProcessEventGUID, Type: event.CreateProcessID}) + assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.ThreadEventGUID, Type: event.CreateThreadID}) + assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.ThreadEventGUID, Type: event.TerminateThreadID}) + assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.FileEventGUID, Type: event.CreateFileID}) + assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.FileEventGUID, Type: event.RenameFileID}) + assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.FileEventGUID, Type: event.DeleteFileID}) + assert.Contains(t, exts.EventIds(), etw.ClassicEventID{GUID: event.MemoryEventGUID, Type: event.VirtualAllocID}) } diff --git a/internal/evasion/direct_syscall_test.go b/internal/evasion/direct_syscall_test.go index 80a394b73..097264317 100644 --- a/internal/evasion/direct_syscall_test.go +++ b/internal/evasion/direct_syscall_test.go @@ -19,13 +19,14 @@ package evasion import ( + "testing" + "time" + "github.com/rabbitstack/fibratus/pkg/callstack" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/fs" "github.com/stretchr/testify/require" - "testing" - "time" ) func TestDirectSyscall(t *testing.T) { @@ -39,9 +40,7 @@ func TestDirectSyscall(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: event.File, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -60,9 +59,7 @@ func TestDirectSyscall(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: event.File, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -82,9 +79,7 @@ func TestDirectSyscall(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: event.File, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -104,9 +99,7 @@ func TestDirectSyscall(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: event.File, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -123,7 +116,7 @@ func TestDirectSyscall(t *testing.T) { } for _, tt := range tests { - t.Run(tt.evt.Name, func(t *testing.T) { + t.Run(tt.evt.Name(), func(t *testing.T) { eva := NewDirectSyscall() matches, err := eva.Eval(tt.evt) require.NoError(t, err) diff --git a/internal/evasion/indirect_syscall_test.go b/internal/evasion/indirect_syscall_test.go index 7b07a655d..d8d7881f8 100644 --- a/internal/evasion/indirect_syscall_test.go +++ b/internal/evasion/indirect_syscall_test.go @@ -45,9 +45,7 @@ func TestIndirectSyscall(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: event.File, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -73,9 +71,7 @@ func TestIndirectSyscall(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "SetThreadContext", Timestamp: time.Now(), - Category: event.Thread, PS: &pstypes.PS{ Modules: []pstypes.Module{ {Name: "C:\\Windows\\System32\\ntdll.dll", Size: 32358, Checksum: 23123343, BaseAddress: getNtdllAddress(), DefaultBaseAddress: getNtdllAddress()}, @@ -96,9 +92,7 @@ func TestIndirectSyscall(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: event.File, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -115,7 +109,7 @@ func TestIndirectSyscall(t *testing.T) { } for _, tt := range tests { - t.Run(tt.evt.Name, func(t *testing.T) { + t.Run(tt.evt.Name(), func(t *testing.T) { eva := NewIndirectSyscall() matches, err := eva.Eval(tt.evt) require.NoError(t, err) diff --git a/internal/evasion/scanner_test.go b/internal/evasion/scanner_test.go index 1cf73a887..19d5b84fa 100644 --- a/internal/evasion/scanner_test.go +++ b/internal/evasion/scanner_test.go @@ -41,9 +41,7 @@ func TestScannerProcessEvent(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: event.File, Metadata: event.Metadata{}, Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, diff --git a/pkg/aggregator/aggregator_test.go b/pkg/aggregator/aggregator_test.go index b48b1a50b..39e5fd679 100644 --- a/pkg/aggregator/aggregator_test.go +++ b/pkg/aggregator/aggregator_test.go @@ -19,15 +19,16 @@ package aggregator import ( + "net" + "testing" + "time" + "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/outputs" "github.com/rabbitstack/fibratus/pkg/outputs/console" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "net" - "testing" - "time" ) func TestNewBufferedAggregator(t *testing.T) { @@ -46,7 +47,7 @@ func TestNewBufferedAggregator(t *testing.T) { for i := 0; i < 4; i++ { evt := &event.Event{ - Type: event.SendTCPv4, + Type: event.Send, Tid: 2484, PID: 859, Params: event.Params{ @@ -63,7 +64,7 @@ func TestNewBufferedAggregator(t *testing.T) { for i := 0; i < 2; i++ { evt := &event.Event{ - Type: event.SendTCPv4, + Type: event.Send, Tid: 2484, PID: 859, Seq: uint64(i), diff --git a/pkg/aggregator/transformers/remove/remove_test.go b/pkg/aggregator/transformers/remove/remove_test.go index 0e114be1a..3add65007 100644 --- a/pkg/aggregator/transformers/remove/remove_test.go +++ b/pkg/aggregator/transformers/remove/remove_test.go @@ -19,18 +19,19 @@ package remove import ( + "net" + "testing" + "github.com/rabbitstack/fibratus/pkg/aggregator/transformers" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "net" - "testing" ) func TestTransform(t *testing.T) { evt := &event.Event{ - Type: event.SendTCPv4, + Type: event.Send, Tid: 2484, PID: 859, Params: event.Params{ diff --git a/pkg/aggregator/transformers/rename/rename_test.go b/pkg/aggregator/transformers/rename/rename_test.go index 8a90259f4..d4cc20dd4 100644 --- a/pkg/aggregator/transformers/rename/rename_test.go +++ b/pkg/aggregator/transformers/rename/rename_test.go @@ -19,18 +19,19 @@ package rename import ( + "net" + "testing" + "github.com/rabbitstack/fibratus/pkg/aggregator/transformers" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "net" - "testing" ) func TestTransform(t *testing.T) { evt := &event.Event{ - Type: event.SendTCPv4, + Type: event.Send, Tid: 2484, PID: 859, Params: event.Params{ diff --git a/pkg/aggregator/transformers/tags/tags_test.go b/pkg/aggregator/transformers/tags/tags_test.go index 4d3a84cf4..c6a161daf 100644 --- a/pkg/aggregator/transformers/tags/tags_test.go +++ b/pkg/aggregator/transformers/tags/tags_test.go @@ -32,7 +32,7 @@ import ( func TestTransform(t *testing.T) { evt := &event.Event{ - Type: event.SendTCPv4, + Type: event.Send, Tid: 2484, PID: 859, Params: event.Params{ diff --git a/pkg/aggregator/transformers/trim/trim_test.go b/pkg/aggregator/transformers/trim/trim_test.go index 43270822b..b5dfa31bd 100644 --- a/pkg/aggregator/transformers/trim/trim_test.go +++ b/pkg/aggregator/transformers/trim/trim_test.go @@ -19,26 +19,24 @@ package trim import ( + "testing" + "time" + "github.com/rabbitstack/fibratus/pkg/aggregator/transformers" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" - "time" ) func TestTransform(t *testing.T) { evt := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, diff --git a/pkg/alertsender/alert.go b/pkg/alertsender/alert.go index eebb3a43b..a785cc9b8 100644 --- a/pkg/alertsender/alert.go +++ b/pkg/alertsender/alert.go @@ -267,8 +267,8 @@ func (a Alert) MarshalJSON() ([]byte, error) { Ancestors []string `json:"ancestors"` } `json:"proc,omitempty"` }{ - Name: e.Name, - Category: string(e.Category), + Name: e.Name(), + Category: e.Category().String(), Timestamp: e.Timestamp, Params: make(map[string]any), Callstack: make([]string, 0, len(e.Callstack)), diff --git a/pkg/alertsender/alert_test.go b/pkg/alertsender/alert_test.go index 2e7b5b08d..4425badfa 100644 --- a/pkg/alertsender/alert_test.go +++ b/pkg/alertsender/alert_test.go @@ -46,13 +46,11 @@ func TestAlertString(t *testing.T) { }, { NewAlertWithEvents("Credential discovery via VaultCmd.exe", "Suspicious vault enumeration via VaultCmd tool", nil, Normal, []*event.Event{{ - Type: event.CreateProcess, - Category: event.Process, + Type: event.CreateProcess, Params: event.Params{ params.Cmdline: {Name: params.Cmdline, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost-fake.exe -k RPCSS"}, params.ProcessName: {Name: params.ProcessName, Type: params.AnsiString, Value: "svchost-fake.exe"}}, - Name: "CreateProcess", - PID: 1023, + PID: 1023, PS: &pstypes.PS{ Name: "svchost.exe", Cmdline: "C:\\Windows\\System32\\svchost.exe", @@ -68,13 +66,11 @@ func TestAlertString(t *testing.T) { }, { NewAlertWithEvents("Credential discovery via VaultCmd.exe", "", nil, Normal, []*event.Event{{ - Type: event.CreateProcess, - Category: event.Process, + Type: event.CreateProcess, Params: event.Params{ params.Cmdline: {Name: params.Cmdline, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost-fake.exe -k RPCSS"}, params.ProcessName: {Name: params.ProcessName, Type: params.AnsiString, Value: "svchost-fake.exe"}}, - Name: "CreateProcess", - PID: 1023, + PID: 1023, PS: &pstypes.PS{ Name: "svchost.exe", Cmdline: "C:\\Windows\\System32\\svchost.exe", @@ -99,13 +95,11 @@ func TestAlertString(t *testing.T) { func TestAlertJSON(t *testing.T) { alert := NewAlertWithEvents("Credential discovery via VaultCmd.exe", "Suspicious vault enumeration via VaultCmd tool", nil, Normal, []*event.Event{{ - Type: event.CreateProcess, - Category: event.Process, + Type: event.CreateProcess, Params: event.Params{ params.Cmdline: {Name: params.Cmdline, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost-fake.exe -k RPCSS"}, params.ProcessName: {Name: params.ProcessName, Type: params.AnsiString, Value: "svchost-fake.exe"}}, - Name: "CreateProcess", - PID: 1023, + PID: 1023, PS: &pstypes.PS{ Name: "svchost.exe", Cmdline: "C:\\Windows\\System32\\svchost.exe", diff --git a/pkg/alertsender/eventlog/eventlog_test.go b/pkg/alertsender/eventlog/eventlog_test.go index 0d9ae3891..7b765d7f2 100644 --- a/pkg/alertsender/eventlog/eventlog_test.go +++ b/pkg/alertsender/eventlog/eventlog_test.go @@ -41,16 +41,13 @@ func TestEventlogSender(t *testing.T) { require.NoError(t, s.Send(alertsender.Alert{ Events: []*event.Event{ { - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -136,16 +133,13 @@ func TestEventlogSender(t *testing.T) { }, }, { - Type: event.CreateProcess, - Tid: 2184, - PID: 1022, - CPU: 2, - Seq: 3, - Name: "CreateProcess", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates a new process", + Type: event.CreateProcess, + Tid: 2184, + PID: 1022, + CPU: 2, + Seq: 3, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.Cmdline: {Name: params.Cmdline, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost.exe -k RPCSS"}, params.Exe: {Name: params.Exe, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost.exe"}, diff --git a/pkg/alertsender/mail/renderer_test.go b/pkg/alertsender/mail/renderer_test.go index 751244347..e5438d3a9 100644 --- a/pkg/alertsender/mail/renderer_test.go +++ b/pkg/alertsender/mail/renderer_test.go @@ -19,6 +19,10 @@ package mail import ( + "strings" + "testing" + "time" + "github.com/antchfx/htmlquery" "github.com/rabbitstack/fibratus/pkg/alertsender" "github.com/rabbitstack/fibratus/pkg/event" @@ -30,9 +34,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sys/windows" - "strings" - "testing" - "time" ) func TestRenderHTMLTemplate(t *testing.T) { @@ -51,16 +52,13 @@ func TestRenderHTMLTemplate(t *testing.T) { }, Events: []*event.Event{ { - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -146,16 +144,13 @@ func TestRenderHTMLTemplate(t *testing.T) { }, }, { - Type: event.CreateProcess, - Tid: 2184, - PID: 1022, - CPU: 2, - Seq: 3, - Name: "CreateProcess", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates a new process", + Type: event.CreateProcess, + Tid: 2184, + PID: 1022, + CPU: 2, + Seq: 3, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.Cmdline: {Name: params.Cmdline, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost.exe -k RPCSS"}, params.Exe: {Name: params.Exe, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost.exe"}, diff --git a/pkg/cap/header.go b/pkg/cap/header.go index c1720164c..7df0a4e5c 100644 --- a/pkg/cap/header.go +++ b/pkg/cap/header.go @@ -34,7 +34,7 @@ const magic = 0x6669627261747573 // major represents the major digit of the cap file format. Incrementing the major digit makes older cap readers not // capable to replay the capture file -const major = uint8(2) +const major = uint8(3) // minor represents the minor digit of the cap file format const minor = uint8(0) diff --git a/pkg/cap/version/version_windows.go b/pkg/cap/version/version_windows.go index d2172bbe9..7585230e5 100644 --- a/pkg/cap/version/version_windows.go +++ b/pkg/cap/version/version_windows.go @@ -26,6 +26,8 @@ const ( EvtSecV1 Version = iota + 1 // EvtSecV2 is the v2 of the event section EvtSecV2 + // EvtSecV3 is the v3 of the event section + EvtSecV3 ) const ( diff --git a/pkg/cap/writer_windows.go b/pkg/cap/writer_windows.go index bd381a263..2cc95f989 100644 --- a/pkg/cap/writer_windows.go +++ b/pkg/cap/writer_windows.go @@ -48,7 +48,7 @@ type stats struct { procsWritten uint64 } -func (s *stats) incKevts(evt *event.Event) { +func (s *stats) incEvts(evt *event.Event) { if !evt.Type.OnlyState() { atomic.AddUint64(&s.evtsWritten, 1) } @@ -201,7 +201,7 @@ func (w *writer) Write(evtsc <-chan *event.Event, errs <-chan error) chan error continue } // update stats - w.stats.incKevts(evt) + w.stats.incEvts(evt) w.stats.incBytes(uint64(l)) w.stats.incProcs(evt) case err := <-errs: @@ -223,7 +223,7 @@ func (w *writer) write(b []byte) error { overflowEvents.Add(1) return fmt.Errorf("event size overflow by %d bytes", l-maxKevtSize) } - if err := w.ws(section.Event, capver.EvtSecV2, 0, uint32(l)); err != nil { + if err := w.ws(section.Event, capver.EvtSecV3, 0, uint32(l)); err != nil { evtWriteErrors.Add(1) return err } diff --git a/pkg/cap/writer_windows_test.go b/pkg/cap/writer_windows_test.go index d13907715..590e05def 100644 --- a/pkg/cap/writer_windows_test.go +++ b/pkg/cap/writer_windows_test.go @@ -67,16 +67,13 @@ func TestWrite(t *testing.T) { for i := 0; i < 100; i++ { evt := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: uint8(i / 2), - Seq: uint64(i + 1), - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: uint8(i / 2), + Seq: uint64(i + 1), + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, diff --git a/pkg/config/eventsource.go b/pkg/config/eventsource.go index d99ad49a9..5d7c4e814 100644 --- a/pkg/config/eventsource.go +++ b/pkg/config/eventsource.go @@ -25,8 +25,8 @@ import ( "runtime" "time" + "github.com/bits-and-blooms/bitset" "github.com/rabbitstack/fibratus/pkg/event" - "github.com/rabbitstack/fibratus/pkg/util/bitmask" pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" "github.com/spf13/viper" @@ -97,8 +97,7 @@ type EventSourceConfig struct { // ExcludedImages are process image names that will be rejected if they generate a kernel event. ExcludedImages []string `json:"blacklist.images" yaml:"blacklist.images"` - dropMasks *bitmask.Bitmask - allMasks *bitmask.Bitmask + dropMasks bitset.BitSet excludedImages map[string]bool } @@ -121,21 +120,14 @@ func (c *EventSourceConfig) initFromViper(v *viper.Viper) { c.ExcludedEvents = v.GetStringSlice(excludedEvents) c.ExcludedImages = v.GetStringSlice(excludedImages) - c.dropMasks = bitmask.New() - c.allMasks = bitmask.New() - c.excludedImages = make(map[string]bool) for _, name := range c.ExcludedEvents { - if typ := event.NameToType(name); typ != event.UnknownType { - c.dropMasks.Set(typ.ID()) + if typ, ok := event.ParseType(name); ok { + c.dropMasks.Set(typ.Uint()) } } - for _, typ := range event.AllWithState() { - c.allMasks.Set(typ.ID()) - } - for _, name := range c.ExcludedImages { c.excludedImages[name] = true } @@ -145,53 +137,34 @@ func (c *EventSourceConfig) initFromViper(v *viper.Viper) { func (c *EventSourceConfig) Init() { c.excludedImages = make(map[string]bool) - if c.dropMasks == nil { - c.dropMasks = bitmask.New() - } for _, name := range c.ExcludedEvents { - for _, typ := range event.NameToTypes(name) { - if typ != event.UnknownType { - c.dropMasks.Set(typ.ID()) - } + if typ, ok := event.ParseType(name); ok { + c.dropMasks.Set(typ.Uint()) } } for _, name := range c.ExcludedImages { c.excludedImages[name] = true } - - if c.allMasks == nil { - c.allMasks = bitmask.New() - } - for _, typ := range event.AllWithState() { - c.allMasks.Set(typ.ID()) - } } // SetDropMask inserts the event mask in the bitset to // instruct the given event type should be dropped from // the event stream. func (c *EventSourceConfig) SetDropMask(typ event.Type) { - c.dropMasks.Set(typ.ID()) + c.dropMasks.Set(typ.Uint()) } // TestDropMask checks if the specified event type has // the drop mask in the bitset. func (c *EventSourceConfig) TestDropMask(typ event.Type) bool { - return c.dropMasks.IsSet(typ.ID()) -} - -// ExcludeEvent determines whether the supplied short -// event ID exists in the bitset of excluded events. -func (c *EventSourceConfig) ExcludeEvent(id uint) bool { - return c.dropMasks.IsSet(id) + return c.dropMasks.Test(typ.Uint()) } -// EventExists determines if the provided event ID exists -// in the internal event catalog by checking the event ID -// bitmask. -func (c *EventSourceConfig) EventExists(id uint) bool { - return c.allMasks.IsSet(id) +// ExcludeEvent determines whether the event type is declared +// in the exclusion list. +func (c *EventSourceConfig) ExcludeEvent(typ event.Type) bool { + return c.dropMasks.Test(typ.Uint()) } // ExcludeImage determines whether the process generating event is present in the diff --git a/pkg/config/eventsource_test.go b/pkg/config/eventsource_test.go index 157b88eb7..815fb8ff9 100644 --- a/pkg/config/eventsource_test.go +++ b/pkg/config/eventsource_test.go @@ -56,7 +56,7 @@ func TestEventSourceConfig(t *testing.T) { assert.False(t, c.EventSource.EnableModuleEvents) assert.False(t, c.EventSource.EnableFileIOEvents) - assert.False(t, c.EventSource.ExcludeEvent(event.CreateProcess.ID())) + assert.False(t, c.EventSource.ExcludeEvent(event.CreateProcess)) assert.True(t, c.EventSource.ExcludeImage(&pstypes.PS{Name: "svchost.exe"})) assert.False(t, c.EventSource.ExcludeImage(&pstypes.PS{Name: "explorer.exe"})) diff --git a/pkg/event/batch_test.go b/pkg/event/batch_test.go index e53039d1b..f82d6d91f 100644 --- a/pkg/event/batch_test.go +++ b/pkg/event/batch_test.go @@ -20,6 +20,9 @@ package event import ( "encoding/json" + "testing" + "time" + "github.com/magiconair/properties/assert" "github.com/rabbitstack/fibratus/pkg/event/params" htypes "github.com/rabbitstack/fibratus/pkg/handle/types" @@ -27,22 +30,17 @@ import ( "github.com/rabbitstack/fibratus/pkg/util/va" "github.com/stretchr/testify/require" "golang.org/x/sys/windows" - "testing" - "time" ) func TestBatchMarshalJSON(t *testing.T) { evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -101,16 +99,13 @@ func TestBatchMarshalJSON(t *testing.T) { } evt1 := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 459, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 459, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -169,16 +164,13 @@ func TestBatchMarshalJSON(t *testing.T) { } evt2 := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 829, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 829, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, diff --git a/pkg/event/bitset.go b/pkg/event/bitset.go deleted file mode 100644 index abfb0a157..000000000 --- a/pkg/event/bitset.go +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 event - -import ( - "github.com/bits-and-blooms/bitset" - "github.com/rabbitstack/fibratus/pkg/util/bitmask" -) - -// BitSetType defines the bitset type -type BitSetType uint8 - -const ( - // BitmaskBitSet designates the mask-based event id bitset - BitmaskBitSet BitSetType = iota + 1 - // TypeBitSet designates the uint16 number space event type bitset - TypeBitSet - // CategoryBitSet designates the event category bitset - CategoryBitSet -) - -// BitSets handles the group of category/event type bitsets -// and the bitmask for evaluating event ids bits. -type BitSets struct { - bitmask *bitmask.Bitmask - cats *bitset.BitSet - types *bitset.BitSet -} - -// SetBit sets the bit dictated by the bitset type. -func (b *BitSets) SetBit(bs BitSetType, typ Type) { - switch bs { - case BitmaskBitSet: - if b.bitmask == nil { - b.bitmask = bitmask.New() - } - b.bitmask.Set(typ.ID()) - - case TypeBitSet: - if b.types == nil { - b.types = bitset.New(uint(MaxTypeID() + 1)) - } - b.types.Set(uint(typ.HookID())) - - case CategoryBitSet: - if b.cats == nil { - b.cats = bitset.New(MaxCategoryIndex + 1) - } - b.cats.Set(uint(typ.Category().Index())) - } -} - -// SetCategoryBit toggles the category bit in the bitset. -func (b *BitSets) SetCategoryBit(c Category) { - if b.cats == nil { - b.cats = bitset.New(MaxCategoryIndex + 1) - } - b.cats.Set(uint(c.Index())) -} - -// IsBitSet checks if any of the populated bitsets -// contain the type, event ID, or category bit. -// This method evaluates first the event type bitset. -// The event type bitset should only be initialized -// if all event types pertain to the same category. -// Otherwise, event id bitset and last category bitset -// are tested for respective bits. -func (b *BitSets) IsBitSet(evt *Event) bool { - if b.types != nil && b.types.Test(uint(evt.Type.HookID())) { - return true - } - return (b.bitmask != nil && b.bitmask.IsSet(evt.Type.ID())) || - (b.cats != nil && b.cats.Test(uint(evt.Category.Index()))) -} - -// IsInitialized checks if the given bitset type is initialized. -func (b *BitSets) IsInitialized(bs BitSetType) bool { - switch bs { - case BitmaskBitSet: - return b.bitmask != nil - case TypeBitSet: - return b.types != nil - case CategoryBitSet: - return b.cats != nil - } - return false -} diff --git a/pkg/event/bitset_test.go b/pkg/event/bitset_test.go deleted file mode 100644 index df69110db..000000000 --- a/pkg/event/bitset_test.go +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 event - -import ( - "testing" - - "github.com/rabbitstack/fibratus/pkg/util/bitmask" - - "github.com/rabbitstack/fibratus/pkg/sys/etw" - "github.com/stretchr/testify/assert" -) - -func TestBitmask(t *testing.T) { - var tests = []struct { - typ Type - expected bool - }{ - {TerminateThread, true}, - {TerminateProcess, true}, - {CreateThread, true}, - {CreateFile, false}, - {WriteFile, false}, - {LoadModule, false}, - {MapFileRundown, true}, - {ProcessRundown, true}, - } - - b := bitmask.New() - for _, typ := range AllWithState() { - if typ == WriteFile || typ == LoadModule || typ == CreateFile { - continue - } - b.Set(typ.ID()) - } - - for _, tt := range tests { - t.Run(tt.typ.String(), func(t *testing.T) { - assert.Equal(t, tt.expected, b.IsSet(tt.typ.ID())) - }) - } -} - -func TestBitSets(t *testing.T) { - var tests = []struct { - evt *Event - expected bool - }{ - {&Event{Type: TerminateThread}, true}, - {&Event{Type: TerminateProcess}, true}, - {&Event{Type: CreateThread, Category: Thread}, true}, - {&Event{Type: CreateFile}, false}, - {&Event{Type: WriteFile}, false}, - {&Event{Type: LoadModule}, false}, - {&Event{Type: MapFileRundown}, true}, - {&Event{Type: ProcessRundown}, true}, - } - - var bitsets BitSets - - bitsets.SetBit(BitmaskBitSet, TerminateThread) - bitsets.SetBit(TypeBitSet, TerminateProcess) - bitsets.SetBit(CategoryBitSet, CreateThread) - bitsets.SetBit(TypeBitSet, MapFileRundown) - bitsets.SetBit(BitmaskBitSet, ProcessRundown) - - for _, tt := range tests { - t.Run(tt.evt.Type.String(), func(t *testing.T) { - assert.Equal(t, tt.expected, bitsets.IsBitSet(tt.evt)) - }) - } -} - -func BenchmarkBitmask(b *testing.B) { - b.ReportAllocs() - - bm := bitmask.New() - bm.Set(TerminateThread.ID()) - bm.Set(CreateThread.ID()) - bm.Set(TerminateProcess.ID()) - bm.Set(CreateFile.ID()) - - evt := &etw.EventRecord{Header: etw.EventHeader{ProviderID: ThreadEventGUID, EventDescriptor: etw.EventDescriptor{Opcode: 2}}} - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if !bm.IsSet(evt.ID()) { - panic("mask should be present") - } - } -} - -func BenchmarkStdlibMap(b *testing.B) { - b.ReportAllocs() - - evts := make(map[Type]bool) - evts[TerminateThread] = true - evts[CreateThread] = true - evts[TerminateProcess] = true - evts[CreateFile] = true - - evt := etw.EventRecord{Header: etw.EventHeader{ProviderID: ThreadEventGUID, EventDescriptor: etw.EventDescriptor{Opcode: 2}}} - etype := NewTypeFromEventRecord(&evt) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - if !evts[etype] { - panic("event should be present") - } - } -} diff --git a/pkg/event/category.go b/pkg/event/category.go index 6204a5214..a6cae35a7 100644 --- a/pkg/event/category.go +++ b/pkg/event/category.go @@ -18,102 +18,103 @@ package event -import ( - "slices" - - "github.com/rabbitstack/fibratus/pkg/util/hashers" -) - // Category is the type alias for event categories -type Category string +type Category uint8 // Subcategory is the type alias for event subcategories -type Subcategory string +type Subcategory uint8 const ( // Registry is the category for registry related events - Registry Category = "registry" + Registry Category = iota + 1 // File is the category for file system events - File Category = "file" - // Net is the category for network events - Net Category = "net" + File + // Network is the category for network events + Network // Process is the category for process events - Process Category = "process" + Process // Thread is the category for thread events - Thread Category = "thread" + Thread // Module is the category for module (dll, exe, sys) events - Module Category = "module" - // Driver is the category for driver events - Driver Category = "driver" - // Mem is the category for memory events - Mem Category = "mem" + Module + // Memory is the category for memory events + Memory // Object the category for object manager events - Object Category = "object" + Object // Other is the category for uncategorized events - Other Category = "other" - // Unknown is the category for events that couldn't match any of the previous categories - Unknown Category = "unknown" + Other + MaxCategory ) const ( // DNS designates the DNS (Domain Name Service) event subcategory - DNS Subcategory = "dns" - // None identifies no subcategory - None Subcategory = "none" + DNS Subcategory = iota + 1 + MaxSubcategory ) -// Hash obtains the hash of the category string. -func (c Category) Hash() uint32 { - return hashers.FnvUint32([]byte(c)) +// Uint coerces the category type to pointer-sized unsigned integer. +func (c Category) Uint() uint { + return uint(c) } -// MaxCategoryIndex designates the maximum category index. -const MaxCategoryIndex = 11 - -// Index returns a numerical category index. -func (c Category) Index() uint8 { +func (c Category) String() string { switch c { case Registry: - return 1 + return "registry" case File: - return 2 - case Net: - return 3 + return "file" + case Network: + return "network" case Process: - return 4 + return "process" case Thread: - return 5 + return "thread" case Module: - return 6 - case Driver: - return 7 - case Mem: - return 8 + return "module" + case Memory: + return "memory" case Object: - return 9 + return "object" case Other: - return 10 + return "other" default: - return MaxCategoryIndex + return "unknown" } } -// Categories returns all available categories. -func Categories() []string { - return []string{ - string(Registry), - string(File), - string(Net), - string(Process), - string(Thread), - string(Module), - string(Mem), - string(Driver), - string(Other), - string(Unknown), - string(Object), +func (sc Subcategory) String() string { + switch sc { + case DNS: + return "dns" + default: + return "unknown" } } +var categories = map[string]Category{ + "registry": Registry, + "file": File, + "network": Network, + "process": Process, + "thread": Thread, + "module": Module, + "memory": Memory, + "object": Object, + "other": Other, +} + +// NumCategories returns a total number of recognized categories. +func NumCategories() int { return len(categories) } + +// ParseCategory converts the category from the bare string. Returns +// the category and the bool indicating if the conversion succeeded. +func ParseCategory(s string) (Category, bool) { + c, ok := categories[s] + return c, ok +} + // IsCategoryKnown indicates if the category is known given its name. -func IsCategoryKnown(name string) bool { return slices.Contains(Categories(), name) } +func IsCategoryKnown(s string) (exists bool) { + _, exists = ParseCategory(s) + return +} diff --git a/pkg/event/event.go b/pkg/event/event.go index 66ca2905c..ba1b03039 100644 --- a/pkg/event/event.go +++ b/pkg/event/event.go @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 by Nedim Sabic Sabic + * Copyright 2019-2026 by Nedim Sabic Sabic * https://www.fibratus.io * All Rights Reserved. * @@ -86,12 +86,6 @@ type Event struct { CPU uint8 `json:"cpu"` _ uint8 // padding - // Name is the human friendly name of the event. - Name string `json:"name"` - // Category designates the category to which this event pertains. - Category Category `json:"category"` - // Description is the short explanation that describes the purpose of the event. - Description string `json:"description"` // Host is the machine name that reported the generated event. Host string `json:"host"` // Params stores the collection of event parameters. @@ -107,6 +101,26 @@ type Event struct { mmux sync.RWMutex } +// Name returns the human friendly event name. +func (e *Event) Name() string { + return e.Type.String() +} + +// Category designates the category to which this event pertains. +func (e *Event) Category() Category { + return table[e.Type].Category +} + +// Subcategory designates the subcategory to which this event pertains. +func (e *Event) Subcategory() Subcategory { + return table[e.Type].Subcategory +} + +// Description is the short explanation that describes the purpose of the event. +func (e *Event) Description() string { + return table[e.Type].Description +} + // String returns event's string representation. func (e *Event) String() string { e.mmux.RLock() @@ -132,9 +146,9 @@ func (e *Event) String() string { e.Tid, e.Type, e.CPU, - e.Name, - e.Category, - e.Description, + e.Name(), + e.Category(), + e.Description(), e.Host, e.Timestamp, e.Params, @@ -161,9 +175,9 @@ func (e *Event) String() string { e.Tid, e.Type, e.CPU, - e.Name, - e.Category, - e.Description, + e.Name(), + e.Category(), + e.Description(), e.Host, e.Timestamp, e.Params, @@ -191,8 +205,8 @@ func (e *Event) StringShort() string { e.Seq, e.PID, e.Tid, - e.Name, - e.Category, + e.Name(), + e.Category(), e.Host, e.Timestamp, e.Params, @@ -212,8 +226,8 @@ func (e *Event) StringShort() string { e.Seq, e.PID, e.Tid, - e.Name, - e.Category, + e.Name(), + e.Category(), e.Host, e.Timestamp, e.Params, diff --git a/pkg/event/event_windows.go b/pkg/event/event_windows.go index 38f1eb95f..b9825a5b7 100644 --- a/pkg/event/event_windows.go +++ b/pkg/event/event_windows.go @@ -27,6 +27,7 @@ import ( "unsafe" "github.com/rabbitstack/fibratus/pkg/event/params" + "github.com/rabbitstack/fibratus/pkg/network" "github.com/rabbitstack/fibratus/pkg/sys" "github.com/rabbitstack/fibratus/pkg/sys/etw" "github.com/rabbitstack/fibratus/pkg/util/filetime" @@ -49,13 +50,12 @@ var ( // New constructs a fresh event instance with basic fields and parameters // from the raw ETW event record. -func New(seq uint64, r *etw.EventRecord) *Event { +func New(seq uint64, r *etw.EventRecord, typ Type) *Event { var ( pid = r.Header.ProcessID tid = r.Header.ThreadID cpu = *(*uint8)(unsafe.Pointer(&r.BufferContext.ProcessorIndex[0])) ts = filetime.ToEpoch(r.Header.Timestamp) - typ = NewTypeFromEventRecord(r) ) e := &Event{ @@ -64,8 +64,6 @@ func New(seq uint64, r *etw.EventRecord) *Event { Tid: tid, CPU: cpu, Type: typ, - Category: typ.Category(), - Name: typ.String(), Params: make(map[string]*Param), Timestamp: ts, Host: hostname.Get(), @@ -86,7 +84,7 @@ func (e *Event) RawTimestamp() uint64 { } func (e *Event) adjustPID() { - switch e.Category { + switch e.Category() { case Module: // sometimes the pid present in event header is invalid // but, we can get the valid one from the event parameters @@ -94,16 +92,7 @@ func (e *Event) adjustPID() { e.PID, _ = e.Params.GetPid() } case File: - if !e.IsMapViewFile() && !e.IsUnmapViewFile() { - // take thread id from the event parameters - e.Tid, _ = e.Params.GetTid() - } - switch { - case e.InvalidPid() && e.Type == MapFileRundown: - // a valid pid for map rundown events - // is located in the event parameters - e.PID = e.Params.MustGetPid() - case e.InvalidPid(): + if e.InvalidPid() { // on some Windows versions the value of // the PID is invalid in the event header access := uint32(windows.THREAD_QUERY_LIMITED_INFORMATION) @@ -123,7 +112,7 @@ func (e *Event) adjustPID() { if e.IsCreateProcess() { e.PID, _ = e.Params.GetPid() } - case Net: + case Network: if !e.IsDNS() { e.PID, _ = e.Params.GetPid() } @@ -132,6 +121,12 @@ func (e *Event) adjustPID() { e.PID, _ = e.Params.GetPid() e.Tid, _ = e.Params.GetTid() } + case Memory: + if e.Type == MapViewSectionRundown { + // a valid pid for map rundown events + // is located in the event parameters + e.PID = e.Params.MustGetPid() + } } } @@ -158,23 +153,17 @@ func IsCurrentProcDropped(pid uint32) bool { return DropCurrentProc && pid == cu // IsNetworkTCP determines whether the event pertains to network TCP events. func (e *Event) IsNetworkTCP() bool { - return e.Category == Net && !e.IsNetworkUDP() -} - -// IsNetworkUDP determines whether the event pertains to network UDP events. -func (e *Event) IsNetworkUDP() bool { - return e.Type == RecvUDPv4 || e.Type == RecvUDPv6 || e.Type == SendUDPv4 || e.Type == SendUDPv6 + return e.Category() == Network && network.L4Proto(e.Params.MustGetUint32(params.NetL4Proto)) == network.TCP } // IsDNS determines whether the event is a DNS question/answer. func (e *Event) IsDNS() bool { - return e.Type.Subcategory() == DNS + return e.Subcategory() == DNS } // IsRundown determines if this is a rundown events. func (e *Event) IsRundown() bool { - return e.Type == ProcessRundown || e.Type == ThreadRundown || e.Type == ModuleRundown || - e.Type == FileRundown || e.Type == RegKCBRundown + return e.Type.StateSnapshot() } // IsSuccess checks if the event contains the status parameter @@ -228,8 +217,8 @@ func (e *Event) IsRegCreateKey() bool { return e.Type == RegCreateKey func (e *Event) IsProcessRundown() bool { return e.Type == ProcessRundown } func (e *Event) IsProcessRundownInternal() bool { return e.Type == ProcessRundownInternal } func (e *Event) IsVirtualAlloc() bool { return e.Type == VirtualAlloc } -func (e *Event) IsMapViewFile() bool { return e.Type == MapViewFile } -func (e *Event) IsUnmapViewFile() bool { return e.Type == UnmapViewFile } +func (e *Event) IsMapViewOfSection() bool { return e.Type == MapViewOfSection } +func (e *Event) IsUnmapViewOfSection() bool { return e.Type == UnmapViewOfSection } func (e *Event) IsStackWalk() bool { return e.Type == StackWalk } func (e *Event) IsOpenThread() bool { return e.Type == OpenThread } func (e *Event) IsOpenProcess() bool { return e.Type == OpenProcess } @@ -336,7 +325,7 @@ func (e *Event) RundownKey() uint64 { binary.LittleEndian.PutUint64(b, fileObject) return hashers.FnvUint64(b) - case MapFileRundown: + case MapViewSectionRundown: b := make([]byte, 12) fileKey, _ := e.Params.GetUint64(params.FileKey) binary.LittleEndian.PutUint32(b, e.PID) @@ -362,7 +351,7 @@ func (e *Event) PartialKey() uint64 { switch e.Type { case WriteFile, ReadFile: return e.Params.MustGetUint64(params.FileObject) + uint64(e.PID) - case MapViewFile, UnmapViewFile: + case MapViewOfSection, UnmapViewOfSection: return e.Params.MustGetUint64(params.FileViewBase) + uint64(e.PID) case CreateFile: file, _ := e.Params.GetString(params.FilePath) @@ -378,38 +367,34 @@ func (e *Event) PartialKey() uint64 { tid := e.Params.MustGetUint32(params.ThreadID) access := e.Params.MustGetUint32(params.DesiredAccess) return uint64(tid + access + e.PID) - case AcceptTCPv4, RecvTCPv4, RecvUDPv4: - b := make([]byte, 10) - ip, _ := e.Params.GetIP(params.NetSIP) - port, _ := e.Params.GetUint16(params.NetSport) - binary.LittleEndian.PutUint32(b, e.PID) - binary.LittleEndian.PutUint32(b, binary.BigEndian.Uint32(ip.To4())) - binary.LittleEndian.PutUint16(b, port) - return hashers.FnvUint64(b) - case AcceptTCPv6, RecvTCPv6, RecvUDPv6: - b := make([]byte, 22) + case Accept, Recv: + var b []byte ip, _ := e.Params.GetIP(params.NetSIP) + if ip.To4() != nil { + b = make([]byte, 10) + binary.LittleEndian.PutUint32(b, binary.BigEndian.Uint32(ip.To4())) + } else { + b = make([]byte, 22) + binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[0:8])) + binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[8:16])) + } port, _ := e.Params.GetUint16(params.NetSport) binary.LittleEndian.PutUint32(b, e.PID) - binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[0:8])) - binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[8:16])) binary.LittleEndian.PutUint16(b, port) return hashers.FnvUint64(b) - case ConnectTCPv4, SendTCPv4, SendUDPv4: - b := make([]byte, 10) - ip, _ := e.Params.GetIP(params.NetDIP) - port, _ := e.Params.GetUint16(params.NetDport) - binary.LittleEndian.PutUint32(b, e.PID) - binary.LittleEndian.PutUint32(b, binary.BigEndian.Uint32(ip.To4())) - binary.LittleEndian.PutUint16(b, port) - return hashers.FnvUint64(b) - case ConnectTCPv6, SendTCPv6, SendUDPv6: - b := make([]byte, 22) + case Connect, Send: + var b []byte ip, _ := e.Params.GetIP(params.NetDIP) + if ip.To4() != nil { + b = make([]byte, 10) + binary.LittleEndian.PutUint32(b, binary.BigEndian.Uint32(ip.To4())) + } else { + b = make([]byte, 22) + binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[0:8])) + binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[8:16])) + } port, _ := e.Params.GetUint16(params.NetDport) binary.LittleEndian.PutUint32(b, e.PID) - binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[0:8])) - binary.LittleEndian.PutUint64(b, binary.BigEndian.Uint64(ip.To16()[8:16])) binary.LittleEndian.PutUint16(b, port) return hashers.FnvUint64(b) case RegOpenKey, RegQueryKey, RegQueryValue, @@ -523,21 +508,21 @@ func (e *Event) Summary() string { case RegQueryValue: key := e.GetParamAsString(params.RegPath) return printSummary(e, fmt.Sprintf("queried %s value", key)) - case AcceptTCPv4, AcceptTCPv6: + case Accept: ip, _ := e.Params.GetIP(params.NetSIP) port, _ := e.Params.GetUint16(params.NetSport) return printSummary(e, fmt.Sprintf("accepted connection from %v and %d port", ip, port)) - case ConnectTCPv4, ConnectTCPv6: + case Connect: ip, _ := e.Params.GetIP(params.NetDIP) port, _ := e.Params.GetUint16(params.NetDport) return printSummary(e, fmt.Sprintf("connected to %v and %d port", ip, port)) - case SendTCPv4, SendTCPv6, SendUDPv4, SendUDPv6: + case Send: ip, _ := e.Params.GetIP(params.NetDIP) port, _ := e.Params.GetUint16(params.NetDport) size, _ := e.Params.GetUint32(params.NetSize) return printSummary(e, fmt.Sprintf("sent %d bytes to %v and %d port", size, ip, port)) - case RecvTCPv4, RecvTCPv6, RecvUDPv4, RecvUDPv6: + case Recv: ip, _ := e.Params.GetIP(params.NetSIP) port, _ := e.Params.GetUint16(params.NetSport) size, _ := e.Params.GetUint32(params.NetSize) @@ -549,10 +534,10 @@ func (e *Event) Summary() string { case VirtualFree: addr := e.GetParamAsString(params.MemBaseAddress) return printSummary(e, fmt.Sprintf("released memory at %s address", addr)) - case MapViewFile: + case MapViewOfSection: sec := e.GetParamAsString(params.FileViewSectionType) return printSummary(e, fmt.Sprintf("mapped view of %s section", sec)) - case UnmapViewFile: + case UnmapViewOfSection: sec := e.GetParamAsString(params.FileViewSectionType) return printSummary(e, fmt.Sprintf("unmapped view of %s section", sec)) case QueryDNS: diff --git a/pkg/event/event_windows_test.go b/pkg/event/event_windows_test.go index 573c2c511..a12094ab2 100644 --- a/pkg/event/event_windows_test.go +++ b/pkg/event/event_windows_test.go @@ -19,41 +19,25 @@ package event import ( + "testing" + "time" + "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/fs" pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" - "time" ) -func TestEventIsNetworkTCP(t *testing.T) { - e1 := Event{Type: AcceptTCPv4, Category: Net} - e2 := Event{Type: SendUDPv6, Category: Net} - assert.True(t, e1.IsNetworkTCP()) - assert.False(t, e2.IsNetworkTCP()) -} - -func TestEventIsNetworkUDP(t *testing.T) { - e1 := Event{Type: RecvUDPv4} - e2 := Event{Type: SendTCPv6} - assert.True(t, e1.IsNetworkUDP()) - assert.False(t, e2.IsNetworkUDP()) -} - func TestEventSummary(t *testing.T) { evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, diff --git a/pkg/event/formatter.go b/pkg/event/formatter.go index d1181ada7..7a3e0361b 100644 --- a/pkg/event/formatter.go +++ b/pkg/event/formatter.go @@ -209,13 +209,10 @@ func (f *ColorFormatter) colourTag(tag string, e *Event) string { case seq: // sequence number is ok to render as dim gray return colorizer.SpanDim(colorizer.Span(colorizer.Gray, strconv.FormatUint(e.Seq, 10))) - case ts: return f.colourTimestamp(e) - case cpu: return colorizer.Span(colorizer.Yellow, strconv.FormatUint(uint64(e.CPU), 10)) - case proc: // render process name with bold green as it is the most important // identity anchor on the line. Analysts scan for it first. @@ -224,85 +221,70 @@ func (f *ColorFormatter) colourTag(tag string, e *Event) string { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.SpanBold(colorizer.Green, ps.Name) - case pid: return colorizer.Span(colorizer.Green, strconv.FormatUint(uint64(e.PID), 10)) - case ppid: ps := e.PS if ps == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.Green, strconv.FormatUint(uint64(ps.Ppid), 10)) - case tid: return colorizer.Span(colorizer.Green, strconv.FormatUint(uint64(e.Tid), 10)) - case exe: ps := e.PS if ps == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.White, ps.Exe) - case pexe: ps := e.PS if ps == nil || ps.Parent == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.White, ps.Parent.Exe) - case cmd: ps := e.PS if ps == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.White, ps.Cmdline) - case pcmd: ps := e.PS if ps == nil || ps.Parent == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.White, ps.Parent.Cmdline) - case cwd: ps := e.PS if ps == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.White, ps.Cwd) - case sid: ps := e.PS if ps == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.Gray, ps.SID) - case pproc: ps := e.PS if ps == nil || ps.Parent == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.Green, ps.Parent.Name) - case typ: return e.Type.color() - case cat: - return colorizer.Span(colorizer.Magenta, string(e.Category)) - + return colorizer.Span(colorizer.Magenta, e.Category().String()) case parameters: return e.Params.Colorize() - case pe: ps := e.PS if ps == nil || ps.PE == nil { return colorizer.Span(colorizer.Gray, "N/A") } return colorizer.Span(colorizer.Magenta, ps.PE.String()) - case cstack: return fmt.Sprintf("\n%s", e.Callstack.Colorize()) } diff --git a/pkg/event/formatter_test.go b/pkg/event/formatter_test.go index 044a4c128..657dba714 100644 --- a/pkg/event/formatter_test.go +++ b/pkg/event/formatter_test.go @@ -19,13 +19,14 @@ package event import ( + "github.com/rabbitstack/fibratus/pkg/event/params" htypes "github.com/rabbitstack/fibratus/pkg/handle/types" pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" "github.com/stretchr/testify/assert" - kpars "github.com/rabbitstack/fibratus/pkg/event/params" - "github.com/stretchr/testify/require" "testing" + + "github.com/stretchr/testify/require" ) func TestTemplateUnknownField(t *testing.T) { @@ -54,10 +55,10 @@ func TestFormat(t *testing.T) { template := "{{ .Seq }} {{.CPU}} - ({{.Type}}) -- pid: {{ .Params.Pid }} ({{.Params}}) {{ .Meta }}" f, err := NewFormatter(template) require.NoError(t, err) - params := Params{ - kpars.ProcessID: {Name: kpars.ProcessID, Type: kpars.PID, Value: uint32(876)}, + pars := Params{ + params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(876)}, } - s := f.Format(&Event{CPU: uint8(4), Name: "CreateProcess", Seq: uint64(1999), Params: params, Metadata: map[MetadataKey]any{"key1": "value1"}}) + s := f.Format(&Event{CPU: uint8(4), Type: CreateProcess, Seq: uint64(1999), Params: pars, Metadata: map[MetadataKey]any{"key1": "value1"}}) assert.Equal(t, "1999 4 - (CreateProcess) -- pid: 876 (pid➜ 876) key1: value1", string(s)) } @@ -65,14 +66,13 @@ func TestFormatPS(t *testing.T) { template := "{{ .Seq }} {{ .Process }} ({{ .Cwd }}) {{ .Ppid }} ({{ .Sid }})" f, err := NewFormatter(template) require.NoError(t, err) - params := Params{ - kpars.ProcessID: {Name: kpars.ProcessID, Type: kpars.PID, Value: uint32(876)}, + pars := Params{ + params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(876)}, } s := f.Format(&Event{ CPU: uint8(4), - Name: "CreateProcess", Seq: uint64(1999), - Params: params, + Params: pars, PS: &pstypes.PS{ Name: "cmd.exe", Cwd: "C:/Windows/System32", @@ -92,47 +92,86 @@ func TestNormalizeTemplate(t *testing.T) { } func TestIsTemplateBalanced(t *testing.T) { - ok, pos := isTemplateBalanced("{{ .Seq }} {{.CPU}}") - require.True(t, ok) - assert.Equal(t, -1, pos) - - ok, pos = isTemplateBalanced("{{ .Seq }} ({{.CPU}}) [] {{.Type}}") - require.True(t, ok) - assert.Equal(t, -1, pos) - - ok, pos = isTemplateBalanced("{{ .Seq }} {.CPU}} {{.Type}}") - require.False(t, ok) - assert.Equal(t, 2, pos) - - ok, pos = isTemplateBalanced("{.Seq}") - require.False(t, ok) - assert.Equal(t, 1, pos) - - ok, pos = isTemplateBalanced("{{ .Seq }} .CPU }}") - require.False(t, ok) - assert.Equal(t, 2, pos) - - ok, pos = isTemplateBalanced("{{{ .Seq }} {{.CPU}} {{} {{ .Params }} { .Params.pid}}") - require.False(t, ok) - assert.Equal(t, 1, pos) - - ok, pos = isTemplateBalanced("{{ .Seq }} {{.CPU}} {{} {{ .Params }} { .Params.pid}}") - require.False(t, ok) - assert.Equal(t, 3, pos) - - ok, pos = isTemplateBalanced("({{ .Seq }}) {{.CPU}} {{}} {{ .Params }} { .Params.pid}}") - require.False(t, ok) - assert.Equal(t, 5, pos) - - ok, pos = isTemplateBalanced("{{ .Seq } {{.CPU}} {.Type}}") - require.False(t, ok) - assert.Equal(t, 1, pos) + tests := []struct { + name string + input string + wantOK bool + wantPos int + }{ + { + name: "balanced templates", + input: "{{ .Seq }} {{.CPU}}", + wantOK: true, + wantPos: -1, + }, + { + name: "balanced templates with other delimiters", + input: "{{ .Seq }} ({{.CPU}}) [] {{.Type}}", + wantOK: true, + wantPos: -1, + }, + { + name: "single opening brace", + input: "{{ .Seq }} {.CPU}} {{.Type}}", + wantOK: false, + wantPos: 2, + }, + { + name: "single template", + input: "{.Seq}", + wantOK: false, + wantPos: 1, + }, + { + name: "unmatched closing braces", + input: "{{ .Seq }} .CPU }}", + wantOK: false, + wantPos: 2, + }, + { + name: "triple opening brace", + input: "{{{ .Seq }} {{.CPU}} {{} {{ .Params }} { .Params.pid}}", + wantOK: false, + wantPos: 1, + }, + { + name: "empty template", + input: "{{ .Seq }} {{.CPU}} {{} {{ .Params }} { .Params.pid}}", + wantOK: false, + wantPos: 3, + }, + { + name: "empty template with other delimiters", + input: "({{ .Seq }}) {{.CPU}} {{}} {{ .Params }} { .Params.pid}}", + wantOK: false, + wantPos: 5, + }, + { + name: "malformed closing delimiter", + input: "{{ .Seq } {{.CPU}} {.Type}}", + wantOK: false, + wantPos: 1, + }, + { + name: "unmatched closing brace", + input: "{{ .Seq }} {{.CPU}} {.Type}}", + wantOK: false, + wantPos: 3, + }, + { + name: "malformed template in complex input", + input: "{{ .Seq }} {{.CPU}} - ({{.Type}}) -- pid: {{]} {{ .Params.Pid }} ({{.Params}}) {{ .Meta }}", + wantOK: false, + wantPos: 4, + }, + } - ok, pos = isTemplateBalanced("{{ .Seq }} {{.CPU}} {.Type}}") - require.False(t, ok) - assert.Equal(t, 3, pos) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ok, pos := isTemplateBalanced(tt.input) - ok, pos = isTemplateBalanced("{{ .Seq }} {{.CPU}} - ({{.Type}}) -- pid: {{]} {{ .Params.Pid }} ({{.Params}}) {{ .Meta }}") - require.False(t, ok) - assert.Equal(t, 4, pos) + require.Equal(t, tt.wantOK, ok) + assert.Equal(t, tt.wantPos, pos) + }) + } } diff --git a/pkg/event/formatter_windows.go b/pkg/event/formatter_windows.go index c441ae650..3c55f3f54 100644 --- a/pkg/event/formatter_windows.go +++ b/pkg/event/formatter_windows.go @@ -33,9 +33,9 @@ func (f *Formatter) Format(evt *Event) []byte { tid: strconv.FormatUint(uint64(evt.Tid), 10), seq: strconv.FormatUint(evt.Seq, 10), cpu: strconv.FormatUint(uint64(evt.CPU), 10), - typ: evt.Name, - cat: evt.Category, - desc: evt.Description, + typ: evt.Name(), + cat: evt.Category(), + desc: evt.Description(), host: evt.Host, meta: evt.Metadata.String(), parameters: evt.Params.String(), diff --git a/pkg/event/marshaller_test.go b/pkg/event/marshaller_test.go index 37be2a3e6..2906b7f62 100644 --- a/pkg/event/marshaller_test.go +++ b/pkg/event/marshaller_test.go @@ -49,16 +49,13 @@ func TestMarshaller(t *testing.T) { require.NoError(t, err) evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: now, - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: now, + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -77,7 +74,7 @@ func TestMarshaller(t *testing.T) { b := evt.MarshalRaw() require.NotEmpty(t, b) - clone, err := NewFromCapture(b, capver.EvtSecV2) + clone, err := NewFromCapture(b, capver.EvtSecV3) require.NoError(t, err) assert.Equal(t, uint64(2), clone.Seq) @@ -85,9 +82,9 @@ func TestMarshaller(t *testing.T) { assert.Equal(t, uint32(2484), clone.Tid) assert.Equal(t, CreateFile, clone.Type) assert.Equal(t, uint8(1), clone.CPU) - assert.Equal(t, "CreateFile", clone.Name) - assert.Equal(t, File, clone.Category) - assert.Equal(t, "Creates or opens a new file, directory, I/O device, pipe, console", clone.Description) + assert.Equal(t, "CreateFile", clone.Name()) + assert.Equal(t, File, clone.Category()) + assert.Equal(t, "Creates or opens a new file, directory, I/O device, pipe, console", clone.Description()) assert.Equal(t, "archrabbit", clone.Host) assert.Equal(t, now, clone.Timestamp) @@ -108,16 +105,13 @@ func TestMarshaller(t *testing.T) { func TestEventMarshalJSON(t *testing.T) { evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -225,16 +219,13 @@ func TestUnmarshalHugeHandles(t *testing.T) { require.NoError(t, err) evt := &Event{ - Type: CreateProcess, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateProcess", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates a new process", + Type: CreateProcess, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -278,7 +269,7 @@ func TestUnmarshalHugeHandles(t *testing.T) { } s := evt.MarshalRaw() - clone, err := NewFromCapture(s, capver.EvtSecV2) + clone, err := NewFromCapture(s, capver.EvtSecV3) require.NoError(t, err) require.NotNil(t, clone) } @@ -287,16 +278,13 @@ func TestEventMarshalJSONMultiple(t *testing.T) { for i := 0; i < 10; i++ { seq := uint64(i + 1) evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: seq, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: seq, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -367,16 +355,13 @@ func TestEventMarshalJSONMultiple(t *testing.T) { func BenchmarkEventMarshalJSON(b *testing.B) { evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -451,16 +436,13 @@ func BenchmarkEventMarshalJSON(b *testing.B) { func BenchmarkEventMarshalJSONStdlib(b *testing.B) { evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -537,16 +519,13 @@ func BenchmarkEventMarshalJSONStdlib(b *testing.B) { func BenchmarkMarshal(b *testing.B) { evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -565,16 +544,13 @@ func BenchmarkMarshal(b *testing.B) { func BenchmarkUnmarshal(b *testing.B) { evt := &Event{ - Type: CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -586,11 +562,11 @@ func BenchmarkUnmarshal(b *testing.B) { buf := evt.MarshalRaw() b.ReportAllocs() for i := 0; i < b.N; i++ { - ke, err := NewFromCapture(buf, capver.EvtSecV2) + evt, err := NewFromCapture(buf, capver.EvtSecV3) if err != nil { b.Fatal(err) } - if ke.Name == "" { + if evt.Name() == "" { b.Fatal("invalid unmarshal byte slice") } } diff --git a/pkg/event/marshaller_windows.go b/pkg/event/marshaller_windows.go index 5ded48295..1473e454b 100644 --- a/pkg/event/marshaller_windows.go +++ b/pkg/event/marshaller_windows.go @@ -64,21 +64,11 @@ func (e *Event) MarshalRaw() []byte { b = append(b, bytes.WriteUint32(e.Tid)...) // write type and CPU - b = append(b, e.Type[:]...) + b = append(b, bytes.WriteUint16(uint16(e.Type))...) b = append(b, e.CPU) // for the string fields we have to write the length prior to // the string buffer itself, so we can decode the string correctly - // - // write event name - b = append(b, bytes.WriteUint16(uint16(len(e.Name)))...) - b = append(b, e.Name...) - // write category - b = append(b, bytes.WriteUint16(uint16(len(e.Category)))...) - b = append(b, e.Category...) - // write description - b = append(b, bytes.WriteUint16(uint16(len(e.Description)))...) - b = append(b, e.Description...) // write host name b = append(b, bytes.WriteUint16(uint16(len(e.Host)))...) b = append(b, e.Host...) @@ -201,8 +191,6 @@ func (e *Event) UnmarshalRaw(b []byte, ver capver.Version) error { e.PID = bytes.ReadUint32(b[8:]) e.Tid = bytes.ReadUint32(b[12:]) - // read type and CPU - var typ Type // set start index depending // on event section version var idx uint32 @@ -211,41 +199,25 @@ func (e *Event) UnmarshalRaw(b []byte, ver capver.Version) error { idx = 33 case capver.EvtSecV2: idx = 34 + case capver.EvtSecV3: + idx = 18 } - copy(typ[:], b[16:idx]) - e.Type = typ + + e.Type = Type(bytes.ReadUint16(b[16:idx])) e.CPU = b[idx : idx+1][0] idx++ // increment index var offset uint32 - // read event name + // read host name l := bytes.ReadUint16(b[inc(idx, 0):]) buf := b[inc(idx, 2):] offset = uint32(l) - e.Name = string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:l:l]) - - // read category - l = bytes.ReadUint16(b[inc(idx, 2)+offset:]) - buf = b[inc(idx, 4)+offset:] - offset += uint32(l) - e.Category = Category(string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:l:l])) - - // read description - l = bytes.ReadUint16(b[inc(idx, 4)+offset:]) - buf = b[inc(idx, 6)+offset:] - offset += uint32(l) - e.Description = string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:l:l]) - - // read host name - l = bytes.ReadUint16(b[inc(idx, 6)+offset:]) - buf = b[inc(idx, 8)+offset:] - offset += uint32(l) e.Host = string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:l:l]) // read timestamp - l = bytes.ReadUint16(b[inc(idx, 8)+offset:]) - buf = b[inc(idx, 10)+offset:] + l = bytes.ReadUint16(b[inc(idx, 2)+offset:]) + buf = b[inc(idx, 4)+offset:] offset += uint32(l) if len(buf) > 0 { var err error @@ -256,87 +228,87 @@ func (e *Event) UnmarshalRaw(b []byte, ver capver.Version) error { } // read parameters - nparams := bytes.ReadUint16(b[inc(idx, 10)+offset:]) + nparams := bytes.ReadUint16(b[inc(idx, 4)+offset:]) // accumulates the offset of all parameter name and value lengths var poffset uint32 for i := 0; i < int(nparams); i++ { - // read Param type - typ := bytes.ReadUint16(b[inc(idx, 12)+offset+poffset:]) - // read Param name - kparamNameLength := uint32(bytes.ReadUint16(b[inc(idx, 14)+offset+poffset:])) - buf = b[inc(idx, 16)+offset+poffset:] - kparamName := string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:kparamNameLength:kparamNameLength]) + // read param type + typ := bytes.ReadUint16(b[inc(idx, 6)+offset+poffset:]) + // read param name + paramNameLength := uint32(bytes.ReadUint16(b[inc(idx, 8)+offset+poffset:])) + buf = b[inc(idx, 10)+offset+poffset:] + kparamName := string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:paramNameLength:paramNameLength]) - pi := inc(idx, 16) // parameter index + pi := inc(idx, 10) // parameter index var val params.Value switch params.Type(typ) { case params.AnsiString, params.UnicodeString, params.Path: // read string parameter - l := bytes.ReadUint16(b[pi+offset+kparamNameLength+poffset:]) - buf = b[inc(idx, 18)+offset+kparamNameLength+poffset:] + l := bytes.ReadUint16(b[pi+offset+paramNameLength+poffset:]) + buf = b[inc(idx, 12)+offset+paramNameLength+poffset:] if len(buf) > 0 { val = string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:l:l]) } // increment parameter offset by string by type length + name length bytes + length of // the string parameter + string parameter size - poffset += kparamNameLength + 6 + uint32(l) + poffset += paramNameLength + 6 + uint32(l) case params.Uint64, params.Address, params.Flags64: - val = bytes.ReadUint64(b[pi+offset+kparamNameLength+poffset:]) + val = bytes.ReadUint64(b[pi+offset+paramNameLength+poffset:]) // increment parameter offset by type length + name length sizes + size of uint64 - poffset += kparamNameLength + 4 + 8 + poffset += paramNameLength + 4 + 8 case params.Int64: - val = int64(bytes.ReadUint64(b[pi+offset+kparamNameLength+poffset:])) + val = int64(bytes.ReadUint64(b[pi+offset+paramNameLength+poffset:])) // increment parameter offset by type length + name length sizes + size of int64 - poffset += kparamNameLength + 4 + 8 + poffset += paramNameLength + 4 + 8 case params.Double: - val = float64(bytes.ReadUint64(b[pi+offset+kparamNameLength+poffset:])) - poffset += kparamNameLength + 4 + 8 + val = float64(bytes.ReadUint64(b[pi+offset+paramNameLength+poffset:])) + poffset += paramNameLength + 4 + 8 case params.Float: - val = float32(bytes.ReadUint32(b[pi+offset+kparamNameLength+poffset:])) - poffset += kparamNameLength + 4 + 4 + val = float32(bytes.ReadUint32(b[pi+offset+paramNameLength+poffset:])) + poffset += paramNameLength + 4 + 4 case params.IPv4: - val = ip.ToIPv4(bytes.ReadUint32(b[pi+offset+kparamNameLength+poffset:])) + val = ip.ToIPv4(bytes.ReadUint32(b[pi+offset+paramNameLength+poffset:])) // // increment by IPv4 length - poffset += kparamNameLength + 4 + 4 + poffset += paramNameLength + 4 + 4 case params.IPv6: - val = ip.ToIPv6(b[pi+offset+kparamNameLength+poffset : pi+offset+kparamNameLength+poffset+16]) + val = ip.ToIPv6(b[pi+offset+paramNameLength+poffset : pi+offset+paramNameLength+poffset+16]) // increment by IPv6 length - poffset += kparamNameLength + 4 + 16 + poffset += paramNameLength + 4 + 16 case params.PID, params.TID: - val = bytes.ReadUint32(b[pi+offset+kparamNameLength+poffset:]) - poffset += kparamNameLength + 4 + 4 + val = bytes.ReadUint32(b[pi+offset+paramNameLength+poffset:]) + poffset += paramNameLength + 4 + 4 case params.Int32: - val = int32(bytes.ReadUint32(b[pi+offset+kparamNameLength+poffset:])) - poffset += kparamNameLength + 4 + 4 + val = int32(bytes.ReadUint32(b[pi+offset+paramNameLength+poffset:])) + poffset += paramNameLength + 4 + 4 case params.Uint32, params.Enum, params.Flags, params.Status: - val = bytes.ReadUint32(b[pi+offset+kparamNameLength+poffset:]) - poffset += kparamNameLength + 4 + 4 + val = bytes.ReadUint32(b[pi+offset+paramNameLength+poffset:]) + poffset += paramNameLength + 4 + 4 case params.Uint16, params.Port: - val = bytes.ReadUint16(b[pi+offset+kparamNameLength+poffset:]) - poffset += kparamNameLength + 4 + 2 + val = bytes.ReadUint16(b[pi+offset+paramNameLength+poffset:]) + poffset += paramNameLength + 4 + 2 case params.Int16: - val = int16(bytes.ReadUint16(b[pi+offset+kparamNameLength+poffset:])) - poffset += kparamNameLength + 4 + 2 + val = int16(bytes.ReadUint16(b[pi+offset+paramNameLength+poffset:])) + poffset += paramNameLength + 4 + 2 case params.Uint8: - val = b[pi+offset+kparamNameLength+poffset : pi+offset+kparamNameLength+poffset+1][0] - poffset += kparamNameLength + 4 + 1 + val = b[pi+offset+paramNameLength+poffset : pi+offset+paramNameLength+poffset+1][0] + poffset += paramNameLength + 4 + 1 case params.Int8: - val = int8(b[pi+offset+kparamNameLength+poffset : pi+offset+kparamNameLength+poffset+1][0]) - poffset += kparamNameLength + 4 + 1 + val = int8(b[pi+offset+paramNameLength+poffset : pi+offset+paramNameLength+poffset+1][0]) + poffset += paramNameLength + 4 + 1 case params.Bool: - v := b[pi+offset+kparamNameLength+poffset : pi+offset+kparamNameLength+poffset+1][0] + v := b[pi+offset+paramNameLength+poffset : pi+offset+paramNameLength+poffset+1][0] if v == 1 { val = true } else { val = false } - poffset += kparamNameLength + 4 + 1 + poffset += paramNameLength + 4 + 1 case params.Time: // read ts length - l := bytes.ReadUint16(b[pi+offset+kparamNameLength+poffset:]) - buf = b[inc(idx, 18)+offset+kparamNameLength+poffset:] + l := bytes.ReadUint16(b[pi+offset+paramNameLength+poffset:]) + buf = b[inc(idx, 12)+offset+paramNameLength+poffset:] if len(buf) > 0 { var err error val, err = time.Parse(time.RFC3339Nano, string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:l:l])) @@ -344,19 +316,19 @@ func (e *Event) UnmarshalRaw(b []byte, ver capver.Version) error { unmarshalTimestampErrors.Add(1) } } - poffset += kparamNameLength + 6 + uint32(l) + poffset += paramNameLength + 6 + uint32(l) case params.Slice: // read slice element type - typ := b[pi+offset+kparamNameLength+poffset] + typ := b[pi+offset+paramNameLength+poffset] // read slice size - l := bytes.ReadUint16(b[inc(idx, 17)+offset+kparamNameLength+poffset:]) + l := bytes.ReadUint16(b[inc(idx, 11)+offset+paramNameLength+poffset:]) var off uint32 switch typ { case 's': s := make([]string, l) for i := 0; i < int(l); i++ { - size := bytes.ReadUint16(b[inc(idx, 19)+offset+kparamNameLength+poffset+off:]) - buf := b[inc(idx, 22)+offset+kparamNameLength+poffset+off:] + size := bytes.ReadUint16(b[inc(idx, 13)+offset+paramNameLength+poffset+off:]) + buf := b[inc(idx, 15)+offset+paramNameLength+poffset+off:] s[i] = string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:size:size]) off += 2 + uint32(size) } @@ -364,19 +336,19 @@ func (e *Event) UnmarshalRaw(b []byte, ver capver.Version) error { case '8': v := make([]uint64, l) for i := 0; i < int(l); i++ { - bytes.ReadUint64(b[inc(idx, 22)+offset+kparamNameLength+poffset+off:]) + bytes.ReadUint64(b[inc(idx, 16)+offset+paramNameLength+poffset+off:]) off += 8 } val = v } - poffset += kparamNameLength + 4 + 1 + 2 + off + poffset += paramNameLength + 4 + 1 + 2 + off case params.Binary, params.SID, params.WbemSID: - l := bytes.ReadUint32(b[pi+offset+kparamNameLength+poffset:]) - buf = b[inc(idx, 18)+offset+kparamNameLength+poffset:] + l := bytes.ReadUint32(b[pi+offset+paramNameLength+poffset:]) + buf = b[inc(idx, 12)+offset+paramNameLength+poffset:] if len(buf) > 0 { val = buf[:l] } - poffset += kparamNameLength + 8 + l + poffset += paramNameLength + 8 + l } if val != nil { @@ -387,16 +359,16 @@ func (e *Event) UnmarshalRaw(b []byte, ver capver.Version) error { offset += poffset // read metadata tags - ntags := bytes.ReadUint16(b[inc(idx, 12)+offset:]) + ntags := bytes.ReadUint16(b[inc(idx, 6)+offset:]) var moffset uint32 for i := 0; i < int(ntags); i++ { // read key - klen := uint32(bytes.ReadUint16(b[inc(idx, 14)+offset+moffset:])) - buf = b[inc(idx, 16)+offset+moffset:] + klen := uint32(bytes.ReadUint16(b[inc(idx, 8)+offset+moffset:])) + buf = b[inc(idx, 10)+offset+moffset:] key := string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:klen:klen]) // read value - vlen := uint32(bytes.ReadUint16(b[inc(idx, 16)+offset+klen+moffset:])) - buf = b[inc(idx, 18)+offset+klen+moffset:] + vlen := uint32(bytes.ReadUint16(b[inc(idx, 10)+offset+klen+moffset:])) + buf = b[inc(idx, 12)+offset+klen+moffset:] value := string((*[1<<30 - 1]byte)(unsafe.Pointer(&buf[0]))[:vlen:vlen]) // increment the offset by the length of the key + length value + size of uint16 * 2 // that corresponds to bytes storing the lengths of keys/values @@ -409,9 +381,9 @@ func (e *Event) UnmarshalRaw(b []byte, ver capver.Version) error { offset += moffset // read process state - sec := section.Read(b[inc(idx, 14)+offset:]) + sec := section.Read(b[inc(idx, 8)+offset:]) if sec.Size() != 0 { - ps, err := ptypes.NewFromCapture(b[inc(idx, 24)+offset:], sec) + ps, err := ptypes.NewFromCapture(b[inc(idx, 18)+offset:], sec) if err != nil { return err } @@ -441,9 +413,9 @@ func (e *Event) MarshalJSON() []byte { js.writeObjectField("tid").writeUint32(e.Tid).writeMore() js.writeObjectField("cpu").writeUint8(e.CPU).writeMore() - js.writeObjectField("name").writeString(e.Name).writeMore() - js.writeObjectField("category").writeString(string(e.Category)).writeMore() - js.writeObjectField("description").writeString(e.Description).writeMore() + js.writeObjectField("name").writeString(e.Name()).writeMore() + js.writeObjectField("category").writeString(e.Category().String()).writeMore() + js.writeObjectField("description").writeString(e.Description()).writeMore() js.writeObjectField("host").writeString(e.Host).writeMore() timestamp := make([]byte, 0) diff --git a/pkg/event/metainfo_windows.go b/pkg/event/metainfo_windows.go index 9e7a7b941..4817fbcd2 100644 --- a/pkg/event/metainfo_windows.go +++ b/pkg/event/metainfo_windows.go @@ -23,229 +23,147 @@ import ( "slices" ) +// Flags represents the event flags +type Flags uint8 + +const ( + // OnlyState indicates the event produces internal state + // and is never published to the event stream. + OnlyState Flags = 1 << 1 + + // StateSnapshot indicates that the event is published once + // at startup time and populates the internal state. + StateSnapshot Flags = 1 << 2 + + // WaitStack indicates that the event awaits the stack walk + // event that carries call stack return addresses. + WaitStack Flags = 1 << 3 +) + // Info describes the event meta info such as human-readable name, category and description. type Info struct { // Name is the human-readable representation of the event (e.g. CreateProcess, DeleteFile). Name string - // Category designates the category to which event pertains. (e.g. process, net) + // Category designates the category to which event pertains. (e.g. process, network) Category Category + // Subcategory designates the event subcategory if any. For example, the network category + // can be further subcategorized, such as DNS subcategory. + Subcategory Subcategory + // Source describes the event source origin for this event. For example, if it was captured + // from the NT Kernel Logger or a different event source. + Source Source // Description is the short explanation that describes the purpose of the event. Description string + // Flags describes additional properties of the event. + Flags Flags } -var events = map[Type]Info{ - CreateProcess: {"CreateProcess", Process, "Creates a new process and its primary thread"}, - TerminateProcess: {"TerminateProcess", Process, "Terminates the process and all of its threads"}, - OpenProcess: {"OpenProcess", Process, "Opens the process handle"}, - CreateThread: {"CreateThread", Thread, "Creates a thread to execute within the virtual address space of the calling process"}, - TerminateThread: {"TerminateThread", Thread, "Terminates a thread within the process"}, - OpenThread: {"OpenThread", Thread, "Opens the thread handle"}, - SetThreadContext: {"SetThreadContext", Thread, "Sets the thread context"}, - ReadFile: {"ReadFile", File, "Reads data from the file or I/O device"}, - WriteFile: {"WriteFile", File, "Writes data to the file or I/O device"}, - CreateFile: {"CreateFile", File, "Creates or opens a file or I/O device"}, - CloseFile: {"CloseFile", File, "Closes the file handle"}, - DeleteFile: {"DeleteFile", File, "Removes the file from the file system"}, - RenameFile: {"RenameFile", File, "Changes the file name"}, - SetFileInformation: {"SetFileInformation", File, "Sets the file meta information"}, - EnumDirectory: {"EnumDirectory", File, "Enumerates a directory or dispatches a directory change notification to registered listeners"}, - RegCreateKey: {"RegCreateKey", Registry, "Creates a registry key or opens it if the key already exists"}, - RegOpenKey: {"RegOpenKey", Registry, "Opens the registry key"}, - RegCloseKey: {"RegCloseKey", Registry, "Closes the registry key"}, - RegSetValue: {"RegSetValue", Registry, "Sets the data for the value of a registry key"}, - RegQueryValue: {"RegQueryValue", Registry, "Reads the data for the value of a registry key"}, - RegQueryKey: {"RegQueryKey", Registry, "Enumerates subkeys of the parent key"}, - RegDeleteKey: {"RegDeleteKey", Registry, "Removes the registry key"}, - RegDeleteValue: {"RegDeleteValue", Registry, "Removes the registry value"}, - AcceptTCPv4: {"Accept", Net, "Accepts the connection request from the socket queue"}, - AcceptTCPv6: {"Accept", Net, "Accepts the connection request from the socket queue"}, - SendTCPv4: {"Send", Net, "Sends data over the wire"}, - SendTCPv6: {"Send", Net, "Sends data over the wire"}, - SendUDPv4: {"Send", Net, "Sends data over the wire"}, - SendUDPv6: {"Send", Net, "Sends data over the wire"}, - RecvTCPv4: {"Recv", Net, "Receives data from the socket"}, - RecvTCPv6: {"Recv", Net, "Receives data from the socket"}, - RecvUDPv4: {"Recv", Net, "Receives data from the socket"}, - RecvUDPv6: {"Recv", Net, "Receives data from the socket"}, - ConnectTCPv4: {"Connect", Net, "Connects establishes a connection to the socket"}, - ConnectTCPv6: {"Connect", Net, "Connects establishes a connection to the socket"}, - DisconnectTCPv4: {"Disconnect", Net, "Terminates data reception on the socket"}, - DisconnectTCPv6: {"Disconnect", Net, "Terminates data reception on the socket"}, - ReconnectTCPv4: {"Reconnect", Net, "Reconnects to the socket"}, - ReconnectTCPv6: {"Reconnect", Net, "Reconnects to the socket"}, - RetransmitTCPv4: {"Retransmit", Net, "Retransmits unacknowledged TCP segments"}, - RetransmitTCPv6: {"Retransmit", Net, "Retransmits unacknowledged TCP segments"}, - LoadModule: {"LoadModule", Module, "Loads the module into the address space of the calling process"}, - UnloadModule: {"UnloadModule", Module, "Unloads the module from the address space of the calling process"}, - VirtualAlloc: {"VirtualAlloc", Mem, "Reserves, commits, or changes the state of a region of memory within the process virtual address space"}, - VirtualFree: {"VirtualFree", Mem, "Releases or decommits a region of memory within the process virtual address space"}, - MapViewFile: {"MapViewFile", File, "Maps a view of a file mapping into the address space of a calling process"}, - UnmapViewFile: {"UnmapViewFile", File, "Unmaps a mapped view of a file from the calling process's address space"}, - QueryDNS: {"QueryDns", Net, "Sends a DNS query to the name server"}, - ReplyDNS: {"ReplyDNS", Net, "Receives the response from the DNS server"}, - CreateSymbolicLinkObject: {"CreateSymbolicLinkObject", Object, "Creates the symbolic link within the object manager directory"}, -} +var table = [MaxEvent]Info{ + CreateProcess: {Name: "CreateProcess", Category: Process, Source: SystemLogger, Description: "Creates a new process and its primary thread", Flags: WaitStack}, + TerminateProcess: {Name: "TerminateProcess", Category: Process, Source: SystemLogger, Description: "Terminates the process and all of its threads"}, + OpenProcess: {Name: "OpenProcess", Category: Process, Source: SecurityTelemetryLogger, Description: "Opens the process handle"}, + ProcessRundown: {Name: "ProcessRundown", Category: Process, Source: SecurityTelemetryLogger, Description: "Builds the snapshot state of running processes in the system.", Flags: OnlyState | StateSnapshot}, + CreateProcessInternal: {Name: "CreateProcessInternal", Category: Process, Source: SystemLogger, Description: "Only purpose of this event is to enrich the process state with some extra attributes. Never published to the event stream", Flags: OnlyState}, + ProcessRundownInternal: {Name: "ProcessRundownInternal", Category: Process, Source: SecurityTelemetryLogger, Description: "Opens the process handle", Flags: OnlyState | StateSnapshot}, -var types = map[string]Type{ - "CreateProcess": CreateProcess, - "TerminateProcess": TerminateProcess, - "OpenProcess": OpenProcess, - "CreateThread": CreateThread, - "TerminateThread": TerminateThread, - "OpenThread": OpenThread, - "SetThreadContext": SetThreadContext, - "LoadModule": LoadModule, - "UnloadModule": UnloadModule, - "CreateFile": CreateFile, - "CloseFile": CloseFile, - "ReadFile": ReadFile, - "WriteFile": WriteFile, - "SetFileInformation": SetFileInformation, - "DeleteFile": DeleteFile, - "RenameFile": RenameFile, - "EnumDirectory": EnumDirectory, - "RegCreateKey": RegCreateKey, - "RegOpenKey": RegOpenKey, - "RegSetValue": RegSetValue, - "RegQueryValue": RegQueryValue, - "RegQueryKey": RegQueryKey, - "RegDeleteKey": RegDeleteKey, - "RegDeleteValue": RegDeleteValue, - "RegCloseKey": RegCloseKey, - "AcceptTCP4": AcceptTCPv4, - "AcceptTCP6": AcceptTCPv6, - "SendTCP4": SendTCPv4, - "SendTCP6": SendTCPv6, - "SendUDP4": SendUDPv4, - "SendUDP6": SendUDPv6, - "RecvTCP4": RecvTCPv4, - "RecvTCP6": RecvTCPv6, - "RecvUDP4": RecvUDPv4, - "RecvUDP6": RecvUDPv6, - "ConnectTCP4": ConnectTCPv4, - "ConnectTCP6": ConnectTCPv6, - "ReconnectTCP4": ReconnectTCPv4, - "ReconnectTCP6": ReconnectTCPv6, - "DisconnectTCP4": DisconnectTCPv4, - "DisconnectTCP6": DisconnectTCPv6, - "RetransmitTCP4": RetransmitTCPv4, - "RetransmitTCP6": RetransmitTCPv6, - "VirtualAlloc": VirtualAlloc, - "VirtualFree": VirtualFree, - "MapViewFile": MapViewFile, - "UnmapViewFile": UnmapViewFile, - "QueryDns": QueryDNS, - "ReplyDns": ReplyDNS, - "CreateSymbolicLinkObject": CreateSymbolicLinkObject, -} + CreateThread: {Name: "CreateThread", Category: Thread, Source: SystemLogger, Description: "Creates a thread to execute within the virtual address space of the calling process", Flags: WaitStack}, + TerminateThread: {Name: "TerminateThread", Category: Thread, Source: SystemLogger, Description: "Terminates a thread within the process", Flags: WaitStack}, + OpenThread: {Name: "OpenThread", Category: Thread, Source: SecurityTelemetryLogger, Description: "Opens the thread handle"}, + SetThreadContext: {Name: "SetThreadContext", Category: Thread, Source: SecurityTelemetryLogger, Description: "Sets the thread context"}, + StackWalk: {Name: "StackWalk", Category: Thread, Source: SystemLogger, Description: "Delivers call stack return addresses. Never published to the event stream", Flags: OnlyState}, + ThreadRundown: {Name: "ThreadRundown", Category: Thread, Source: SystemLogger, Description: "Builds the snapshot state of running threads in the system", Flags: OnlyState | StateSnapshot}, -// All returns all event types. -func All() []Type { - s := make([]Type, 0, len(types)) - for _, typ := range types { - s = append(s, typ) - } - return s -} + UnloadModule: {Name: "UnloadModule", Category: Module, Source: SystemLogger, Description: "Unloads the module from the address space of the calling process"}, + LoadModule: {Name: "LoadModule", Category: Module, Source: SystemLogger, Description: "Loads the module into the address space of the calling process", Flags: WaitStack}, + ModuleRundown: {Name: "ModuleRundown", Category: Module, Source: SystemLogger, Description: "Builds the snapshot of loaded modules in the system", Flags: OnlyState | StateSnapshot}, + LoadModuleInternal: {Name: "LoadModuleInternal", Category: Module, Source: SecurityTelemetryLogger, Description: "Only purpose is to populate the module state. Never published to the event stream", Flags: OnlyState}, -// AllWithState returns all event types + -// event types used for state management. -func AllWithState() []Type { - s := All() - - s = append(s, ProcessRundown) - s = append(s, ThreadRundown) - s = append(s, ModuleRundown) - s = append(s, FileRundown) - s = append(s, RegKCBRundown) - s = append(s, RegCreateKCB) - s = append(s, RegDeleteKCB) - s = append(s, FileOpEnd) - s = append(s, ReleaseFile) - s = append(s, MapFileRundown) - s = append(s, StackWalk) - s = append(s, CreateProcessInternal) - s = append(s, ProcessRundownInternal) - s = append(s, LoadModuleInternal) - s = append(s, RegSetValueInternal) - - return s -} + RegCreateKey: {Name: "RegCreateKey", Category: Registry, Source: SystemLogger, Description: "Creates a registry key or opens it if the key already exists", Flags: WaitStack}, + RegOpenKey: {Name: "RegOpenKey", Category: Registry, Source: SystemLogger, Description: "Opens the registry key"}, + RegDeleteKey: {Name: "RegDeleteKey", Category: Registry, Source: SystemLogger, Description: "Removes the registry key", Flags: WaitStack}, + RegQueryKey: {Name: "RegQueryKey", Category: Registry, Source: SystemLogger, Description: "Enumerates subkeys of the parent key"}, + RegSetValue: {Name: "RegSetValue", Category: Registry, Source: SystemLogger, Description: "Sets the data for the value of a registry key", Flags: WaitStack}, + RegSetValueInternal: {Name: "RegSetValueInternal", Category: Registry, Source: SecurityTelemetryLogger, Description: "Closes the registry key", Flags: OnlyState}, + RegDeleteValue: {Name: "RegDeleteValue", Category: Registry, Source: SystemLogger, Description: "Removes the registry value", Flags: WaitStack}, + RegQueryValue: {Name: "RegQueryValue", Category: Registry, Source: SystemLogger, Description: "Reads the data for the value of a registry key"}, + RegCloseKey: {Name: "RegCloseKey", Category: Registry, Source: SystemLogger, Description: "Closes the registry key. Never published to the event stream", Flags: OnlyState}, + RegCreateKCB: {Name: "RegCreateKCB", Category: Registry, Source: SystemLogger, Description: "Create the Key Control Block. Never published to the event stream", Flags: OnlyState}, + RegDeleteKCB: {Name: "RegDeleteKCB", Category: Registry, Source: SystemLogger, Description: "Removes the Key Control Block. Never published to the event stream", Flags: OnlyState}, + RegKCBRundown: {Name: "RegKCBRundown", Category: Registry, Source: SystemLogger, Description: "Builds the snapshot of existing Key Control Block objects", Flags: OnlyState | StateSnapshot}, -// MaxTypeID returns the maximum event type (hook id) value. -func MaxTypeID() uint16 { - types := AllWithState() - ids := make([]uint16, len(types)) - for i, t := range types { - ids[i] = t.HookID() - } - return slices.Max(ids) -} + CreateFile: {Name: "CreateFile", Category: File, Source: SystemLogger, Description: "Creates or opens a new file, directory, I/O device, pipe, console"}, + ReleaseFile: {Name: "ReleaseFile", Category: File, Source: SystemLogger, Description: "Closes the last handle to the file object. Never published to the event stream", Flags: OnlyState}, + CloseFile: {Name: "CloseFile", Category: File, Source: SystemLogger, Description: "Closes the file handle. Never published to the event stream", Flags: OnlyState}, + ReadFile: {Name: "ReadFile", Category: File, Source: SystemLogger, Description: "Reads data from the file or I/O device"}, + WriteFile: {Name: "WriteFile", Category: File, Source: SystemLogger, Description: "Writes data to the file or I/O device"}, + SetFileInformation: {Name: "SetFileInformation", Category: File, Source: SystemLogger, Description: "Sets the file meta information"}, + DeleteFile: {Name: "DeleteFile", Category: File, Source: SystemLogger, Description: "Removes the file from the file system", Flags: WaitStack}, + RenameFile: {Name: "RenameFile", Category: File, Source: SystemLogger, Description: "Changes the file name", Flags: WaitStack}, + EnumDirectory: {Name: "EnumDirectory", Category: File, Source: SystemLogger, Description: "Enumerates a directory or dispatches a directory change notification to registered listeners"}, + FileRundown: {Name: "FileRundown", Category: File, Source: SystemLogger, Description: "Builds the snapshot of existing file objects", Flags: OnlyState | StateSnapshot}, + FileOpEnd: {Name: "FileOpEnd", Category: File, Source: SystemLogger, Description: "Reports the I/O request packet status. Never published to the event stream", Flags: OnlyState}, -// TypeToEventInfo maps the event type to the structure storing detailed information about the event. -func TypeToEventInfo(typ Type) Info { - if info, ok := events[typ]; ok { - return info - } - return Info{Name: "N/A", Category: Unknown} + Accept: {Name: "Accept", Category: Network, Source: SystemLogger, Description: "Accepts the connection request from the socket queue"}, + Send: {Name: "Send", Category: Network, Source: SystemLogger, Description: "Sends data over the wire"}, + Recv: {Name: "Recv", Category: Network, Source: SystemLogger, Description: "Receives data from the socket"}, + Connect: {Name: "Connect", Category: Network, Source: SystemLogger, Description: "Connects establishes a connection to the socket"}, + Disconnect: {Name: "Disconnect", Category: Network, Source: SystemLogger, Description: "Terminates data reception on the socket"}, + Reconnect: {Name: "Reconnect", Category: Network, Source: SystemLogger, Description: "Reconnects to the socket"}, + Retransmit: {Name: "Retransmit", Category: Network, Source: SystemLogger, Description: "Retransmits unacknowledged TCP segments"}, + QueryDNS: {Name: "QueryDns", Category: Network, Subcategory: DNS, Source: SecurityTelemetryLogger, Description: "Sends a DNS query to the name server"}, + ReplyDNS: {Name: "ReplyDNS", Category: Network, Subcategory: DNS, Source: SecurityTelemetryLogger, Description: "Receives the response from the DNS server"}, + + MapViewOfSection: {Name: "MapViewOfSection", Category: Memory, Source: SystemLogger, Description: "Maps a view of a file mapping into the address space of a calling process"}, + UnmapViewOfSection: {Name: "UnmapViewOfSection", Category: Memory, Source: SystemLogger, Description: "Unmaps a mapped view of a file from the calling process's address space"}, + MapViewSectionRundown: {Name: "MapViewSectionRundown", Category: Memory, Source: SystemLogger, Description: "Builds the snapshot of existing memory section views", Flags: OnlyState | StateSnapshot}, + VirtualAlloc: {Name: "VirtualAlloc", Category: Memory, Source: SystemLogger, Description: "Reserves, commits, or changes the state of a region of memory within the process virtual address space", Flags: WaitStack}, + VirtualFree: {Name: "VirtualFree", Category: Memory, Source: SystemLogger, Description: "Releases or decommits a region of memory within the process virtual address space"}, + + CreateSymbolicLinkObject: {Name: "CreateSymbolicLinkObject", Category: Object, Source: SecurityTelemetryLogger, Description: "Creates the symbolic link within the object manager directory"}, } -// NameToType converts a human-readable event name to its internal type representation. -func NameToType(name string) Type { - if typ, ok := types[name]; ok { - return typ +// All returns all event types. +func AllTypes() []Type { + types := make([]Type, 0) + for i := range table { + if Type(i) == Unknown { + continue + } + types = append(types, Type(i)) } - return UnknownType + return types } -// NameToTypes maps the event name to internal type representations, specifically, network -// events that have multiple internal types for a single event name. For example, the Accept -// event name has AcceptTCP4 and AcceptTCP6 types. -func NameToTypes(name string) []Type { - switch name { - case "Accept": - return []Type{AcceptTCPv4, AcceptTCPv6} - case "Send": - return []Type{SendTCPv4, SendTCPv6, SendUDPv4, SendUDPv6} - case "Recv": - return []Type{RecvTCPv4, RecvTCPv6, RecvUDPv4, RecvUDPv6} - case "Connect": - return []Type{ConnectTCPv4, ConnectTCPv6} - case "Reconnect": - return []Type{ReconnectTCPv4, ReconnectTCPv6} - case "Disconnect": - return []Type{DisconnectTCPv4, DisconnectTCPv6} - case "Retransmit": - return []Type{RetransmitTCPv4, RetransmitTCPv6} - default: - return []Type{NameToType(name)} - } +// GetTypeInfo returns metadata about the specified event type. +func GetTypeInfo(typ Type) Info { + return table[typ] } -// GetTypesMeta returns event types metadata. -func GetTypesMeta() []Info { - typs := make([]Info, 0) -outer: - for _, ev := range events { - for _, typ := range typs { - if typ.Name == ev.Name { - continue outer - } - } - typs = append(typs, ev) - } - slices.SortFunc(typs, func(a, b Info) int { +// GetTypesInfo returns event types metadata excluding only-state events. +func GetTypesInfo() []Info { + t := table[:] + t = slices.DeleteFunc(t, func(info Info) bool { + return info.Flags&OnlyState != 0 || info.Name == "" + }) + slices.SortFunc(t, func(a, b Info) int { return cmp.Or(cmp.Compare(a.Category, b.Category), cmp.Compare(a.Name, b.Name)) }) - return typs + return t } -// IsKnown indicates if the event type is known given the event name. -func IsKnown(name string) bool { - for _, evt := range GetTypesMeta() { - if evt.Name == name { - return true - } +// NameToType converts a human-readable event name to its internal type representation. +func ParseType(s string) (Type, bool) { + i := slices.IndexFunc(table[:], func(info Info) bool { + return info.Name == s + }) + if i == -1 { + return Unknown, false } - return false + return Type(i), true +} + +// IsKnown indicates if the event type is known given the event name. +func IsTypeKnown(s string) (exists bool) { + _, exists = ParseType(s) + return } diff --git a/pkg/event/metainfo_windows_test.go b/pkg/event/metainfo_windows_test.go index e23be0d5c..3c5096555 100644 --- a/pkg/event/metainfo_windows_test.go +++ b/pkg/event/metainfo_windows_test.go @@ -19,28 +19,33 @@ package event import ( - "github.com/stretchr/testify/assert" "testing" -) - -func TestEventNameToType(t *testing.T) { - typ := NameToType("CreateProcess") - assert.Equal(t, CreateProcess, typ) + "github.com/stretchr/testify/assert" +) - typ = NameToType("CreateRemoteThread") - assert.Equal(t, UnknownType, typ) +func TestParseType(t *testing.T) { + var tests = []struct { + name string + expectedType Type + }{ + {"CreateProcess", CreateProcess}, + {"CreateRemoteThread", Unknown}, + {"FileOpEnd", FileOpEnd}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + etype, _ := ParseType(tt.name) + assert.Equal(t, tt.expectedType, etype) + }) + } } func TestEventToEventInfo(t *testing.T) { - info := TypeToEventInfo(CreateProcess) + info := GetTypeInfo(CreateProcess) assert.Equal(t, "CreateProcess", info.Name) assert.Equal(t, Process, info.Category) assert.Equal(t, "Creates a new process and its primary thread", info.Description) - - info = TypeToEventInfo(UnknownType) - assert.Equal(t, "N/A", info.Name) - assert.Equal(t, Unknown, info.Category) - assert.Empty(t, info.Description) } diff --git a/pkg/event/param_decoder_windows.go b/pkg/event/param_decoder_windows.go index 813247a70..6f55d224a 100644 --- a/pkg/event/param_decoder_windows.go +++ b/pkg/event/param_decoder_windows.go @@ -25,6 +25,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/fs" + "github.com/rabbitstack/fibratus/pkg/network" "github.com/rabbitstack/fibratus/pkg/sys/etw" "github.com/rabbitstack/fibratus/pkg/util/filetime" "github.com/rabbitstack/fibratus/pkg/util/key" @@ -149,8 +150,8 @@ func (d *ParamDecoder) DecodeRegSetValueInternal(r *etw.EventRecord, e *Event) { // DecodeFile decodes file I/O operations such as file creation, access, // or file metadata manipulation. func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { - switch r.Header.EventDescriptor.Opcode { - case CreateFileID: + switch e.Type { + case CreateFile: // typedef struct _PERFINFO_FILE_CREATE { // LONG_PTR Irp; // ULONG_PTR FileObject; @@ -173,7 +174,7 @@ func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { e.AppendParam(params.NTStatus, params.Status, status) e.AppendEnum(params.FileOperation, disposition, fs.FileCreateDispositions) e.AppendParam(params.Callstack, params.Slice, r.ReadEventHeaderFileExtendedDataItemsCallstack()) - case FileOpEndID: + case FileOpEnd: // typedef struct _PERFINFO_FILE_OPERATION_END { // ULONG_PTR Irp; // ULONG_PTR ExtraInformation; @@ -182,7 +183,7 @@ func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { e.AppendParam(params.FileIrpPtr, params.Address, r.ReadUint64(0)) e.AppendParam(params.FileExtraInfo, params.Address, r.ReadUint64(8)) e.AppendParam(params.NTStatus, params.Status, r.ReadUint32(16)) - case MapViewFileID, UnmapViewFileID, MapFileRundownID: + case MapViewOfSection, UnmapViewOfSection, MapViewSectionRundown: e.AppendParam(params.FileViewBase, params.Address, r.ReadUint64(0)) e.AppendParam(params.FileKey, params.Address, r.ReadUint64(8)) e.AppendParam(params.MemProtect, params.Flags, uint32(r.ReadUint64(16)>>32), WithFlags(ViewProtectionFlags)) @@ -190,7 +191,7 @@ func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { e.AppendParam(params.FileViewSize, params.Uint64, r.ReadUint64(24)) e.AppendParam(params.FileOffset, params.Uint64, r.ReadUint64(32)) e.AppendParam(params.ProcessID, params.PID, r.ReadUint32(40)) - case SetFileInformationID, DeleteFileID, RenameFileID: + case SetFileInformation, DeleteFile, RenameFile: // DeleteFile, RenameFile, and SetFileInformation share the same layout // typedef struct _PERFINFO_FILE_INFORMATION { // ULONG_PTR Irp; @@ -206,7 +207,7 @@ func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { e.AppendParam(params.FileExtraInfo, params.Uint64, r.ReadUint64(24)) e.AppendParam(params.ThreadID, params.TID, r.ReadUint32(32)) e.AppendParam(params.FileInfoClass, params.Enum, r.ReadUint32(36), WithEnum(fs.FileInfoClasses)) - case ReleaseFileID, CloseFileID: + case ReleaseFile, CloseFile: // typedef struct _PERFINFO_FILE_SIMPLE_OPERATION { // ULONG_PTR Irp; // ULONG_PTR FileObject; @@ -217,7 +218,7 @@ func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { e.AppendParam(params.FileObject, params.Address, r.ReadUint64(8)) e.AppendParam(params.FileKey, params.Address, r.ReadUint64(16)) e.AppendParam(params.ThreadID, params.TID, r.ReadUint32(24)) - case ReadFileID, WriteFileID: + case ReadFile, WriteFile: // typedef struct _PERFINFO_FILE_READ_WRITE { // ULONGLONG Offset; // ULONG_PTR Irp; @@ -234,7 +235,7 @@ func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { e.AppendParam(params.FileKey, params.Address, r.ReadUint64(24)) e.AppendParam(params.ThreadID, params.TID, r.ReadUint32(32)) e.AppendParam(params.FileIoSize, params.Uint32, r.ReadUint32(34)) - case EnumDirectoryID: + case EnumDirectory: // typedef struct _PERFINFO_FILE_DIRENUM { // ULONG_PTR Irp; // ULONG_PTR FileObject; @@ -253,7 +254,7 @@ func (d *ParamDecoder) DecodeFile(r *etw.EventRecord, e *Event) { e.AppendParam(params.FileInfoClass, params.Enum, r.ReadUint32(32), WithEnum(fs.FileInfoClasses)) // skip FileIndex (uint32) e.AppendParam(params.FilePath, params.UnicodeString, r.ConsumeUTF16String(40)) - case FileRundownID: + case FileRundown: e.AppendParam(params.FileObject, params.Address, r.ReadUint64(0)) e.AppendParam(params.FilePath, params.DOSPath, r.ConsumeUTF16String(8)) } @@ -527,6 +528,12 @@ func (d *ParamDecoder) DecodeNetwork(r *etw.EventRecord, e *Event) { e.AppendParam(params.NetDport, params.Port, r.ReadUint16(16)) e.AppendParam(params.NetSport, params.Port, r.ReadUint16(18)) } + + if r.Header.ProviderID == NetworkTCPEventGUID { + e.AppendEnum(params.NetL4Proto, uint32(network.TCP), network.ProtoNames) + } else { + e.AppendEnum(params.NetL4Proto, uint32(network.UDP), network.ProtoNames) + } } // DecodeDNS decodes DNS query/reply event payloads. diff --git a/pkg/event/param_decoder_windows_test.go b/pkg/event/param_decoder_windows_test.go index 014c6023b..46110e69c 100644 --- a/pkg/event/param_decoder_windows_test.go +++ b/pkg/event/param_decoder_windows_test.go @@ -26,6 +26,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/sys/etw" "github.com/rabbitstack/fibratus/pkg/util/va" "github.com/stretchr/testify/assert" + "golang.org/x/sys/windows" ) func TestDecodeRegistry(t *testing.T) { @@ -141,6 +142,7 @@ func TestDecodeFile(t *testing.T) { var tests = []struct { name string opcode uint8 + event *Event buf []byte assertions func(t *testing.T, e *Event) }{ @@ -158,6 +160,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, "Success", e.GetParamAsString(params.NTStatus)) assert.Contains(t, e.Params, params.Callstack) }, + event: &Event{Params: make(Params), Type: CreateFile}, buf: []byte{ 200, 7, 94, 150, 141, 215, 255, 255, 80, 102, 11, 146, 141, 215, 255, 255, @@ -196,6 +199,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint32(0), e.Params.MustGetUint32(params.NTStatus)) assert.Equal(t, uint64(0x28), e.Params.MustGetUint64(params.FileExtraInfo)) }, + event: &Event{Params: make(Params), Type: FileOpEnd}, buf: []byte{ 248, 240, 61, 151, 141, 215, 255, 255, 40, 0, 0, 0, @@ -204,7 +208,7 @@ func TestDecodeFile(t *testing.T) { }, }, { - name: "MapViewFile", opcode: MapViewFileID, + name: "MapViewOfSection", opcode: MapViewOfSectionID, assertions: func(t *testing.T, e *Event) { assert.Len(t, e.Params, 7) assert.Equal(t, uint64(0xffffb58b75fb7e10), e.Params.MustGetUint64(params.FileKey)) @@ -215,6 +219,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint64(0x191ab210000), e.Params.MustGetUint64(params.FileViewBase)) assert.Equal(t, uint64(4096), e.Params.MustGetUint64(params.FileViewSize)) }, + event: &Event{Params: make(Params), Type: MapViewOfSection}, buf: []byte{ 0, 0, 33, 171, 145, 1, 0, 0, 16, 126, 251, 117, 139, 181, 255, 255, @@ -225,7 +230,7 @@ func TestDecodeFile(t *testing.T) { }, }, { - name: "UnmapViewFile", opcode: UnmapViewFileID, + name: "UnmapViewOfSection", opcode: UnmapViewOfSectionID, assertions: func(t *testing.T, e *Event) { assert.Len(t, e.Params, 7) assert.Equal(t, uint64(0xffffb58bc1f91010), e.Params.MustGetUint64(params.FileKey)) @@ -236,6 +241,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint64(0x1675e410000), e.Params.MustGetUint64(params.FileViewBase)) assert.Equal(t, uint64(921600), e.Params.MustGetUint64(params.FileViewSize)) }, + event: &Event{Params: make(Params), Type: UnmapViewOfSection}, buf: []byte{ 0, 0, 65, 94, 103, 1, 0, 0, 16, 16, 249, 193, 139, 181, 255, 255, @@ -256,6 +262,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint64(0xffffd78d9b6470f8), e.Params.MustGetUint64(params.FileIrpPtr)) assert.Equal(t, uint32(16404), e.Params.MustGetTid()) }, + event: &Event{Params: make(Params), Type: SetFileInformation}, buf: []byte{ 248, 112, 100, 155, 141, 215, 255, 255, 128, 55, 64, 118, 141, 215, 255, 255, @@ -276,6 +283,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint64(0xffffd78d7c5860f8), e.Params.MustGetUint64(params.FileIrpPtr)) assert.Equal(t, uint32(13656), e.Params.MustGetTid()) }, + event: &Event{Params: make(Params), Type: DeleteFile}, buf: []byte{ 248, 96, 88, 124, 141, 215, 255, 255, 128, 125, 108, 155, 141, 215, 255, 255, @@ -294,6 +302,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint64(0xffffd78d7ca0b0f8), e.Params.MustGetUint64(params.FileIrpPtr)) assert.Equal(t, uint32(3096), e.Params.MustGetTid()) }, + event: &Event{Params: make(Params), Type: ReleaseFile}, buf: []byte{ 248, 176, 160, 124, 141, 215, 255, 255, 176, 82, 69, 155, 141, 215, 255, 255, @@ -312,6 +321,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint64(573440), e.Params.MustGetUint64(params.FileOffset)) assert.Equal(t, uint32(1073741824), e.Params.MustGetUint32(params.FileIoSize)) }, + event: &Event{Params: make(Params), Type: WriteFile}, buf: []byte{ 0, 192, 8, 0, 0, 0, 0, 0, 8, 106, 120, 154, 141, 215, 255, 255, @@ -332,6 +342,7 @@ func TestDecodeFile(t *testing.T) { assert.Equal(t, uint64(0xffff8084da3e7788), e.Params.MustGetUint64(params.FileIrpPtr)) assert.Equal(t, uint32(12860), e.Params.MustGetTid()) }, + event: &Event{Params: make(Params), Type: EnumDirectory}, buf: []byte{ 136, 119, 62, 218, 132, 128, 255, 255, 144, 201, 67, 203, 132, 128, 255, 255, @@ -347,6 +358,7 @@ func TestDecodeFile(t *testing.T) { assert.Len(t, e.Params, 2) assert.Equal(t, `\Device\HarddiskVolume3\Windows\System32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\Microsoft-Windows-TerminalServices-AppServerClient-Opt-WOW64-Package~31bf3856ad364e35~wow64~~10.0.26100.8115.cat`, e.Params.MustGetString(params.FilePath)) }, + event: &Event{Params: make(Params), Type: FileRundown}, buf: []byte{ 80, 71, 18, 158, 139, 181, 255, 255, 92, 0, 68, 0, 101, 0, 118, 0, @@ -407,9 +419,8 @@ func TestDecodeFile(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := initEventRecord(tt.opcode, 0, tt.buf) - e := &Event{Params: make(Params)} - paramDecoder.DecodeFile(r, e) - tt.assertions(t, e) + paramDecoder.DecodeFile(r, tt.event) + tt.assertions(t, tt.event) }) } } @@ -830,18 +841,20 @@ func TestDecodeNetwork(t *testing.T) { name string opcode uint8 buf []byte + providerID windows.GUID assertions func(t *testing.T, e *Event) }{ { name: "SendTCPv4", opcode: SendV4ID, assertions: func(t *testing.T, e *Event) { - assert.Len(t, e.Params, 6) + assert.Len(t, e.Params, 7) assert.Equal(t, "172.64.148.235", e.GetParamAsString(params.NetDIP)) assert.Equal(t, uint16(443), e.Params.MustGetUint16(params.NetDport)) assert.Equal(t, "192.168.1.44", e.GetParamAsString(params.NetSIP)) assert.Equal(t, uint16(61552), e.Params.MustGetUint16(params.NetSport)) assert.Equal(t, uint32(12448), e.Params.MustGetPid()) assert.Equal(t, uint32(28), e.Params.MustGetUint32(params.NetSize)) + assert.Equal(t, "TCP", e.GetParamAsString(params.NetL4Proto)) }, buf: []byte{ 160, 48, 0, 0, @@ -854,17 +867,19 @@ func TestDecodeNetwork(t *testing.T) { 0, 0, 0, 0, 0, 0, 0, 0, }, + providerID: NetworkTCPEventGUID, }, { name: "ConnectTCPv4", opcode: ConnectTCPv4ID, assertions: func(t *testing.T, e *Event) { - assert.Len(t, e.Params, 6) + assert.Len(t, e.Params, 7) assert.Equal(t, "151.101.193.91", e.GetParamAsString(params.NetDIP)) assert.Equal(t, uint16(443), e.Params.MustGetUint16(params.NetDport)) assert.Equal(t, "192.168.1.44", e.GetParamAsString(params.NetSIP)) assert.Equal(t, uint16(61931), e.Params.MustGetUint16(params.NetSport)) assert.Equal(t, uint32(12448), e.Params.MustGetPid()) assert.Equal(t, uint32(0), e.Params.MustGetUint32(params.NetSize)) + assert.Equal(t, "TCP", e.GetParamAsString(params.NetL4Proto)) }, buf: []byte{ 160, 48, 0, 0, @@ -878,17 +893,19 @@ func TestDecodeNetwork(t *testing.T) { 0, 0, 0, 0, 0, 0, 0, 0, }, + providerID: NetworkTCPEventGUID, }, { name: "RecvUDPv6", opcode: RecvV6ID, assertions: func(t *testing.T, e *Event) { - assert.Len(t, e.Params, 6) + assert.Len(t, e.Params, 7) assert.Equal(t, "ff02::c", e.GetParamAsString(params.NetDIP)) assert.Equal(t, uint16(1900), e.Params.MustGetUint16(params.NetDport)) assert.Equal(t, "fe80::1", e.GetParamAsString(params.NetSIP)) assert.Equal(t, uint16(56797), e.Params.MustGetUint16(params.NetSport)) assert.Equal(t, uint32(5128), e.Params.MustGetPid()) assert.Equal(t, uint32(127), e.Params.MustGetUint32(params.NetSize)) + assert.Equal(t, "UDP", e.GetParamAsString(params.NetL4Proto)) }, buf: []byte{ 8, 20, 0, 0, @@ -904,12 +921,13 @@ func TestDecodeNetwork(t *testing.T) { 221, 221, 0, 0, 0, 0, 0, 0, 0, 0, }, + providerID: NetworkUDPEventGUID, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - r := initEventRecord(tt.opcode, 0, tt.buf) + r := initEventRecord(tt.opcode, 0, tt.buf, withProviderID(tt.providerID)) e := &Event{Params: make(Params)} paramDecoder.DecodeNetwork(r, e) tt.assertions(t, e) @@ -960,10 +978,28 @@ func TestDecodeDNS(t *testing.T) { assert.Equal(t, "AAAA", e.GetParamAsString(params.DNSRR)) } -func initEventRecord(opcode uint8, id uint16, buf []byte) *etw.EventRecord { +type option func(*options) + +type options struct { + providerID windows.GUID +} + +func withProviderID(id windows.GUID) option { + return func(o *options) { + o.providerID = id + } +} + +func initEventRecord(opcode uint8, id uint16, buf []byte, opts ...option) *etw.EventRecord { + var options options + for _, opt := range opts { + opt(&options) + } + return &etw.EventRecord{ Header: etw.EventHeader{ - ProcessID: 13440, + ProcessID: 13440, + ProviderID: options.providerID, EventDescriptor: etw.EventDescriptor{ Opcode: opcode, ID: id, diff --git a/pkg/event/param_windows.go b/pkg/event/param_windows.go index 691505629..7d718eb0e 100644 --- a/pkg/event/param_windows.go +++ b/pkg/event/param_windows.go @@ -213,14 +213,14 @@ var paramDecoder = &ParamDecoder{} // version number which helps us determine when the event // schema changes in order to parse new fields. func (e *Event) decodeParams(r *etw.EventRecord) { - switch r.Header.ProviderID { - case RegistryEventGUID: + switch r.Header.ProviderID.Data1 { + case RegistryEventGUID.Data1: paramDecoder.DecodeRegistry(r, e) - case FileEventGUID: + case FileEventGUID.Data1: paramDecoder.DecodeFile(r, e) - case StackWalkEventGUID: + case StackWalkEventGUID.Data1: paramDecoder.DecodeStackwalk(r, e) - case AuditAPIEventGUID: + case AuditAPIEventGUID.Data1: switch r.Header.EventDescriptor.ID { case OpenProcessID: paramDecoder.DecodeOpenProcess(r, e) @@ -231,21 +231,21 @@ func (e *Event) decodeParams(r *etw.EventRecord) { case CreateSymbolicLinkObjectID: paramDecoder.DecodeCreateSymbolicLinkObject(r, e) } - case MemEventGUID: + case MemoryEventGUID.Data1: paramDecoder.DecodeMemory(r, e) - case NetworkTCPEventGUID, NetworkUDPEventGUID: + case NetworkTCPEventGUID.Data1, NetworkUDPEventGUID.Data1: paramDecoder.DecodeNetwork(r, e) - case DNSEventGUID: + case DNSEventGUID.Data1: paramDecoder.DecodeDNS(r, e) - case ProcessEventGUID: + case ProcessEventGUID.Data1: paramDecoder.DecodeProcess(r, e) - case ModuleEventGUID: + case ModuleEventGUID.Data1: paramDecoder.DecodeModule(r, e) - case ThreadEventGUID: + case ThreadEventGUID.Data1: paramDecoder.DecodeThread(r, e) - case RegistryKernelEventGUID: + case RegistryKernelEventGUID.Data1: paramDecoder.DecodeRegSetValueInternal(r, e) - case ProcessKernelEventGUID: + case ProcessKernelEventGUID.Data1: switch r.Header.EventDescriptor.ID { case CreateProcessInternalID, ProcessRundownInternalID: paramDecoder.DecodeProcessInternal(r, e) diff --git a/pkg/event/queue.go b/pkg/event/queue.go index a580e253d..f76460ca1 100644 --- a/pkg/event/queue.go +++ b/pkg/event/queue.go @@ -109,7 +109,7 @@ func (q *Queue) Close() { q.decorator.Stop() } func (q *Queue) Push(e *Event) error { if q.stackEnrichment { // store pending event for callstack enrichment - if e.Type.CanEnrichStack() { + if e.Type.WaitStack() { q.decorator.Push(e) return nil } diff --git a/pkg/event/queue_test.go b/pkg/event/queue_test.go index cd3fed7a0..c1ab2345b 100644 --- a/pkg/event/queue_test.go +++ b/pkg/event/queue_test.go @@ -70,9 +70,7 @@ func TestQueuePush(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: File, Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -97,9 +95,7 @@ func TestQueuePush(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: File, Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -124,9 +120,7 @@ func TestQueuePush(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: File, Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -152,9 +146,7 @@ func TestQueuePush(t *testing.T) { PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", Timestamp: time.Now(), - Category: File, Params: Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, diff --git a/pkg/event/stackwalk.go b/pkg/event/stackwalk.go index 752ae39ad..c6a7fb7ae 100644 --- a/pkg/event/stackwalk.go +++ b/pkg/event/stackwalk.go @@ -221,7 +221,7 @@ func (s *StackwalkDecorator) flush() []error { if evt.PS != nil { stackwalkFlushesProcs.Add(evt.PS.Name, 1) } - stackwalkFlushesEvents.Add(evt.Name, 1) + stackwalkFlushesEvents.Add(evt.Name(), 1) } return errs diff --git a/pkg/event/types_windows.go b/pkg/event/types_windows.go index c80687f01..beb4e98da 100644 --- a/pkg/event/types_windows.go +++ b/pkg/event/types_windows.go @@ -19,11 +19,8 @@ package event import ( - "encoding/binary" - "github.com/rabbitstack/fibratus/pkg/sys/etw" "github.com/rabbitstack/fibratus/pkg/util/colorizer" - "github.com/rabbitstack/fibratus/pkg/util/hashers" "golang.org/x/sys/windows" ) @@ -34,13 +31,13 @@ const ( // SystemLogger event is emitted by the system provider. SystemLogger Source = iota // SecurityTelemetryLogger event is emitted by the combination of multiple providers. - // Most notably, DNS, thread pool, and kernel audit API providers are in charge of - // publishing the events. + // Most notably, DNS, and kernel audit API providers are in charge of publishing the + // events. SecurityTelemetryLogger ) -// Type identifies an event type. It comprises the event GUID + hook ID to uniquely identify the event -type Type [18]byte +// Type identifies an event type. +type Type uint16 var ( // ProcessEventGUID represents process provider event GUID @@ -57,8 +54,8 @@ var ( NetworkTCPEventGUID = windows.GUID{Data1: 0x9a280ac0, Data2: 0xc8e0, Data3: 0x11d1, Data4: [8]byte{0x84, 0xe2, 0x0, 0xc0, 0x4f, 0xb9, 0x98, 0xa2}} // NetworkUDPEventGUID represents network UDP provider event GUID NetworkUDPEventGUID = windows.GUID{Data1: 0xbf3a50c5, Data2: 0xa9c9, Data3: 0x4988, Data4: [8]byte{0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80}} - // MemEventGUID represents memory provider event GUID - MemEventGUID = windows.GUID{Data1: 0x3d6fa8d3, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}} + // MemoryEventGUID represents memory provider event GUID + MemoryEventGUID = windows.GUID{Data1: 0x3d6fa8d3, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}} // AuditAPIEventGUID represents audit API calls event GUID AuditAPIEventGUID = windows.GUID{Data1: 0xe02a841c, Data2: 0x75a3, Data3: 0x4fa7, Data4: [8]byte{0xaf, 0xc8, 0xae, 0x09, 0xcf, 0x9b, 0x7f, 0x23}} // DNSEventGUID represents DNS provider event GUID @@ -71,6 +68,65 @@ var ( StackWalkEventGUID = windows.GUID{Data1: 0xdef2fe46, Data2: 0x7bd6, Data3: 0x4b80, Data4: [8]byte{0xbd, 0x94, 0xf5, 0x7f, 0xe2, 0x0d, 0x0c, 0xe3}} ) +const ( + Unknown Type = iota + CreateProcess + TerminateProcess + ProcessRundown + OpenProcess + CreateProcessInternal // only purpose of this event is to enrich the process state with some extra attributes + ProcessRundownInternal // populates the snapshotter for events running in the Security Telemetry session + CreateThread + TerminateThread + ThreadRundown + OpenThread + SetThreadContext + StackWalk + UnloadModule + LoadModule + ModuleRundown + LoadModuleInternal // only purpose is to populate the module state for events running in the Security Telemetry session + RegCreateKey + RegOpenKey + RegDeleteKey + RegQueryKey + RegSetValue + RegSetValueInternal // internal event that is used to enrich the corresponding public RegSetValue event with captured data + RegDeleteValue + RegQueryValue + RegCloseKey + RegCreateKCB + RegDeleteKCB + RegKCBRundown + CreateFile + ReleaseFile + CloseFile + ReadFile + WriteFile + SetFileInformation + DeleteFile + RenameFile + EnumDirectory + FileRundown + FileOpEnd + Accept + Send + Recv + Connect + Disconnect + Reconnect + Retransmit + QueryDNS + ReplyDNS + MapViewOfSection + UnmapViewOfSection + MapViewSectionRundown + VirtualAlloc + VirtualFree + CreateSymbolicLinkObject + MaxEvent // sentinel +) + const ( CreateProcessID uint8 = 1 CreateProcessInternalID uint16 = 1 @@ -90,20 +146,20 @@ const ( LoadModuleInternalID uint16 = 5 LoadModuleID uint8 = 10 - FileRundownID uint8 = 36 - MapViewFileID uint8 = 37 - UnmapViewFileID uint8 = 38 - MapFileRundownID uint8 = 39 - CreateFileID uint8 = 64 - ReleaseFileID uint8 = 65 - CloseFileID uint8 = 66 - ReadFileID uint8 = 67 - WriteFileID uint8 = 68 - SetFileInformationID uint8 = 69 - DeleteFileID uint8 = 70 - RenameFileID uint8 = 71 - EnumDirectoryID uint8 = 72 - FileOpEndID uint8 = 76 + FileRundownID uint8 = 36 + MapViewOfSectionID uint8 = 37 + UnmapViewOfSectionID uint8 = 38 + MapViewSectionRundownID uint8 = 39 + CreateFileID uint8 = 64 + ReleaseFileID uint8 = 65 + CloseFileID uint8 = 66 + ReadFileID uint8 = 67 + WriteFileID uint8 = 68 + SetFileInformationID uint8 = 69 + DeleteFileID uint8 = 70 + RenameFileID uint8 = 71 + EnumDirectoryID uint8 = 72 + FileOpEndID uint8 = 76 RegCreateKeyID uint8 = 10 RegOpenKeyID uint8 = 11 @@ -144,530 +200,214 @@ const ( StackWalkID uint8 = 32 ) -var ( - // CreateProcess identifies process creation kernel events - CreateProcess = pack(ProcessEventGUID, uint16(CreateProcessID)) - // TerminateProcess identifies process termination kernel events - TerminateProcess = pack(ProcessEventGUID, uint16(TerminateProcessID)) - // ProcessRundown represents the start data collection process event that enumerates processes that are currently running at the time the kernel session starts - ProcessRundown = pack(ProcessEventGUID, uint16(ProcessRundownID)) - // OpenProcess identifies the kernel events that are triggered when the process handle is acquired - OpenProcess = pack(AuditAPIEventGUID, OpenProcessID) - // CreateProcessInternal identifies the process creation event emitted by the Microsoft Windows Kernel Process provider. - // The only purpose of this event is to enrich the process state with some extra attributes, and populates the snapshotter - // for events running in the Security Telemetry session that might miss process lookups because the core NT Kernel Provider - // hasn't still published the CreateProcess or ProcessRundown event - CreateProcessInternal = pack(ProcessKernelEventGUID, CreateProcessInternalID) - // ProcessRundownInternal same as above but for process rundown events originating from the Microsoft Windows Kernel Process provider. - ProcessRundownInternal = pack(ProcessKernelEventGUID, ProcessRundownInternalID) - - // CreateThread identifies thread creation kernel events - CreateThread = pack(ThreadEventGUID, uint16(CreateThreadID)) - // TerminateThread identifies thread termination kernel events - TerminateThread = pack(ThreadEventGUID, uint16(TerminateThreadID)) - // ThreadRundown represents the start data collection thread event that enumerates threads that are currently running at the time the kernel session starts - ThreadRundown = pack(ThreadEventGUID, uint16(ThreadRundownID)) - // OpenThread identifies the kernel events that are triggered when the process acquires a thread handle - OpenThread = pack(AuditAPIEventGUID, OpenThreadID) - // SetThreadContext identifies the kernel event that is fired when the thread context is changed - SetThreadContext = pack(AuditAPIEventGUID, SetThreadContextID) - - // MapViewFile represents events that map a view of a file mapping into the address space of a calling process - MapViewFile = pack(FileEventGUID, uint16(MapViewFileID)) - // UnmapViewFile represents events that unmap a view of a file mapping from the address space of a calling process - UnmapViewFile = pack(FileEventGUID, uint16(UnmapViewFileID)) - // MapFileRundown represents the event that is emitted at the start of the tracing session to enumerate I/O mapped files - MapFileRundown = pack(FileEventGUID, uint16(MapFileRundownID)) - - // FileRundown events are generated by kernel rundown logger to enumerate all open files on the start of the kernel session - FileRundown = pack(FileEventGUID, uint16(FileRundownID)) - // CreateFile represents events that create/open a file or I/O device - CreateFile = pack(FileEventGUID, uint16(CreateFileID)) - // ReleaseFile represents events that occur when the last file handle is disposed - ReleaseFile = pack(FileEventGUID, uint16(ReleaseFileID)) - // CloseFile represents events that dispose existing kernel file objects - CloseFile = pack(FileEventGUID, uint16(CloseFileID)) - // ReadFile represents events that read data from the file or I/O device - ReadFile = pack(FileEventGUID, uint16(ReadFileID)) - // WriteFile represents events that write data to the file or I/O device - WriteFile = pack(FileEventGUID, uint16(WriteFileID)) - // SetFileInformation represents events that set file information - SetFileInformation = pack(FileEventGUID, uint16(SetFileInformationID)) - // DeleteFile identifies file deletion events - DeleteFile = pack(FileEventGUID, uint16(DeleteFileID)) - // RenameFile identifies events that are responsible for renaming files - RenameFile = pack(FileEventGUID, uint16(RenameFileID)) - // EnumDirectory identifies enumerate directory and directory notification events - EnumDirectory = pack(FileEventGUID, uint16(EnumDirectoryID)) - // FileOpEnd signals the finalization of the file operation - FileOpEnd = pack(FileEventGUID, uint16(FileOpEndID)) - - // RegCreateKey represents registry key creation kernel events - RegCreateKey = pack(RegistryEventGUID, uint16(RegCreateKeyID)) - // RegOpenKey represents registry open key kernel events - RegOpenKey = pack(RegistryEventGUID, uint16(RegOpenKeyID)) - // RegCloseKey represents registry close key kernel event. - RegCloseKey = pack(RegistryEventGUID, uint16(RegCloseKeyID)) - // RegDeleteKey represents registry key deletion kernel events - RegDeleteKey = pack(RegistryEventGUID, uint16(RegDeleteKeyID)) - // RegQueryKey represents registry query key kernel events - RegQueryKey = pack(RegistryEventGUID, uint16(RegQueryKeyID)) - // RegSetValue represents registry set value kernel events - RegSetValue = pack(RegistryEventGUID, uint16(RegSetValueID)) - // RegDeleteValue are kernel events for registry value removals - RegDeleteValue = pack(RegistryEventGUID, uint16(RegDeleteValueID)) - // RegQueryValue are kernel events for registry value queries - RegQueryValue = pack(RegistryEventGUID, uint16(RegQueryValueID)) - // RegCreateKCB represents kernel events for KCB (Key Control Block) creation requests - RegCreateKCB = pack(RegistryEventGUID, uint16(RegCreateKCBID)) - // RegDeleteKCB represents kernel events for KCB(Key Control Block) closures - RegDeleteKCB = pack(RegistryEventGUID, uint16(RegDeleteKCBID)) - // RegKCBRundown enumerates the registry keys open at the start of the kernel session. - RegKCBRundown = pack(RegistryEventGUID, uint16(RegKCBRundownID)) - // RegSetValueInternal is the internal event that is used to - // enrich the corresponding public RegSetValue event with - // extra attributes - RegSetValueInternal = pack(RegistryKernelEventGUID, RegSetValueInternalID) - - // UnloadModule represents unload module kernel events - UnloadModule = pack(ModuleEventGUID, uint16(UnloadModuleID)) - // ModuleRundown represents kernel events that is triggered to enumerate all loaded modules - ModuleRundown = pack(ModuleEventGUID, uint16(ModuleRundownID)) - // LoadModule represents module load kernel events that are triggered when a DLL or executable file is loaded - LoadModule = pack(ModuleEventGUID, uint16(LoadModuleID)) - // LoadModuleInternal same as for process internal event originating from the Microsoft Windows Kernel Process provider - LoadModuleInternal = pack(ProcessKernelEventGUID, LoadModuleInternalID) - - // AcceptTCPv4 represents the TCPv4 kernel events for accepting connection requests from the socket queue. - AcceptTCPv4 = pack(NetworkTCPEventGUID, uint16(AcceptTCPv4ID)) - // AcceptTCPv6 represents the TCPv6 kernel events for accepting connection requests from the socket queue. - AcceptTCPv6 = pack(NetworkTCPEventGUID, uint16(AcceptTCPv6ID)) - // SendTCPv4 represents the TCPv4 kernel events for sending data to the connected socket. - SendTCPv4 = pack(NetworkTCPEventGUID, uint16(SendV4ID)) - // SendTCPv6 represents the TCPv6 kernel events for sending data to the connected socket. - SendTCPv6 = pack(NetworkTCPEventGUID, uint16(SendV6ID)) - // SendUDPv4 represents the UDPv4 kernel events for sending datagrams to connectionless sockets. - SendUDPv4 = pack(NetworkUDPEventGUID, uint16(SendV4ID)) - // SendUDPv6 represents the UDPv6 kernel events for sending datagrams to connectionless sockets. - SendUDPv6 = pack(NetworkUDPEventGUID, uint16(SendV6ID)) - // RecvTCPv4 represents the TCP IPv4 network receive event. - RecvTCPv4 = pack(NetworkTCPEventGUID, uint16(RecvV4ID)) - // RecvTCPv6 represents the TCP IPv6 network receive event. - RecvTCPv6 = pack(NetworkTCPEventGUID, uint16(RecvV6ID)) - // RecvUDPv4 represents the UDP IPv4 network receive event. - RecvUDPv4 = pack(NetworkUDPEventGUID, uint16(RecvV4ID)) - // RecvUDPv6 represents the UDP IPv6 network receive event. - RecvUDPv6 = pack(NetworkUDPEventGUID, uint16(RecvV6ID)) - // ConnectTCPv4 represents the TCP IPv4 network connect event. - ConnectTCPv4 = pack(NetworkTCPEventGUID, uint16(ConnectTCPv4ID)) - // ConnectTCPv6 represents the TCP IPv6 network connect event. - ConnectTCPv6 = pack(NetworkTCPEventGUID, uint16(ConnectTCPv6ID)) - // DisconnectTCPv4 is the TCP IPv4 network disconnect event. - DisconnectTCPv4 = pack(NetworkTCPEventGUID, uint16(DisconnectTCPv4ID)) - // DisconnectTCPv6 is the TCP IPv6 network disconnect event. - DisconnectTCPv6 = pack(NetworkTCPEventGUID, uint16(DisconnectTCPv6ID)) - // ReconnectTCPv4 is the TCP IPv4 network reconnect event. - ReconnectTCPv4 = pack(NetworkTCPEventGUID, uint16(ReconnectTCPv4ID)) - // ReconnectTCPv6 is the TCP IPv6 network reconnect event. - ReconnectTCPv6 = pack(NetworkTCPEventGUID, uint16(ReconnectTCPv6ID)) - // RetransmitTCPv4 is the TCP IPv4 network retransmit event. - RetransmitTCPv4 = pack(NetworkTCPEventGUID, uint16(RetransmitTCPv4ID)) - // RetransmitTCPv6 is the TCP IPv6 network retransmit event. - RetransmitTCPv6 = pack(NetworkTCPEventGUID, uint16(RetransmitTCPv6ID)) - - // VirtualAlloc represents virtual memory allocation event - VirtualAlloc = pack(MemEventGUID, uint16(VirtualAllocID)) - // VirtualFree represents virtual memory release event - VirtualFree = pack(MemEventGUID, uint16(VirtualFreeID)) - - // QueryDNS represents DNS query events - QueryDNS = pack(DNSEventGUID, QueryDNSID) - // ReplyDNS represents the DNS response events - ReplyDNS = pack(DNSEventGUID, ReplyDNSID) - - // StackWalk represents stack walk event with the collection of return addresses - StackWalk = pack(StackWalkEventGUID, uint16(StackWalkID)) +// NewTypeFromEventRecord derives the event type from the Data1 member of the provider GUID +// and the opcode/event ID integer. +// Go only jump-tables switches over integer types and only when case values are reasonably +// dense. A switch over provider ID which is GUID struct compiles to a sequential chain of +// struct-equality compares instead of a jump table. +// So we split the GUID into a fast-reject key and switch on that instead. The first member of +// the GUID (Data1) is a dense uint32 integer and is certainly unique across all providers. +func NewTypeFromEventRecord(r *etw.EventRecord) Type { + switch r.Header.ProviderID.Data1 { + case RegistryEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case RegCreateKeyID: + return RegCreateKey + case RegOpenKeyID: + return RegOpenKey + case RegDeleteKeyID: + return RegDeleteKey + case RegQueryKeyID: + return RegQueryKey + case RegSetValueID: + return RegSetValue + case RegDeleteValueID: + return RegDeleteValue + case RegQueryValueID: + return RegQueryValue + case RegCreateKCBID: + return RegCreateKCB + case RegDeleteKCBID: + return RegDeleteKCB + case RegKCBRundownID: + return RegKCBRundown + case RegCloseKeyID: + return RegCloseKey + } + case FileEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case FileRundownID: + return FileRundown + case MapViewOfSectionID: + return MapViewOfSection + case UnmapViewOfSectionID: + return UnmapViewOfSection + case MapViewSectionRundownID: + return MapViewSectionRundown + case CreateFileID: + return CreateFile + case ReleaseFileID: + return ReleaseFile + case CloseFileID: + return CloseFile + case ReadFileID: + return ReadFile + case WriteFileID: + return WriteFile + case SetFileInformationID: + return SetFileInformation + case DeleteFileID: + return DeleteFile + case RenameFileID: + return RenameFile + case EnumDirectoryID: + return EnumDirectory + case FileOpEndID: + return FileOpEnd + } + case AuditAPIEventGUID.Data1: + switch r.Header.EventDescriptor.ID { + case OpenProcessID: + return OpenProcess + case OpenThreadID: + return OpenThread + case SetThreadContextID: + return SetThreadContext + case CreateSymbolicLinkObjectID: + return CreateSymbolicLinkObject + } + case StackWalkEventGUID.Data1: + return StackWalk + case MemoryEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case VirtualAllocID: + return VirtualAlloc + case VirtualFreeID: + return VirtualFree + } + case NetworkTCPEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case AcceptTCPv4ID, AcceptTCPv6ID: + return Accept + case SendV4ID, SendV6ID: + return Send + case RecvV4ID, RecvV6ID: + return Recv + case ConnectTCPv4ID, ConnectTCPv6ID: + return Connect + case DisconnectTCPv4ID, DisconnectTCPv6ID: + return Disconnect + case ReconnectTCPv4ID, ReconnectTCPv6ID: + return Reconnect + case RetransmitTCPv4ID, RetransmitTCPv6ID: + return Retransmit + } + case NetworkUDPEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case SendV4ID, SendV6ID: + return Send + case RecvV4ID, RecvV6ID: + return Recv + } + case DNSEventGUID.Data1: + switch r.Header.EventDescriptor.ID { + case QueryDNSID: + return QueryDNS + case ReplyDNSID: + return ReplyDNS + } + case ProcessEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case CreateProcessID: + return CreateProcess + case TerminateProcessID: + return TerminateProcess + case ProcessRundownID: + return ProcessRundown + } + case ProcessKernelEventGUID.Data1: + switch r.Header.EventDescriptor.ID { + case CreateProcessInternalID: + return CreateProcessInternal + case ProcessRundownInternalID: + return ProcessRundownInternal + case LoadModuleInternalID: + return LoadModuleInternal + } + case ModuleEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case UnloadModuleID: + return UnloadModule + case ModuleRundownID: + return ModuleRundown + case LoadModuleID: + return LoadModule + } + case ThreadEventGUID.Data1: + switch r.Header.EventDescriptor.Opcode { + case CreateThreadID: + return CreateThread + case TerminateThreadID: + return TerminateThread + case ThreadRundownID: + return ThreadRundown + } + } + return Unknown +} - // CreateSymbolicLinkObject represents the event emitted by the object manager when the new symbolic link is created within the object manager directory - CreateSymbolicLinkObject = pack(AuditAPIEventGUID, CreateSymbolicLinkObjectID) +// String returns the event type string representation. +func (t Type) String() string { return table[t].Name } - // UnknownType designates unknown event type - UnknownType = pack(windows.GUID{}, 0) -) +// Uint coerces the type to pointer-sized unsigned integer. +func (t Type) Uint() uint { return uint(t) } -// NewTypeFromEventRecord creates a new event type from ETW event record. -func NewTypeFromEventRecord(ev *etw.EventRecord) Type { - return pack(ev.Header.ProviderID, ev.HookID()) -} - -// String returns the string representation of the event type. Returns an empty string -// if the event type is not recognized. -func (t Type) String() string { - switch t { - case CreateProcess, CreateProcessInternal: - return "CreateProcess" - case TerminateProcess: - return "TerminateProcess" - case ProcessRundown, ProcessRundownInternal: - return "ProcessRundown" - case OpenProcess: - return "OpenProcess" - case CreateThread: - return "CreateThread" - case TerminateThread: - return "TerminateThread" - case ThreadRundown: - return "ThreadRundown" - case OpenThread: - return "OpenThread" - case SetThreadContext: - return "SetThreadContext" - case CreateFile: - return "CreateFile" - case CloseFile: - return "CloseFile" - case ReleaseFile: - return "ReleaseFile" - case ReadFile: - return "ReadFile" - case WriteFile: - return "WriteFile" - case SetFileInformation: - return "SetFileInformation" - case DeleteFile: - return "DeleteFile" - case RenameFile: - return "RenameFile" - case EnumDirectory: - return "EnumDirectory" - case FileOpEnd: - return "FileOpEnd" - case FileRundown: - return "FileRundown" - case MapViewFile: - return "MapViewFile" - case UnmapViewFile: - return "UnmapViewFile" - case MapFileRundown: - return "MapFileRundown" - case RegKCBRundown: - return "RegKCBRundown" - case RegOpenKey: - return "RegOpenKey" - case RegCloseKey: - return "RegCloseKey" - case RegCreateKey: - return "RegCreateKey" - case RegDeleteKey: - return "RegDeleteKey" - case RegDeleteValue: - return "RegDeleteValue" - case RegQueryKey: - return "RegQueryKey" - case RegQueryValue: - return "RegQueryValue" - case RegCreateKCB: - return "RegCreateKCB" - case RegSetValue, RegSetValueInternal: - return "RegSetValue" - case LoadModule, LoadModuleInternal: - return "LoadModule" - case UnloadModule: - return "UnloadModule" - case ModuleRundown: - return "ModuleRundown" - case AcceptTCPv4, AcceptTCPv6: - return "Accept" - case SendTCPv4, SendTCPv6, SendUDPv4, SendUDPv6: - return "Send" - case RecvTCPv4, RecvTCPv6, RecvUDPv4, RecvUDPv6: - return "Recv" - case ConnectTCPv4, ConnectTCPv6: - return "Connect" - case ReconnectTCPv4, ReconnectTCPv6: - return "Reconnect" - case DisconnectTCPv4, DisconnectTCPv6: - return "Disconnect" - case RetransmitTCPv4, RetransmitTCPv6: - return "Retransmit" - case VirtualAlloc: - return "VirtualAlloc" - case VirtualFree: - return "VirtualFree" - case QueryDNS: - return "QueryDns" - case ReplyDNS: - return "ReplyDns" - case StackWalk: - return "StackWalk" - case CreateSymbolicLinkObject: - return "CreateSymbolicLinkObject" - default: - return "" - } -} +// OnlyState determines whether the event type is solely used for state management. +func (t Type) OnlyState() bool { return table[t].Flags&OnlyState != 0 } -// Category determines the category to which the event type pertains. -func (t Type) Category() Category { - switch t { - case CreateProcess, CreateProcessInternal, TerminateProcess, OpenProcess, ProcessRundown, ProcessRundownInternal: - return Process - case CreateThread, TerminateThread, OpenThread, SetThreadContext, ThreadRundown, StackWalk: - return Thread - case LoadModule, UnloadModule, ModuleRundown, LoadModuleInternal: - return Module - case CreateFile, ReadFile, WriteFile, EnumDirectory, DeleteFile, RenameFile, CloseFile, SetFileInformation, - FileRundown, FileOpEnd, ReleaseFile, MapViewFile, UnmapViewFile, MapFileRundown: - return File - case RegCreateKey, RegDeleteKey, RegOpenKey, RegCloseKey, RegQueryKey, RegQueryValue, RegSetValue, RegDeleteValue, - RegKCBRundown, RegDeleteKCB, RegCreateKCB, RegSetValueInternal: - return Registry - case AcceptTCPv4, AcceptTCPv6, - ConnectTCPv4, ConnectTCPv6, - ReconnectTCPv4, ReconnectTCPv6, - RetransmitTCPv4, RetransmitTCPv6, - DisconnectTCPv4, DisconnectTCPv6, - SendTCPv4, SendTCPv6, SendUDPv4, SendUDPv6, - RecvTCPv4, RecvTCPv6, RecvUDPv4, RecvUDPv6, - QueryDNS, ReplyDNS: - return Net - case VirtualAlloc, VirtualFree: - return Mem - case CreateSymbolicLinkObject: - return Object - default: - return Unknown - } -} +// IsRundown indicates if this type represents a rundown event that seeds the state. +func (t Type) StateSnapshot() bool { return table[t].Flags&StateSnapshot != 0 } -// Subcategory determines the event subcategory, if any. -func (t Type) Subcategory() Subcategory { - switch t { - case QueryDNS, ReplyDNS: - return DNS - default: - return None - } -} +// WaitStack determines if the event waits for call stack return addresses. +func (t Type) WaitStack() bool { return table[t].Flags&WaitStack != 0 } -// Description returns a brief description of the event type. -func (t Type) Description() string { +// EventID produces a native ETW classic event identifier to +// indicate which event types are enabled for stack walk tracing. +func (t Type) EventID() etw.ClassicEventID { switch t { case CreateProcess: - return "Creates a new process and its primary thread" - case TerminateProcess: - return "Terminates the process and all of its threads" - case OpenProcess: - return "Opens the process handle" + return etw.ClassicEventID{GUID: ProcessEventGUID, Type: CreateProcessID} case CreateThread: - return "Creates a thread to execute within the virtual address space of the calling process" + return etw.ClassicEventID{GUID: ThreadEventGUID, Type: CreateThreadID} case TerminateThread: - return "Terminates a thread within the process" - case OpenThread: - return "Opens the thread handle" - case SetThreadContext: - return "Sets the thread context" - case ReadFile: - return "Reads data from the file or I/O device" - case WriteFile: - return "Writes data to the file or I/O device" + return etw.ClassicEventID{GUID: ThreadEventGUID, Type: TerminateThreadID} + case LoadModule: + return etw.ClassicEventID{GUID: ProcessEventGUID, Type: LoadModuleID} case CreateFile: - return "Creates or opens a file or I/O device" - case CloseFile: - return "Closes the file handle" + return etw.ClassicEventID{GUID: FileEventGUID, Type: CreateFileID} case DeleteFile: - return "Removes the file from the file system" + return etw.ClassicEventID{GUID: FileEventGUID, Type: DeleteFileID} case RenameFile: - return "Changes the file name" - case SetFileInformation: - return "Sets the file meta information" - case EnumDirectory: - return "Enumerates a directory or dispatches a directory change notification to registered listeners" - case MapViewFile: - return "Maps a view of a file mapping into the address space of a calling process" - case UnmapViewFile: - return "Unmaps a mapped view of a file from the calling process's address space" + return etw.ClassicEventID{GUID: FileEventGUID, Type: RenameFileID} case RegCreateKey: - return "Creates a registry key or opens it if the key already exists" - case RegOpenKey: - return "Opens the registry key" - case RegCloseKey: - return "Closes the registry key" - case RegSetValue: - return "Sets the data for the value of a registry key" - case RegQueryValue: - return "Reads the data for the value of a registry key" - case RegQueryKey: - return "Enumerates subkeys of the parent key" + return etw.ClassicEventID{GUID: RegistryEventGUID, Type: RegCreateKeyID} case RegDeleteKey: - return "Removes the registry key" + return etw.ClassicEventID{GUID: RegistryEventGUID, Type: RegDeleteKeyID} + case RegSetValue: + return etw.ClassicEventID{GUID: RegistryEventGUID, Type: RegSetValueID} case RegDeleteValue: - return "Removes the registry value" - case AcceptTCPv4, AcceptTCPv6: - return "Accepts the connection request from the socket queue" - case ConnectTCPv4, ConnectTCPv6: - return "Connects establishes a connection to the socket" - case DisconnectTCPv4, DisconnectTCPv6: - return "Terminates data reception on the socket" - case ReconnectTCPv4, ReconnectTCPv6: - return "Reconnects to the socket" - case RetransmitTCPv4, RetransmitTCPv6: - return "Retransmits unacknowledged TCP segments" - case SendTCPv4, SendUDPv4, SendTCPv6, SendUDPv6: - return "Sends data over the wire" - case RecvTCPv4, RecvUDPv4, RecvTCPv6, RecvUDPv6: - return "Receives data from the socket" - case LoadModule: - return "Loads the module into the address space of the calling process" - case UnloadModule: - return "Unloads the module from the address space of the calling process" + return etw.ClassicEventID{GUID: RegistryEventGUID, Type: RegDeleteValueID} case VirtualAlloc: - return "Reserves, commits, or changes the state of a region of memory within the process virtual address space" - case VirtualFree: - return "Releases or decommits a region of memory within the process virtual address space" - case QueryDNS: - return "Sends a DNS query to the name server" - case ReplyDNS: - return "Receives the response from the DNS server" - case CreateSymbolicLinkObject: - return "Creates the symbolic link within the object manager directory" + return etw.ClassicEventID{GUID: MemoryEventGUID, Type: VirtualAllocID} default: - return "" - } -} - -// Hash calculates the hash number of the event type. -func (t Type) Hash() uint32 { - if t == UnknownType { - return 0 - } - return hashers.FnvUint32([]byte(t.String())) -} - -// Exists determines whether particular event type exists. -func (t Type) Exists() bool { - return t.String() != "" -} - -// OnlyState determines whether the event type is solely used for state management. -func (t Type) OnlyState() bool { - switch t { - case ProcessRundown, - ProcessRundownInternal, - CreateProcessInternal, - ThreadRundown, - ModuleRundown, - LoadModuleInternal, - FileRundown, - RegKCBRundown, - FileOpEnd, - ReleaseFile, - MapFileRundown, - RegCreateKCB, - RegDeleteKCB, - RegSetValueInternal: - return true - default: - return false - } -} - -// CanEnrichStack determines if the event can be enriched with a callstack. -func (t Type) CanEnrichStack() bool { - switch t { - case CreateProcess, - CreateThread, - TerminateThread, - LoadModule, - RegCreateKey, - RegDeleteKey, - RegSetValue, - RegDeleteValue, - DeleteFile, - RenameFile, - VirtualAlloc: - return true - default: - return false - } -} - -// UnmarshalYAML converts the Type name to Type array type. -func (t *Type) UnmarshalYAML(unmarshal func(interface{}) error) error { - var typ string - err := unmarshal(&typ) - if err != nil { - return err - } - *t = NameToType(typ) - return nil -} - -// GUID returns the event GUID from the raw event type. -func (t *Type) GUID() windows.GUID { - return windows.GUID{ - Data1: binary.BigEndian.Uint32(t[0:4]), - Data2: binary.BigEndian.Uint16(t[4:6]), - Data3: binary.BigEndian.Uint16(t[6:8]), - Data4: [8]byte{t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15]}, - } -} - -// HookID returns the event operation code (hook ID) from the raw event type. -func (t *Type) HookID() uint16 { - return binary.BigEndian.Uint16(t[16:]) -} - -// ID is an unsigned integer that uniquely -// identifies the event. Handy for bitmask -// operations. -func (t Type) ID() uint { - id := uint(t[0])<<56 | - uint(t[1])<<48 | - uint(t[2])<<40 | - uint(t[3])<<32 | - uint(t[4])<<24 | - uint(t[5])<<16 | - uint(t.HookID()) - return id -} - -// Source designates the provenance of this event type. -func (t Type) Source() Source { - switch t.GUID() { - case AuditAPIEventGUID, DNSEventGUID, ProcessKernelEventGUID, RegistryKernelEventGUID: - return SecurityTelemetryLogger - default: - return SystemLogger - } -} - -// TypeFromParts builds the event type from provider GUID and hook ID. -func TypeFromParts(g windows.GUID, id uint16) Type { return pack(g, id) } - -// pack merges event provider GUID and the hook ID into `Type` array. -// The type provides a convenient way for comparing event types. -func pack(g windows.GUID, id uint16) Type { - return [18]byte{ - byte(g.Data1 >> 24), byte(g.Data1 >> 16), byte(g.Data1 >> 8), byte(g.Data1), - byte(g.Data2 >> 8), byte(g.Data2), - byte(g.Data3 >> 8), byte(g.Data3), - g.Data4[0], - g.Data4[1], - g.Data4[2], - g.Data4[3], - g.Data4[4], - g.Data4[5], - g.Data4[6], - g.Data4[7], - byte(id >> 8), byte(id), + return etw.ClassicEventID{} } } // color return the colorized event type to render by the color formatter. func (t Type) color() string { switch t { - case CreateFile, ReadFile, CloseFile, SetFileInformation, MapViewFile, UnmapViewFile: + case CreateFile, ReadFile, CloseFile, SetFileInformation: return colorizer.SpanBold(colorizer.Cyan, t.String()) case RenameFile: return colorizer.SpanBold(colorizer.Amber, t.String()) @@ -693,18 +433,17 @@ func (t Type) color() string { return colorizer.SpanBold(colorizer.Amber, t.String()) case LoadModule, UnloadModule: return colorizer.SpanBold(colorizer.Magenta, t.String()) - case SendTCPv4, SendTCPv6, SendUDPv4, SendUDPv6, - RecvTCPv4, RecvTCPv6, RecvUDPv4, RecvUDPv6: + case Send, Recv: return colorizer.SpanBold(colorizer.Blue, t.String()) - case ConnectTCPv4, ConnectTCPv6: + case Connect: return colorizer.SpanBold(colorizer.Teal, t.String()) - case DisconnectTCPv4, DisconnectTCPv6: + case Disconnect: return colorizer.SpanBold(colorizer.Blue, t.String()) - case AcceptTCPv4, AcceptTCPv6: + case Accept: return colorizer.SpanBold(colorizer.Teal, t.String()) case QueryDNS, ReplyDNS: return colorizer.SpanBold(colorizer.Indigo, t.String()) - case VirtualAlloc, VirtualFree: + case VirtualAlloc, VirtualFree, MapViewOfSection, UnmapViewOfSection: return colorizer.SpanBold(colorizer.Magenta, t.String()) case CreateSymbolicLinkObject: return colorizer.SpanBold(colorizer.Lavender, t.String()) @@ -729,21 +468,19 @@ func (t Type) arrow() string { var clr uint8 switch t { case TerminateProcess, TerminateThread, DeleteFile, RegDeleteKey, - RegDeleteValue, UnloadModule, VirtualFree, UnmapViewFile: + RegDeleteValue, UnloadModule, VirtualFree, UnmapViewOfSection: clr = colorizer.Red case CreateProcess, CreateFile, WriteFile, RenameFile, SetFileInformation, - RegCreateKey, RegSetValue, CreateThread, SetThreadContext, VirtualAlloc, MapViewFile, - ConnectTCPv4, ConnectTCPv6, AcceptTCPv4, AcceptTCPv6, - SendTCPv4, SendTCPv6, SendUDPv4, SendUDPv6: + RegCreateKey, RegSetValue, CreateThread, SetThreadContext, VirtualAlloc, + MapViewOfSection, Connect, Accept, Send: clr = colorizer.Amber - case ReadFile, EnumDirectory, LoadModule, RegOpenKey, RegQueryKey, RegQueryValue, OpenProcess, - OpenThread, RecvTCPv4, RecvTCPv6, RecvUDPv4, RecvUDPv6: + case ReadFile, EnumDirectory, LoadModule, RegOpenKey, RegQueryKey, RegQueryValue, + OpenProcess, OpenThread, Recv: clr = colorizer.Teal case QueryDNS, ReplyDNS: clr = colorizer.Indigo default: clr = colorizer.Gray } - return colorizer.SpanBold(clr, "› ") } diff --git a/pkg/event/types_windows_test.go b/pkg/event/types_windows_test.go index 84863c4db..c20a83668 100644 --- a/pkg/event/types_windows_test.go +++ b/pkg/event/types_windows_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 by Nedim Sabic Sabic + * Copyright 2019-2026 by Nedim Sabic Sabic * https://www.fibratus.io * All Rights Reserved. * @@ -22,126 +22,442 @@ import ( "testing" "github.com/rabbitstack/fibratus/pkg/sys/etw" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "golang.org/x/sys/windows" ) -func TestEventTypePackAllBytes(t *testing.T) { - assert.Equal(t, byte(0x3d), CreateProcess[0]) - assert.Equal(t, byte(0x6f), CreateProcess[1]) - assert.Equal(t, byte(0xa8), CreateProcess[2]) - assert.Equal(t, byte(0xd0), CreateProcess[3]) - - assert.Equal(t, byte(0xfe), CreateProcess[4]) - assert.Equal(t, byte(0x05), CreateProcess[5]) - - assert.Equal(t, byte(0x11), CreateProcess[6]) - assert.Equal(t, byte(0xd0), CreateProcess[7]) - - assert.Equal(t, byte(0x9d), CreateProcess[8]) - assert.Equal(t, byte(0xda), CreateProcess[9]) - assert.Equal(t, byte(0x0), CreateProcess[10]) - assert.Equal(t, byte(0xc0), CreateProcess[11]) - assert.Equal(t, byte(0x4f), CreateProcess[12]) - assert.Equal(t, byte(0xd7), CreateProcess[13]) - assert.Equal(t, byte(0xba), CreateProcess[14]) - assert.Equal(t, byte(0x7c), CreateProcess[15]) - assert.Equal(t, byte(0x0), CreateProcess[16]) - assert.Equal(t, byte(0x1), CreateProcess[17]) - - assert.Equal(t, byte(0x0b), QueryDNS[16]) - assert.Equal(t, byte(0xbe), QueryDNS[17]) -} - -func TestEventTypeComparison(t *testing.T) { - var tests = []struct { - name string - ktyp Type - wants Type +func TestNewTypeFromEventRecord(t *testing.T) { + tests := []struct { + name string + provider windows.GUID + id uint16 + opcode uint8 + want Type }{ + // Registry { - "equals CreateProcess", - pack(windows.GUID{Data1: 0x3d6fa8d0, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x0, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}, 1), - CreateProcess, + name: "Registry/CreateKey", + provider: RegistryEventGUID, + opcode: RegCreateKeyID, + want: RegCreateKey, }, { - "equals TerminateProcess", - pack(windows.GUID{Data1: 0x3d6fa8d0, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x0, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}, 2), - TerminateProcess, + name: "Registry/OpenKey", + provider: RegistryEventGUID, + opcode: RegOpenKeyID, + want: RegOpenKey, + }, + { + name: "Registry/DeleteKey", + provider: RegistryEventGUID, + opcode: RegDeleteKeyID, + want: RegDeleteKey, + }, + { + name: "Registry/QueryKey", + provider: RegistryEventGUID, + opcode: RegQueryKeyID, + want: RegQueryKey, + }, + { + name: "Registry/SetValue", + provider: RegistryEventGUID, + opcode: RegSetValueID, + want: RegSetValue, + }, + { + name: "Registry/DeleteValue", + provider: RegistryEventGUID, + opcode: RegDeleteValueID, + want: RegDeleteValue, + }, + { + name: "Registry/QueryValue", + provider: RegistryEventGUID, + opcode: RegQueryValueID, + want: RegQueryValue, + }, + { + name: "Registry/CreateKCB", + provider: RegistryEventGUID, + opcode: RegCreateKCBID, + want: RegCreateKCB, + }, + { + name: "Registry/DeleteKCB", + provider: RegistryEventGUID, + opcode: RegDeleteKCBID, + want: RegDeleteKCB, + }, + { + name: "Registry/KCBRundown", + provider: RegistryEventGUID, + opcode: RegKCBRundownID, + want: RegKCBRundown, + }, + { + name: "Registry/CloseKey", + provider: RegistryEventGUID, + opcode: RegCloseKeyID, + want: RegCloseKey, }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - lhs, rhs := tt.ktyp, tt.wants - assert.Equal(t, lhs, rhs) - }) - } -} + // File + { + name: "File/FileRundown", + provider: FileEventGUID, + opcode: FileRundownID, + want: FileRundown, + }, + { + name: "File/MapViewOfSection", + provider: FileEventGUID, + opcode: MapViewOfSectionID, + want: MapViewOfSection, + }, + { + name: "File/UnmapViewOfSection", + provider: FileEventGUID, + opcode: UnmapViewOfSectionID, + want: UnmapViewOfSection, + }, + { + name: "File/MapViewSectionRundown", + provider: FileEventGUID, + opcode: MapViewSectionRundownID, + want: MapViewSectionRundown, + }, + { + name: "File/CreateFile", + provider: FileEventGUID, + opcode: CreateFileID, + want: CreateFile, + }, + { + name: "File/ReleaseFile", + provider: FileEventGUID, + opcode: ReleaseFileID, + want: ReleaseFile, + }, + { + name: "File/CloseFile", + provider: FileEventGUID, + opcode: CloseFileID, + want: CloseFile, + }, + { + name: "File/ReadFile", + provider: FileEventGUID, + opcode: ReadFileID, + want: ReadFile, + }, + { + name: "File/WriteFile", + provider: FileEventGUID, + opcode: WriteFileID, + want: WriteFile, + }, + { + name: "File/SetFileInformation", + provider: FileEventGUID, + opcode: SetFileInformationID, + want: SetFileInformation, + }, + { + name: "File/DeleteFile", + provider: FileEventGUID, + opcode: DeleteFileID, + want: DeleteFile, + }, + { + name: "File/RenameFile", + provider: FileEventGUID, + opcode: RenameFileID, + want: RenameFile, + }, + { + name: "File/EnumDirectory", + provider: FileEventGUID, + opcode: EnumDirectoryID, + want: EnumDirectory, + }, + { + name: "File/FileOpEnd", + provider: FileEventGUID, + opcode: FileOpEndID, + want: FileOpEnd, + }, -func TestNewEventTypeFromEventRecord(t *testing.T) { - assert.Equal(t, CreateProcess, NewTypeFromEventRecord(&etw.EventRecord{ - Header: etw.EventHeader{ - ProviderID: windows.GUID{Data1: 0x3d6fa8d0, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x0, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}, - EventDescriptor: etw.EventDescriptor{ - Opcode: 1, - }, - }, - })) - assert.Equal(t, OpenProcess, NewTypeFromEventRecord(&etw.EventRecord{ - Header: etw.EventHeader{ - ProviderID: windows.GUID{Data1: 0xe02a841c, Data2: 0x75a3, Data3: 0x4fa7, Data4: [8]byte{0xaf, 0xc8, 0xae, 0x09, 0xcf, 0x9b, 0x7f, 0x23}}, - EventDescriptor: etw.EventDescriptor{ - ID: 5, - }, - }, - })) -} + // Audit API -- these use EventDescriptor.ID, not Opcode. + { + name: "AuditAPI/OpenProcess", + provider: AuditAPIEventGUID, + id: OpenProcessID, + want: OpenProcess, + }, + { + name: "AuditAPI/OpenThread", + provider: AuditAPIEventGUID, + id: OpenThreadID, + want: OpenThread, + }, + { + name: "AuditAPI/SetThreadContext", + provider: AuditAPIEventGUID, + id: SetThreadContextID, + want: SetThreadContext, + }, + { + name: "AuditAPI/CreateSymbolicLinkObject", + provider: AuditAPIEventGUID, + id: CreateSymbolicLinkObjectID, + want: CreateSymbolicLinkObject, + }, -func TestEventTypeExists(t *testing.T) { - require.True(t, AcceptTCPv4.Exists()) - require.True(t, AcceptTCPv6.Exists()) -} + // Stack walk + { + name: "StackWalk", + provider: StackWalkEventGUID, + want: StackWalk, + }, -func TestGUIDAndHookIDFromEventType(t *testing.T) { - var tests = []struct { - Type Type - opcode uint16 - guid windows.GUID - }{ + // Memory + { + name: "Memory/VirtualAlloc", + provider: MemoryEventGUID, + opcode: VirtualAllocID, + want: VirtualAlloc, + }, + { + name: "Memory/VirtualFree", + provider: MemoryEventGUID, + opcode: VirtualFreeID, + want: VirtualFree, + }, + + // TCP { - LoadModule, - 10, - windows.GUID{Data1: 0x2cb15d1d, Data2: 0x5fc1, Data3: 0x11d2, Data4: [8]byte{0xab, 0xe1, 0x0, 0xa0, 0xc9, 0x11, 0xf5, 0x18}}, + name: "TCP/AcceptIPv4", + provider: NetworkTCPEventGUID, + opcode: AcceptTCPv4ID, + want: Accept, }, { - WriteFile, - 68, - windows.GUID{Data1: 0x90cbdc39, Data2: 0x4a3e, Data3: 0x11d1, Data4: [8]byte{0x84, 0xf4, 0x0, 0x0, 0xf8, 0x04, 0x64, 0xe3}}, + name: "TCP/AcceptIPv6", + provider: NetworkTCPEventGUID, + opcode: AcceptTCPv6ID, + want: Accept, + }, + { + name: "TCP/SendIPv4", + provider: NetworkTCPEventGUID, + opcode: SendV4ID, + want: Send, + }, + { + name: "TCP/SendIPv6", + provider: NetworkTCPEventGUID, + opcode: SendV6ID, + want: Send, + }, + { + name: "TCP/RecvIPv4", + provider: NetworkTCPEventGUID, + opcode: RecvV4ID, + want: Recv, + }, + { + name: "TCP/RecvIPv6", + provider: NetworkTCPEventGUID, + opcode: RecvV6ID, + want: Recv, + }, + { + name: "TCP/ConnectIPv4", + provider: NetworkTCPEventGUID, + opcode: ConnectTCPv4ID, + want: Connect, + }, + { + name: "TCP/ConnectIPv6", + provider: NetworkTCPEventGUID, + opcode: ConnectTCPv6ID, + want: Connect, + }, + { + name: "TCP/DisconnectIPv4", + provider: NetworkTCPEventGUID, + opcode: DisconnectTCPv4ID, + want: Disconnect, + }, + { + name: "TCP/DisconnectIPv6", + provider: NetworkTCPEventGUID, + opcode: DisconnectTCPv6ID, + want: Disconnect, + }, + { + name: "TCP/ReconnectIPv4", + provider: NetworkTCPEventGUID, + opcode: ReconnectTCPv4ID, + want: Reconnect, + }, + { + name: "TCP/ReconnectIPv6", + provider: NetworkTCPEventGUID, + opcode: ReconnectTCPv6ID, + want: Reconnect, + }, + { + name: "TCP/RetransmitIPv4", + provider: NetworkTCPEventGUID, + opcode: RetransmitTCPv4ID, + want: Retransmit, + }, + { + name: "TCP/RetransmitIPv6", + provider: NetworkTCPEventGUID, + opcode: RetransmitTCPv6ID, + want: Retransmit, + }, + + // UDP + { + name: "UDP/SendIPv4", + provider: NetworkUDPEventGUID, + opcode: SendV4ID, + want: Send, + }, + { + name: "UDP/SendIPv6", + provider: NetworkUDPEventGUID, + opcode: SendV6ID, + want: Send, + }, + { + name: "UDP/RecvIPv4", + provider: NetworkUDPEventGUID, + opcode: RecvV4ID, + want: Recv, + }, + { + name: "UDP/RecvIPv6", + provider: NetworkUDPEventGUID, + opcode: RecvV6ID, + want: Recv, + }, + + // DNS -- uses EventDescriptor.ID. + { + name: "DNS/Query", + provider: DNSEventGUID, + id: QueryDNSID, + want: QueryDNS, + }, + { + name: "DNS/Reply", + provider: DNSEventGUID, + id: ReplyDNSID, + want: ReplyDNS, + }, + + // Process + { + name: "Process/CreateProcess", + provider: ProcessEventGUID, + opcode: CreateProcessID, + want: CreateProcess, + }, + { + name: "Process/TerminateProcess", + provider: ProcessEventGUID, + opcode: TerminateProcessID, + want: TerminateProcess, + }, + { + name: "Process/ProcessRundown", + provider: ProcessEventGUID, + opcode: ProcessRundownID, + want: ProcessRundown, + }, + + // Process kernel -- uses EventDescriptor.ID. + { + name: "ProcessKernel/CreateProcessInternal", + provider: ProcessKernelEventGUID, + id: CreateProcessInternalID, + want: CreateProcessInternal, + }, + { + name: "ProcessKernel/ProcessRundownInternal", + provider: ProcessKernelEventGUID, + id: ProcessRundownInternalID, + want: ProcessRundownInternal, + }, + { + name: "ProcessKernel/LoadModuleInternal", + provider: ProcessKernelEventGUID, + id: LoadModuleInternalID, + want: LoadModuleInternal, + }, + + // Module + { + name: "Module/UnloadModule", + provider: ModuleEventGUID, + opcode: UnloadModuleID, + want: UnloadModule, + }, + { + name: "Module/ModuleRundown", + provider: ModuleEventGUID, + opcode: ModuleRundownID, + want: ModuleRundown, + }, + { + name: "Module/LoadModule", + provider: ModuleEventGUID, + opcode: LoadModuleID, + want: LoadModule, + }, + + // Thread + { + name: "Thread/CreateThread", + provider: ThreadEventGUID, + opcode: CreateThreadID, + want: CreateThread, + }, + { + name: "Thread/TerminateThread", + provider: ThreadEventGUID, + opcode: TerminateThreadID, + want: TerminateThread, + }, + { + name: "Thread/ThreadRundown", + provider: ThreadEventGUID, + opcode: ThreadRundownID, + want: ThreadRundown, }, } for _, tt := range tests { - t.Run(tt.Type.String(), func(t *testing.T) { - assert.Equal(t, tt.guid.String(), tt.Type.GUID().String()) - assert.Equal(t, tt.opcode, tt.Type.HookID()) - }) - } -} + t.Run(tt.name, func(t *testing.T) { + r := &etw.EventRecord{ + Header: etw.EventHeader{ + ProviderID: tt.provider, + EventDescriptor: etw.EventDescriptor{ + ID: tt.id, + Opcode: tt.opcode, + }, + }, + } -func TestIDEquality(t *testing.T) { - evt := etw.EventRecord{Header: etw.EventHeader{ProviderID: ThreadEventGUID, EventDescriptor: etw.EventDescriptor{Opcode: 1}}} - typ := CreateThread - require.Equal(t, typ.ID(), evt.ID()) -} + got := NewTypeFromEventRecord(r) -func TestEventTypeIDCollision(t *testing.T) { - ids := make(map[uint]Type) - for _, typ := range AllWithState() { - if etype, ok := ids[typ.ID()]; ok { - t.Fatalf("id collision for %s event type. Mapped event type: %s", typ.String(), etype.String()) - } - ids[typ.ID()] = typ + if got != tt.want { + t.Fatalf("NewTypeFromEventRecord() = %v, want %v", got, tt.want) + } + }) } } diff --git a/pkg/filament/dict.go b/pkg/filament/dict.go index 967ec7dcf..0150d334b 100644 --- a/pkg/filament/dict.go +++ b/pkg/filament/dict.go @@ -62,9 +62,9 @@ func newEventDict(evt *event.Event) (*cpython.Dict, error) { dict.Insert(pid, cpython.NewPyObjectFromValue(evt.PID)) dict.Insert(tid, cpython.NewPyObjectFromValue(evt.Tid)) dict.Insert(cpu, cpython.NewPyObjectFromValue(evt.CPU)) - dict.Insert(name, cpython.NewPyObjectFromValue(evt.Name)) - dict.Insert(cat, cpython.NewPyObjectFromValue(string(evt.Category))) - dict.Insert(desc, cpython.NewPyObjectFromValue(evt.Description)) + dict.Insert(name, cpython.NewPyObjectFromValue(evt.Name())) + dict.Insert(cat, cpython.NewPyObjectFromValue(evt.Category().String())) + dict.Insert(desc, cpython.NewPyObjectFromValue(evt.Description())) dict.Insert(host, cpython.NewPyObjectFromValue(evt.Host)) dict.Insert(ts, cpython.NewPyObjectFromValue(evt.Timestamp)) diff --git a/pkg/filament/dict_test.go b/pkg/filament/dict_test.go index 7d714cf25..308c240ca 100644 --- a/pkg/filament/dict_test.go +++ b/pkg/filament/dict_test.go @@ -22,14 +22,15 @@ package filament import ( + "net" + "testing" + "time" + "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/filament/cpython" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "net" - "testing" - "time" ) func TestProduceEventDict(t *testing.T) { @@ -41,16 +42,15 @@ func TestProduceEventDict(t *testing.T) { defer cpython.Finalize() now := time.Now() evt := &event.Event{ - Seq: uint64(12456738026482168384), - Tid: 2484, - PID: 859, - CPU: 1, - Name: "CreateFile", - Timestamp: now, - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Seq: uint64(12456738026482168384), + Tid: 2484, + PID: 859, + CPU: 1, + Type: event.CreateFile, + Timestamp: now, + Host: "archrabbit", } + dict, err := newEventDict(evt) require.NoError(t, err) require.NotNil(t, dict) @@ -80,7 +80,7 @@ func TestProduceEventDictWithIPAddresses(t *testing.T) { defer cpython.Finalize() evt := &event.Event{ - Name: "Send", + Type: event.Send, Tid: 2484, PID: 859, Params: event.Params{ @@ -112,15 +112,13 @@ func BenchmarkTestProduceEventDict(b *testing.B) { defer cpython.Finalize() evt := &event.Event{ - Seq: uint64(12456738026482168384), - Tid: 2484, - PID: 859, - CPU: 1, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Seq: uint64(12456738026482168384), + Tid: 2484, + PID: 859, + CPU: 1, + Type: event.CreateFile, + Timestamp: time.Now(), + Host: "archrabbit", } for i := 0; i < b.N; i++ { diff --git a/pkg/filament/filament_test.go b/pkg/filament/filament_test.go index 0e457e361..57e57c3b6 100644 --- a/pkg/filament/filament_test.go +++ b/pkg/filament/filament_test.go @@ -72,10 +72,8 @@ func TestOnNextEvent(t *testing.T) { Type: event.RegCreateKey, Tid: 2484, PID: 859, - Name: "RegCreateKey", Host: "archrabbit", CPU: uint8(i / 2), - Category: event.Registry, Seq: uint64(i), Timestamp: time.Now(), Params: event.Params{ @@ -105,7 +103,7 @@ func TestFilamentFilter(t *testing.T) { require.NotNil(t, filament) defer filament.Close() require.NotNil(t, filament.Filter()) - kpars := event.Params{ + pars := event.Params{ params.Cmdline: {Name: params.Cmdline, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost.exe -k RPCSS"}, params.ProcessName: {Name: params.ProcessName, Type: params.AnsiString, Value: "svchost.exe"}, params.ProcessID: {Name: params.ProcessID, Type: params.Uint32, Value: uint32(1234)}, @@ -114,8 +112,7 @@ func TestFilamentFilter(t *testing.T) { evt := &event.Event{ Type: event.CreateProcess, - Params: kpars, - Name: "CreateProcess", + Params: pars, } require.True(t, filament.Filter().Eval(evt)) diff --git a/pkg/filter/accessor.go b/pkg/filter/accessor.go index 52e7ee717..ea14a98f8 100644 --- a/pkg/filter/accessor.go +++ b/pkg/filter/accessor.go @@ -77,11 +77,11 @@ func (*evtAccessor) Get(f Field, evt *event.Event) (params.Value, error) { case fields.EvtCPU, fields.KevtCPU: return evt.CPU, nil case fields.EvtName, fields.KevtName: - return evt.Name, nil + return evt.Name(), nil case fields.EvtCategory, fields.KevtCategory: - return string(evt.Category), nil + return evt.Category().String(), nil case fields.EvtDesc, fields.KevtDesc: - return evt.Description, nil + return evt.Description(), nil case fields.EvtHost, fields.KevtHost: return evt.Host, nil case fields.EvtTime, fields.KevtTime: diff --git a/pkg/filter/accessor_windows.go b/pkg/filter/accessor_windows.go index 0f2062416..e39cd59ef 100644 --- a/pkg/filter/accessor_windows.go +++ b/pkg/filter/accessor_windows.go @@ -76,7 +76,7 @@ type psAccessor struct { func (psAccessor) SetFields([]Field) {} func (psAccessor) SetSegments([]fields.Segment) {} func (psAccessor) IsFieldAccessible(e *event.Event) bool { - return e.PS != nil || e.Category == event.Process + return e.PS != nil || e.Category() == event.Process } func newPSAccessor(psnap psnap.Snapshotter) Accessor { return &psAccessor{psnap: psnap} } @@ -475,7 +475,7 @@ type threadAccessor struct{} func (threadAccessor) SetFields([]Field) {} func (threadAccessor) SetSegments([]fields.Segment) {} func (threadAccessor) IsFieldAccessible(e *event.Event) bool { - return !e.Callstack.IsEmpty() || e.Category == event.Thread + return !e.Callstack.IsEmpty() || e.Category() == event.Thread } func newThreadAccessor() Accessor { @@ -676,7 +676,9 @@ type fileAccessor struct{} func (fileAccessor) SetFields(fields []Field) {} func (fileAccessor) SetSegments([]fields.Segment) {} -func (fileAccessor) IsFieldAccessible(e *event.Event) bool { return e.Category == event.File } +func (fileAccessor) IsFieldAccessible(e *event.Event) bool { + return e.Category() == event.File || e.Category() == event.Memory +} func newFileAccessor() Accessor { return &fileAccessor{} @@ -770,7 +772,7 @@ func (moduleAccessor) SetFields(fields []Field) {} func (moduleAccessor) SetSegments([]fields.Segment) {} func (moduleAccessor) IsFieldAccessible(e *event.Event) bool { - return e.Category == event.Module + return e.Category() == event.Module } func newModuleAccessor() Accessor { @@ -868,7 +870,7 @@ type registryAccessor struct{} func (registryAccessor) SetFields([]Field) {} func (registryAccessor) SetSegments([]fields.Segment) {} func (registryAccessor) IsFieldAccessible(e *event.Event) bool { - return e.Category == event.Registry + return e.Category() == event.Registry } func newRegistryAccessor() Accessor { @@ -920,7 +922,7 @@ func (n *networkAccessor) SetFields(flds []Field) { func (networkAccessor) SetSegments([]fields.Segment) {} func (networkAccessor) IsFieldAccessible(e *event.Event) bool { - return e.Category == event.Net + return e.Category() == event.Network } func newNetworkAccessor() Accessor { return &networkAccessor{} } @@ -1205,7 +1207,7 @@ type memAccessor struct{} func (memAccessor) SetFields([]Field) {} func (memAccessor) SetSegments([]fields.Segment) {} -func (memAccessor) IsFieldAccessible(e *event.Event) bool { return e.Category == event.Mem } +func (memAccessor) IsFieldAccessible(e *event.Event) bool { return e.Category() == event.Memory } func newMemAccessor() Accessor { return &memAccessor{} @@ -1236,7 +1238,7 @@ type dnsAccessor struct{} func (dnsAccessor) SetFields([]Field) {} func (dnsAccessor) SetSegments([]fields.Segment) {} func (dnsAccessor) IsFieldAccessible(e *event.Event) bool { - return e.Type.Subcategory() == event.DNS + return e.Subcategory() == event.DNS } func newDNSAccessor() Accessor { diff --git a/pkg/filter/accessor_windows_test.go b/pkg/filter/accessor_windows_test.go index 6d52f0717..0035e7f52 100644 --- a/pkg/filter/accessor_windows_test.go +++ b/pkg/filter/accessor_windows_test.go @@ -79,67 +79,67 @@ func TestIsFieldAccessible(t *testing.T) { }{ { newEventAccessor(), - &event.Event{Type: event.QueryDNS, Category: event.Net}, + &event.Event{Type: event.QueryDNS}, true, }, { newPSAccessor(nil), - &event.Event{Type: event.CreateProcess, Category: event.Process}, + &event.Event{Type: event.CreateProcess}, true, }, { newPSAccessor(nil), - &event.Event{PS: &ptypes.PS{}, Type: event.CreateFile, Category: event.File}, + &event.Event{PS: &ptypes.PS{}, Type: event.CreateFile}, true, }, { newPSAccessor(nil), - &event.Event{Type: event.SetThreadContext, Category: event.Thread}, + &event.Event{Type: event.SetThreadContext}, false, }, { newThreadAccessor(), - &event.Event{Type: event.SetThreadContext, Category: event.Thread}, + &event.Event{Type: event.SetThreadContext}, true, }, { newThreadAccessor(), - &event.Event{Type: event.CreateProcess, Category: event.Process, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, + &event.Event{Type: event.CreateProcess, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, true, }, { newThreadAccessor(), - &event.Event{Type: event.RegSetValue, Category: event.Registry, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, + &event.Event{Type: event.RegSetValue, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, true, }, { newRegistryAccessor(), - &event.Event{Type: event.RegSetValue, Category: event.Registry, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, + &event.Event{Type: event.RegSetValue, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, true, }, { newNetworkAccessor(), - &event.Event{Type: event.RegSetValue, Category: event.Registry, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, + &event.Event{Type: event.RegSetValue, Callstack: []callstack.Frame{{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}}}, false, }, { newNetworkAccessor(), - &event.Event{Type: event.ConnectTCPv6, Category: event.Net}, + &event.Event{Type: event.Connect}, true, }, { newDNSAccessor(), - &event.Event{Type: event.ReplyDNS, Category: event.Net}, + &event.Event{Type: event.ReplyDNS}, true, }, { newModuleAccessor(), - &event.Event{Type: event.LoadModule, Category: event.Module}, + &event.Event{Type: event.LoadModule}, true, }, { newMemAccessor(), - &event.Event{Type: event.VirtualAlloc, Category: event.Mem}, + &event.Event{Type: event.VirtualAlloc}, true, }, } diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go index 1c3fb95ae..b79aa493d 100644 --- a/pkg/filter/filter_test.go +++ b/pkg/filter/filter_test.go @@ -175,8 +175,7 @@ func TestProcFilter(t *testing.T) { } evt := &event.Event{ - Type: event.CreateProcess, - Category: event.Process, + Type: event.CreateProcess, Params: event.Params{ params.Cmdline: {Name: params.Cmdline, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\svchost.exe -k DcomLaunch -p -s LSM"}, params.ProcessName: {Name: params.ProcessName, Type: params.AnsiString, Value: "svchost.exe"}, @@ -190,8 +189,7 @@ func TestProcFilter(t *testing.T) { params.ProcessTokenIsElevated: {Name: params.ProcessTokenIsElevated, Type: params.Bool, Value: true}, params.ProcessTokenElevationType: {Name: params.ProcessTokenElevationType, Type: params.AnsiString, Value: "DEFAULT"}, }, - Name: "CreateProcess", - PID: 1234, + PID: 1234, PS: &pstypes.PS{ Name: "svchost.exe", Cmdline: "C:\\Windows\\System32\\svchost.exe -k DcomLaunch -p -s LSM", @@ -226,13 +224,11 @@ func TestProcFilter(t *testing.T) { evt.Timestamp, _ = time.Parse(time.RFC3339, "2011-05-03T15:04:05.323Z") evt1 := &event.Event{ - Type: event.OpenProcess, - Category: event.Process, + Type: event.OpenProcess, Params: event.Params{ params.DesiredAccess: {Name: params.DesiredAccess, Type: params.Flags, Value: uint32(0x1400), Flags: event.PsAccessRightFlags}, }, - Name: "OpenProcess", - PID: 1023, + PID: 1023, PS: &pstypes.PS{ Name: "svchost.exe", Parent: parent, @@ -246,13 +242,11 @@ func TestProcFilter(t *testing.T) { } evt2 := &event.Event{ - Type: event.OpenProcess, - Category: event.Process, + Type: event.OpenProcess, Params: event.Params{ params.DesiredAccess: {Name: params.DesiredAccess, Type: params.Flags, Value: uint32(0x1400), Flags: event.PsAccessRightFlags}, }, - Name: "OpenProcess", - PID: 1023, + PID: 1023, } var tests = []struct { @@ -424,11 +418,9 @@ func TestThreadFilter(t *testing.T) { params.StartAddressModule: {Name: params.StartAddressModule, Type: params.UnicodeString, Value: "C:\\Windows\\System32\\kernel32.dll"}, } evt := &event.Event{ - Type: event.CreateThread, - Params: pars, - Name: "CreateThread", - PID: windows.GetCurrentProcessId(), - Category: event.Thread, + Type: event.CreateThread, + Params: pars, + PID: windows.GetCurrentProcessId(), PS: &pstypes.PS{ Name: "svchost.exe", Envs: map[string]string{"ALLUSERSPROFILE": "C:\\ProgramData", "OS": "Windows_NT", "ProgramFiles(x86)": "C:\\Program Files (x86)"}, @@ -615,15 +607,12 @@ func TestThreadFilter(t *testing.T) { func TestFileFilter(t *testing.T) { evt := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, @@ -705,9 +694,7 @@ func TestFileInfoFilter(t *testing.T) { { `file.info_class = 'Allocation'`, &event.Event{ - Category: event.File, - Type: event.SetFileInformation, - Name: "SetFileInformation", + Type: event.SetFileInformation, Params: event.Params{ params.FileInfoClass: {Name: params.FileInfoClass, Type: params.Enum, Value: fs.AllocationClass, Enum: fs.FileInfoClasses}, }, @@ -717,9 +704,7 @@ func TestFileInfoFilter(t *testing.T) { { `file.info.allocation_size = 64500`, &event.Event{ - Category: event.File, - Type: event.SetFileInformation, - Name: "SetFileInformation", + Type: event.SetFileInformation, Params: event.Params{ params.FileInfoClass: {Name: params.FileInfoClass, Type: params.Enum, Value: fs.AllocationClass, Enum: fs.FileInfoClasses}, params.FileExtraInfo: {Name: params.FileExtraInfo, Type: params.Uint64, Value: uint64(64500)}, @@ -730,9 +715,7 @@ func TestFileInfoFilter(t *testing.T) { { `file.info.eof_size = 64500`, &event.Event{ - Category: event.File, - Type: event.SetFileInformation, - Name: "SetFileInformation", + Type: event.SetFileInformation, Params: event.Params{ params.FileInfoClass: {Name: params.FileInfoClass, Type: params.Enum, Value: fs.EOFClass, Enum: fs.FileInfoClasses}, params.FileExtraInfo: {Name: params.FileExtraInfo, Type: params.Uint64, Value: uint64(64500)}, @@ -743,9 +726,7 @@ func TestFileInfoFilter(t *testing.T) { { `file.info.eof_size = 64500`, &event.Event{ - Category: event.File, - Type: event.SetFileInformation, - Name: "SetFileInformation", + Type: event.SetFileInformation, Params: event.Params{ params.FileInfoClass: {Name: params.FileInfoClass, Type: params.Enum, Value: fs.DispositionClass, Enum: fs.FileInfoClasses}, params.FileExtraInfo: {Name: params.FileExtraInfo, Type: params.Uint64, Value: uint64(1)}, @@ -756,9 +737,7 @@ func TestFileInfoFilter(t *testing.T) { { `file.info.is_disposition_delete_file = true`, &event.Event{ - Category: event.File, - Type: event.DeleteFile, - Name: "DeleteFile", + Type: event.DeleteFile, Params: event.Params{ params.FileInfoClass: {Name: params.FileInfoClass, Type: params.Enum, Value: fs.DispositionClass, Enum: fs.FileInfoClasses}, params.FileExtraInfo: {Name: params.FileExtraInfo, Type: params.Uint64, Value: uint64(1)}, @@ -782,16 +761,13 @@ func TestFileInfoFilter(t *testing.T) { func TestEventFilter(t *testing.T) { evt := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", - Evasions: uint32(evasion.IndirectSyscall), + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Host: "archrabbit", + Evasions: uint32(evasion.IndirectSyscall), Params: event.Params{ params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(3434)}, params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, @@ -860,13 +836,12 @@ func TestEventFilter(t *testing.T) { func TestNetFilter(t *testing.T) { evt := &event.Event{ - Type: event.SendTCPv4, + Type: event.Accept, Tid: 2484, PID: 859, PS: &pstypes.PS{ Name: "cmd.exe", }, - Category: event.Net, Params: event.Params{ params.NetDport: {Name: params.NetDport, Type: params.Uint16, Value: uint16(443)}, params.NetSport: {Name: params.NetSport, Type: params.Uint16, Value: uint16(43123)}, @@ -911,13 +886,12 @@ func TestNetFilter(t *testing.T) { } evt1 := &event.Event{ - Type: event.SendTCPv4, + Type: event.Send, Tid: 2484, PID: 859, PS: &pstypes.PS{ Name: "cmd.exe", }, - Category: event.Net, Params: event.Params{ params.NetDport: {Name: params.NetDport, Type: params.Uint16, Value: uint16(53)}, params.NetSport: {Name: params.NetSport, Type: params.Uint16, Value: uint16(43123)}, @@ -950,10 +924,9 @@ func TestNetFilter(t *testing.T) { func TestRegistryFilter(t *testing.T) { evt := &event.Event{ - Type: event.RegSetValue, - Tid: 2484, - PID: 859, - Category: event.Registry, + Type: event.RegSetValue, + Tid: 2484, + PID: 859, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.UnicodeString, Value: `HKEY_LOCAL_MACHINE\SYSTEM\Setup\Pid`}, params.RegData: {Name: params.RegData, Type: params.Uint32, Value: uint32(10234)}, @@ -994,8 +967,7 @@ func TestModuleFilter(t *testing.T) { fs.GetMetadataStore().AddFile(filepath.Join(os.Getenv("windir"), "System32", "kernel32.dll"), &fs.FileInfo{IsDLL: true}) e1 := &event.Event{ - Type: event.LoadModule, - Category: event.Module, + Type: event.LoadModule, Params: event.Params{ params.ModulePath: {Name: params.ModulePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "System32", "kernel32.dll")}, params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(1023)}, @@ -1065,8 +1037,7 @@ func TestModuleFilter(t *testing.T) { // now exercise unsigned/unchecked signature e2 := &event.Event{ - Type: event.LoadModule, - Category: event.Module, + Type: event.LoadModule, Params: event.Params{ params.ModulePath: {Name: params.ModulePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "System32", "kernel32.dll")}, params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(1023)}, @@ -1119,8 +1090,7 @@ func TestModuleFilter(t *testing.T) { assert.NotNil(t, signature.GetSignatures().GetSignature(key)) e3 := &event.Event{ - Type: event.LoadModule, - Category: event.Module, + Type: event.LoadModule, Params: event.Params{ params.ModulePath: {Name: params.ModulePath, Type: params.UnicodeString, Value: "..\\pe\\_fixtures\\mscorlib.dll"}, params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(1023)}, @@ -1273,10 +1243,8 @@ func TestMemFilter(t *testing.T) { } evt := &event.Event{ - Type: event.VirtualAlloc, - Params: pars, - Name: "VirtualAlloc", - Category: event.Mem, + Type: event.VirtualAlloc, + Params: pars, PS: &pstypes.PS{ Name: "svchost.exe", Envs: map[string]string{"ALLUSERSPROFILE": "C:\\ProgramData", "OS": "Windows_NT", "ProgramFiles(x86)": "C:\\Program Files (x86)"}, @@ -1318,7 +1286,6 @@ func TestDNSFilter(t *testing.T) { PS: &pstypes.PS{ Name: "cmd.exe", }, - Category: event.Net, Params: event.Params{ params.DNSName: {Name: params.DNSName, Type: params.UnicodeString, Value: "r3.o.lencr.org"}, params.DNSRR: {Name: params.DNSRR, Type: params.Enum, Value: uint32(0x0001), Enum: event.DNSRecordTypes}, @@ -1364,10 +1331,8 @@ func TestInterpolateFields(t *testing.T) { interpolated: "Credential discovery via VaultCmd.exe (VaultCmd.exe /listcreds:Windows Credentials /all) and user LOCAL\\tor", evts: []*event.Event{ { - Type: event.CreateProcess, - Category: event.Process, - Name: "CreateProcess", - PID: 1023, + Type: event.CreateProcess, + PID: 1023, PS: &pstypes.PS{ Name: "VaultCmd.exe", Ppid: 345, @@ -1384,10 +1349,8 @@ func TestInterpolateFields(t *testing.T) { interpolated: "Credential discovery via N/A and pid 1023", evts: []*event.Event{ { - Type: event.CreateProcess, - Category: event.Process, - Name: "CreateProcess", - PID: 1023, + Type: event.CreateProcess, + PID: 1023, }, }, }, @@ -1396,10 +1359,8 @@ func TestInterpolateFields(t *testing.T) { interpolated: "Suspicious thread start module C:\\Windows\\System32\\vault.dll", evts: []*event.Event{ { - Type: event.CreateThread, - Category: event.Thread, - Name: "CreateThread", - PID: 1023, + Type: event.CreateThread, + PID: 1023, Params: event.Params{ params.StartAddressModule: {Name: params.StartAddressModule, Type: params.UnicodeString, Value: "C:\\Windows\\System32\\vault.dll"}, }, @@ -1416,10 +1377,8 @@ and subsequently write the C:\Users eo\Temp\lsass.dump dump file to the disk device`, evts: []*event.Event{ { - Type: event.OpenProcess, - Category: event.Process, - Name: "OpenProcess", - PID: 1023, + Type: event.OpenProcess, + PID: 1023, PS: &pstypes.PS{ Name: "taskmgr.exe", Ppid: 345, @@ -1427,10 +1386,8 @@ eo\Temp\lsass.dump dump file to the disk device`, }, }, { - Type: event.WriteFile, - Category: event.File, - Name: "WriteFile", - PID: 1023, + Type: event.WriteFile, + PID: 1023, Params: event.Params{ params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Users\neo\\Temp\\lsass.dump"}, }, @@ -1452,10 +1409,8 @@ and subsequently write the C:\Users eo\Temp\lsass.dump dump file to the disk device`, evts: []*event.Event{ { - Type: event.OpenProcess, - Category: event.Process, - Name: "OpenProcess", - PID: 1023, + Type: event.OpenProcess, + PID: 1023, PS: &pstypes.PS{ Name: "taskmgr.exe", Ppid: 345, @@ -1463,10 +1418,8 @@ eo\Temp\lsass.dump dump file to the disk device`, }, }, { - Type: event.WriteFile, - Category: event.File, - Name: "WriteFile", - PID: 1023, + Type: event.WriteFile, + PID: 1023, Params: event.Params{ params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Users\neo\\Temp\\lsass.dump"}, }, @@ -1503,7 +1456,6 @@ func BenchmarkFilterRun(b *testing.B) { evt := &event.Event{ Type: event.CreateProcess, Params: pars, - Name: "CreateProcess", } for i := 0; i < b.N; i++ { diff --git a/pkg/filter/ql/literal.go b/pkg/filter/ql/literal.go index 65140dea1..042c7d548 100644 --- a/pkg/filter/ql/literal.go +++ b/pkg/filter/ql/literal.go @@ -25,6 +25,7 @@ import ( "strings" "time" + "github.com/bits-and-blooms/bitset" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/filter/fields" @@ -288,8 +289,10 @@ type SequenceExpr struct { // Alias represents the sequence expression alias when bound fields are used. Alias string - bitsets event.BitSets - types []event.Type + categoryMask bitset.BitSet + eventMask bitset.BitSet + + types []event.Type } func (e *SequenceExpr) init() { @@ -343,39 +346,26 @@ func (e *SequenceExpr) walk() { WalkFunc(e.Expr, walk) - uniqCats := make(map[event.Category]bool) - // initialize event type/category buckets for every such field for name, values := range stringFields { for _, v := range values { switch name { case fields.EvtName: - for _, typ := range event.NameToTypes(v) { - if typ == event.UnknownType { - continue - } - e.types = append(e.types, typ) - uniqCats[event.TypeToEventInfo(typ).Category] = true + typ, ok := event.ParseType(v) + if !ok { + continue } + e.types = append(e.types, typ) + e.eventMask.Set(typ.Uint()) case fields.EvtCategory: - e.bitsets.SetCategoryBit(event.Category(v)) + category, ok := event.ParseCategory(v) + if !ok { + continue + } + e.categoryMask.Set(category.Uint()) } } } - - for _, t := range e.types { - switch len(uniqCats) { - case 0: - continue - case 1: - // happy path can use a single bitmask for all - // event types pertaining to the same category - e.bitsets.SetBit(event.TypeBitSet, t) - default: - // use map-backed bitmask for event identifiers - e.bitsets.SetBit(event.BitmaskBitSet, t) - } - } } // IsEvaluable determines if the expression should be evaluated by inspecting @@ -383,7 +373,7 @@ func (e *SequenceExpr) walk() { // to be evaluated when the incoming event type, ID, or category pertains to the one // defined in the field literal. func (e *SequenceExpr) IsEvaluable(evt *event.Event) bool { - return e.bitsets.IsBitSet(evt) + return e.eventMask.Test(evt.Type.Uint()) || e.categoryMask.Test(evt.Category().Uint()) } // HasBoundFields determines if this sequence expression references any bound field. @@ -434,7 +424,7 @@ func (s *Sequence) init() { for _, expr := range s.Expressions { for _, etype := range expr.types { - sources[etype.Source()] = true + sources[event.GetTypeInfo(etype).Source] = true } } diff --git a/pkg/filter/ql/literal_test.go b/pkg/filter/ql/literal_test.go index 3c978cbf5..19067b5c7 100644 --- a/pkg/filter/ql/literal_test.go +++ b/pkg/filter/ql/literal_test.go @@ -19,64 +19,28 @@ package ql import ( + "testing" + "github.com/rabbitstack/fibratus/pkg/event" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) func TestSequenceExprIsEvaluable(t *testing.T) { var tests = []struct { - expr string - evt *event.Event - isEval bool - assertions func(t *testing.T, sexpr *SequenceExpr) + expr string + evt *event.Event + isEval bool }{ - {"evt.name = 'CreateProcess'", &event.Event{Type: event.CreateProcess, Category: event.Process}, true, - func(t *testing.T, sexpr *SequenceExpr) { - assert.True(t, sexpr.bitsets.IsInitialized(event.TypeBitSet)) - assert.False(t, sexpr.bitsets.IsInitialized(event.BitmaskBitSet)) - assert.False(t, sexpr.bitsets.IsInitialized(event.CategoryBitSet)) - }, - }, - {"evt.name = 'CreateProcess'", &event.Event{Type: event.TerminateProcess, Category: event.Process}, false, nil}, - {"evt.name = 'CreateProcess' or evt.name = 'TerminateThread'", &event.Event{Type: event.TerminateProcess, Category: event.Process}, false, nil}, - {"evt.name = 'CreateProcess' or evt.category = 'object'", &event.Event{Type: event.TerminateProcess, Category: event.Process}, false, nil}, - {"evt.name = 'CreateProcess' or evt.name = 'OpenProcess'", &event.Event{Type: event.OpenProcess, Category: event.Process}, true, - func(t *testing.T, sexpr *SequenceExpr) { - assert.True(t, sexpr.bitsets.IsInitialized(event.TypeBitSet)) - assert.False(t, sexpr.bitsets.IsInitialized(event.BitmaskBitSet)) - assert.False(t, sexpr.bitsets.IsInitialized(event.CategoryBitSet)) - }, - }, - {"evt.name = 'CreateProcess' or evt.name = 'CreateThread'", &event.Event{Type: event.CreateThread, Category: event.Thread}, true, - func(t *testing.T, sexpr *SequenceExpr) { - assert.False(t, sexpr.bitsets.IsInitialized(event.TypeBitSet)) - assert.True(t, sexpr.bitsets.IsInitialized(event.BitmaskBitSet)) - assert.False(t, sexpr.bitsets.IsInitialized(event.CategoryBitSet)) - }, - }, - {"evt.name = 'CreateProcess' or evt.category = 'registry'", &event.Event{Type: event.RegSetValue, Category: event.Registry}, true, - func(t *testing.T, sexpr *SequenceExpr) { - assert.True(t, sexpr.bitsets.IsInitialized(event.TypeBitSet)) - assert.False(t, sexpr.bitsets.IsInitialized(event.BitmaskBitSet)) - assert.True(t, sexpr.bitsets.IsInitialized(event.CategoryBitSet)) - }, - }, - {"evt.name = 'CreateProcess' or evt.name = 'OpenProcess' or evt.category = 'registry'", &event.Event{Type: event.OpenProcess, Category: event.Process}, true, - func(t *testing.T, sexpr *SequenceExpr) { - assert.True(t, sexpr.bitsets.IsInitialized(event.TypeBitSet)) - assert.False(t, sexpr.bitsets.IsInitialized(event.BitmaskBitSet)) - assert.True(t, sexpr.bitsets.IsInitialized(event.CategoryBitSet)) - }, - }, - {"evt.name = 'CreateProcess' or evt.name = 'SetThreadContext' or evt.category = 'registry'", &event.Event{Type: event.CreateProcess, Category: event.Process}, true, - func(t *testing.T, sexpr *SequenceExpr) { - assert.False(t, sexpr.bitsets.IsInitialized(event.TypeBitSet)) - assert.True(t, sexpr.bitsets.IsInitialized(event.BitmaskBitSet)) - assert.True(t, sexpr.bitsets.IsInitialized(event.CategoryBitSet)) - }, - }, + {"evt.name = 'CreateProcess'", &event.Event{Type: event.CreateProcess}, true}, + {"evt.name = 'CreateProcess'", &event.Event{Type: event.TerminateProcess}, false}, + {"evt.name = 'CreateProcess' or evt.name = 'TerminateThread'", &event.Event{Type: event.TerminateProcess}, false}, + {"evt.name = 'CreateProcess' or evt.category = 'object'", &event.Event{Type: event.TerminateProcess}, false}, + {"evt.name = 'CreateProcess' or evt.name = 'OpenProcess'", &event.Event{Type: event.OpenProcess}, true}, + {"evt.name = 'CreateProcess' or evt.name = 'CreateThread'", &event.Event{Type: event.CreateThread}, true}, + {"evt.name = 'CreateProcess' or evt.category = 'registry'", &event.Event{Type: event.RegSetValue}, true}, + {"evt.name = 'CreateProcess' or evt.name = 'OpenProcess' or evt.category = 'registry'", &event.Event{Type: event.OpenProcess}, true}, + {"evt.name = 'CreateProcess' or evt.name = 'SetThreadContext' or evt.category = 'registry'", &event.Event{Type: event.CreateProcess}, true}, } for _, tt := range tests { @@ -90,9 +54,6 @@ func TestSequenceExprIsEvaluable(t *testing.T) { sexpr.walk() assert.Equal(t, tt.isEval, sexpr.IsEvaluable(tt.evt)) - if tt.assertions != nil { - tt.assertions(t, sexpr) - } }) } } diff --git a/pkg/outputs/amqp/amqp_test.go b/pkg/outputs/amqp/amqp_test.go index 9a1539635..f7413f32c 100644 --- a/pkg/outputs/amqp/amqp_test.go +++ b/pkg/outputs/amqp/amqp_test.go @@ -21,11 +21,12 @@ package amqp import ( "encoding/json" "fmt" - "github.com/rabbitstack/fibratus/pkg/util/va" - "golang.org/x/sys/windows" "testing" "time" + "github.com/rabbitstack/fibratus/pkg/util/va" + "golang.org/x/sys/windows" + "github.com/phayes/freeport" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" @@ -194,16 +195,13 @@ func amqpURL(port int) string { //nolint:unused func getBatch() *event.Batch { evt := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -262,16 +260,13 @@ func getBatch() *event.Batch { } evt1 := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 459, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 459, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -330,16 +325,13 @@ func getBatch() *event.Batch { } evt2 := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 829, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 829, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, diff --git a/pkg/outputs/elasticsearch/elasticsearch_test.go b/pkg/outputs/elasticsearch/elasticsearch_test.go index b956e8aba..d25c93dbb 100644 --- a/pkg/outputs/elasticsearch/elasticsearch_test.go +++ b/pkg/outputs/elasticsearch/elasticsearch_test.go @@ -21,8 +21,6 @@ package elasticsearch import ( "bytes" "encoding/json" - "github.com/rabbitstack/fibratus/pkg/util/va" - "golang.org/x/sys/windows" "io" "net/http" "net/http/httptest" @@ -30,6 +28,9 @@ import ( "testing" "time" + "github.com/rabbitstack/fibratus/pkg/util/va" + "golang.org/x/sys/windows" + "github.com/olivere/elastic/v7" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/event/params" @@ -161,16 +162,13 @@ func getBatch() *event.Batch { ts, _ := time.Parse(time.RFC3339, "2018-05-03T15:04:05.323Z") evt := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: ts, - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: ts, + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -229,16 +227,13 @@ func getBatch() *event.Batch { } evt1 := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 459, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: ts, - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 459, + CPU: 1, + Seq: 2, + Timestamp: ts, + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -297,16 +292,13 @@ func getBatch() *event.Batch { } evt2 := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 829, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: ts, - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 829, + CPU: 1, + Seq: 2, + Timestamp: ts, + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, diff --git a/pkg/outputs/http/http_test.go b/pkg/outputs/http/http_test.go index c3995b560..e46e18a30 100644 --- a/pkg/outputs/http/http_test.go +++ b/pkg/outputs/http/http_test.go @@ -21,9 +21,6 @@ package http import ( "compress/gzip" "encoding/json" - "github.com/rabbitstack/fibratus/pkg/outputs" - "github.com/rabbitstack/fibratus/pkg/util/va" - "golang.org/x/sys/windows" "io" "log" "net" @@ -32,6 +29,10 @@ import ( "testing" "time" + "github.com/rabbitstack/fibratus/pkg/outputs" + "github.com/rabbitstack/fibratus/pkg/util/va" + "golang.org/x/sys/windows" + "github.com/stretchr/testify/assert" "github.com/rabbitstack/fibratus/pkg/event" @@ -148,16 +149,13 @@ func TestHttpGzipPublish(t *testing.T) { func getBatch() *event.Batch { evt := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 859, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -216,16 +214,13 @@ func getBatch() *event.Batch { } evt1 := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 459, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 459, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, @@ -284,16 +279,13 @@ func getBatch() *event.Batch { } evt2 := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: 829, - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: 829, + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "\\Device\\HarddiskVolume2\\Windows\\system32\\user32.dll"}, diff --git a/pkg/rules/_fixtures/field_values/correct_category_name_field.yml b/pkg/rules/_fixtures/field_values/correct_category_name_field.yml index 377b123cb..0970eb35a 100644 --- a/pkg/rules/_fixtures/field_values/correct_category_name_field.yml +++ b/pkg/rules/_fixtures/field_values/correct_category_name_field.yml @@ -1,5 +1,5 @@ name: match https connections id: 8f36f8e0-a5c2-498f-9563-eea306daa586 version: 1.0.0 -condition: evt.category = 'net' and net.dport = 443 +condition: evt.category = 'network' and net.dport = 443 min-engine-version: 2.0.0 diff --git a/pkg/rules/_fixtures/field_values/incorrect_category_name_field.yml b/pkg/rules/_fixtures/field_values/incorrect_category_name_field.yml index 0970eb35a..a3fa11c3c 100644 --- a/pkg/rules/_fixtures/field_values/incorrect_category_name_field.yml +++ b/pkg/rules/_fixtures/field_values/incorrect_category_name_field.yml @@ -1,5 +1,5 @@ name: match https connections id: 8f36f8e0-a5c2-498f-9563-eea306daa586 version: 1.0.0 -condition: evt.category = 'network' and net.dport = 443 +condition: evt.category = 'pipe' and net.dport = 443 min-engine-version: 2.0.0 diff --git a/pkg/rules/_fixtures/merged_filters/filter3.yml b/pkg/rules/_fixtures/merged_filters/filter3.yml index 66ac3f5e1..66fe167e8 100644 --- a/pkg/rules/_fixtures/merged_filters/filter3.yml +++ b/pkg/rules/_fixtures/merged_filters/filter3.yml @@ -1,5 +1,5 @@ name: match http connections id: 6f36f8e0-a5c2-498f-9563-eea306daa586 version: 1.0.0 -condition: evt.category = 'net' and net.dport = 80 +condition: evt.category = 'network' and net.dport = 80 min-engine-version: 2.0.0 diff --git a/pkg/rules/compiler.go b/pkg/rules/compiler.go index 65e76d7ed..a45a87bc4 100644 --- a/pkg/rules/compiler.go +++ b/pkg/rules/compiler.go @@ -125,7 +125,7 @@ func (c *compiler) compile() (map[*config.FilterConfig]filter.Filter, *config.Ru for _, v := range values { switch field { case fields.EvtName, fields.KevtName: - if !event.IsKnown(v) { + if !event.IsTypeKnown(v) { return nil, nil, ErrUnknownEventName(f.Name, v) } case fields.EvtCategory, fields.KevtCategory: @@ -266,7 +266,11 @@ func (c *compiler) containsEventTypes(root ql.Node, types ...event.Type) bool { evts := make([]event.Type, 0, len(vals)) for _, v := range vals { - evts = append(evts, event.NameToType(v)) + typ, ok := event.ParseType(v) + if !ok { + continue + } + evts = append(evts, typ) } for _, typ := range types { @@ -337,42 +341,46 @@ func (c *compiler) buildCompileResult(filters map[*config.FilterConfig]filter.Fi for name, values := range f.GetStringFields() { for _, v := range values { if name == fields.EvtName || name == fields.EvtCategory { - types := event.NameToTypes(v) - for _, typ := range types { - switch typ.Category() { - case event.Process: - rs.HasProcEvents = true - case event.Thread: - rs.HasThreadEvents = true - case event.Module: - rs.HasModuleEvents = true - case event.File: - rs.HasFileEvents = true - case event.Net: - rs.HasNetworkEvents = true - case event.Registry: - rs.HasRegistryEvents = true - case event.Mem: - rs.HasMemEvents = true - } - if typ.Subcategory() == event.DNS { - rs.HasDNSEvents = true - } - if typ == event.MapViewFile || typ == event.UnmapViewFile { - rs.HasVAMapEvents = true - } - if typ == event.OpenProcess || typ == event.OpenThread || typ == event.SetThreadContext || - typ == event.CreateSymbolicLinkObject { - rs.HasAuditAPIEvents = true - } - - if m[typ] { - continue - } - - events = append(events, typ) - m[typ] = true + typ, ok := event.ParseType(v) + if !ok { + continue + } + + info := event.GetTypeInfo(typ) + + switch info.Category { + case event.Process: + rs.HasProcEvents = true + case event.Thread: + rs.HasThreadEvents = true + case event.Module: + rs.HasModuleEvents = true + case event.File: + rs.HasFileEvents = true + case event.Network: + rs.HasNetworkEvents = true + case event.Registry: + rs.HasRegistryEvents = true + case event.Memory: + rs.HasMemEvents = true + } + if info.Subcategory == event.DNS { + rs.HasDNSEvents = true + } + if typ == event.MapViewOfSection || typ == event.UnmapViewOfSection { + rs.HasVAMapEvents = true } + if typ == event.OpenProcess || typ == event.OpenThread || typ == event.SetThreadContext || + typ == event.CreateSymbolicLinkObject { + rs.HasAuditAPIEvents = true + } + + if m[typ] { + continue + } + + events = append(events, typ) + m[typ] = true } } } diff --git a/pkg/rules/compiler_test.go b/pkg/rules/compiler_test.go index 404bf0c64..4bb2ab9bc 100644 --- a/pkg/rules/compiler_test.go +++ b/pkg/rules/compiler_test.go @@ -44,8 +44,7 @@ func TestCompile(t *testing.T) { assert.Contains(t, rs.UsedEvents, event.CreateProcess) assert.Contains(t, rs.UsedEvents, event.LoadModule) assert.Contains(t, rs.UsedEvents, event.QueryDNS) - assert.Contains(t, rs.UsedEvents, event.ConnectTCPv4) - assert.Contains(t, rs.UsedEvents, event.ConnectTCPv6) + assert.Contains(t, rs.UsedEvents, event.Connect) } func TestCompileMinEngineVersion(t *testing.T) { @@ -82,7 +81,7 @@ func TestCompileEventCategoryFieldNames(t *testing.T) { {"_fixtures/field_values/incorrect_event_name_field.yml", ErrUnknownEventName("match https connections", "RecvTcp4")}, {"_fixtures/field_values/incorrect_event_name_in_operator.yml", ErrUnknownEventName("match https connections", "CreateProc")}, {"_fixtures/field_values/correct_category_name_field.yml", nil}, - {"_fixtures/field_values/incorrect_category_name_field.yml", ErrUnknownCategoryName("match https connections", "network")}, + {"_fixtures/field_values/incorrect_category_name_field.yml", ErrUnknownCategoryName("match https connections", "pipe")}, } for _, tt := range tests { diff --git a/pkg/rules/engine.go b/pkg/rules/engine.go index cd0f4974a..618880af8 100644 --- a/pkg/rules/engine.go +++ b/pkg/rules/engine.go @@ -81,13 +81,13 @@ type compiledFilter struct { // filterset contains compiled filters indexed by event type and category. type filterset struct { types map[event.Type][]*compiledFilter - categories map[uint8][]*compiledFilter + categories map[event.Category][]*compiledFilter } func newFilterset() *filterset { fs := &filterset{ types: make(map[event.Type][]*compiledFilter), - categories: make(map[uint8][]*compiledFilter), + categories: make(map[event.Category][]*compiledFilter), } return fs } @@ -100,7 +100,7 @@ func (f *filterset) collect(e *event.Event) []*compiledFilter { if len(f.categories) == 0 { return f.types[e.Type] } - return append(f.types[e.Type], f.categories[e.Category.Index()]...) + return append(f.types[e.Type], f.categories[e.Category()]...) } func newCompiledFilter(f filter.Filter, c *config.FilterConfig, ss *sequenceState) *compiledFilter { @@ -196,12 +196,17 @@ func (e *Engine) Compile() (*config.RulesCompileResult, error) { for _, v := range values { switch name { case fields.EvtName: - for _, typ := range event.NameToTypes(v) { - e.filters.types[typ] = append(e.filters.types[typ], fltr) + typ, ok := event.ParseType(v) + if !ok { + continue } + e.filters.types[typ] = append(e.filters.types[typ], fltr) case fields.EvtCategory: - category := event.Category(v) - e.filters.categories[category.Index()] = append(e.filters.categories[category.Index()], fltr) + category, ok := event.ParseCategory(v) + if !ok { + continue + } + e.filters.categories[category] = append(e.filters.categories[category], fltr) } } } diff --git a/pkg/rules/engine_test.go b/pkg/rules/engine_test.go index f3d649052..96974545c 100644 --- a/pkg/rules/engine_test.go +++ b/pkg/rules/engine_test.go @@ -116,11 +116,9 @@ func wrapProcessEvent(e *event.Event, fn func(*event.Event) (bool, error)) bool func fireRules(t *testing.T, c *config.Config) bool { e := NewEngine(new(ps.SnapshotterMock), c) evt := &event.Event{ - Type: event.RecvTCPv4, - Name: "Recv", - Tid: 2484, - PID: 859, - Category: event.Net, + Type: event.Recv, + Tid: 2484, + PID: 859, Params: event.Params{ params.NetDport: {Name: params.NetDport, Type: params.Uint16, Value: uint16(443)}, params.NetSport: {Name: params.NetSport, Type: params.Uint16, Value: uint16(43123)}, @@ -141,7 +139,7 @@ func TestCompileIndexableFilters(t *testing.T) { compileRules(t, e) - assert.Len(t, e.filters.types, 5) + assert.Len(t, e.filters.types, 2) assert.Len(t, e.filters.categories, 1) var tests = []struct { @@ -149,10 +147,7 @@ func TestCompileIndexableFilters(t *testing.T) { wants int }{ {&event.Event{Type: event.CreateProcess}, 2}, - {&event.Event{Type: event.RecvUDPv6}, 3}, - {&event.Event{Type: event.RecvTCPv4}, 3}, - {&event.Event{Type: event.RecvTCPv4, Category: event.Net}, 4}, - {&event.Event{Category: event.Net}, 1}, + {&event.Event{Type: event.Recv}, 4}, } for _, tt := range tests { @@ -189,8 +184,6 @@ func TestRunSequenceRule(t *testing.T) { Seq: 1, Type: event.CreateProcess, Timestamp: time.Now(), - Category: event.Process, - Name: "CreateProcess", Tid: 2484, PID: 2243, PS: &types.PS{ @@ -208,10 +201,8 @@ func TestRunSequenceRule(t *testing.T) { Seq: 2, Type: event.CreateFile, Timestamp: time.Now().Add(time.Millisecond * 250), - Name: "CreateFile", Tid: 2484, PID: 2243, - Category: event.File, PS: &types.PS{ Name: "firefox.exe", Exe: "C:\\Program Files\\Mozilla Firefox\\firefox.exe", @@ -226,10 +217,8 @@ func TestRunSequenceRule(t *testing.T) { e3 := &event.Event{ Seq: 4, - Type: event.ConnectTCPv4, + Type: event.Connect, Timestamp: time.Now().Add(time.Second), - Category: event.Net, - Name: "Connect", Tid: 244, PID: 2243, PS: &types.PS{ @@ -279,8 +268,6 @@ func TestRunSequenceRuleWithPsUUIDLink(t *testing.T) { Seq: 1, Type: event.CreateProcess, Timestamp: time.Now(), - Category: event.Process, - Name: "CreateProcess", Tid: 2243, PID: uint32(os.Getpid()), PS: &types.PS{ @@ -299,10 +286,8 @@ func TestRunSequenceRuleWithPsUUIDLink(t *testing.T) { Seq: 2, Type: event.CreateFile, Timestamp: time.Now().Add(time.Second), - Name: "CreateFile", Tid: 2484, PID: uint32(os.Getpid()), - Category: event.File, PS: &types.PS{ PID: uint32(os.Getpid()), Name: "firefox.exe", @@ -342,8 +327,6 @@ func TestRunSimpleAndSequenceRules(t *testing.T) { Seq: 1, Type: event.CreateProcess, Timestamp: time.Now(), - Category: event.Process, - Name: "CreateProcess", Tid: 2484, PID: 2243, PS: &types.PS{ @@ -360,10 +343,8 @@ func TestRunSimpleAndSequenceRules(t *testing.T) { Seq: 2, Type: event.CreateFile, Timestamp: time.Now().Add(time.Millisecond * 544), - Name: "CreateFile", Tid: 2484, PID: 2243, - Category: event.File, PS: &types.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\cmd.exe", @@ -378,8 +359,6 @@ func TestRunSimpleAndSequenceRules(t *testing.T) { Seq: 10, Type: event.CreateProcess, Timestamp: time.Now().Add(time.Second * 2), - Category: event.Process, - Name: "CreateProcess", Tid: 2484, PID: 2243, PS: &types.PS{ @@ -427,11 +406,9 @@ func TestAlertAction(t *testing.T) { compileRules(t, e) evt := &event.Event{ - Type: event.RecvTCPv4, - Name: "Recv", - Tid: 2484, - PID: 859, - Category: event.Net, + Type: event.Recv, + Tid: 2484, + PID: 859, PS: &types.PS{ Name: "cmd.exe", }, @@ -489,10 +466,8 @@ func TestKillAction(t *testing.T) { evt := &event.Event{ Type: event.CreateProcess, Timestamp: time.Now(), - Name: "CreateProcess", Tid: 2484, PID: pi.ProcessId, - Category: event.Process, PS: &types.PS{ Name: "calc.exe", Exe: "C:\\Windows\\system32\\calc.exe", @@ -520,11 +495,9 @@ func BenchmarkRunRules(b *testing.B) { evts := []*event.Event{ { - Type: event.ConnectTCPv4, - Name: "Recv", - Tid: 2484, - PID: 859, - Category: event.Net, + Type: event.Connect, + Tid: 2484, + PID: 859, PS: &types.PS{ Name: "cmd.exe", }, @@ -537,11 +510,9 @@ func BenchmarkRunRules(b *testing.B) { Metadata: make(map[event.MetadataKey]any), }, { - Type: event.CreateProcess, - Name: "CreateProcess", - Category: event.Process, - Tid: 2484, - PID: 859, + Type: event.CreateProcess, + Tid: 2484, + PID: 859, PS: &types.PS{ Name: "powershell.exe", }, @@ -556,11 +527,9 @@ func BenchmarkRunRules(b *testing.B) { Metadata: make(map[event.MetadataKey]any), }, { - Type: event.CreateFile, - Name: "CreateFile", - Category: event.File, - Tid: 2484, - PID: 859, + Type: event.CreateFile, + Tid: 2484, + PID: 859, PS: &types.PS{ Name: "powershell.exe", }, diff --git a/pkg/rules/sequence_test.go b/pkg/rules/sequence_test.go index eb6397f55..dcf475307 100644 --- a/pkg/rules/sequence_test.go +++ b/pkg/rules/sequence_test.go @@ -66,7 +66,6 @@ func TestSequenceState(t *testing.T) { e1 := &event.Event{ Type: event.CreateProcess, - Name: "CreateProcess", Tid: 2484, PID: 859, Timestamp: time.Now(), @@ -82,7 +81,6 @@ func TestSequenceState(t *testing.T) { e2 := &event.Event{ Type: event.CreateFile, - Name: "CreateFile", Tid: 2484, PID: 4143, Timestamp: time.Now().Add(time.Second * 5), @@ -108,7 +106,6 @@ func TestSequenceState(t *testing.T) { e3 := &event.Event{ Type: event.CreateProcess, - Name: "CreateProcess", Timestamp: time.Now().Add(time.Second * 10), Tid: 2484, PID: 4143, @@ -171,7 +168,6 @@ func TestSequenceState(t *testing.T) { // expire entire sequence e4 := &event.Event{ Type: event.TerminateProcess, - Name: "TerminateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -212,7 +208,6 @@ func TestSimpleSequence(t *testing.T) { }{ {[]*event.Event{{ Type: event.CreateProcess, - Name: "CreateProcess", Timestamp: time.Now(), Tid: 2484, PID: 859, @@ -226,11 +221,9 @@ func TestSimpleSequence(t *testing.T) { Metadata: map[event.MetadataKey]any{"foo": "bar", "fooz": "barzz"}, }, { Type: event.CreateFile, - Name: "CreateFile", Timestamp: time.Now().Add(time.Second), Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", }, @@ -240,7 +233,6 @@ func TestSimpleSequence(t *testing.T) { Metadata: map[event.MetadataKey]any{"foo": "bar", "fooz": "barzz"}}}, []bool{false, true}}, {[]*event.Event{{ Type: event.CreateProcess, - Name: "CreateProcess", Timestamp: time.Now(), Tid: 2484, PID: 859, @@ -254,11 +246,9 @@ func TestSimpleSequence(t *testing.T) { Metadata: map[event.MetadataKey]any{"foo": "bar", "fooz": "barzz"}, }, { Type: event.CreateFile, - Name: "CreateFile", Timestamp: time.Now().Add(time.Second), Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", }, @@ -297,7 +287,6 @@ func TestSimpleSequenceMultiplePartials(t *testing.T) { e1 := &event.Event{ Type: event.CreateProcess, Timestamp: time.Now().Add(time.Duration(i) * time.Millisecond), - Name: "CreateProcess", Tid: 2484, PID: pid % 2, PS: &pstypes.PS{ @@ -312,10 +301,8 @@ func TestSimpleSequenceMultiplePartials(t *testing.T) { e2 := &event.Event{ Type: event.CreateFile, Timestamp: time.Now().Add(time.Duration(i) * time.Millisecond * 2), - Name: "CreateFile", Tid: 2484, PID: pid * 2, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\cmd.exe", @@ -337,7 +324,6 @@ func TestSimpleSequenceMultiplePartials(t *testing.T) { Seq: 20, Type: event.CreateProcess, Timestamp: time.Now().Add(time.Second), - Name: "CreateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -357,10 +343,8 @@ func TestSimpleSequenceMultiplePartials(t *testing.T) { Type: event.CreateFile, Seq: 22, Timestamp: time.Now().Add(time.Second * time.Duration(2)), - Name: "CreateFile", Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\cmd.exe", @@ -404,7 +388,6 @@ func TestUnconstrainedSequenceMatches(t *testing.T) { Seq: 20, Type: event.CreateProcess, Timestamp: time.Now().Add(time.Second), - Name: "CreateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -424,7 +407,6 @@ func TestUnconstrainedSequenceMatches(t *testing.T) { Seq: 21, Type: event.CreateProcess, Timestamp: time.Now().Add(time.Second * 2), - Name: "CreateProcess", Tid: 2484, PID: 1859, PS: &pstypes.PS{ @@ -444,10 +426,8 @@ func TestUnconstrainedSequenceMatches(t *testing.T) { Type: event.CreateFile, Seq: 25, Timestamp: time.Now().Add(time.Second * 3), - Name: "CreateFile", Tid: 2484, PID: 3859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\cmd.exe", @@ -490,7 +470,6 @@ func TestSimpleSequenceDeadline(t *testing.T) { e1 := &event.Event{ Type: event.CreateProcess, Timestamp: time.Now(), - Name: "CreateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -508,10 +487,8 @@ func TestSimpleSequenceDeadline(t *testing.T) { e2 := &event.Event{ Type: event.CreateFile, Timestamp: time.Now().Add(time.Millisecond * 200), - Name: "CreateFile", Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\svchost.exe", @@ -562,7 +539,6 @@ func TestSequenceMultiLinks(t *testing.T) { e1 := &event.Event{ Type: event.CreateProcess, Timestamp: time.Now(), - Name: "CreateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -580,10 +556,8 @@ func TestSequenceMultiLinks(t *testing.T) { e2 := &event.Event{ Type: event.CreateFile, Timestamp: time.Now().Add(time.Second), - Name: "CreateFile", Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\svchost.exe", @@ -616,8 +590,6 @@ func TestComplexSequence(t *testing.T) { Seq: 1, Type: event.CreateProcess, Timestamp: time.Now(), - Category: event.Process, - Name: "CreateProcess", Tid: 2484, PID: 2243, PS: &pstypes.PS{ @@ -637,10 +609,8 @@ func TestComplexSequence(t *testing.T) { Seq: 2, Type: event.CreateFile, Timestamp: time.Now().Add(time.Millisecond * 250), - Name: "CreateFile", Tid: 2484, PID: 2243, - Category: event.File, PS: &pstypes.PS{ Name: "firefox.exe", Exe: "C:\\Program Files\\Mozilla Firefox\\firefox.exe", @@ -660,10 +630,8 @@ func TestComplexSequence(t *testing.T) { e3 := &event.Event{ Seq: 4, - Type: event.ConnectTCPv4, + Type: event.Connect, Timestamp: time.Now().Add(time.Second), - Category: event.Net, - Name: "Connect", Tid: 244, PID: 2243, PS: &pstypes.PS{ @@ -710,10 +678,8 @@ func TestSequenceOOO(t *testing.T) { e1 := &event.Event{ Type: event.CreateFile, Timestamp: time.Now(), - Name: "CreateFile", Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\rundll32.exe", @@ -732,7 +698,6 @@ func TestSequenceOOO(t *testing.T) { e2 := &event.Event{ Type: event.OpenProcess, Timestamp: time.Now(), - Name: "OpenProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -771,7 +736,6 @@ func TestSequenceGC(t *testing.T) { e := &event.Event{ Type: event.OpenProcess, Timestamp: time.Now(), - Name: "OpenProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -816,7 +780,6 @@ func TestSequenceExpire(t *testing.T) { { Type: event.OpenProcess, Timestamp: time.Now(), - Name: "OpenProcess", Tid: 2484, PID: 4143, PS: &pstypes.PS{ @@ -832,7 +795,6 @@ func TestSequenceExpire(t *testing.T) { }, { Type: event.TerminateProcess, - Name: "TerminateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -859,8 +821,6 @@ func TestSequenceExpire(t *testing.T) { Seq: 1, Type: event.CreateProcess, Timestamp: time.Now(), - Category: event.Process, - Name: "CreateProcess", Tid: 2484, PID: 2243, PS: &pstypes.PS{ @@ -877,8 +837,6 @@ func TestSequenceExpire(t *testing.T) { Seq: 2, Type: event.CreateProcess, Timestamp: time.Now().Add(time.Second), - Category: event.Process, - Name: "CreateProcess", Tid: 2484, PID: 12243, PS: &pstypes.PS{ @@ -897,7 +855,6 @@ func TestSequenceExpire(t *testing.T) { }, { Type: event.TerminateProcess, - Name: "TerminateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -956,7 +913,6 @@ func TestSequenceBoundFields(t *testing.T) { e1 := &event.Event{ Type: event.CreateProcess, Timestamp: time.Now(), - Name: "CreateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -973,7 +929,6 @@ func TestSequenceBoundFields(t *testing.T) { e2 := &event.Event{ Type: event.CreateProcess, Timestamp: time.Now().Add(time.Millisecond * 20), - Name: "CreateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -990,10 +945,8 @@ func TestSequenceBoundFields(t *testing.T) { e3 := &event.Event{ Type: event.CreateFile, Timestamp: time.Now().Add(time.Second), - Name: "CreateFile", Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\svchost.exe", @@ -1006,12 +959,10 @@ func TestSequenceBoundFields(t *testing.T) { } e4 := &event.Event{ - Type: event.ConnectTCPv4, + Type: event.Connect, Timestamp: time.Now().Add(time.Second * 3), - Name: "Connect", Tid: 2484, PID: 859, - Category: event.File, PS: &pstypes.PS{ Name: "cmd.exe", Exe: "C:\\Windows\\system32\\svchost.exe", @@ -1050,8 +1001,6 @@ func TestSequenceBoundFieldsWithFunctions(t *testing.T) { e1 := &event.Event{ Type: event.CreateFile, - Name: "CreateFile", - Category: event.File, Timestamp: time.Now(), Tid: 2484, PID: 859, @@ -1067,8 +1016,6 @@ func TestSequenceBoundFieldsWithFunctions(t *testing.T) { e2 := &event.Event{ Type: event.RegSetValue, - Name: "RegSetValue", - Category: event.Registry, Timestamp: time.Now().Add(time.Millisecond * 5), Tid: 2484, PID: 859, @@ -1112,7 +1059,6 @@ func TestIsExpressionEvaluable(t *testing.T) { e1 := &event.Event{ Type: event.CreateProcess, - Name: "CreateProcess", Tid: 2484, PID: 859, PS: &pstypes.PS{ @@ -1127,7 +1073,6 @@ func TestIsExpressionEvaluable(t *testing.T) { e2 := &event.Event{ Type: event.RenameFile, - Name: "RenameFile", Tid: 2484, PID: 859, PS: &pstypes.PS{ diff --git a/pkg/symbolize/symbolizer_test.go b/pkg/symbolize/symbolizer_test.go index 1d27b1886..506905880 100644 --- a/pkg/symbolize/symbolizer_test.go +++ b/pkg/symbolize/symbolizer_test.go @@ -143,16 +143,13 @@ func TestProcessCallstackPeExports(t *testing.T) { } e := &event.Event{ - Type: event.CreateFile, - Tid: 2484, - PID: uint32(os.Getpid()), - CPU: 1, - Seq: 2, - Name: "CreateFile", - Timestamp: time.Now(), - Category: event.File, - Host: "archrabbit", - Description: "Creates or opens a new file, directory, I/O device, pipe, console", + Type: event.CreateFile, + Tid: 2484, + PID: uint32(os.Getpid()), + CPU: 1, + Seq: 2, + Timestamp: time.Now(), + Host: "archrabbit", Params: event.Params{ params.FileObject: {Name: params.FileObject, Type: params.Uint64, Value: uint64(12456738026482168384)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\system32\\mimi.dll"}, @@ -205,9 +202,7 @@ func TestProcessCallstackPeExports(t *testing.T) { PID: uint32(os.Getpid()), CPU: 1, Seq: 2, - Name: "UnloadModule", Timestamp: time.Now(), - Category: event.Module, Params: event.Params{ params.ModuleBase: {Name: params.ModuleBase, Type: params.Address, Value: uint64(0x7ffb5d8e11c4)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: `C:\Windows\System32\user32.dll`}, @@ -266,9 +261,7 @@ func TestProcessCallstack(t *testing.T) { PID: 2232, CPU: 1, Seq: 2, - Name: "CreatedProcess", Timestamp: time.Now(), - Category: event.Process, Host: "archrabbit", Params: event.Params{ params.ProcessParentID: {Name: params.ProcessParentID, Type: params.PID, Value: (uint32(os.Getpid()))}, @@ -289,9 +282,7 @@ func TestProcessCallstack(t *testing.T) { PID: 12345, CPU: 1, Seq: 3, - Name: "TerminateProcess", Timestamp: time.Now(), - Category: event.Process, Host: "archrabbit", Params: event.Params{ params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(os.Getpid())}, @@ -359,9 +350,7 @@ func TestKernelCallstackSymbolizationFromDriverStore(t *testing.T) { PID: 2232, CPU: 1, Seq: 2, - Name: "CreatedProcess", Timestamp: time.Now(), - Category: event.Process, Host: "archrabbit", Params: event.Params{ params.ProcessParentID: {Name: params.ProcessParentID, Type: params.PID, Value: uint32(os.Getpid())}, @@ -465,9 +454,7 @@ func TestSymbolizeEventParamAddress(t *testing.T) { PID: uint32(os.Getpid()), CPU: 1, Seq: 2, - Name: "CreateThread", Timestamp: time.Now(), - Category: event.Thread, Host: "archrabbit", Params: event.Params{ params.Callstack: {Name: params.Callstack, Type: params.Slice, Value: []va.Address{0x7ffb5c1d0396, 0x7ffb5d8e61f4, 0x7ffb3138592e, 0x7ffb313853b2, 0x2638e59e0a5}}, @@ -514,9 +501,7 @@ func TestProcessCallstackProcsTTL(t *testing.T) { PID: 1232, CPU: 1, Seq: 2, - Name: "CreatedProcess", Timestamp: time.Now().Add(time.Millisecond * time.Duration(n)), - Category: event.Process, Host: "archrabbit", Params: event.Params{ params.ProcessParentID: {Name: params.ProcessParentID, Type: params.PID, Value: (uint32(os.Getpid()))}, diff --git a/pkg/sys/etw/types.go b/pkg/sys/etw/types.go index cea293271..2d9fbaf53 100644 --- a/pkg/sys/etw/types.go +++ b/pkg/sys/etw/types.go @@ -64,6 +64,9 @@ const ImageKeyword = 0x40 // SetValueKeyword enables registry key value set events for Microsoft Windows Kernel Registry provider const SetValueKeyword = 0x100 +// CaptureRegistryValue is the undocumented ETW feature to enable captured data in RegSetValue events +var CaptureRegistryValue = 0x2 + const ( // EventHeaderExtTypeStackTrace64 indicates that the extended data contains the call stack if the event is captured on a 64-bit host EventHeaderExtTypeStackTrace64 uint16 = 0x0006 @@ -559,6 +562,11 @@ type ClassicEventID struct { _ [7]uint8 // reserved } +// IsEmpty indicates if event id has been initialized. +func (e ClassicEventID) IsEmpty() bool { + return (e.GUID.Data1 == 0 && e.GUID.Data2 == 0 && e.GUID.Data3 == 0 && len(e.GUID.Data4[:]) == 0) || e.Type == 0 +} + // EventFilterDescriptor defines the filter data that // a session passes to the provider's enable callback. type EventFilterDescriptor struct { @@ -674,32 +682,6 @@ func (e *EventRecord) Version() uint8 { return e.Header.EventDescriptor.Version } -// HookID returns either the opcode or the event ID. -func (e *EventRecord) HookID() uint16 { - if e.Header.EventDescriptor.Opcode > 0 { - return uint16(e.Header.EventDescriptor.Opcode) - } - return e.Header.EventDescriptor.ID -} - -// ID is an unsigned integer that uniquely -// identifies the event. Handy for bitmask -// operations. -func (e *EventRecord) ID() uint { - d1 := e.Header.ProviderID.Data1 - d2 := e.Header.ProviderID.Data2 - - id := uint(byte(d1>>24))<<56 | - uint(byte(d1>>16))<<48 | - uint(byte(d1>>8))<<40 | - uint(byte(d1))<<32 | - uint(byte(d2>>8))<<24 | - uint(byte(d2))<<16 | - uint(e.HookID()) - - return id -} - // Clone makes a copy of this event record and returns the // copy itself and the event buffer. The buffer must outlive // the event record instance. Both are drawn from pools. diff --git a/pkg/yara/config/config.go b/pkg/yara/config/config.go index c3385b116..ee0dcc0f3 100644 --- a/pkg/yara/config/config.go +++ b/pkg/yara/config/config.go @@ -164,7 +164,7 @@ func (c Config) ShouldSkipFile(file string) bool { // whether the process scan took place or a file/registry // key was scanned. func (c Config) AlertTitle(e *event.Event) string { - if (e.Category == event.File && e.GetParamAsString(params.FilePath) != "") || e.Category == event.Registry { + if (e.Category() == event.File && e.GetParamAsString(params.FilePath) != "") || e.Category() == event.Registry { return FileThreatAlertTitle } return MemoryThreatAlertTitle diff --git a/pkg/yara/config/config_test.go b/pkg/yara/config/config_test.go index 5ef364f86..96691d445 100644 --- a/pkg/yara/config/config_test.go +++ b/pkg/yara/config/config_test.go @@ -110,21 +110,21 @@ func TestAlertTitle(t *testing.T) { t string }{ { - &event.Event{Type: event.MapViewFile, Category: event.File}, + &event.Event{Type: event.CreateFile}, MemoryThreatAlertTitle, }, { - &event.Event{Type: event.MapViewFile, Category: event.File, + &event.Event{Type: event.CreateFile, Params: event.Params{params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: "C:\\Windows\\System32\\wusa.exe"}}, }, FileThreatAlertTitle, }, { - &event.Event{Type: event.RegSetValue, Category: event.Registry}, + &event.Event{Type: event.RegSetValue}, FileThreatAlertTitle, }, { - &event.Event{Type: event.LoadModule, Category: event.Module}, + &event.Event{Type: event.LoadModule}, MemoryThreatAlertTitle, }, } @@ -149,7 +149,7 @@ func TestAlertText(t *testing.T) { { "empty template and no threat_name meta", Config{}, - &event.Event{Type: event.LoadModule, Category: event.Module}, + &event.Event{Type: event.LoadModule}, ytypes.MatchRule{Rule: "Badlands Trojan"}, "Threat detected Badlands Trojan", nil, @@ -157,7 +157,7 @@ func TestAlertText(t *testing.T) { { "empty template and threat_name meta", Config{}, - &event.Event{Type: event.LoadModule, Category: event.Module}, + &event.Event{Type: event.LoadModule}, ytypes.MatchRule{Rule: "Badlands Trojan", Metas: []ytypes.Meta{{Identifier: "threat_name", Value: "Gravity Trojan"}}}, "Threat detected Gravity Trojan", nil, @@ -170,7 +170,7 @@ func TestAlertText(t *testing.T) { Event name: {{ .Event.Name -}} `, }, - &event.Event{Type: event.LoadModule, Name: "LoadModule", Category: event.Module}, + &event.Event{Type: event.LoadModule}, ytypes.MatchRule{Rule: "Badlands Trojan", Metas: []ytypes.Meta{{Identifier: "threat_name", Value: "Gravity Trojan"}}}, ` Rule name: Badlands Trojan @@ -185,7 +185,7 @@ func TestAlertText(t *testing.T) { Event name: {{ .Evet.Name -}} `, }, - &event.Event{Type: event.LoadModule, Name: "LoadModule", Category: event.Module}, + &event.Event{Type: event.LoadModule}, ytypes.MatchRule{Rule: "Badlands Trojan", Metas: []ytypes.Meta{{Identifier: "threat_name", Value: "Gravity Trojan"}}}, "", errors.New("yara alert template syntax error"), diff --git a/pkg/yara/scanner.go b/pkg/yara/scanner.go index 951246685..971740e67 100644 --- a/pkg/yara/scanner.go +++ b/pkg/yara/scanner.go @@ -271,7 +271,7 @@ func (s scanner) Scan(e *event.Event) (bool, error) { s.rwxs[pid] = addr } } - case event.MapViewFile: + case event.MapViewOfSection: if s.config.SkipMmaps { return false, nil } diff --git a/pkg/yara/scanner_test.go b/pkg/yara/scanner_test.go index 0912f1187..b8ef79804 100644 --- a/pkg/yara/scanner_test.go +++ b/pkg/yara/scanner_test.go @@ -125,7 +125,6 @@ func TestScan(t *testing.T) { e := &event.Event{ Type: event.CreateProcess, - Name: "CreateProcess", Tid: 2484, PID: 859, Params: event.Params{ @@ -206,7 +205,6 @@ func TestScan(t *testing.T) { e := &event.Event{ Type: event.CreateProcess, - Name: "CreateProcess", Tid: 2484, PID: 859, Params: event.Params{ @@ -289,7 +287,6 @@ func TestScan(t *testing.T) { e := &event.Event{ Type: event.LoadModule, - Name: "LoadModule", Tid: 2484, PID: pid, Params: event.Params{ @@ -343,11 +340,9 @@ func TestScan(t *testing.T) { psnap.On("Find", 565).Return(true, proc) e := &event.Event{ - Type: event.CreateFile, - Name: "CreateFile", - Category: event.File, - Tid: 2484, - PID: 565, + Type: event.CreateFile, + Tid: 2484, + PID: 565, Params: event.Params{ params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "notepad.exe")}, params.FileOperation: {Name: params.FileOperation, Type: params.Uint32, Value: uint32(windows.FILE_CREATE)}, @@ -396,11 +391,9 @@ func TestScan(t *testing.T) { psnap.On("Find", 565).Return(true, proc) e := &event.Event{ - Type: event.CreateFile, - Name: "CreateFile", - Category: event.File, - Tid: 2484, - PID: 565, + Type: event.CreateFile, + Tid: 2484, + PID: 565, Params: event.Params{ params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "System32", "cmd.exe")}, params.FileOperation: {Name: params.FileOperation, Type: params.Uint32, Value: uint32(windows.FILE_CREATE)}, @@ -447,11 +440,9 @@ func TestScan(t *testing.T) { psnap.On("Find", 565).Return(true, proc) e := &event.Event{ - Type: event.CreateFile, - Name: "CreateFile", - Category: event.File, - Tid: 2484, - PID: 565, + Type: event.CreateFile, + Tid: 2484, + PID: 565, Params: event.Params{ params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "splwow64.xml")}, params.FileOperation: {Name: params.FileOperation, Type: params.Uint32, Value: uint32(windows.FILE_CREATE)}, @@ -495,11 +486,9 @@ func TestScan(t *testing.T) { psnap.On("Find", 565).Return(true, proc) e := &event.Event{ - Type: event.CreateFile, - Name: "CreateFile", - Category: event.File, - Tid: 2484, - PID: 565, + Type: event.CreateFile, + Tid: 2484, + PID: 565, Params: event.Params{ params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "System32", "cmd.exe")}, params.FileOperation: {Name: params.FileOperation, Type: params.Uint32, Value: uint32(windows.FILE_CREATE)}, @@ -557,11 +546,9 @@ func TestScan(t *testing.T) { psnap.On("Find", 565).Return(true, proc) e := &event.Event{ - Type: event.CreateFile, - Name: "CreateFile", - Category: event.File, - Tid: 2484, - PID: 565, + Type: event.CreateFile, + Tid: 2484, + PID: 565, Params: event.Params{ params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: ads}, params.FileOperation: {Name: params.FileOperation, Type: params.Uint32, Value: uint32(windows.FILE_CREATE)}, @@ -640,11 +627,9 @@ func TestScan(t *testing.T) { psnap.On("Find", pid).Return(true, proc) e := &event.Event{ - Type: event.VirtualAlloc, - Name: "VirtualAlloc", - Category: event.Mem, - Tid: 2484, - PID: 565, + Type: event.VirtualAlloc, + Tid: 2484, + PID: 565, Params: event.Params{ params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: pid}, params.MemBaseAddress: {Name: params.MemBaseAddress, Type: params.Address, Value: uint64(0x7ffe0000)}, @@ -724,11 +709,9 @@ func TestScan(t *testing.T) { psnap.On("Find", pid).Return(true, proc) e := &event.Event{ - Type: event.MapViewFile, - Name: "MapViewFile", - Category: event.File, - Tid: 2484, - PID: 565, + Type: event.MapViewOfSection, + Tid: 2484, + PID: 565, Params: event.Params{ params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: pid}, params.FileViewBase: {Name: params.FileViewBase, Type: params.Address, Value: uint64(0x7ffe0000)}, @@ -778,11 +761,9 @@ func TestScan(t *testing.T) { psnap.On("Find", uint32(321321)).Return(true, proc) e := &event.Event{ - Type: event.MapViewFile, - Name: "MapViewFile", - Category: event.File, - Tid: 2484, - PID: 321321, + Type: event.MapViewOfSection, + Tid: 2484, + PID: 321321, Params: event.Params{ params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(321321)}, params.FileViewBase: {Name: params.FileViewBase, Type: params.Address, Value: uint64(0x7ffe0000)}, @@ -827,11 +808,9 @@ func TestScan(t *testing.T) { psnap.On("Find", 1123).Return(true, proc) e := &event.Event{ - Type: event.MapViewFile, - Name: "MapViewFile", - Category: event.File, - Tid: 2484, - PID: 565, + Type: event.MapViewOfSection, + Tid: 2484, + PID: 565, Params: event.Params{ params.ProcessID: {Name: params.ProcessID, Type: params.PID, Value: uint32(1123)}, params.FilePath: {Name: params.FilePath, Type: params.UnicodeString, Value: filepath.Join(os.Getenv("windir"), "regedit.exe")}, @@ -859,7 +838,7 @@ func TestScan(t *testing.T) { }) }, alertsender.Alert{ - Title: "File Threat Detected", + Title: "Memory Threat Detected", Text: "Threat detected Regedit", ID: "1abf9101-1e6e-4268-a530-e99e2c905b0d", Tags: []string{"T1"}, @@ -883,11 +862,9 @@ func TestScan(t *testing.T) { data := []byte{0x6F, 0x66, 0x74, 0x2E, 0x4E, 0x6F, 0x74, 0x65, 0x70, 0x61, 0x64, 0x00, 0x13, 0x00, 0x01, 0x1A} e := &event.Event{ - Type: event.RegSetValue, - Name: "RegSetValue", - Category: event.Registry, - Tid: 2484, - PID: 565, + Type: event.RegSetValue, + Tid: 2484, + PID: 565, Params: event.Params{ params.RegValueType: {Name: params.RegValueType, Type: params.Uint32, Value: uint32(registry.BINARY)}, params.RegData: {Name: params.RegValue, Type: params.Binary, Value: data}, @@ -949,7 +926,7 @@ func TestScan(t *testing.T) { assert.Contains(t, e.Metadata, event.YaraMatchesKey) } - if e.IsCreateProcess() || e.IsLoadModule() || e.IsVirtualAlloc() || e.IsMapViewFile() { + if e.IsCreateProcess() || e.IsLoadModule() || e.IsVirtualAlloc() || e.IsMapViewOfSection() { // cleanup proc, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, e.Params.MustGetPid()) if err == nil { From cb7c745496f9af0560be44366468b1c43f51992f Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Thu, 17 Sep 2026 18:11:16 +0200 Subject: [PATCH 2/3] refactor(rules): Adapt to refactored mmap events --- ...ion_activation_context_memory_section_hijacking.yml | 8 ++++---- ...sion_potential_ntdll_unhooking_via_file_mapping.yml | 6 +++--- ...al_process_injection_via_tainted_memory_section.yml | 5 +++-- ..._process_execution_from_hollowed_memory_section.yml | 6 +++--- ...on_process_execution_from_remote_memory_section.yml | 8 ++++---- rules/macros/macros.yml | 10 ++-------- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/rules/defense_evasion_activation_context_memory_section_hijacking.yml b/rules/defense_evasion_activation_context_memory_section_hijacking.yml index a7f9a1ae1..5e709e4b8 100644 --- a/rules/defense_evasion_activation_context_memory_section_hijacking.yml +++ b/rules/defense_evasion_activation_context_memory_section_hijacking.yml @@ -1,6 +1,6 @@ name: Activation Context memory section hijacking id: 3d56281e-9608-4a70-b7b7-7651ccd3752b -version: 1.0.1 +version: 1.0.2 description: | Detects abuses of a legitimate Windows feature present in most processes called Activation Contexts with the objective of loading an arbitrary DLL @@ -23,7 +23,7 @@ condition: > sequence maxspan 40s by ps.uuid, file.view.base - |unmap_view_file and + |unmap_view_of_section and file.view.type = 'PAGEFILE' and file.view.protection = 'READONLY' and (file.view.size = 12288 or (file.view.size = 4096 and not (ps.exe imatches '?:\\Windows\\explorer.exe') and @@ -38,8 +38,8 @@ condition: > not (ps.name iin ('procexp.exe', 'procexp64.exe') and ps.signature.trusted = true and ps.signature.subject imatches '*Microsoft Corporation*') ) | - |map_view_file and file.view.size = 12288 and file.view.type = 'PAGEFILE' and file.view.protection = 'READWRITE'| + |map_view_of_section and file.view.size = 12288 and file.view.type = 'PAGEFILE' and file.view.protection = 'READWRITE'| severity: high -min-engine-version: 3.0.0 +min-engine-version: 3.2.0 diff --git a/rules/defense_evasion_potential_ntdll_unhooking_via_file_mapping.yml b/rules/defense_evasion_potential_ntdll_unhooking_via_file_mapping.yml index d57bf6df9..99652e033 100644 --- a/rules/defense_evasion_potential_ntdll_unhooking_via_file_mapping.yml +++ b/rules/defense_evasion_potential_ntdll_unhooking_via_file_mapping.yml @@ -1,6 +1,6 @@ name: Potential NTDLL unhooking via file mapping id: b000955d-90df-44eb-8e32-8269d395f0ef -version: 1.0.1 +version: 1.0.2 description: | Identifies processes that map a fresh image view of NTDLL.dll from disk, a behavior commonly associated with user-mode API @@ -18,7 +18,7 @@ references: - https://github.com/hwbp/NTDLL-Unhook condition: > - map_view_file and + map_view_of_section and file.view.type = 'IMAGE' and evt.pid not in (0, 4) and file.path imatches ( @@ -36,4 +36,4 @@ condition: > severity: high -min-engine-version: 3.0.0 +min-engine-version: 3.2.0 diff --git a/rules/defense_evasion_potential_process_injection_via_tainted_memory_section.yml b/rules/defense_evasion_potential_process_injection_via_tainted_memory_section.yml index 89d877e76..2484ce030 100644 --- a/rules/defense_evasion_potential_process_injection_via_tainted_memory_section.yml +++ b/rules/defense_evasion_potential_process_injection_via_tainted_memory_section.yml @@ -1,6 +1,6 @@ name: Potential process injection via tainted memory section id: 8e4182f3-02e7-4e95-afc3-93d18c9a9c09 -version: 1.0.8 +version: 1.0.9 description: | Identifies potential process injection when the adversary creates and maps a memory section with RW protection rights followed by mapping of the same memory section in @@ -25,6 +25,7 @@ condition: > maxspan 1m |map_view_of_section and file.view.protection = 'READWRITE' and evt.pid != 4 and file.view.size >= 4096 and + file.view.type in ('IMAGE', 'IMAGE_NO_EXECUTE', 'PAGEFILE') and ps.exe not imatches ( '?:\\Program Files\\*.exe', @@ -52,4 +53,4 @@ condition: > action: - name: kill -min-engine-version: 3.0.0 +min-engine-version: 3.2.0 diff --git a/rules/defense_evasion_process_execution_from_hollowed_memory_section.yml b/rules/defense_evasion_process_execution_from_hollowed_memory_section.yml index b9e3f2374..b764c9b33 100644 --- a/rules/defense_evasion_process_execution_from_hollowed_memory_section.yml +++ b/rules/defense_evasion_process_execution_from_hollowed_memory_section.yml @@ -1,6 +1,6 @@ name: Process execution from hollowed memory section id: 2a3fbae8-5e8c-4b71-b9da-56c3958c0d53 -version: 2.1.4 +version: 2.1.5 description: | Adversaries may inject malicious code into suspended and hollowed processes in order to evade process-based defenses. Process hollowing is a method of executing arbitrary code @@ -23,7 +23,7 @@ condition: > maxspan 40s |unmap_view_of_section and evt.pid != 4 and ps.sid not in ('S-1-5-18', 'S-1-5-19', 'S-1-5-20') and - file.view.size > 20000 and file.view.protection != 'READONLY' and + file.view.type in ('IMAGE', 'IMAGE_NO_EXECUTE') and file.view.size > 20000 and file.view.protection != 'READONLY' and (file.name = '' or file.extension != '.dll') and ps.parent.exe not imatches ( @@ -47,4 +47,4 @@ output: > Process %2.ps.exe executed from hollowed memory section severity: high -min-engine-version: 3.0.0 +min-engine-version: 3.2.0 diff --git a/rules/defense_evasion_process_execution_from_remote_memory_section.yml b/rules/defense_evasion_process_execution_from_remote_memory_section.yml index 675f14035..d23446acf 100644 --- a/rules/defense_evasion_process_execution_from_remote_memory_section.yml +++ b/rules/defense_evasion_process_execution_from_remote_memory_section.yml @@ -1,6 +1,6 @@ name: Process execution from remote memory section id: 6e4cc918-a30e-4167-ba26-6356d6384f30 -version: 1.0.0 +version: 1.0.1 description: | Detects execution of a process image originating from a memory section mapped without a backing file, a strong indicator of advanced process @@ -21,9 +21,9 @@ references: condition: > sequence maxspan 1m - |map_view_file and + |map_view_of_section and evt.pid != 4 and ps.sid not in ('S-1-5-18', 'S-1-5-19', 'S-1-5-20') and - file.view.size > 50000 and file.path = '' and file.view.type = 'IMAGE' and + file.view.type in ('IMAGE', 'IMAGE_NO_EXECUTE') and file.view.size > 50000 and file.path = '' and file.view.type = 'IMAGE' and ps.exe not imatches ( '?:\\Windows\\System32\\dwm.exe', @@ -51,4 +51,4 @@ output: > Process %3.ps.exe executed from a remotely mapped memory section with no backing file severity: high -min-engine-version: 3.0.0 +min-engine-version: 3.2.0 diff --git a/rules/macros/macros.yml b/rules/macros/macros.yml index f4677eb4c..5205a48fd 100644 --- a/rules/macros/macros.yml +++ b/rules/macros/macros.yml @@ -91,17 +91,11 @@ - macro: virtual_free expr: evt.name = 'VirtualFree' -- macro: map_view_file - expr: evt.name = 'MapViewFile' - -- macro: unmap_view_file - expr: evt.name = 'UnmapViewFile' - - macro: map_view_of_section - expr: map_view_file and file.view.type in ('IMAGE', 'IMAGE_NO_EXECUTE', 'PAGEFILE') + expr: evt.name = 'MapViewOfSection' - macro: unmap_view_of_section - expr: unmap_view_file and file.view.type in ('IMAGE', 'IMAGE_NO_EXECUTE') + expr: evt.name = 'UnmapViewOfSection' - macro: query_dns expr: evt.name = 'QueryDns' From f048853677b2d1d0db1c5c48b5c1da4261e6e5e9 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Sat, 19 Sep 2026 13:44:59 +0200 Subject: [PATCH 3/3] refactor(event): Replace bitset with bespoke bitmap --- go.mod | 1 - go.sum | 2 - pkg/config/eventsource.go | 14 +-- pkg/event/category.go | 12 +-- pkg/event/types_windows.go | 3 - pkg/filter/ql/literal.go | 12 +-- pkg/util/bitmap/bitmap.go | 39 ++++++++ pkg/util/bitmap/bitmap_test.go | 169 +++++++++++++++++++++++++++++++++ pkg/util/bitmask/bitmask.go | 57 ----------- 9 files changed, 225 insertions(+), 84 deletions(-) create mode 100644 pkg/util/bitmap/bitmap.go create mode 100644 pkg/util/bitmap/bitmap_test.go delete mode 100644 pkg/util/bitmask/bitmask.go diff --git a/go.mod b/go.mod index 25f8b279b..b15fa21d4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ require ( github.com/Masterminds/sprig/v3 v3.2.2 github.com/Microsoft/go-winio v0.4.14 github.com/antchfx/htmlquery v1.2.5 - github.com/bits-and-blooms/bitset v1.13.0 github.com/briandowns/spinner v1.12.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/dustin/go-humanize v1.0.0 diff --git a/go.sum b/go.sum index b3a239c3c..b1ad4f534 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,6 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/aws/aws-sdk-go v1.34.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/briandowns/spinner v1.12.0 h1:72O0PzqGJb6G3KgrcIOtL/JAGGZ5ptOMCn9cUHmqsmw= github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= diff --git a/pkg/config/eventsource.go b/pkg/config/eventsource.go index 5d7c4e814..2d691a316 100644 --- a/pkg/config/eventsource.go +++ b/pkg/config/eventsource.go @@ -25,8 +25,8 @@ import ( "runtime" "time" - "github.com/bits-and-blooms/bitset" "github.com/rabbitstack/fibratus/pkg/event" + "github.com/rabbitstack/fibratus/pkg/util/bitmap" pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" "github.com/spf13/viper" @@ -97,7 +97,7 @@ type EventSourceConfig struct { // ExcludedImages are process image names that will be rejected if they generate a kernel event. ExcludedImages []string `json:"blacklist.images" yaml:"blacklist.images"` - dropMasks bitset.BitSet + dropBitmap bitmap.Bitmap[event.Type] excludedImages map[string]bool } @@ -124,7 +124,7 @@ func (c *EventSourceConfig) initFromViper(v *viper.Viper) { for _, name := range c.ExcludedEvents { if typ, ok := event.ParseType(name); ok { - c.dropMasks.Set(typ.Uint()) + c.dropBitmap.Set(typ) } } @@ -139,7 +139,7 @@ func (c *EventSourceConfig) Init() { for _, name := range c.ExcludedEvents { if typ, ok := event.ParseType(name); ok { - c.dropMasks.Set(typ.Uint()) + c.dropBitmap.Set(typ) } } @@ -152,19 +152,19 @@ func (c *EventSourceConfig) Init() { // instruct the given event type should be dropped from // the event stream. func (c *EventSourceConfig) SetDropMask(typ event.Type) { - c.dropMasks.Set(typ.Uint()) + c.dropBitmap.Set(typ) } // TestDropMask checks if the specified event type has // the drop mask in the bitset. func (c *EventSourceConfig) TestDropMask(typ event.Type) bool { - return c.dropMasks.Test(typ.Uint()) + return c.dropBitmap.Has(typ) } // ExcludeEvent determines whether the event type is declared // in the exclusion list. func (c *EventSourceConfig) ExcludeEvent(typ event.Type) bool { - return c.dropMasks.Test(typ.Uint()) + return c.dropBitmap.Has(typ) } // ExcludeImage determines whether the process generating event is present in the diff --git a/pkg/event/category.go b/pkg/event/category.go index a6cae35a7..1e807d365 100644 --- a/pkg/event/category.go +++ b/pkg/event/category.go @@ -43,20 +43,16 @@ const ( Object // Other is the category for uncategorized events Other - MaxCategory + MaxCategory // sentinel ) const ( // DNS designates the DNS (Domain Name Service) event subcategory - DNS Subcategory = iota + 1 - MaxSubcategory + DNS Subcategory = iota + 1 + MaxSubcategory // sentinel ) -// Uint coerces the category type to pointer-sized unsigned integer. -func (c Category) Uint() uint { - return uint(c) -} - +// String returns the category string representation. func (c Category) String() string { switch c { case Registry: diff --git a/pkg/event/types_windows.go b/pkg/event/types_windows.go index beb4e98da..d67e29bc5 100644 --- a/pkg/event/types_windows.go +++ b/pkg/event/types_windows.go @@ -359,9 +359,6 @@ func NewTypeFromEventRecord(r *etw.EventRecord) Type { // String returns the event type string representation. func (t Type) String() string { return table[t].Name } -// Uint coerces the type to pointer-sized unsigned integer. -func (t Type) Uint() uint { return uint(t) } - // OnlyState determines whether the event type is solely used for state management. func (t Type) OnlyState() bool { return table[t].Flags&OnlyState != 0 } diff --git a/pkg/filter/ql/literal.go b/pkg/filter/ql/literal.go index 042c7d548..9e632b289 100644 --- a/pkg/filter/ql/literal.go +++ b/pkg/filter/ql/literal.go @@ -25,9 +25,9 @@ import ( "strings" "time" - "github.com/bits-and-blooms/bitset" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/filter/fields" + "github.com/rabbitstack/fibratus/pkg/util/bitmap" "github.com/rabbitstack/fibratus/pkg/filter/ql/functions" ) @@ -289,8 +289,8 @@ type SequenceExpr struct { // Alias represents the sequence expression alias when bound fields are used. Alias string - categoryMask bitset.BitSet - eventMask bitset.BitSet + eventBitmap bitmap.Bitmap[event.Type] + categoryBitmap bitmap.Bitmap[event.Category] types []event.Type } @@ -356,13 +356,13 @@ func (e *SequenceExpr) walk() { continue } e.types = append(e.types, typ) - e.eventMask.Set(typ.Uint()) + e.eventBitmap.Set(typ) case fields.EvtCategory: category, ok := event.ParseCategory(v) if !ok { continue } - e.categoryMask.Set(category.Uint()) + e.categoryBitmap.Set(category) } } } @@ -373,7 +373,7 @@ func (e *SequenceExpr) walk() { // to be evaluated when the incoming event type, ID, or category pertains to the one // defined in the field literal. func (e *SequenceExpr) IsEvaluable(evt *event.Event) bool { - return e.eventMask.Test(evt.Type.Uint()) || e.categoryMask.Test(evt.Category().Uint()) + return e.eventBitmap.Has(evt.Type) || e.categoryBitmap.Has(evt.Category()) } // HasBoundFields determines if this sequence expression references any bound field. diff --git a/pkg/util/bitmap/bitmap.go b/pkg/util/bitmap/bitmap.go new file mode 100644 index 000000000..3ead349a2 --- /dev/null +++ b/pkg/util/bitmap/bitmap.go @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2026 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 bitmap + +// Bitmap is a set of bits backed by a uint64. The type parameter +// T represents the type used to identify bits. It may be any unsigned +// integer type or an alias of one. Valid bit values range from 0 to 63. +type Bitmap[T ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64] uint64 + +// Has reports whether the bit identified by b is set. +func (s Bitmap[T]) Has(b T) bool { + return s&(1<