feat(go): port memory-map detector tranche
This commit is contained in:
parent
c6f7219de8
commit
6a27e6e770
13 changed files with 377 additions and 41 deletions
153
go-agent/internal/detectors/memory_obfuscation.go
Normal file
153
go-agent/internal/detectors/memory_obfuscation.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package detectors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
SIDMemoryObfuscation = 100039
|
||||
SIDProcessHidingLibrary = 100048
|
||||
SIDProcessInjectionLibrary = 100049
|
||||
)
|
||||
|
||||
var memoryWritablePrefixes = []string{"/tmp/", "/dev/shm/", "/var/tmp/", "/run/user/"}
|
||||
var memoryLibraryExtensions = []string{".so", ".so.", ".dylib"}
|
||||
var memoryHideHints = []string{
|
||||
"libhide", "libprocesshide", "process_hide", "proc_hide", "rootkit_hide",
|
||||
}
|
||||
|
||||
// MemoryObfuscation ports the /proc/<pid>/maps detector. It reports one alert
|
||||
// per signature and process, preserving Python's useful signal compression
|
||||
// when a process has many suspicious mappings of the same kind.
|
||||
func MemoryObfuscation(state model.State, cfg config.Config) []model.Alert {
|
||||
alerts := make([]model.Alert, 0)
|
||||
for _, process := range state.Processes {
|
||||
comm := process.Comm
|
||||
if comm == "" {
|
||||
comm = "?"
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, memory := range process.MemoryMaps {
|
||||
signature, severity, detail := memoryIndicator(memory, cfg)
|
||||
if signature == "" || seen[signature] {
|
||||
continue
|
||||
}
|
||||
// JIT allowlisting applies only to generic executable memory. A
|
||||
// library whose name suggests hiding or injection stays reviewable.
|
||||
if signature == "memory_obfuscation" && cfg.MemoryObfuscationAllowComms[comm] {
|
||||
continue
|
||||
}
|
||||
seen[signature] = true
|
||||
sid, classtype := SIDMemoryObfuscation, "memory-obfuscation"
|
||||
if signature == "process_hiding_library" {
|
||||
sid, classtype = SIDProcessHidingLibrary, "process-hiding"
|
||||
} else if signature == "process_injection_library" {
|
||||
sid, classtype = SIDProcessInjectionLibrary, "process-injection"
|
||||
}
|
||||
alerts = append(alerts, model.Alert{
|
||||
SID: sid, Severity: severity, Signature: signature,
|
||||
Classtype: classtype,
|
||||
Key: fmt.Sprintf("mem:%s:%d:%x-%x", signature, process.PID, memory.Start, memory.End),
|
||||
Detail: fmt.Sprintf("pid=%d comm=%s %s range=%x-%x",
|
||||
process.PID, comm, detail, memory.Start, memory.End),
|
||||
PIDs: []int{process.PID},
|
||||
})
|
||||
}
|
||||
}
|
||||
return alerts
|
||||
}
|
||||
|
||||
func memoryIndicator(memory model.MemoryMap, cfg config.Config) (string, string, string) {
|
||||
path := memory.Path
|
||||
if pathAllowed(path, cfg.MemoryObfuscationAllowPaths) {
|
||||
return "", "MEDIUM", ""
|
||||
}
|
||||
if path != "" && hasMemoryHint(path) {
|
||||
return "process_hiding_library", "CRITICAL",
|
||||
fmt.Sprintf("mapped library name suggests process hiding: %s", path)
|
||||
}
|
||||
executable := strings.Contains(memory.Perms, "x")
|
||||
writable := strings.Contains(memory.Perms, "w")
|
||||
if executable && path != "" && writableLibrary(path) {
|
||||
return "process_injection_library", "HIGH",
|
||||
fmt.Sprintf("executable library mapped from writable/runtime path: %s", path)
|
||||
}
|
||||
if executable && (strings.Contains(path, "memfd:") || strings.Contains(path, "(deleted)")) {
|
||||
return "memory_obfuscation", "CRITICAL",
|
||||
fmt.Sprintf("executable transient mapping perms=%s path=%s", memory.Perms, displayPath(path))
|
||||
}
|
||||
if executable && writable {
|
||||
return "memory_obfuscation", "HIGH",
|
||||
fmt.Sprintf("writable+executable memory mapping perms=%s path=%s", memory.Perms, displayPath(path))
|
||||
}
|
||||
if executable && anonymousPath(path) {
|
||||
return "memory_obfuscation", "HIGH",
|
||||
fmt.Sprintf("executable anonymous memory mapping perms=%s path=%s", memory.Perms, displayPath(path))
|
||||
}
|
||||
return "", "MEDIUM", ""
|
||||
}
|
||||
|
||||
func pathAllowed(path string, prefixes []string) bool {
|
||||
for _, prefix := range prefixes {
|
||||
if prefix != "" && strings.HasPrefix(path, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasMemoryHint(path string) bool {
|
||||
name := path
|
||||
if index := strings.LastIndex(name, "/"); index >= 0 {
|
||||
name = name[index+1:]
|
||||
}
|
||||
name = strings.ToLower(name)
|
||||
for _, hint := range memoryHideHints {
|
||||
if strings.Contains(name, hint) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func writableLibrary(path string) bool {
|
||||
lower := strings.ToLower(path)
|
||||
allowedPrefix := false
|
||||
for _, prefix := range memoryWritablePrefixes {
|
||||
if strings.HasPrefix(lower, prefix) {
|
||||
allowedPrefix = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowedPrefix {
|
||||
return false
|
||||
}
|
||||
name := lower
|
||||
if index := strings.LastIndex(name, "/"); index >= 0 {
|
||||
name = name[index+1:]
|
||||
}
|
||||
for _, extension := range memoryLibraryExtensions {
|
||||
if strings.Contains(name, extension) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func anonymousPath(path string) bool {
|
||||
return path == "" || path == "[heap]" || path == "[stack]" ||
|
||||
strings.HasPrefix(path, "[anon") || strings.HasPrefix(path, "[stack:")
|
||||
}
|
||||
|
||||
func displayPath(path string) string {
|
||||
if path != "" {
|
||||
return path
|
||||
}
|
||||
return "<anonymous>"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue