Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45d3ed4db0 | |||
| 670e89efa9 | |||
| 7515bda711 |
325
main.go
325
main.go
@@ -32,7 +32,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceName = "WinEventForwarder"
|
||||
ServiceName = "SENDNRW-SIEM-AGENT"
|
||||
|
||||
BatchSize = 8
|
||||
PollWaitMS = 2000
|
||||
@@ -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 {
|
||||
@@ -48,31 +52,6 @@ type ChannelConfig struct {
|
||||
IDs map[uint32]bool
|
||||
}
|
||||
|
||||
var channelConfigs = []ChannelConfig{
|
||||
{
|
||||
Name: "System",
|
||||
IDs: map[uint32]bool{
|
||||
1074: true, // Shutdown/Reboot
|
||||
6005: true, // Eventlog gestartet
|
||||
6006: true, // Eventlog gestoppt
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Application",
|
||||
IDs: map[uint32]bool{
|
||||
1000: true, // Beispiel: Application Error
|
||||
},
|
||||
},
|
||||
// Beispiel:
|
||||
// {
|
||||
// Name: "Security",
|
||||
// IDs: map[uint32]bool{
|
||||
// 4624: true,
|
||||
// 4625: true,
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
BackendURL string `json:"backend_url"`
|
||||
EnrollmentKey string `json:"enrollment_key"`
|
||||
@@ -181,6 +160,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 +305,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 +323,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 +355,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 +557,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 +682,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 {
|
||||
@@ -734,16 +759,6 @@ func evtClose(h windows.Handle) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 false
|
||||
}
|
||||
|
||||
func installService() error {
|
||||
exepath, err := os.Executable()
|
||||
if err != nil {
|
||||
@@ -901,8 +916,146 @@ func defaultAgentConfig() *AgentConfig {
|
||||
EnrollmentKey: getenvDefault("SIEM_ENROLLMENT_KEY", "BITTE_SEHR_LANG_UND_ZUFAELLIG"),
|
||||
StateFile: `C:\ProgramData\WinEventForwarder\state.json`,
|
||||
ChannelRules: []ChannelRule{
|
||||
{Name: "System", IDs: []uint32{1074, 6005, 6006}},
|
||||
{Name: "Security", IDs: []uint32{4624, 4625}},
|
||||
|
||||
// =========================
|
||||
// SYSTEM
|
||||
// =========================
|
||||
{
|
||||
Name: "System",
|
||||
IDs: []uint32{
|
||||
1074, // planned shutdown
|
||||
6005, // startup
|
||||
6006, // shutdown
|
||||
6008, // unexpected shutdown
|
||||
7045, // service installed
|
||||
},
|
||||
},
|
||||
|
||||
// =========================
|
||||
// SECURITY
|
||||
// =========================
|
||||
{
|
||||
Name: "Security",
|
||||
IDs: []uint32{
|
||||
|
||||
// --- Logon / Auth ---
|
||||
4624, // logon success
|
||||
4625, // logon failed
|
||||
4648, // explicit credentials
|
||||
4672, // special privileges
|
||||
4673, 4674,
|
||||
|
||||
// --- Security / Audit ---
|
||||
1102, // log cleared
|
||||
4719, // audit policy
|
||||
4902, 4904, 4905, 4906, 4907, 4908, 4912,
|
||||
|
||||
// --- Time ---
|
||||
4616, // system time changed
|
||||
|
||||
// --- User ---
|
||||
4720, 4722, 4723, 4724, 4725, 4726,
|
||||
4738,
|
||||
4740,
|
||||
|
||||
// --- Groups ---
|
||||
4727, 4728, 4729,
|
||||
4730, 4731, 4732, 4733, 4734, 4735, 4737,
|
||||
4754, 4755, 4756, 4757, 4758,
|
||||
|
||||
// --- Computer ---
|
||||
4741, 4742, 4743,
|
||||
|
||||
// --- Kerberos / NTLM ---
|
||||
4768, 4769,
|
||||
4771, 4776,
|
||||
|
||||
// --- Services / Tasks ---
|
||||
4697,
|
||||
4698, 4699,
|
||||
4700, 4701, 4702,
|
||||
|
||||
// --- AD / Directory ---
|
||||
4662, // object access
|
||||
4670, // permission change
|
||||
5136, 5137, 5141,
|
||||
|
||||
// --- Shares ---
|
||||
5140, 5145,
|
||||
|
||||
// --- AD CS ---
|
||||
4882, 4885, 4886, 4887,
|
||||
4890, 4891, 4892,
|
||||
4898, 4899, 4900,
|
||||
|
||||
// --- Defender ---
|
||||
5001, // real-time protection disabled
|
||||
},
|
||||
},
|
||||
|
||||
// =========================
|
||||
// POWERSHELL
|
||||
// =========================
|
||||
{
|
||||
Name: "Microsoft-Windows-PowerShell/Operational",
|
||||
IDs: []uint32{
|
||||
4104, // script block
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Windows PowerShell",
|
||||
IDs: []uint32{
|
||||
4104,
|
||||
},
|
||||
},
|
||||
|
||||
// =========================
|
||||
// DEFENDER
|
||||
// =========================
|
||||
{
|
||||
Name: "Microsoft-Windows-Windows Defender/Operational",
|
||||
IDs: []uint32{
|
||||
1116, // malware detected
|
||||
1117, // remediation
|
||||
1118, 1119,
|
||||
5007, // config change
|
||||
5013,
|
||||
},
|
||||
},
|
||||
|
||||
// =========================
|
||||
// WMI (Lateral Movement!)
|
||||
// =========================
|
||||
{
|
||||
Name: "Microsoft-Windows-WMI-Activity/Operational",
|
||||
IDs: []uint32{
|
||||
5857, 5858, 5859, 5860, 5861,
|
||||
},
|
||||
},
|
||||
|
||||
// =========================
|
||||
// RDP
|
||||
// =========================
|
||||
{
|
||||
Name: "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational",
|
||||
IDs: []uint32{
|
||||
1149, // RDP login
|
||||
},
|
||||
},
|
||||
|
||||
// =========================
|
||||
// OPTIONAL (laut!)
|
||||
// =========================
|
||||
|
||||
/*
|
||||
{
|
||||
Name: "Security",
|
||||
IDs: []uint32{
|
||||
4688, // process creation (SEHR LAUT!)
|
||||
},
|
||||
},
|
||||
*/
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
BIN
siem-agent.exe
Normal file
BIN
siem-agent.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user