// SPDX-License-Identifier: GPL-3.0-or-later package detectors import ( "fmt" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model" ) // ReverseShell ports the network-socket-on-stdio detector (SID 100010). func ReverseShell(state model.State, cfg config.Config) []model.Alert { // Build the inode correlation table once. A stdio socket alone could be a // benign Unix socket; requiring the inode in the network socket view is the // high-signal condition that distinguishes a likely reverse shell. peerByInode := make(map[int]string) for _, socket := range state.Sockets { if socket.Inode != nil { peerByInode[*socket.Inode] = fmt.Sprintf("%s -> %s", socket.Local, socket.Peer) } } alerts := make([]model.Alert, 0) for _, process := range state.Processes { // Restrict to shells/interpreters exactly as Python does. Networked // daemons commonly inherit sockets and must not become false positives. if !cfg.Interpreters[process.Comm] { continue } inode := stdioSocketInode(process) if inode == nil || peerByInode[*inode] == "" { continue } alerts = append(alerts, model.Alert{ SID: 100010, Severity: "CRITICAL", Signature: "reverse_shell", Classtype: "c2-reverse-shell", Key: fmt.Sprintf("rsh:%d", process.PID), Detail: fmt.Sprintf( "pid=%d comm=%s stdio=net-socket peer=[%s] cmd=[%s]", process.PID, process.Comm, peerByInode[*inode], truncateRunes(process.Cmdline, 90)), PIDs: []int{process.PID}, }) } return alerts }