diff --git a/internal/etw/consumer.go b/internal/etw/consumer.go index 6f5d2d979..03787376a 100644 --- a/internal/etw/consumer.go +++ b/internal/etw/consumer.go @@ -23,7 +23,6 @@ import ( "github.com/rabbitstack/fibratus/pkg/config" "github.com/rabbitstack/fibratus/pkg/event" "github.com/rabbitstack/fibratus/pkg/filter" - "github.com/rabbitstack/fibratus/pkg/handle" "github.com/rabbitstack/fibratus/pkg/ps" "github.com/rabbitstack/fibratus/pkg/sys/etw" ) @@ -47,15 +46,15 @@ type Consumer struct { // NewConsumer builds a new event consumer. func NewConsumer( psnap ps.Snapshotter, - hsnap handle.Snapshotter, config *config.Config, sequencer *event.Sequencer, evts chan *event.Event, + processors processors.Chain, ) *Consumer { return &Consumer{ q: event.NewQueueWithChannel(evts, config.EventSource.StackEnrichment, config.ForwardMode || config.IsCaptureSet()), sequencer: sequencer, - processors: processors.NewChain(psnap, hsnap, config), + processors: processors, psnap: psnap, config: config, } diff --git a/internal/etw/processors/registry_windows.go b/internal/etw/processors/registry_windows.go index 02f132aa7..a3ca6c7ea 100644 --- a/internal/etw/processors/registry_windows.go +++ b/internal/etw/processors/registry_windows.go @@ -27,6 +27,7 @@ import ( "os" "path/filepath" "strings" + "sync" "sync/atomic" "time" @@ -41,11 +42,20 @@ var ( return fmt.Errorf("unable to read value %s : %v", filepath.Join(key, subkey), err) } + // valueTTL specifies the maximum allowed period for RegSetValueInternal events + // to remain in the queue + valueTTL = time.Minute * 2 + // valuePurgerInterval specifies the purge interval for stale values + valuePurgerInterval = time.Minute + // kcbCount counts the total KCBs found during the duration of the kernel session kcbCount = expvar.NewInt("registry.kcb.count") kcbMissCount = expvar.NewInt("registry.kcb.misses") keyHandleHits = expvar.NewInt("registry.key.handle.hits") + readValueOps = expvar.NewInt("registry.read.value.ops") + capturedDataHits = expvar.NewInt("registry.data.hits") + handleThrottleCount uint32 ) @@ -57,6 +67,13 @@ type registryProcessor struct { // keys stores the mapping between the KCB (Key Control Block) and the key name. keys map[uint64]string hsnap handle.Snapshotter + + values map[uint32][]*event.Event + mu sync.Mutex + + purger *time.Ticker + + quit chan struct{} } func newRegistryProcessor(hsnap handle.Snapshotter) Processor { @@ -68,10 +85,18 @@ func newRegistryProcessor(hsnap handle.Snapshotter) Processor { atomic.StoreUint32(&handleThrottleCount, 0) } }() - return ®istryProcessor{ - keys: make(map[uint64]string), - hsnap: hsnap, + + r := ®istryProcessor{ + keys: make(map[uint64]string), + hsnap: hsnap, + values: make(map[uint32][]*event.Event), + purger: time.NewTicker(valuePurgerInterval), + quit: make(chan struct{}, 1), } + + go r.housekeep() + + return r } func (r *registryProcessor) ProcessEvent(e *event.Event) (*event.Event, bool, error) { @@ -93,6 +118,12 @@ func (r *registryProcessor) processEvent(e *event.Event) (*event.Event, error) { delete(r.keys, khandle) kcbCount.Add(-1) default: + if e.IsRegSetValueInternal() { + // store the event in temporary queue + r.pushSetValue(e) + return e, nil + } + khandle := e.Params.MustGetUint64(params.RegKeyHandle) // we have to obey a straightforward algorithm to connect relative // key names to their root keys. If key handle is equal to zero we @@ -116,7 +147,32 @@ func (r *registryProcessor) processEvent(e *event.Event) (*event.Event, error) { } } - // get the type/value of the registry key and append to parameters + if e.IsRegSetValue() { + // previously stored RegSetValueInternal event + // is popped from the queue. RegSetValue can + // be enriched with registry value type/data + v := r.popSetValue(e) + if v == nil { + // try to read captured data from userspace + goto readValue + } + + capturedDataHits.Add(1) + + // enrich the event with value data/type parameters + typ, err := v.Params.GetUint32(params.RegValueType) + if err == nil { + e.AppendEnum(params.RegValueType, typ, key.RegistryValueTypes) + } + data, err := v.Params.Get(params.RegData) + if err == nil { + e.AppendParam(params.RegData, data.Type, data.Value) + } + + return e, nil + } + + readValue: if !e.IsRegSetValue() || !e.IsSuccess() { return e, nil } @@ -126,36 +182,42 @@ func (r *registryProcessor) processEvent(e *event.Event) (*event.Event, error) { return e, nil } + // get the type/value of the registry key and append to parameters rootkey, subkey := key.Format(keyName) - if rootkey != key.Invalid { - typ, val, err := rootkey.ReadValue(subkey) - if err != nil { - errno, ok := err.(windows.Errno) - if ok && (errno.Is(os.ErrNotExist) || err == windows.ERROR_ACCESS_DENIED) { - return e, nil - } - return e, ErrReadValue(rootkey.String(), keyName, err) - } - e.AppendEnum(params.RegValueType, typ, key.RegistryValueTypes) - switch typ { - case registry.SZ, registry.EXPAND_SZ: - e.AppendParam(params.RegValue, params.UnicodeString, val) - case registry.MULTI_SZ: - e.AppendParam(params.RegValue, params.Slice, val) - case registry.BINARY: - e.AppendParam(params.RegValue, params.Binary, val) - case registry.QWORD: - e.AppendParam(params.RegValue, params.Uint64, val) - case registry.DWORD: - e.AppendParam(params.RegValue, params.Uint32, uint32(val.(uint64))) + if rootkey == key.Invalid { + return e, nil + } + + readValueOps.Add(1) + typ, val, err := rootkey.ReadValue(subkey) + if err != nil { + errno, ok := err.(windows.Errno) + if ok && (errno.Is(os.ErrNotExist) || err == windows.ERROR_ACCESS_DENIED) { + return e, nil } + return e, ErrReadValue(rootkey.String(), keyName, err) + } + e.AppendEnum(params.RegValueType, typ, key.RegistryValueTypes) + + switch typ { + case registry.SZ, registry.EXPAND_SZ: + e.AppendParam(params.RegData, params.UnicodeString, val) + case registry.MULTI_SZ: + e.AppendParam(params.RegData, params.Slice, val) + case registry.BINARY: + e.AppendParam(params.RegData, params.Binary, val) + case registry.QWORD: + e.AppendParam(params.RegData, params.Uint64, val) + case registry.DWORD: + e.AppendParam(params.RegData, params.Uint32, uint32(val.(uint64))) } } + return e, nil } -func (registryProcessor) Name() ProcessorType { return Registry } -func (registryProcessor) Close() {} +func (*registryProcessor) Name() ProcessorType { return Registry } +func (r *registryProcessor) Close() { r.quit <- struct{}{} } func (r *registryProcessor) findMatchingKey(pid uint32, relativeKeyName string) string { // we want to prevent too frequent queries on the process' handles @@ -166,10 +228,12 @@ func (r *registryProcessor) findMatchingKey(pid uint32, relativeKeyName string) if atomic.LoadUint32(&handleThrottleCount) > maxHandleQueries { return relativeKeyName } + handles, err := r.hsnap.FindHandles(pid) if err != nil { return relativeKeyName } + for _, h := range handles { if h.Type != handle.Key { continue @@ -179,5 +243,71 @@ func (r *registryProcessor) findMatchingKey(pid uint32, relativeKeyName string) return h.Name } } + return relativeKeyName } + +// pushSetValue stores the internal RegSetValue event +// into per process identifier queue. +func (r *registryProcessor) pushSetValue(e *event.Event) { + r.mu.Lock() + defer r.mu.Unlock() + vals, ok := r.values[e.PID] + if !ok { + r.values[e.PID] = []*event.Event{e} + } else { + r.values[e.PID] = append(vals, e) + } +} + +// popSetValue traverses the internal RegSetValue queue +// and pops the event if the suffixes match. +func (r *registryProcessor) popSetValue(e *event.Event) *event.Event { + r.mu.Lock() + defer r.mu.Unlock() + vals, ok := r.values[e.PID] + if !ok { + return nil + } + + var v *event.Event + for i := len(vals) - 1; i >= 0; i-- { + val := vals[i] + if strings.HasSuffix(e.GetParamAsString(params.RegPath), val.GetParamAsString(params.RegPath)) { + v = val + r.values[e.PID] = append(vals[:i], vals[i+1:]...) + break + } + } + + return v +} + +func (r *registryProcessor) valuesSize(pid uint32) int { + r.mu.Lock() + defer r.mu.Unlock() + return len(r.values[pid]) +} + +func (r *registryProcessor) housekeep() { + for { + select { + case <-r.purger.C: + r.mu.Lock() + for pid, vals := range r.values { + for i, val := range vals { + if time.Since(val.Timestamp) < valueTTL { + continue + } + r.values[pid] = append(vals[:i], vals[i+1:]...) + } + if len(vals) == 0 { + delete(r.values, pid) + } + } + r.mu.Unlock() + case <-r.quit: + return + } + } +} diff --git a/internal/etw/processors/registry_windows_test.go b/internal/etw/processors/registry_windows_test.go index 16c0d88db..fb2b6f014 100644 --- a/internal/etw/processors/registry_windows_test.go +++ b/internal/etw/processors/registry_windows_test.go @@ -23,11 +23,18 @@ import ( "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/handle" htypes "github.com/rabbitstack/fibratus/pkg/handle/types" + "github.com/rabbitstack/fibratus/pkg/util/key" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" + "time" ) +func init() { + valueTTL = time.Millisecond * 150 + valuePurgerInterval = time.Millisecond * 300 +} + func TestRegistryProcessor(t *testing.T) { var tests = []struct { name string @@ -161,7 +168,53 @@ func TestRegistryProcessor(t *testing.T) { func(e *event.Event, t *testing.T, hsnap *handle.SnapshotterMock, p Processor) { assert.Equal(t, `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`, e.GetParamAsString(params.RegPath)) assert.Equal(t, `REG_EXPAND_SZ`, e.GetParamAsString(params.RegValueType)) - assert.Equal(t, `%SystemRoot%`, e.GetParamAsString(params.RegValue)) + assert.Equal(t, `%SystemRoot%`, e.GetParamAsString(params.RegData)) + }, + }, + { + "process registry set value from internal event", + &event.Event{ + Type: event.RegSetValue, + Category: event.Registry, + PID: 23234, + Params: event.Params{ + params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`}, + params.RegKeyHandle: {Name: params.RegKeyHandle, Type: params.Uint64, Value: uint64(0)}, + }, + }, + func(p Processor) { + p.(*registryProcessor).values[23234] = []*event.Event{ + { + Type: event.RegSetValueInternal, + Timestamp: time.Now(), + Params: event.Params{ + params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\SessionId`}, + params.RegData: {Name: params.RegData, Type: params.UnicodeString, Value: "{ABD9EA10-87F6-11EB-9ED5-645D86501328}"}, + params.RegValueType: {Name: params.RegValueType, Type: params.Enum, Value: uint32(1), Enum: key.RegistryValueTypes}, + params.RegKeyHandle: {Name: params.RegKeyHandle, Type: params.Uint64, Value: uint64(0)}}, + }, + { + Type: event.RegSetValueInternal, + Timestamp: time.Now(), + Params: event.Params{ + params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\Directory`}, + params.RegData: {Name: params.RegData, Type: params.UnicodeString, Value: "%SYSTEMROOT%"}, + params.RegValueType: {Name: params.RegValueType, Type: params.Enum, Value: uint32(2), Enum: key.RegistryValueTypes}, + params.RegKeyHandle: {Name: params.RegKeyHandle, Type: params.Uint64, Value: uint64(0)}}, + }, + } + }, + func() *handle.SnapshotterMock { + hsnap := new(handle.SnapshotterMock) + return hsnap + }, + func(e *event.Event, t *testing.T, hsnap *handle.SnapshotterMock, p Processor) { + assert.Equal(t, `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`, e.GetParamAsString(params.RegPath)) + assert.Equal(t, `REG_EXPAND_SZ`, e.GetParamAsString(params.RegValueType)) + assert.Equal(t, `%SYSTEMROOT%`, e.GetParamAsString(params.RegData)) + assert.Equal(t, p.(*registryProcessor).valuesSize(23234), 1) + time.Sleep(time.Millisecond * 500) + assert.Equal(t, p.(*registryProcessor).valuesSize(23234), 0) }, }, } diff --git a/internal/etw/source.go b/internal/etw/source.go index 888d72ee6..347b99160 100644 --- a/internal/etw/source.go +++ b/internal/etw/source.go @@ -22,6 +22,7 @@ import ( "errors" "expvar" "fmt" + "github.com/rabbitstack/fibratus/internal/etw/processors" "github.com/rabbitstack/fibratus/pkg/config" errs "github.com/rabbitstack/fibratus/pkg/errors" "github.com/rabbitstack/fibratus/pkg/event" @@ -34,6 +35,7 @@ import ( log "github.com/sirupsen/logrus" "golang.org/x/sys/windows/registry" "time" + "unsafe" ) const ( @@ -69,9 +71,10 @@ var ( // starting ETW tracing sessions and setting up event // consumers. type EventSource struct { - r *config.RulesCompileResult - traces []*Trace - consumers []*Consumer + r *config.RulesCompileResult + traces []*Trace + consumers []*Consumer + processors processors.Chain errs chan error evts chan *event.Event @@ -96,17 +99,18 @@ func NewEventSource( compiler *config.RulesCompileResult, ) source.EventSource { evs := &EventSource{ - r: compiler, - traces: make([]*Trace, 0), - consumers: make([]*Consumer, 0), - errs: make(chan error, 1000), - evts: make(chan *event.Event, 500), - sequencer: event.NewSequencer(), - config: config, - stop: make(chan struct{}), - psnap: psnap, - hsnap: hsnap, - listeners: make([]event.Listener, 0), + r: compiler, + traces: make([]*Trace, 0), + consumers: make([]*Consumer, 0), + processors: processors.NewChain(psnap, hsnap, config), + errs: make(chan error, 1000), + evts: make(chan *event.Event, 500), + sequencer: event.NewSequencer(), + config: config, + stop: make(chan struct{}), + psnap: psnap, + hsnap: hsnap, + listeners: make([]event.Listener, 0), } return evs } @@ -170,6 +174,20 @@ func (e *EventSource) Open(config *config.Config) error { // from the snapshotter trace.AddProvider(etw.WindowsKernelProcessGUID, false, WithKeywords(etw.ProcessKeyword|etw.ImageKeyword), WithCaptureState()) + // in a similar vein, Windows Kernel Registry provider publishes + // the RegSetValue event with the full captured data for the + // 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)), + Size: 4, + } + trace.AddProvider(etw.WindowsKernelRegistryGUID, false, WithKeywords(etw.SetValueKeyword), WithEventFilterDescriptors(eventFilterDescriptor)) + } + if config.EventSource.EnableDNSEvents { trace.AddProvider(etw.DNSClientGUID, false) } @@ -228,7 +246,13 @@ func (e *EventSource) Open(config *config.Config) error { } // Init consumer and open the trace for processing - consumer := NewConsumer(e.psnap, e.hsnap, config, e.sequencer, e.evts) + consumer := NewConsumer( + e.psnap, + config, + e.sequencer, + e.evts, + e.processors, + ) consumer.SetFilter(e.filter) // Attach event listeners @@ -245,9 +269,8 @@ func (e *EventSource) Open(config *config.Config) error { log.Infof("starting [%s] trace processing", t.Name) // Instruct the provider to emit state information - err = t.CaptureState() - if err != nil { - log.Warn(err) + if err := t.CaptureState(); err != nil { + log.Warnf("unable to capture trace %s state: %v", t.Name, err) } // Start event processing loop diff --git a/internal/etw/trace.go b/internal/etw/trace.go index 98d7eb31b..e12969b8d 100644 --- a/internal/etw/trace.go +++ b/internal/etw/trace.go @@ -95,13 +95,18 @@ type ProviderInfo struct { // stackExtensions manager stack tracing enablement. // For each event present in the stack identifiers, // the StackWalk event is published by the provider. - stackExtensions *StackExtensions + stackExtensions *StackExtensions + eventFilterDescriptors []etw.EventFilterDescriptor } func (p *ProviderInfo) HasStackExtensions() bool { return p.stackExtensions != nil && !p.stackExtensions.Empty() } +func (p *ProviderInfo) HasEventFilterDescriptors() bool { + return len(p.eventFilterDescriptors) > 0 +} + // Trace is the essential building block for controlling // trace sessions and configuring event consumers. Such // operations include starting, stopping, and flushing @@ -150,9 +155,10 @@ type Trace struct { } type opts struct { - stackexts *StackExtensions - keywords uint64 - captureState bool + stackexts *StackExtensions + keywords uint64 + captureState bool + eventFilterDescriptors []etw.EventFilterDescriptor } // Option represents the option for the trace. @@ -181,6 +187,14 @@ func WithCaptureState() Option { } } +// WithEventFilterDescriptors assigns filters to be passed +// to the provider's enable callback function. +func WithEventFilterDescriptors(descriptors ...etw.EventFilterDescriptor) Option { + return func(o *opts) { + o.eventFilterDescriptors = descriptors + } +} + // NewKernelTrace creates a new NT Kernel Logger trace. func NewKernelTrace(config *config.Config) *Trace { t := &Trace{Name: etw.KernelLoggerSession, GUID: etw.KernelTraceControlGUID, stackExtensions: NewStackExtensions(config.EventSource), config: config} @@ -208,7 +222,7 @@ func (t *Trace) AddProvider(guid windows.GUID, enableStacks bool, options ...Opt t.Providers = append( t.Providers, - ProviderInfo{GUID: guid, Keywords: opts.keywords, EnableStacks: enableStacks, CaptureState: opts.captureState, stackExtensions: opts.stackexts}, + ProviderInfo{GUID: guid, Keywords: opts.keywords, EnableStacks: enableStacks, CaptureState: opts.captureState, stackExtensions: opts.stackexts, eventFilterDescriptors: opts.eventFilterDescriptors}, ) } @@ -325,8 +339,11 @@ func (t *Trace) Start() error { if err := etw.EnableTrace(provider.GUID, t.startHandle, provider.Keywords); err != nil { return err } - case provider.EnableStacks: - opts := etw.EnableTraceOpts{WithStacktrace: true} + case provider.EnableStacks || provider.HasEventFilterDescriptors(): + opts := etw.EnableTraceOpts{ + WithStacktrace: provider.EnableStacks, + EventFilterDescriptors: provider.eventFilterDescriptors, + } if err := etw.EnableTraceWithOpts(provider.GUID, t.startHandle, provider.Keywords, opts); err != nil { return err } diff --git a/pkg/event/event_windows.go b/pkg/event/event_windows.go index 0782a9740..70ffb16ad 100644 --- a/pkg/event/event_windows.go +++ b/pkg/event/event_windows.go @@ -233,6 +233,7 @@ func (e *Event) IsLoadImageInternal() bool { return e.Type == LoadImageInte func (e *Event) IsImageRundown() bool { return e.Type == ImageRundown } func (e *Event) IsFileOpEnd() bool { return e.Type == FileOpEnd } func (e *Event) IsRegSetValue() bool { return e.Type == RegSetValue } +func (e *Event) IsRegSetValueInternal() bool { return e.Type == RegSetValueInternal } 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 } diff --git a/pkg/event/metainfo_windows.go b/pkg/event/metainfo_windows.go index 7794315f0..d7913cf22 100644 --- a/pkg/event/metainfo_windows.go +++ b/pkg/event/metainfo_windows.go @@ -241,6 +241,7 @@ func AllWithState() []Type { s = append(s, CreateProcessInternal) s = append(s, ProcessRundownInternal) s = append(s, LoadImageInternal) + s = append(s, RegSetValueInternal) return s } diff --git a/pkg/event/param_windows.go b/pkg/event/param_windows.go index 870dd7ef9..0130265b8 100644 --- a/pkg/event/param_windows.go +++ b/pkg/event/param_windows.go @@ -19,6 +19,7 @@ package event import ( + "encoding/binary" "expvar" "fmt" "github.com/rabbitstack/fibratus/pkg/event/params" @@ -33,7 +34,9 @@ import ( "github.com/rabbitstack/fibratus/pkg/util/signature" "github.com/rabbitstack/fibratus/pkg/util/va" "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" "net" + "path/filepath" "strconv" "strings" "time" @@ -255,7 +258,7 @@ func (e *Event) produceParams(evt *etw.EventRecord) { } sid, soffset = evt.ReadSID(offset, true) name, noffset = evt.ReadAnsiString(soffset) - cmdline, _ = evt.ReadUTF16String(soffset + noffset) + cmdline, _ = evt.ReadUTF16String(noffset) e.AppendParam(params.ProcessObject, params.Address, kproc) e.AppendParam(params.ProcessID, params.PID, pid) e.AppendParam(params.ProcessParentID, params.PID, ppid) @@ -508,6 +511,34 @@ func (e *Event) produceParams(evt *etw.EventRecord) { e.AppendParam(params.RegKeyHandle, params.Address, keyHandle) e.AppendParam(params.RegPath, params.Key, keyName) e.AppendParam(params.NTStatus, params.Status, status) + case RegSetValueInternal: + keyObject := evt.ReadUint64(0) + status := evt.ReadUint32(8) + valueType := evt.ReadUint32(12) + keyName, koffset := evt.ReadUTF16String(20) // skip data size param (4 bytes) + valueName, voffset := evt.ReadUTF16String(koffset) + capturedSize := evt.ReadUint16(voffset) + capturedData := evt.ReadBytes(2+voffset, capturedSize) + + e.AppendParam(params.RegKeyHandle, params.Address, keyObject) + e.AppendParam(params.NTStatus, params.Status, status) + e.AppendParam(params.RegPath, params.Key, filepath.Join(keyName, valueName)) + e.AppendEnum(params.RegValueType, valueType, key.RegistryValueTypes) + + if len(capturedData) > 0 { + switch valueType { + case registry.SZ, registry.MULTI_SZ, registry.EXPAND_SZ: + e.AppendParam(params.RegData, params.UnicodeString, string(capturedData)) + case registry.BINARY: + e.AppendParam(params.RegData, params.Binary, capturedData) + case registry.DWORD: + e.AppendParam(params.RegData, params.Uint32, binary.LittleEndian.Uint32(capturedData)) + case registry.DWORD_BIG_ENDIAN: + e.AppendParam(params.RegData, params.Uint32, binary.BigEndian.Uint32(capturedData)) + case registry.QWORD: + e.AppendParam(params.RegData, params.Uint64, binary.LittleEndian.Uint64(capturedData)) + } + } case CreateFile: var ( irp uint64 diff --git a/pkg/event/params/params_windows.go b/pkg/event/params/params_windows.go index 8be05a3bf..1a860094d 100644 --- a/pkg/event/params/params_windows.go +++ b/pkg/event/params/params_windows.go @@ -149,6 +149,8 @@ const ( RegValue = "value" // RegValueType identifies the parameter that represents registry value type e.g (DWORD, BINARY) RegValueType = "value_type" + // RegData identifies the parameter that stores the captured registry data + RegData = "data" // ImageBase identifies the parameter name for the base address of the process in which the image is loaded. ImageBase = "base_address" diff --git a/pkg/event/types_windows.go b/pkg/event/types_windows.go index 52f7bb04b..d727e9178 100644 --- a/pkg/event/types_windows.go +++ b/pkg/event/types_windows.go @@ -67,6 +67,8 @@ var ( ThreadpoolGUID = windows.GUID{Data1: 0xc861d0e2, Data2: 0xa2c1, Data3: 0x4d36, Data4: [8]byte{0x9f, 0x9c, 0x97, 0x0b, 0xab, 0x94, 0x3a, 0x12}} // ProcessKernelEventGUID represents the Process Kernel event GUID ProcessKernelEventGUID = windows.GUID{Data1: 0x22fb2cd6, Data2: 0x0e7b, Data3: 0x422b, Data4: [8]byte{0xa0, 0xc7, 0x2f, 0xad, 0x1f, 0xd0, 0xe7, 0x16}} + // RegistryKernelEventGUID represents the Registry Kernel event GUID + RegistryKernelEventGUID = windows.GUID{Data1: 0x70eb4f03, Data2: 0xc1de, Data3: 0x4f73, Data4: [8]byte{0xa0, 0x51, 0x33, 0xd1, 0x3d, 0x54, 0x13, 0xbd}} ) var ( @@ -149,6 +151,10 @@ var ( RegDeleteKCB = pack(RegistryEventGUID, 23) // RegKCBRundown enumerates the registry keys open at the start of the kernel session. RegKCBRundown = pack(RegistryEventGUID, 25) + // RegSetValueInternal is the internal event that is used to + // enrich the corresponding public RegSetValue event with + // extra attributes + RegSetValueInternal = pack(RegistryKernelEventGUID, 36) // UnloadImage represents unload image kernel events UnloadImage = pack(ImageEventGUID, 2) @@ -309,7 +315,7 @@ func (t Type) String() string { return "RegQueryValue" case RegCreateKCB: return "RegCreateKCB" - case RegSetValue: + case RegSetValue, RegSetValueInternal: return "RegSetValue" case LoadImage, LoadImageInternal: return "LoadImage" @@ -367,7 +373,7 @@ func (t Type) Category() Category { FileRundown, FileOpEnd, ReleaseFile, MapViewFile, UnmapViewFile, MapFileRundown: return File case RegCreateKey, RegDeleteKey, RegOpenKey, RegCloseKey, RegQueryKey, RegQueryValue, RegSetValue, RegDeleteValue, - RegKCBRundown, RegDeleteKCB, RegCreateKCB: + RegKCBRundown, RegDeleteKCB, RegCreateKCB, RegSetValueInternal: return Registry case AcceptTCPv4, AcceptTCPv6, ConnectTCPv4, ConnectTCPv6, @@ -527,7 +533,8 @@ func (t Type) OnlyState() bool { ReleaseFile, MapFileRundown, RegCreateKCB, - RegDeleteKCB: + RegDeleteKCB, + RegSetValueInternal: return true default: return false @@ -600,7 +607,7 @@ func (t Type) ID() uint { // Source designates the provenance of this event type. func (t Type) Source() Source { switch t.GUID() { - case AuditAPIEventGUID, DNSEventGUID, ThreadpoolGUID, ProcessKernelEventGUID: + case AuditAPIEventGUID, DNSEventGUID, ThreadpoolGUID, ProcessKernelEventGUID, RegistryKernelEventGUID: return SecurityTelemetryLogger default: return SystemLogger diff --git a/pkg/filter/accessor_windows.go b/pkg/filter/accessor_windows.go index e3069ee59..d2ea35fd6 100644 --- a/pkg/filter/accessor_windows.go +++ b/pkg/filter/accessor_windows.go @@ -940,9 +940,14 @@ func (r *registryAccessor) Get(f Field, e *event.Event) (params.Value, error) { case fields.RegistryKeyHandle: return e.GetParamAsString(params.RegKeyHandle), nil case fields.RegistryValue: - return e.Params.GetRaw(params.RegValue) + if e.IsRegSetValue() { + return filepath.Base(filepath.Base(e.GetParamAsString(params.RegPath))), nil + } + return nil, nil case fields.RegistryValueType: return e.Params.GetString(params.RegValueType) + case fields.RegistryData: + return e.GetParamAsString(params.RegData), nil case fields.RegistryStatus: return e.GetParamAsString(params.NTStatus), nil } diff --git a/pkg/filter/fields/fields_windows.go b/pkg/filter/fields/fields_windows.go index 4c50c826f..84904fe02 100644 --- a/pkg/filter/fields/fields_windows.go +++ b/pkg/filter/fields/fields_windows.go @@ -479,10 +479,12 @@ const ( RegistryKeyName Field = "registry.key.name" // RegistryKeyHandle represents the registry KCB address RegistryKeyHandle Field = "registry.key.handle" - // RegistryValue represents the registry value + // RegistryValue represents the registry value name field RegistryValue Field = "registry.value" - // RegistryValueType represents the registry value type + // RegistryValueType represents the registry value type field RegistryValueType Field = "registry.value.type" + // RegistryData represents the captured registry data field + RegistryData Field = "registry.data" // RegistryStatus represent the registry operation status RegistryStatus Field = "registry.status" @@ -1000,8 +1002,9 @@ var fields = map[Field]FieldInfo{ RegistryPath: {RegistryPath, "fully qualified registry path", params.UnicodeString, []string{"registry.path = 'HKEY_LOCAL_MACHINE\\SYSTEM'"}, nil, nil}, RegistryKeyName: {RegistryKeyName, "registry key name", params.UnicodeString, []string{"registry.key.name = 'CurrentControlSet'"}, nil, nil}, RegistryKeyHandle: {RegistryKeyHandle, "registry key object address", params.Address, []string{"registry.key.handle = 'FFFFB905D60C2268'"}, nil, nil}, - RegistryValue: {RegistryValue, "registry value content", params.UnicodeString, []string{"registry.value = '%SystemRoot%\\system32'"}, nil, nil}, + RegistryValue: {RegistryValue, "registry value name", params.UnicodeString, []string{"registry.value = 'Epoch'"}, nil, nil}, RegistryValueType: {RegistryValueType, "type of registry value", params.UnicodeString, []string{"registry.value.type = 'REG_SZ'"}, nil, nil}, + RegistryData: {RegistryData, "registry value captured data", params.Object, []string{"registry.data = '%SystemRoot%'"}, nil, nil}, RegistryStatus: {RegistryStatus, "status of registry operation", params.UnicodeString, []string{"registry.status != 'success'"}, nil, nil}, NetDIP: {NetDIP, "destination IP address", params.IP, []string{"net.dip = 172.17.0.3"}, nil, nil}, diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go index a105f25d7..2b46669fc 100644 --- a/pkg/filter/filter_test.go +++ b/pkg/filter/filter_test.go @@ -863,7 +863,7 @@ func TestRegistryFilter(t *testing.T) { Category: event.Registry, Params: event.Params{ params.RegPath: {Name: params.RegPath, Type: params.UnicodeString, Value: `HKEY_LOCAL_MACHINE\SYSTEM\Setup\Pid`}, - params.RegValue: {Name: params.RegValue, Type: params.Uint32, Value: uint32(10234)}, + params.RegData: {Name: params.RegData, Type: params.Uint32, Value: uint32(10234)}, params.RegValueType: {Name: params.RegValueType, Type: params.AnsiString, Value: "DWORD"}, params.NTStatus: {Name: params.NTStatus, Type: params.AnsiString, Value: "success"}, params.RegKeyHandle: {Name: params.RegKeyHandle, Type: params.Address, Value: uint64(18446666033449935464)}, @@ -878,8 +878,9 @@ func TestRegistryFilter(t *testing.T) { {`registry.status startswith ('key not', 'succ')`, true}, {`registry.path = 'HKEY_LOCAL_MACHINE\\SYSTEM\\Setup\\Pid'`, true}, {`registry.key.name icontains ('Setup', 'setup')`, true}, - {`registry.value = 10234`, true}, + {`registry.value = 'Pid'`, true}, {`registry.value.type in ('DWORD', 'QWORD')`, true}, + {`registry.data = '10234'`, true}, {`MD5(registry.path) = 'eab870b2a516206575d2ffa2b98d8af5'`, true}, } diff --git a/pkg/sys/etw/etw.go b/pkg/sys/etw/etw.go index 32fbc1996..16905b101 100644 --- a/pkg/sys/etw/etw.go +++ b/pkg/sys/etw/etw.go @@ -236,6 +236,9 @@ func CaptureProviderState(guid windows.GUID, handle TraceHandle) error { type EnableTraceOpts struct { // WithStacktrace indicates call stack trace is added to the extended data of events. WithStacktrace bool + // EventFilterDescriptors defines the filter data that a session passes to the provider's + // enable callback function. + EventFilterDescriptors []EventFilterDescriptor } // EnableTraceWithOpts influences the behaviour of the specified event trace provider @@ -245,9 +248,16 @@ func EnableTraceWithOpts(guid windows.GUID, handle TraceHandle, keywords uint64, Version: EnableTraceParametersVersion, SourceID: guid, } + if opts.WithStacktrace { params.EnableProperty = EventEnablePropertyStacktrace } + + if len(opts.EventFilterDescriptors) > 0 { + params.EnableFilterDesc = uintptr(unsafe.Pointer(&opts.EventFilterDescriptors[0])) + params.FilterDescCount = uint32(len(opts.EventFilterDescriptors)) + } + err := enableTraceEx2(handle, &guid, ControlCodeEnableProvider, TraceLevelInformation, keywords, 0, 0, params) if err != nil { return os.NewSyscallError("EnableTraceEx2", err) diff --git a/pkg/sys/etw/types.go b/pkg/sys/etw/types.go index cd4c78ae7..34a9c6c6b 100644 --- a/pkg/sys/etw/types.go +++ b/pkg/sys/etw/types.go @@ -47,6 +47,9 @@ var ThreadpoolGUID = windows.GUID{Data1: 0xc861d0e2, Data2: 0xa2c1, Data3: 0x4d3 // WindowsKernelProcessGUID represents the GUID for the Microsoft Windows Kernel Process provider var WindowsKernelProcessGUID = windows.GUID{Data1: 0x22fb2cd6, Data2: 0x0e7b, Data3: 0x422b, Data4: [8]byte{0xa0, 0xc7, 0x2f, 0xad, 0x1f, 0xd0, 0xe7, 0x16}} +// WindowsKernelRegistryGUID represents the GUID for the Microsoft Windows Kernel Registry provider +var WindowsKernelRegistryGUID = windows.GUID{Data1: 0x70eb4f03, Data2: 0xc1de, Data3: 0x4f73, Data4: [8]byte{0xa0, 0x51, 0x33, 0xd1, 0x3d, 0x54, 0x13, 0xbd}} + const ( // TraceStackTracingInfo controls call stack tracing for kernel events TraceStackTracingInfo = uint8(3) @@ -60,6 +63,9 @@ const ProcessKeyword = 0x10 // ImageKeyword enables images events for Microsoft Windows Kernel Process provider const ImageKeyword = 0x40 +// SetValueKeyword enables registry key value set events for Microsoft Windows Kernel Registry provider +const SetValueKeyword = 0x100 + const ( // EventHeaderExtTypeStackTrace64 indicates that the extended data contains the call stack if the event is captured on a 64-bit host EventHeaderExtTypeStackTrace64 uint16 = 0x0006 @@ -555,6 +561,14 @@ type ClassicEventID struct { _ [7]uint8 // reserved } +// EventFilterDescriptor defines the filter data that +// a session passes to the provider's enable callback. +type EventFilterDescriptor struct { + Ptr uintptr + Size uint32 + Type uint32 +} + // NewClassicEventID creates a new instance of classic event identifier. func NewClassicEventID(guid windows.GUID, typ uint16) ClassicEventID { return ClassicEventID{GUID: guid, Type: uint8(typ)} @@ -604,7 +618,7 @@ func (e *EventRecord) ReadBytes(offset uint16, count uint16) []byte { if offset > e.BufferLen { return nil } - return (*[1<<30 - 1]byte)(unsafe.Pointer(e.Buffer + uintptr(offset) + uintptr(count)))[:count:count] + return (*[1<<30 - 1]byte)(unsafe.Pointer(e.Buffer + uintptr(offset)))[:count:count] } // ReadUint16 reads the uint16 value from the buffer at the specified offset. @@ -637,6 +651,7 @@ func (e *EventRecord) ReadAnsiString(offset uint16) (string, uint16) { if offset > e.BufferLen { return "", 0 } + b := make([]byte, e.BufferLen) var i uint16 for i < e.BufferLen { @@ -647,49 +662,52 @@ func (e *EventRecord) ReadAnsiString(offset uint16) (string, uint16) { b[i] = c i++ } + + if i == 0 { + return "", offset + 1 + } + if int(i) > len(b) { - return string(b[:len(b)-1]), uint16(len(b)) + return string(b[:len(b)-1]), uint16(len(b)) + offset } - return string(b[:i]), i + 1 + + return string(b[:i]), i + 1 + offset } // ReadUTF16String reads the UTF-16 string from the buffer at the specified offset. -// Returns the UTF-8 string and the number of bytes read from the string. +// Returns the UTF-8 string and the number of bytes read from the string + the offset. func (e *EventRecord) ReadUTF16String(offset uint16) (string, uint16) { if offset > e.BufferLen { return "", 0 } + // we're reading the leading string. First, calculate + // the length of the null-terminated UTF16 string + i := offset var length uint16 - - if offset > 0 { - length = e.BufferLen - offset - } else { - // we're reading the leading string. First, calculate - // the length of the null-terminated UTF16 string - var i uint16 - for i < e.BufferLen { - c := *(*uint16)(unsafe.Pointer(e.Buffer + uintptr(i))) - if c == 0 { - break // null terminator - } - length += 2 - i += 2 + for i < e.BufferLen { + c := *(*uint16)(unsafe.Pointer(e.Buffer + uintptr(i))) + if c == 0 { + break // null terminator } + length += 2 + i += 2 } - s := (*[1<<30 - 1]uint16)(unsafe.Pointer(e.Buffer + uintptr(offset)))[:length:length] - if offset > 0 { - return utf16.Decode(s[:len(s)/2-1-2]), uint16(len(s) + 2) + if length == 0 { + return "", offset + 2 // null terminator size } - return utf16.Decode(s[:len(s)/2]), uint16(len(s) + 2) + b := (*[1<<30 - 1]uint16)(unsafe.Pointer(e.Buffer + uintptr(offset)))[:length:length] + s := b[:len(b)/2] + + return utf16.Decode(s), uint16((len(s)+1)*2) + offset } // ReadNTUnicodeString reads the native Unicode string at the given offset. func (e *EventRecord) ReadNTUnicodeString(offset uint16) (string, uint16) { if offset > e.BufferLen { - return "", offset + return "", 0 } i := offset @@ -704,7 +722,7 @@ func (e *EventRecord) ReadNTUnicodeString(offset uint16) (string, uint16) { } if length == 0 { - return "", offset + return "", offset + 2 // null terminator size } b := (*[1<<30 - 1]byte)(unsafe.Pointer(e.Buffer + uintptr(offset)))[:length:length] @@ -715,7 +733,7 @@ func (e *EventRecord) ReadNTUnicodeString(offset uint16) (string, uint16) { Buffer: (*uint16)(unsafe.Pointer(&b[0])), } - return s.String(), offset + s.Length + return s.String(), s.Length + offset } // ConsumeUTF16String reads the byte slice with UTF16-encoded string diff --git a/pkg/sys/etw/types_test.go b/pkg/sys/etw/types_test.go index f82c7c7ef..f65318e81 100644 --- a/pkg/sys/etw/types_test.go +++ b/pkg/sys/etw/types_test.go @@ -84,7 +84,7 @@ func TestReadBuffer(t *testing.T) { name, noffset := ev.ReadAnsiString(offset) assert.Equal(t, "cmd.exe", name) - cmdline, _ := ev.ReadUTF16String(noffset + offset) + cmdline, _ := ev.ReadUTF16String(noffset) assert.Equal(t, "C:\\WINDOWS\\system32\\cmd.exe /c dir /-C /W \"\\\\?\\c:\\Users\\nedo\\AppData\\Roaming\\RabbitMQ\\db\\rabbit@archrabbit-mnesia\"", cmdline) }, }, diff --git a/pkg/yara/scanner.go b/pkg/yara/scanner.go index fb3bf21bd..513ea64ce 100644 --- a/pkg/yara/scanner.go +++ b/pkg/yara/scanner.go @@ -334,7 +334,7 @@ func (s scanner) Scan(e *event.Event) (bool, error) { if typ := e.Params.TryGetUint32(params.RegValueType); typ != registry.BINARY { return false, nil } - v, err := e.Params.Get(params.RegValue) + v, err := e.Params.Get(params.RegData) if err != nil { // value not attached to the event return false, nil diff --git a/pkg/yara/scanner_test.go b/pkg/yara/scanner_test.go index 80f0d3a36..500a9df1d 100644 --- a/pkg/yara/scanner_test.go +++ b/pkg/yara/scanner_test.go @@ -933,7 +933,7 @@ func TestScan(t *testing.T) { PID: 565, Params: event.Params{ params.RegValueType: {Name: params.RegValueType, Type: params.Uint32, Value: uint32(registry.BINARY)}, - params.RegValue: {Name: params.RegValue, Type: params.Binary, Value: data}, + params.RegData: {Name: params.RegValue, Type: params.Binary, Value: data}, params.RegPath: {Name: params.RegPath, Type: params.UnicodeString, Value: `HKEY_LOCAL_MACHINE\CurrentControlSet\Control\DeviceGuard\Mal`}, }, Metadata: make(map[event.MetadataKey]any),