Skip to content

Commit e38d5a3

Browse files
committed
feat(etw): Enable Windows Kernel Registry provider
This provider is enabled with the hidden filter flag to make the ETW session write captured registry data. The registry event processor keeps the queue of received internal set value events and enriches subsequent RegSetValue events emitted by the NT Kernel Logger provider.
1 parent b4c44cc commit e38d5a3

File tree

6 files changed

+284
-56
lines changed

6 files changed

+284
-56
lines changed

internal/etw/consumer.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/rabbitstack/fibratus/pkg/config"
2424
"github.com/rabbitstack/fibratus/pkg/event"
2525
"github.com/rabbitstack/fibratus/pkg/filter"
26-
"github.com/rabbitstack/fibratus/pkg/handle"
2726
"github.com/rabbitstack/fibratus/pkg/ps"
2827
"github.com/rabbitstack/fibratus/pkg/sys/etw"
2928
)
@@ -47,15 +46,15 @@ type Consumer struct {
4746
// NewConsumer builds a new event consumer.
4847
func NewConsumer(
4948
psnap ps.Snapshotter,
50-
hsnap handle.Snapshotter,
5149
config *config.Config,
5250
sequencer *event.Sequencer,
5351
evts chan *event.Event,
52+
processors processors.Chain,
5453
) *Consumer {
5554
return &Consumer{
5655
q: event.NewQueueWithChannel(evts, config.EventSource.StackEnrichment, config.ForwardMode || config.IsCaptureSet()),
5756
sequencer: sequencer,
58-
processors: processors.NewChain(psnap, hsnap, config),
57+
processors: processors,
5958
psnap: psnap,
6059
config: config,
6160
}

internal/etw/processors/registry_windows.go

Lines changed: 157 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"os"
2828
"path/filepath"
2929
"strings"
30+
"sync"
3031
"sync/atomic"
3132
"time"
3233

@@ -41,11 +42,20 @@ var (
4142
return fmt.Errorf("unable to read value %s : %v", filepath.Join(key, subkey), err)
4243
}
4344

45+
// valueTTL specifies the maximum allowed period for RegSetValueInternal events
46+
// to remain in the queue
47+
valueTTL = time.Minute * 2
48+
// valuePurgerInterval specifies the purge interval for stale values
49+
valuePurgerInterval = time.Minute
50+
4451
// kcbCount counts the total KCBs found during the duration of the kernel session
4552
kcbCount = expvar.NewInt("registry.kcb.count")
4653
kcbMissCount = expvar.NewInt("registry.kcb.misses")
4754
keyHandleHits = expvar.NewInt("registry.key.handle.hits")
4855

56+
readValueOps = expvar.NewInt("registry.read.value.ops")
57+
capturedDataHits = expvar.NewInt("registry.data.hits")
58+
4959
handleThrottleCount uint32
5060
)
5161

@@ -57,6 +67,13 @@ type registryProcessor struct {
5767
// keys stores the mapping between the KCB (Key Control Block) and the key name.
5868
keys map[uint64]string
5969
hsnap handle.Snapshotter
70+
71+
values map[uint32][]*event.Event
72+
mu sync.Mutex
73+
74+
purger *time.Ticker
75+
76+
quit chan struct{}
6077
}
6178

6279
func newRegistryProcessor(hsnap handle.Snapshotter) Processor {
@@ -68,10 +85,18 @@ func newRegistryProcessor(hsnap handle.Snapshotter) Processor {
6885
atomic.StoreUint32(&handleThrottleCount, 0)
6986
}
7087
}()
71-
return &registryProcessor{
72-
keys: make(map[uint64]string),
73-
hsnap: hsnap,
88+
89+
r := &registryProcessor{
90+
keys: make(map[uint64]string),
91+
hsnap: hsnap,
92+
values: make(map[uint32][]*event.Event),
93+
purger: time.NewTicker(valuePurgerInterval),
94+
quit: make(chan struct{}, 1),
7495
}
96+
97+
go r.housekeep()
98+
99+
return r
75100
}
76101

77102
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) {
93118
delete(r.keys, khandle)
94119
kcbCount.Add(-1)
95120
default:
121+
if e.IsRegSetValueInternal() {
122+
// store the event in temporary queue
123+
r.pushSetValue(e)
124+
return e, nil
125+
}
126+
96127
khandle := e.Params.MustGetUint64(params.RegKeyHandle)
97128
// we have to obey a straightforward algorithm to connect relative
98129
// 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) {
116147
}
117148
}
118149

119-
// get the type/value of the registry key and append to parameters
150+
if e.IsRegSetValue() {
151+
// previously stored RegSetValueInternal event
152+
// is popped from the queue. RegSetValue can
153+
// be enriched with registry value type/data
154+
v := r.popSetValue(e)
155+
if v == nil {
156+
// try to read captured data from userspace
157+
goto readValue
158+
}
159+
160+
capturedDataHits.Add(1)
161+
162+
// enrich the event with value data/type parameters
163+
typ, err := v.Params.GetUint32(params.RegValueType)
164+
if err == nil {
165+
e.AppendEnum(params.RegValueType, typ, key.RegistryValueTypes)
166+
}
167+
data, err := v.Params.Get(params.RegData)
168+
if err == nil {
169+
e.AppendParam(params.RegData, data.Type, data.Value)
170+
}
171+
172+
return e, nil
173+
}
174+
175+
readValue:
120176
if !e.IsRegSetValue() || !e.IsSuccess() {
121177
return e, nil
122178
}
@@ -126,36 +182,42 @@ func (r *registryProcessor) processEvent(e *event.Event) (*event.Event, error) {
126182
return e, nil
127183
}
128184

185+
// get the type/value of the registry key and append to parameters
129186
rootkey, subkey := key.Format(keyName)
130-
if rootkey != key.Invalid {
131-
typ, val, err := rootkey.ReadValue(subkey)
132-
if err != nil {
133-
errno, ok := err.(windows.Errno)
134-
if ok && (errno.Is(os.ErrNotExist) || err == windows.ERROR_ACCESS_DENIED) {
135-
return e, nil
136-
}
137-
return e, ErrReadValue(rootkey.String(), keyName, err)
138-
}
139-
e.AppendEnum(params.RegValueType, typ, key.RegistryValueTypes)
140-
switch typ {
141-
case registry.SZ, registry.EXPAND_SZ:
142-
e.AppendParam(params.RegValue, params.UnicodeString, val)
143-
case registry.MULTI_SZ:
144-
e.AppendParam(params.RegValue, params.Slice, val)
145-
case registry.BINARY:
146-
e.AppendParam(params.RegValue, params.Binary, val)
147-
case registry.QWORD:
148-
e.AppendParam(params.RegValue, params.Uint64, val)
149-
case registry.DWORD:
150-
e.AppendParam(params.RegValue, params.Uint32, uint32(val.(uint64)))
187+
if rootkey == key.Invalid {
188+
return e, nil
189+
}
190+
191+
readValueOps.Add(1)
192+
typ, val, err := rootkey.ReadValue(subkey)
193+
if err != nil {
194+
errno, ok := err.(windows.Errno)
195+
if ok && (errno.Is(os.ErrNotExist) || err == windows.ERROR_ACCESS_DENIED) {
196+
return e, nil
151197
}
198+
return e, ErrReadValue(rootkey.String(), keyName, err)
199+
}
200+
e.AppendEnum(params.RegValueType, typ, key.RegistryValueTypes)
201+
202+
switch typ {
203+
case registry.SZ, registry.EXPAND_SZ:
204+
e.AppendParam(params.RegData, params.UnicodeString, val)
205+
case registry.MULTI_SZ:
206+
e.AppendParam(params.RegData, params.Slice, val)
207+
case registry.BINARY:
208+
e.AppendParam(params.RegData, params.Binary, val)
209+
case registry.QWORD:
210+
e.AppendParam(params.RegData, params.Uint64, val)
211+
case registry.DWORD:
212+
e.AppendParam(params.RegData, params.Uint32, uint32(val.(uint64)))
152213
}
153214
}
215+
154216
return e, nil
155217
}
156218

157-
func (registryProcessor) Name() ProcessorType { return Registry }
158-
func (registryProcessor) Close() {}
219+
func (*registryProcessor) Name() ProcessorType { return Registry }
220+
func (r *registryProcessor) Close() { r.quit <- struct{}{} }
159221

160222
func (r *registryProcessor) findMatchingKey(pid uint32, relativeKeyName string) string {
161223
// we want to prevent too frequent queries on the process' handles
@@ -166,10 +228,12 @@ func (r *registryProcessor) findMatchingKey(pid uint32, relativeKeyName string)
166228
if atomic.LoadUint32(&handleThrottleCount) > maxHandleQueries {
167229
return relativeKeyName
168230
}
231+
169232
handles, err := r.hsnap.FindHandles(pid)
170233
if err != nil {
171234
return relativeKeyName
172235
}
236+
173237
for _, h := range handles {
174238
if h.Type != handle.Key {
175239
continue
@@ -179,5 +243,71 @@ func (r *registryProcessor) findMatchingKey(pid uint32, relativeKeyName string)
179243
return h.Name
180244
}
181245
}
246+
182247
return relativeKeyName
183248
}
249+
250+
// pushSetValue stores the internal RegSetValue event
251+
// into per process identifier queue.
252+
func (r *registryProcessor) pushSetValue(e *event.Event) {
253+
r.mu.Lock()
254+
defer r.mu.Unlock()
255+
vals, ok := r.values[e.PID]
256+
if !ok {
257+
r.values[e.PID] = []*event.Event{e}
258+
} else {
259+
r.values[e.PID] = append(vals, e)
260+
}
261+
}
262+
263+
// popSetValue traverses the internal RegSetValue queue
264+
// and pops the event if the suffixes match.
265+
func (r *registryProcessor) popSetValue(e *event.Event) *event.Event {
266+
r.mu.Lock()
267+
defer r.mu.Unlock()
268+
vals, ok := r.values[e.PID]
269+
if !ok {
270+
return nil
271+
}
272+
273+
var v *event.Event
274+
for i := len(vals) - 1; i >= 0; i-- {
275+
val := vals[i]
276+
if strings.HasSuffix(e.GetParamAsString(params.RegPath), val.GetParamAsString(params.RegPath)) {
277+
v = val
278+
r.values[e.PID] = append(vals[:i], vals[i+1:]...)
279+
break
280+
}
281+
}
282+
283+
return v
284+
}
285+
286+
func (r *registryProcessor) valuesSize(pid uint32) int {
287+
r.mu.Lock()
288+
defer r.mu.Unlock()
289+
return len(r.values[pid])
290+
}
291+
292+
func (r *registryProcessor) housekeep() {
293+
for {
294+
select {
295+
case <-r.purger.C:
296+
r.mu.Lock()
297+
for pid, vals := range r.values {
298+
for i, val := range vals {
299+
if time.Since(val.Timestamp) < valueTTL {
300+
continue
301+
}
302+
r.values[pid] = append(vals[:i], vals[i+1:]...)
303+
}
304+
if len(vals) == 0 {
305+
delete(r.values, pid)
306+
}
307+
}
308+
r.mu.Unlock()
309+
case <-r.quit:
310+
return
311+
}
312+
}
313+
}

internal/etw/processors/registry_windows_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,18 @@ import (
2323
"github.com/rabbitstack/fibratus/pkg/event/params"
2424
"github.com/rabbitstack/fibratus/pkg/handle"
2525
htypes "github.com/rabbitstack/fibratus/pkg/handle/types"
26+
"github.com/rabbitstack/fibratus/pkg/util/key"
2627
"github.com/stretchr/testify/assert"
2728
"github.com/stretchr/testify/require"
2829
"testing"
30+
"time"
2931
)
3032

33+
func init() {
34+
valueTTL = time.Millisecond * 150
35+
valuePurgerInterval = time.Millisecond * 300
36+
}
37+
3138
func TestRegistryProcessor(t *testing.T) {
3239
var tests = []struct {
3340
name string
@@ -161,7 +168,53 @@ func TestRegistryProcessor(t *testing.T) {
161168
func(e *event.Event, t *testing.T, hsnap *handle.SnapshotterMock, p Processor) {
162169
assert.Equal(t, `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`, e.GetParamAsString(params.RegPath))
163170
assert.Equal(t, `REG_EXPAND_SZ`, e.GetParamAsString(params.RegValueType))
164-
assert.Equal(t, `%SystemRoot%`, e.GetParamAsString(params.RegValue))
171+
assert.Equal(t, `%SystemRoot%`, e.GetParamAsString(params.RegData))
172+
},
173+
},
174+
{
175+
"process registry set value from internal event",
176+
&event.Event{
177+
Type: event.RegSetValue,
178+
Category: event.Registry,
179+
PID: 23234,
180+
Params: event.Params{
181+
params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`},
182+
params.RegKeyHandle: {Name: params.RegKeyHandle, Type: params.Uint64, Value: uint64(0)},
183+
},
184+
},
185+
func(p Processor) {
186+
p.(*registryProcessor).values[23234] = []*event.Event{
187+
{
188+
Type: event.RegSetValueInternal,
189+
Timestamp: time.Now(),
190+
Params: event.Params{
191+
params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\SessionId`},
192+
params.RegData: {Name: params.RegData, Type: params.UnicodeString, Value: "{ABD9EA10-87F6-11EB-9ED5-645D86501328}"},
193+
params.RegValueType: {Name: params.RegValueType, Type: params.Enum, Value: uint32(1), Enum: key.RegistryValueTypes},
194+
params.RegKeyHandle: {Name: params.RegKeyHandle, Type: params.Uint64, Value: uint64(0)}},
195+
},
196+
{
197+
Type: event.RegSetValueInternal,
198+
Timestamp: time.Now(),
199+
Params: event.Params{
200+
params.RegPath: {Name: params.RegPath, Type: params.Key, Value: `\Directory`},
201+
params.RegData: {Name: params.RegData, Type: params.UnicodeString, Value: "%SYSTEMROOT%"},
202+
params.RegValueType: {Name: params.RegValueType, Type: params.Enum, Value: uint32(2), Enum: key.RegistryValueTypes},
203+
params.RegKeyHandle: {Name: params.RegKeyHandle, Type: params.Uint64, Value: uint64(0)}},
204+
},
205+
}
206+
},
207+
func() *handle.SnapshotterMock {
208+
hsnap := new(handle.SnapshotterMock)
209+
return hsnap
210+
},
211+
func(e *event.Event, t *testing.T, hsnap *handle.SnapshotterMock, p Processor) {
212+
assert.Equal(t, `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows\Directory`, e.GetParamAsString(params.RegPath))
213+
assert.Equal(t, `REG_EXPAND_SZ`, e.GetParamAsString(params.RegValueType))
214+
assert.Equal(t, `%SYSTEMROOT%`, e.GetParamAsString(params.RegData))
215+
assert.Equal(t, p.(*registryProcessor).valuesSize(23234), 1)
216+
time.Sleep(time.Millisecond * 500)
217+
assert.Equal(t, p.(*registryProcessor).valuesSize(23234), 0)
165218
},
166219
},
167220
}

0 commit comments

Comments
 (0)