131 lines
2.4 KiB
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)
|
|
}
|