Cybersecurity-Projects/PROJECTS/beginner/systemd-persistence-scanner/internal/scanner/udev.go

131 lines
2.4 KiB
Go

/*
©AngelaMos | 2026
udev.go
Scans udev rules for persistence through device event triggers
Parses udev rule files for RUN+= directives that execute commands
on hardware events. Flags rules pointing to suspicious paths or
containing shell interpreters and network tools.
MITRE ATT&CK:
T1546 - Event Triggered Execution
*/
package scanner
import (
"strings"
"github.com/CarterPerez-dev/sentinel/pkg/types"
)
const (
udevScannerName = "udev"
mitreUdev = "T1546"
)
var udevDirs = []string{
"/etc/udev/rules.d",
"/lib/udev/rules.d",
"/usr/lib/udev/rules.d",
"/run/udev/rules.d",
}
func init() {
Register(&UdevScanner{})
}
type UdevScanner struct{}
func (u *UdevScanner) Name() string {
return udevScannerName
}
func (u *UdevScanner) Scan(
root string,
) []types.Finding {
var findings []types.Finding
for _, dir := range udevDirs {
resolved := ResolveRoot(root, dir)
for _, path := range ListFiles(resolved) {
if !strings.HasSuffix(path, ".rules") {
continue
}
findings = append(
findings,
u.scanRuleFile(path)...,
)
}
}
return findings
}
func (u *UdevScanner) scanRuleFile(
path string,
) []types.Finding {
lines := ReadLines(path)
if lines == nil {
return nil
}
var findings []types.Finding
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if IsCommentOrEmpty(trimmed) {
continue
}
if !strings.Contains(trimmed, "RUN+=") &&
!strings.Contains(trimmed, "RUN+=\"") {
continue
}
runCmd := extractRunDirective(trimmed)
if runCmd == "" {
continue
}
matched, sev, label := MatchLine(runCmd)
if matched {
findings = append(findings, types.Finding{
Scanner: udevScannerName,
Severity: sev,
Title: "Suspicious udev RUN directive: " + label,
Path: path,
Evidence: trimmed,
MITRE: mitreUdev,
})
continue
}
if TempDirPattern.MatchString(runCmd) ||
containsShellCommand(runCmd) {
findings = append(findings, types.Finding{
Scanner: udevScannerName,
Severity: types.SeverityMedium,
Title: "Udev rule executes command",
Path: path,
Evidence: trimmed,
MITRE: mitreUdev,
})
}
}
return findings
}
func extractRunDirective(line string) string {
idx := strings.Index(line, "RUN+=")
if idx == -1 {
return ""
}
rest := line[idx+5:]
rest = strings.TrimPrefix(rest, "\"")
if end := strings.Index(rest, "\""); end != -1 {
return rest[:end]
}
return strings.TrimSpace(rest)
}