// SPDX-License-Identifier: GPL-3.0-or-later package events import ( "fmt" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model" ) const ( protWrite = 0x2 protExec = 0x4 ptraceTrace = 0 ptraceAttach = 16 ptraceSeize = 0x4206 prSetSeccomp = 22 ) type syscallPredicate func(SyscallEvent) bool type SyscallRule struct { SID int Message string Severity string Classtype string Syscalls map[string]bool Predicate syscallPredicate } func (r SyscallRule) Matches(event SyscallEvent) bool { return r.Syscalls[event.Syscall] && r.Predicate(event) } func (r SyscallRule) Alert(event SyscallEvent) model.Alert { detail := fmt.Sprintf( "sid=%d %s — pid=%d ppid=%d comm=%s syscall=%s args=[%#x, %#x, %#x, %#x]", r.SID, r.Message, event.PID, event.PPID, event.Comm, event.Syscall, event.Args[0], event.Args[1], event.Args[2], event.Args[3]) if event.Text != "" { detail += " text=[" + truncateRunes(event.Text, 80) + "]" } return model.Alert{ SID: r.SID, Severity: r.Severity, Signature: "syscall_rule." + r.Classtype, Classtype: r.Classtype, Key: fmt.Sprintf("syscall:%d:%d:%s:%x:%x", r.SID, event.PID, event.Syscall, event.Args[0], event.Args[2]), Detail: detail, PIDs: []int{event.PID}, } } type SyscallRuleEngine struct { Rules []SyscallRule } func DefaultSyscallRuleEngine() SyscallRuleEngine { return SyscallRuleEngine{Rules: []SyscallRule{ { SID: 100060, Message: "mprotect made memory writable and executable", Severity: "CRITICAL", Classtype: "memory-obfuscation", Syscalls: stringSet([]string{"mprotect"}), Predicate: func(event SyscallEvent) bool { return protectedWX(event.Args[2]) }, }, { SID: 100061, Message: "mmap requested writable and executable memory", Severity: "CRITICAL", Classtype: "memory-obfuscation", Syscalls: stringSet([]string{"mmap"}), Predicate: func(event SyscallEvent) bool { return protectedWX(event.Args[2]) }, }, { SID: 100062, Message: "memfd_create used for anonymous in-memory file staging", Severity: "MEDIUM", Classtype: "fileless-execution", Syscalls: stringSet([]string{"memfd_create"}), Predicate: alwaysSyscall, }, { SID: 100063, Message: "ptrace anti-debug or attach operation observed", Severity: "HIGH", Classtype: "anti-analysis", Syscalls: stringSet([]string{"ptrace"}), Predicate: func(event SyscallEvent) bool { request := event.Args[0] return request == ptraceTrace || request == ptraceAttach || request == ptraceSeize }, }, { SID: 100064, Message: "seccomp sandboxing call observed (possible anti-analysis hardening)", Severity: "MEDIUM", Classtype: "anti-analysis", Syscalls: stringSet([]string{"prctl", "seccomp"}), Predicate: func(event SyscallEvent) bool { return event.Syscall == "seccomp" || event.Args[0] == prSetSeccomp }, }, { SID: 100065, Message: "cross-process memory read/write syscall observed", Severity: "HIGH", Classtype: "credential-access", Syscalls: stringSet([]string{"process_vm_readv", "process_vm_writev"}), Predicate: alwaysSyscall, }, { SID: 100066, Message: "memory locking syscall observed (possible protected in-memory payload)", Severity: "MEDIUM", Classtype: "memory-obfuscation", Syscalls: stringSet([]string{"mlock", "mlock2", "mlockall"}), Predicate: alwaysSyscall, }, }} } func (e SyscallRuleEngine) Match(event SyscallEvent) []model.Alert { alerts := make([]model.Alert, 0) for _, rule := range e.Rules { if rule.Matches(event) { alerts = append(alerts, rule.Alert(event)) } } return alerts } func protectedWX(protection uint64) bool { return protection&protWrite != 0 && protection&protExec != 0 } func alwaysSyscall(SyscallEvent) bool { return true }