diff --git a/main.go b/main.go index 0f4728a..4c62a84 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,10 @@ const ( ServiceLogInfo = 1 ) +const ( + ERROR_EVT_INVALID_OPERATION syscall.Errno = 15010 +) + const AgentConfigPath = `C:\ProgramData\WinEventForwarder\agent.json` type ChannelConfig struct { @@ -181,6 +185,7 @@ func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, status ch log.Printf("[%s] Starte Watcher-Chunk %d/%d mit %d IDs", cfg.Name, chunkNo+1, len(chunks), len(cfg.IDs)) runChannelWatcher(ctx, hostname, cfg, out) + log.Println("Prozess fertig oder abgebrochen", cfg) }(i, workerCfg) } } @@ -325,16 +330,16 @@ func runDebug(cfg *AgentConfig, state *AgentState) { } func runChannelWatcher(ctx context.Context, hostname string, cfg ChannelConfig, out chan<- LogPayload) { - query := buildXPathQuery(cfg.IDs) + query := buildXPathQuery2(cfg.IDs) - signal, err := windows.CreateEvent(nil, 1, 0, nil) + signalEvent, err := windows.CreateEvent(nil, 1, 0, nil) if err != nil { log.Printf("[%s] CreateEvent-Fehler: %v", cfg.Name, err) return } - defer windows.CloseHandle(signal) + defer windows.CloseHandle(signalEvent) - sub, err := evtSubscribe(cfg.Name, query, signal, evtSubscribeToFutureEvents) + sub, err := evtSubscribe(cfg.Name, query, signalEvent, evtSubscribeToFutureEvents) if err != nil { log.Printf("[%s] Subscribe-Fehler: %v | Query=%s", cfg.Name, err, query) return @@ -343,35 +348,28 @@ func runChannelWatcher(ctx context.Context, hostname string, cfg ChannelConfig, log.Printf("[%s] Überwachung gestartet mit Filter %s", cfg.Name, query) - for { - select { - case <-ctx.Done(): - return - default: - } - - waitStatus, err := windows.WaitForSingleObject(signal, PollWaitMS) - if err != nil { - log.Printf("[%s] WaitForSingleObject-Fehler: %v", cfg.Name, err) - time.Sleep(2 * time.Second) - continue - } - - switch waitStatus { - case uint32(windows.WAIT_OBJECT_0): + drain := func() { + for { events, err := evtNext(sub, BatchSize, 0) if err != nil { - if isIgnorableEvtNextError(err) { - _ = windows.ResetEvent(signal) - continue + code := winErrCode(err) + + // Diese Fehler sind bei deinem Polling nicht fatal. + if code == windows.ERROR_TIMEOUT || + code == windows.ERROR_NO_MORE_ITEMS || + strings.Contains(strings.ToLower(err.Error()), "operation identifier is not valid") { + return } - log.Printf("[%s] EvtNext-Fehler: %v", cfg.Name, err) - _ = windows.ResetEvent(signal) - time.Sleep(2 * time.Second) - continue + + log.Printf("[%s] EvtNext-Fehler: %v | Code=%d", cfg.Name, err, uint32(code)) + return } - log.Printf("[%s] EvtNext lieferte %d Events", cfg.Name, len(events)) + if len(events) == 0 { + return + } + + log.Printf("[%s] %d neue Events gefunden", cfg.Name, len(events)) for _, h := range events { payload, err := buildPayloadFromEventHandle(hostname, cfg.Name, h) @@ -382,37 +380,64 @@ func runChannelWatcher(ctx context.Context, hostname string, cfg ChannelConfig, continue } - log.Printf("[%s] Event empfangen: EventID=%d Source=%s Time=%s", + if !cfg.IDs[payload.EventID] { + log.Printf("[%s] EventID %d ignoriert, nicht in Filter", cfg.Name, payload.EventID) + continue + } + + log.Printf("[%s] Event erkannt: ID=%d Source=%s Time=%s", cfg.Name, payload.EventID, payload.Source, payload.Time.Format(time.RFC3339), ) - if !cfg.IDs[payload.EventID] { - log.Printf("[%s] Event weggefiltert: EventID=%d", cfg.Name, payload.EventID) - continue - } - - log.Printf("[%s] Event wird gesendet: EventID=%d", cfg.Name, payload.EventID) - select { case out <- payload: case <-ctx.Done(): return } } - - _ = windows.ResetEvent(signal) - - case uint32(windows.WAIT_TIMEOUT): - continue - - default: - log.Printf("[%s] Unerwarteter Wait-Status: %d", cfg.Name, waitStatus) - time.Sleep(2 * time.Second) } } + + for { + select { + case <-ctx.Done(): + return + default: + } + + waitStatus, err := windows.WaitForSingleObject(signalEvent, PollWaitMS) + if err != nil { + log.Printf("[%s] WaitForSingleObject-Fehler: %v", cfg.Name, err) + time.Sleep(2 * time.Second) + continue + } + + if waitStatus == uint32(windows.WAIT_OBJECT_0) { + _ = windows.ResetEvent(signalEvent) + drain() + continue + } + + if waitStatus == uint32(windows.WAIT_TIMEOUT) { + // Wichtig: bei dir nötig, weil das Signal offenbar nicht zuverlässig kommt. + drain() + continue + } + _ = windows.ResetEvent(signalEvent) + log.Printf("[%s] Unerwarteter Wait-Status: %d", cfg.Name, waitStatus) + time.Sleep(2 * time.Second) + } +} + +func winErrCode(err error) syscall.Errno { + var errno syscall.Errno + if errors.As(err, &errno) { + return errno + } + return 0 } func runSender( @@ -557,6 +582,28 @@ func buildXPathQuery(ids map[uint32]bool) string { return fmt.Sprintf("*[System[(%s)]]", strings.Join(parts, " or ")) } +func buildXPathQuery2(ids map[uint32]bool) string { + if len(ids) == 0 { + return "*" + } + + list := make([]int, 0, len(ids)) + for id := range ids { + list = append(list, int(id)) + } + sort.Ints(list) + + parts := make([]string, 0, len(list)) + for _, id := range list { + // Manche Windows-Versionen bevorzugen EventID ohne System/ davor + // innerhalb des System-Knotens. + parts = append(parts, fmt.Sprintf("EventID=%d", id)) + } + + // WICHTIG: Keine unnötigen Leerzeichen innerhalb der XPath-Klammern + return fmt.Sprintf("*[System[(%s)]]", strings.Join(parts, " or ")) +} + func sendBatch(client *http.Client, backendURL string, state *AgentState, enrollmentKey string, batch []LogPayload) (bool, error) { data, err := json.Marshal(batch) if err != nil { @@ -660,11 +707,14 @@ func evtNext(resultSet windows.Handle, maxHandles uint32, timeout uint32) ([]win 0, uintptr(unsafe.Pointer(&returned)), ) + + // r1 == 0 bedeutet, die Funktion war nicht erfolgreich (False) if r1 == 0 { - if e1 != syscall.Errno(0) { - return nil, e1 + // Wir prüfen, welcher Fehlercode vorliegt + if e1 == windows.ERROR_NO_MORE_ITEMS || e1 == windows.ERROR_TIMEOUT { + return nil, nil // Das ist kein Fehler, nur das Ende der Schlange } - return nil, errors.New("EvtNext fehlgeschlagen") + return nil, e1 // Ein echter Windows-Fehler } if returned == 0 { @@ -737,9 +787,9 @@ func evtClose(h windows.Handle) error { func isIgnorableEvtNextError(err error) bool { var errno syscall.Errno if errors.As(err, &errno) { - if errno == windows.ERROR_TIMEOUT || errno == windows.ERROR_NO_MORE_ITEMS { - return true - } + return errno == windows.ERROR_TIMEOUT || + errno == windows.ERROR_NO_MORE_ITEMS || + errno == ERROR_EVT_INVALID_OPERATION } return false } diff --git a/siem-agent.exe b/siem-agent.exe new file mode 100644 index 0000000..f6eb293 Binary files /dev/null and b/siem-agent.exe differ