Skip to content

feat: improve parsing in etw receiver #2378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions receiver/windowseventtracereceiver/internal/etw/advapi32/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@ const (
EVENT_TRACE_REAL_TIME_MODE = 0x00000100
)

const (
EVENT_ENABLE_PROPERTY_SID = 0x00000001
)

const (
WNODE_FLAG_ALL_DATA uint32 = 0x00000001
WNODE_FLAG_SINGLE_INSTANCE uint32 = 0x00000002
Expand Down Expand Up @@ -676,6 +680,7 @@ func (e *EventRecord) PointerSize() uint32 {

const (
EventHeaderExtTypeRelatedActivityID = 0x0001
EventHeaderExtTypeSID = 0x0002
)

func (e *EventRecord) ExtendedDataItem(i uint16) *EventHeaderExtendedDataItem {
Expand All @@ -688,12 +693,23 @@ func (e *EventRecord) ExtendedDataItem(i uint16) *EventHeaderExtendedDataItem {
func (e *EventRecord) RelatedActivityID() string {
for i := uint16(0); i < e.ExtendedDataCount; i++ {
item := e.ExtendedDataItem(i)
if item.ExtType == EventHeaderExtTypeRelatedActivityID {
if item != nil && item.ExtType == EventHeaderExtTypeRelatedActivityID {
g := (*windows_.GUID)(unsafe.Pointer(item.DataPtr))
return g.String()
}
}
return "{00000000-0000-0000-0000-000000000000}"
return ""
}

func (e *EventRecord) SID() string {
for i := uint16(0); i < e.ExtendedDataCount; i++ {
item := e.ExtendedDataItem(i)
if item != nil && item.ExtType == EventHeaderExtTypeSID {
sid := (*windows.SID)(unsafe.Pointer(item.DataPtr))
return sid.String()
}
}
return ""
}

/*
Expand Down
69 changes: 53 additions & 16 deletions receiver/windowseventtracereceiver/internal/etw/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ func (c *Consumer) rawEventCallback(eventRecord *advapi32.EventRecord) uintptr {
return 1
}

channelName, opcodeName, taskName, eventID := c.getEventInfoFromRecord(eventRecord)

// Create an XML-like representation
var xmlBuilder strings.Builder
xmlBuilder.WriteString("<Event>\n")
Expand All @@ -127,24 +129,38 @@ func (c *Consumer) rawEventCallback(eventRecord *advapi32.EventRecord) uintptr {
xmlBuilder.WriteString(fmt.Sprintf(" <Provider Name=\"%s\" Guid=\"{%s}\"/>\n",
providerName, providerGUID))
xmlBuilder.WriteString(fmt.Sprintf(" <EventID>%d</EventID>\n",
eventRecord.EventHeader.EventDescriptor.Id))
eventID))
xmlBuilder.WriteString(fmt.Sprintf(" <Version>%d</Version>\n",
eventRecord.EventHeader.EventDescriptor.Version))
xmlBuilder.WriteString(fmt.Sprintf(" <Level>%d</Level>\n",
eventRecord.EventHeader.EventDescriptor.Level))
xmlBuilder.WriteString(fmt.Sprintf(" <Task>%d</Task>\n",
eventRecord.EventHeader.EventDescriptor.Task))
xmlBuilder.WriteString(fmt.Sprintf(" <Opcode>%d</Opcode>\n",
eventRecord.EventHeader.EventDescriptor.Opcode))
xmlBuilder.WriteString(fmt.Sprintf(" <Task>%s</Task>\n",
taskName))
xmlBuilder.WriteString(fmt.Sprintf(" <Opcode>%s</Opcode>\n",
opcodeName))
xmlBuilder.WriteString(fmt.Sprintf(" <Keywords>0x%x</Keywords>\n",
eventRecord.EventHeader.EventDescriptor.Keyword))

timeStr := eventRecord.EventHeader.UTC().Format(time.RFC3339Nano)
xmlBuilder.WriteString(fmt.Sprintf(" <TimeCreated SystemTime=\"%s\"/>\n", timeStr))

if !eventRecord.EventHeader.ActivityId.Equals(&windows.GUID{}) {
xmlBuilder.WriteString(fmt.Sprintf(" <Correlation ActivityID=\"%s\" RelatedActivityID=\"%s\"/>\n",
eventRecord.EventHeader.ActivityId.String(), eventRecord.RelatedActivityID()))
} else {
xmlBuilder.WriteString(" <Correlation />\n")
}

xmlBuilder.WriteString(fmt.Sprintf(" <Execution ProcessID=\"%d\" ThreadID=\"%d\"/>\n",
eventRecord.EventHeader.ProcessId, eventRecord.EventHeader.ThreadId))

xmlBuilder.WriteString(fmt.Sprintf(" <Channel>%s</Channel>\n", channelName))

xmlBuilder.WriteString(fmt.Sprintf(" <Computer>%s</Computer>\n", hostname))

if sid := eventRecord.SID(); sid != "" {
xmlBuilder.WriteString(fmt.Sprintf(" <Security UserID=\"%s\"/>\n", sid))
}
xmlBuilder.WriteString(" </System>\n")

// EventData section
Expand Down Expand Up @@ -188,38 +204,50 @@ func (c *Consumer) parsedEventCallback(eventRecord *advapi32.EventRecord) uintpt
if provider, ok := c.providerMap[providerGUID]; ok {
providerName = provider.Name
}

// Get event information from TraceEventInfo
channelName, opcodeName, taskName, eventID := c.getEventInfoFromRecord(eventRecord)

level := eventRecord.EventHeader.EventDescriptor.Level
event := &Event{
Flags: strconv.FormatUint(uint64(eventRecord.EventHeader.Flags), 10),
Session: c.sessionName,
Timestamp: parseTimestamp(uint64(eventRecord.EventHeader.TimeStamp)),
System: EventSystem{
ActivityID: eventRecord.EventHeader.ActivityId.String(),
Channel: strconv.FormatInt(int64(eventRecord.EventHeader.EventDescriptor.Channel), 10),
Keywords: strconv.FormatUint(eventRecord.EventHeader.EventDescriptor.Keyword, 10),
EventID: fmt.Sprintf("%d", eventRecord.EventHeader.EventDescriptor.Id),
Opcode: strconv.FormatUint(uint64(eventRecord.EventHeader.EventDescriptor.Opcode), 10),
Task: strconv.FormatUint(uint64(eventRecord.EventHeader.EventDescriptor.Task), 10),
Channel: channelName,
Keywords: strconv.FormatUint(uint64(eventRecord.EventHeader.EventDescriptor.Keyword), 10),
EventID: strconv.FormatUint(uint64(eventID), 10),
Opcode: opcodeName,
Task: taskName,
Provider: EventProvider{
GUID: providerGUID,
Name: providerName,
},
Level: level,
Computer: hostname,
Correlation: EventCorrelation{
ActivityID: eventRecord.EventHeader.ActivityId.String(),
RelatedActivityID: eventRecord.RelatedActivityID(),
},
Level: level,
Computer: hostname,
Correlation: EventCorrelation{},
Execution: EventExecution{
ThreadID: eventRecord.EventHeader.ThreadId,
ProcessID: eventRecord.EventHeader.ProcessId,
},
Version: eventRecord.EventHeader.EventDescriptor.Version,
},
Security: EventSecurity{
SID: eventRecord.SID(),
},
EventData: data,
ExtendedData: []string{},
}

if activityID := eventRecord.EventHeader.ActivityId.String(); activityID != zeroGUID {
event.System.Correlation.ActivityID = activityID
}

if relatedActivityID := eventRecord.RelatedActivityID(); relatedActivityID != zeroGUID {
event.System.Correlation.RelatedActivityID = relatedActivityID
}

select {
case c.Events <- event:
return 0
Expand All @@ -228,6 +256,15 @@ func (c *Consumer) parsedEventCallback(eventRecord *advapi32.EventRecord) uintpt
}
}

func (c *Consumer) getEventInfoFromRecord(eventRecord *advapi32.EventRecord) (channelName string, opcodeName string, taskName string, eventID uint16) {
ti, err := getEventInformation(eventRecord)
if err != nil {
c.logger.Error("Failed to get event information", zap.Error(err))
return "", "", "", 0
}
return ti.ChannelName(), ti.OpcodeName(), ti.TaskName(), ti.EventID()
}

func (c *Consumer) defaultBufferCallback(buffer *advapi32.EventTraceLogfile) uintptr {
select {
case <-c.doneChan:
Expand Down
17 changes: 16 additions & 1 deletion receiver/windowseventtracereceiver/internal/etw/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build windows

// Package etw contains the functionality for interacting with the ETW API.
package etw

import "time"
import (
"time"

"golang.org/x/sys/windows"
)

// EventFlags contains flags for the event
type EventFlags struct {
// Use to flag event as being skippable for performance reason
Skippable bool `json:"skippable"`
}

// zeroGUID is a string representation of the zero GUID, which generally indicates that the value is not applicable
var zeroGUID = windows.GUID{}.String()

// EventCorrelation contains correlation information for the event
type EventCorrelation struct {
ActivityID string `json:"activityID"`
Expand Down Expand Up @@ -91,4 +100,10 @@ type Event struct {
UserData map[string]any `json:"userData,omitempty"`
System EventSystem `json:"system"`
ExtendedData []string `json:"extendedData,omitempty"`
Security EventSecurity `json:"security,omitempty"`
}

// EventSecurity contains security information for the event
type EventSecurity struct {
SID string `json:"sid"`
}
13 changes: 10 additions & 3 deletions receiver/windowseventtracereceiver/internal/etw/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,29 @@ func (p *parser) getPropertyValue(r *advapi32.EventRecord, propInfo *tdh.TraceEv
return nil, fmt.Errorf("failed to get array size: %w", err)
}

if arraySize == 1 {
if (propertyInfo.Flags & tdh.PropertyStruct) == tdh.PropertyStruct {
return p.parseObject(propertyInfo)
}
return p.parseSimpleType(r, propertyInfo, 0)
}

result := make([]any, arraySize)
for i := range arraySize {
for idx := range arraySize {
var (
value any
err error
)
if (propertyInfo.Flags & tdh.PropertyStruct) == tdh.PropertyStruct {
value, err = p.parseObject(propertyInfo)
} else {
value, err = p.parseSimpleType(r, propertyInfo, uint32(i))
value, err = p.parseSimpleType(r, propertyInfo, uint32(idx))
}

if err != nil {
return nil, err
}
result[i] = value
result[idx] = value
}

return result, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ func allocBuffer(logSessionName string) (propertyBuffer *advapi32.EventTraceProp
// if we successfully enable the provider, we will return nil.
func (s *SessionController) enableProvider(handle syscall.Handle, providerGUID *windows.GUID, provider *Provider, traceLevel advapi32.TraceLevel, matchAnyKeyword uint64, matchAllKeyword uint64) error {
params := advapi32.EnableTraceParameters{
Version: 2,
Version: 2,
EnableProperty: advapi32.EVENT_ENABLE_PROPERTY_SID,
}

const maxAttempts = 5
Expand Down
16 changes: 15 additions & 1 deletion receiver/windowseventtracereceiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func (lr *logsReceiver) parseEvent(event *etw.Event) (plog.Logs, error) {
resourceLog.Resource().Attributes().PutStr("provider", event.System.Provider.Name)
resourceLog.Resource().Attributes().PutStr("provider_guid", event.System.Provider.GUID)
resourceLog.Resource().Attributes().PutStr("computer", event.System.Computer)
resourceLog.Resource().Attributes().PutStr("channel", event.System.Channel)

scopeLog := resourceLog.ScopeLogs().AppendEmpty()
record := scopeLog.LogRecords().AppendEmpty()
Expand Down Expand Up @@ -224,10 +225,23 @@ func (lr *logsReceiver) parseEventData(event *etw.Event, record plog.LogRecord)

if event.System.EventID != "" {
eventID := record.Body().Map().PutEmptyMap("event_id")
// eventID.PutStr("guid", event.System.EventGUID)
eventID.PutStr("id", event.System.EventID)
}

correlation := record.Body().Map().PutEmptyMap("correlation")
if event.System.Correlation.ActivityID != "" {
correlation.PutStr("activity_id", event.System.Correlation.ActivityID)
}

if event.System.Correlation.RelatedActivityID != "" {
correlation.PutStr("related_activity_id", event.System.Correlation.RelatedActivityID)
}

if event.Security.SID != "" {
security := record.Body().Map().PutEmptyMap("security")
security.PutStr("sid", event.Security.SID)
}

if event.System.Execution.ProcessID != 0 {
execution := record.Body().Map().PutEmptyMap("execution")
execution.PutStr("process_id", strconv.FormatUint(uint64(event.System.Execution.ProcessID), 10))
Expand Down