feat(go): port memory-map detector tranche

This commit is contained in:
Luna 2026-07-11 01:19:56 -07:00
parent c6f7219de8
commit 6a27e6e770
No known key found for this signature in database
13 changed files with 377 additions and 41 deletions

View file

@ -66,9 +66,10 @@ Project docs:
Phase 1 now also has an **experimental Go sidecar** under `go-agent/`. It emits Phase 1 now also has an **experimental Go sidecar** under `go-agent/`. It emits
the same `enodia.event.v1` envelope and captures an injectable `/proc` process the same `enodia.event.v1` envelope and captures an injectable `/proc` process
and `ss` socket view. The parity-checked cohort is `reverse_shell`, and memory-map view plus `ss` sockets. The parity-checked cohort is `reverse_shell`,
`ld_preload`, `deleted_exe`, `input_snooper`, `credential_access`, `ld_preload`, `deleted_exe`, `input_snooper`, `credential_access`,
`stealth_network`, and `egress`. It is stdout-only and does not replace, `stealth_network`, `memory_obfuscation` (including mapped hide/injection
libraries), and `egress`. It is stdout-only and does not replace,
install over, or write state for the Python daemon; Python remains authoritative install over, or write state for the Python daemon; Python remains authoritative
until subsystem parity is complete. until subsystem parity is complete.

View file

@ -228,9 +228,10 @@ signature keeps `sid` / `signature` / `classtype` / tests / docs.
equivalent, poll detectors ported, JSON output matching schemas. Runs equivalent, poll detectors ported, JSON output matching schemas. Runs
alongside Python; harness compares both. *In progress:* the isolated alongside Python; harness compares both. *In progress:* the isolated
`go-agent/` sidecar now has stdlib config loading, an injectable `/proc` `go-agent/` sidecar now has stdlib config loading, an injectable `/proc`
process/file-descriptor and `ss` socket view, the JSONL sweep loop, and process/file-descriptor/memory-map and `ss` socket view, the JSONL sweep
fixture parity for `reverse_shell`, `ld_preload`, `deleted_exe`, loop, and fixture parity for `reverse_shell`, `ld_preload`, `deleted_exe`,
`input_snooper`, `credential_access`, `stealth_network`, and `egress`; `input_snooper`, `credential_access`, `stealth_network`, the
`memory_obfuscation` cohort, and `egress`;
Python remains the production agent. Python remains the production agent.
- **Phase 2 — eBPF via `cilium/ebpf`.** Port exec/syscall rules; drop the - **Phase 2 — eBPF via `cilium/ebpf`.** Port exec/syscall rules; drop the
bcc/Python runtime dependency; single static binary. bcc/Python runtime dependency; single static binary.

View file

@ -7,13 +7,14 @@ behavioral oracle until every subsystem reaches fixture and red-team parity.
Current scope: Current scope:
- standard-library-only flat TOML loading for the settings used so far; - standard-library-only flat TOML loading for the settings used so far;
- an injectable `/proc` process/file-descriptor snapshot plus fail-open `ss` - an injectable `/proc` process/file-descriptor/memory-map snapshot plus
socket collection; fail-open `ss` socket collection;
- a cancellable sweep loop that emits JSONL `enodia.event.v1` records; - a cancellable sweep loop that emits JSONL `enodia.event.v1` records;
- exact `enodia.alert.v1` parity for `ld_preload` (SID `100011`), - exact `enodia.alert.v1` parity for `ld_preload` (SID `100011`),
`deleted_exe` (`100012`), `input_snooper` (`100032`), and `deleted_exe` (`100012`), `input_snooper` (`100032`), and
`credential_access` (`100033`), plus `reverse_shell` (`100010`), `egress` `credential_access` (`100033`), plus `reverse_shell` (`100010`), `egress`
(`100016`), and `stealth_network` (`100036`); (`100016`), `stealth_network` (`100036`), and the `/proc/<pid>/maps`
`memory_obfuscation` cohort (`100039`, `100048`, `100049`);
- `enodia.status.v1` heartbeat records with no persistence or retained-alert - `enodia.status.v1` heartbeat records with no persistence or retained-alert
claims; claims;
- a shared fixture checked against the Python detector by - a shared fixture checked against the Python detector by

View file

@ -78,6 +78,9 @@ func (a *Agent) Sweep() ([]map[string]any, error) {
if a.Config.Enabled("stealth_network") { if a.Config.Enabled("stealth_network") {
alerts = append(alerts, detectors.StealthNetwork(state, a.Config)...) alerts = append(alerts, detectors.StealthNetwork(state, a.Config)...)
} }
if a.Config.Enabled("memory_obfuscation") {
alerts = append(alerts, detectors.MemoryObfuscation(state, a.Config)...)
}
if a.Config.Enabled("egress") { if a.Config.Enabled("egress") {
alerts = append(alerts, detectors.Egress(state, a.Config)...) alerts = append(alerts, detectors.Egress(state, a.Config)...)
} }

View file

@ -44,6 +44,12 @@ var defaultStealthNetworkAllowComms = []string{
"tcpdump", "dumpcap", "wireshark", "suricata", "zeek", "tcpdump", "dumpcap", "wireshark", "suricata", "zeek",
} }
var defaultMemoryObfuscationAllowComms = []string{
"java", "node", "firefox", "chromium", "chrome", "google-chrome",
"brave", "brave-browser", "WebKitWebProcess", "dotnet", "qemu-system-x86",
"qemu-system-x86_64", "wine", "wasmtime",
}
// Config mirrors the Phase 1 settings consumed by the Go sweep loop. More // Config mirrors the Phase 1 settings consumed by the Go sweep loop. More
// fields are added as their detectors are ported. // fields are added as their detectors are ported.
type Config struct { type Config struct {
@ -57,6 +63,8 @@ type Config struct {
EgressAllowCIDRs []string EgressAllowCIDRs []string
StealthNetworkAllowComms map[string]bool StealthNetworkAllowComms map[string]bool
StealthNetworkAllowKinds map[string]bool StealthNetworkAllowKinds map[string]bool
MemoryObfuscationAllowComms map[string]bool
MemoryObfuscationAllowPaths []string
} }
// Default returns Python-compatible defaults for the fields currently used. // Default returns Python-compatible defaults for the fields currently used.
@ -76,6 +84,8 @@ func Default() Config {
EgressAllowCIDRs: []string{}, EgressAllowCIDRs: []string{},
StealthNetworkAllowComms: stringSet(defaultStealthNetworkAllowComms), StealthNetworkAllowComms: stringSet(defaultStealthNetworkAllowComms),
StealthNetworkAllowKinds: map[string]bool{}, StealthNetworkAllowKinds: map[string]bool{},
MemoryObfuscationAllowComms: stringSet(defaultMemoryObfuscationAllowComms),
MemoryObfuscationAllowPaths: []string{},
} }
} }
@ -173,6 +183,18 @@ func Load(path string) (Config, error) {
return Config{}, fmt.Errorf("stealth_network_allow_kinds: %w", err) return Config{}, fmt.Errorf("stealth_network_allow_kinds: %w", err)
} }
cfg.StealthNetworkAllowKinds = stringSet(kinds) cfg.StealthNetworkAllowKinds = stringSet(kinds)
case "memory_obfuscation_allow_comms":
comms, err := stringArray(value)
if err != nil {
return Config{}, fmt.Errorf("memory_obfuscation_allow_comms: %w", err)
}
cfg.MemoryObfuscationAllowComms = stringSet(comms)
case "memory_obfuscation_allow_paths":
paths, err := stringArray(value)
if err != nil {
return Config{}, fmt.Errorf("memory_obfuscation_allow_paths: %w", err)
}
cfg.MemoryObfuscationAllowPaths = paths
} }
} }
return cfg, nil return cfg, nil

View file

@ -35,6 +35,8 @@ interpreters = ["bash", "python3"]
egress_allow_cidrs = ["203.0.113.0/24"] egress_allow_cidrs = ["203.0.113.0/24"]
stealth_network_allow_comms = ["tcpdump"] stealth_network_allow_comms = ["tcpdump"]
stealth_network_allow_kinds = ["mptcp"] stealth_network_allow_kinds = ["mptcp"]
memory_obfuscation_allow_comms = ["jit-runtime"]
memory_obfuscation_allow_paths = ["/tmp/known-profiler/"]
unknown_future_key = true unknown_future_key = true
`) `)
if err := os.WriteFile(path, raw, 0o600); err != nil { if err := os.WriteFile(path, raw, 0o600); err != nil {
@ -64,6 +66,11 @@ unknown_future_key = true
t.Fatalf("unexpected stealth tuning: %#v %#v", t.Fatalf("unexpected stealth tuning: %#v %#v",
cfg.StealthNetworkAllowComms, cfg.StealthNetworkAllowKinds) cfg.StealthNetworkAllowComms, cfg.StealthNetworkAllowKinds)
} }
if !cfg.MemoryObfuscationAllowComms["jit-runtime"] || len(cfg.MemoryObfuscationAllowPaths) != 1 ||
cfg.MemoryObfuscationAllowPaths[0] != "/tmp/known-profiler/" {
t.Fatalf("unexpected memory tuning: %#v %#v",
cfg.MemoryObfuscationAllowComms, cfg.MemoryObfuscationAllowPaths)
}
} }
func TestMissingFileKeepsDefaults(t *testing.T) { func TestMissingFileKeepsDefaults(t *testing.T) {

View 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>"
}

View file

@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package detectors
import (
"testing"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
)
func TestMemoryObfuscationMatchesPythonShapes(t *testing.T) {
cases := []struct {
name string
process model.Process
signature string
severity string
sid int
}{
{"memfd", model.Process{PID: 350, Comm: "hoxha", MemoryMaps: []model.MemoryMap{{
Start: 0x1000, End: 0x2000, Perms: "r-xp", Path: "/memfd:stage (deleted)",
}}}, "memory_obfuscation", "CRITICAL", SIDMemoryObfuscation},
{"anonymous-rwx", model.Process{PID: 351, Comm: "packer", MemoryMaps: []model.MemoryMap{{
Start: 0x3000, End: 0x4000, Perms: "rwxp",
}}}, "memory_obfuscation", "HIGH", SIDMemoryObfuscation},
{"hide-library", model.Process{PID: 352, Comm: "bash", MemoryMaps: []model.MemoryMap{{
Start: 0x5000, End: 0x6000, Perms: "r-xp", Path: "/usr/lib/libhide.so",
}}}, "process_hiding_library", "CRITICAL", SIDProcessHidingLibrary},
{"injection-library", model.Process{PID: 355, Comm: "sshd", MemoryMaps: []model.MemoryMap{{
Start: 0xb000, End: 0xc000, Perms: "r-xp", Path: "/tmp/libinject.so",
}}}, "process_injection_library", "HIGH", SIDProcessInjectionLibrary},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
alerts := MemoryObfuscation(model.State{Processes: []model.Process{tc.process}}, config.Default())
if len(alerts) != 1 {
t.Fatalf("expected one alert, got %#v", alerts)
}
if alerts[0].Signature != tc.signature || alerts[0].Severity != tc.severity || alerts[0].SID != tc.sid {
t.Fatalf("unexpected alert: %#v", alerts[0])
}
})
}
}
func TestMemoryObfuscationAllowlistsMatchPython(t *testing.T) {
cfg := config.Default()
if len(MemoryObfuscation(model.State{Processes: []model.Process{{
PID: 353, Comm: "node", MemoryMaps: []model.MemoryMap{{Perms: "rwxp"}},
}}}, cfg)) != 0 {
t.Fatal("generic executable mapping should honor the runtime allowlist")
}
cfg.MemoryObfuscationAllowPaths = []string{"/tmp/known-profiler/"}
if len(MemoryObfuscation(model.State{Processes: []model.Process{{
PID: 356, Comm: "python3", MemoryMaps: []model.MemoryMap{{
Perms: "r-xp", Path: "/tmp/known-profiler/libhook.so",
}},
}}}, cfg)) != 0 {
t.Fatal("configured mapping path should be silent")
}
alerts := MemoryObfuscation(model.State{Processes: []model.Process{{
PID: 354, Comm: "node", MemoryMaps: []model.MemoryMap{{
Perms: "r-xp", Path: "/tmp/libhide.so",
}},
}}}, config.Default())
if len(alerts) != 1 || alerts[0].Signature != "process_hiding_library" {
t.Fatalf("hide-library evidence must survive comm allowlist: %#v", alerts)
}
}

View file

@ -13,6 +13,17 @@ type Process struct {
Exe string `json:"exe"` Exe string `json:"exe"`
Environ map[string]string `json:"environ"` Environ map[string]string `json:"environ"`
FDTargets map[string]string `json:"fd_targets"` FDTargets map[string]string `json:"fd_targets"`
MemoryMaps []MemoryMap `json:"memory_maps"`
}
// MemoryMap is one parsed /proc/<pid>/maps row. The detector only needs the
// address range, permission bits, and optional pathname; retaining those raw
// fields keeps fixture input and alert explanations close to Python.
type MemoryMap struct {
Start uint64 `json:"start"`
End uint64 `json:"end"`
Perms string `json:"perms"`
Path string `json:"path"`
} }
// State is one injectable detector sweep, equivalent to the Python // State is one injectable detector sweep, equivalent to the Python

View file

@ -84,6 +84,7 @@ func CaptureWithPaths(paths Paths) (model.State, error) {
Exe: readLink(filepath.Join(base, "exe")), Exe: readLink(filepath.Join(base, "exe")),
Environ: parseEnviron(readText(filepath.Join(base, "environ"))), Environ: parseEnviron(readText(filepath.Join(base, "environ"))),
FDTargets: readFDTargets(filepath.Join(base, "fd")), FDTargets: readFDTargets(filepath.Join(base, "fd")),
MemoryMaps: parseMemoryMaps(readText(filepath.Join(base, "maps"))),
}) })
} }
sort.Slice(processes, func(i, j int) bool { return processes[i].PID < processes[j].PID }) sort.Slice(processes, func(i, j int) bool { return processes[i].PID < processes[j].PID })
@ -193,6 +194,49 @@ func readFDTargets(path string) map[string]string {
return result return result
} }
// parseMemoryMaps mirrors Python's split(maxsplit=5) parser. A process may
// disappear or deny maps access between directory enumeration and this read;
// returning an empty slice preserves the detector's fail-open behavior.
func parseMemoryMaps(text string) []model.MemoryMap {
result := make([]model.MemoryMap, 0)
for _, line := range strings.Split(text, "\n") {
fields := strings.Fields(line)
if len(fields) < 5 {
continue
}
bounds := strings.SplitN(fields[0], "-", 2)
if len(bounds) != 2 {
continue
}
start, err := strconv.ParseUint(bounds[0], 16, 64)
if err != nil {
continue
}
end, err := strconv.ParseUint(bounds[1], 16, 64)
if err != nil {
continue
}
path := ""
if len(fields) > 5 {
// Keep pathname spaces intact, just as Python's maxsplit=5 does.
remaining := strings.TrimSpace(line)
for index := 0; index < 5; index++ {
space := strings.IndexAny(remaining, " \t")
if space < 0 {
remaining = ""
break
}
remaining = strings.TrimSpace(remaining[space:])
}
path = remaining
}
result = append(result, model.MemoryMap{
Start: start, End: end, Perms: fields[1], Path: path,
})
}
return result
}
func readText(path string) string { func readText(path string) string {
raw, err := os.ReadFile(path) raw, err := os.ReadFile(path)
if err != nil { if err != nil {

View file

@ -22,6 +22,9 @@ func TestCaptureUsesInjectableProcRoot(t *testing.T) {
if err := os.WriteFile(filepath.Join(proc, "cmdline"), []byte("bash\x00-i\x00"), 0o600); err != nil { if err := os.WriteFile(filepath.Join(proc, "cmdline"), []byte("bash\x00-i\x00"), 0o600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := os.WriteFile(filepath.Join(proc, "maps"), []byte("7f00-8000 rwxp 00000000 00:00 0 /memfd:stage (deleted)\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(proc, "environ"), []byte("LD_PRELOAD=/tmp/x.so\x00A=B\x00"), 0o600); err != nil { if err := os.WriteFile(filepath.Join(proc, "environ"), []byte("LD_PRELOAD=/tmp/x.so\x00A=B\x00"), 0o600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -56,11 +59,23 @@ func TestCaptureUsesInjectableProcRoot(t *testing.T) {
if got.Environ["LD_PRELOAD"] != "/tmp/x.so" || got.FDTargets["7"] != "/dev/input/event3" { if got.Environ["LD_PRELOAD"] != "/tmp/x.so" || got.FDTargets["7"] != "/dev/input/event3" {
t.Fatalf("missing environment or fd state: %#v", got) t.Fatalf("missing environment or fd state: %#v", got)
} }
if len(got.MemoryMaps) != 1 || got.MemoryMaps[0].Start != 0x7f00 ||
got.MemoryMaps[0].Path != "/memfd:stage (deleted)" {
t.Fatalf("missing memory-map state: %#v", got.MemoryMaps)
}
if state.LDPreload != "/tmp/global.so" { if state.LDPreload != "/tmp/global.so" {
t.Fatalf("unexpected global preload: %q", state.LDPreload) t.Fatalf("unexpected global preload: %q", state.LDPreload)
} }
} }
func TestParseMemoryMapsPreservesPathSpaces(t *testing.T) {
maps := parseMemoryMaps("7f00-8000 r-xp 00000000 00:00 0 /tmp/a path.so\n")
if len(maps) != 1 || maps[0].Start != 0x7f00 || maps[0].End != 0x8000 ||
maps[0].Perms != "r-xp" || maps[0].Path != "/tmp/a path.so" {
t.Fatalf("unexpected maps: %#v", maps)
}
}
func TestParseSSMatchesPythonShapes(t *testing.T) { func TestParseSSMatchesPythonShapes(t *testing.T) {
text := "tcp ESTAB 0 0 10.0.0.2:5555 8.8.8.8:443 users:((\"bash\",pid=42,fd=3)) ino:999\n" text := "tcp ESTAB 0 0 10.0.0.2:5555 8.8.8.8:443 users:((\"bash\",pid=42,fd=3)) ino:999\n"
sockets := parseSS(text, "tcp") sockets := parseSS(text, "tcp")

View file

@ -22,10 +22,11 @@ from enodia_sentinel.detectors import ( # noqa: E402
egress, egress,
input_snooper, input_snooper,
ld_preload, ld_preload,
memory_obfuscation,
reverse_shell, reverse_shell,
stealth_network, stealth_network,
) )
from enodia_sentinel.system import Socket, SystemState # noqa: E402 from enodia_sentinel.system import MemoryMap, Socket, SystemState # noqa: E402
HOST = "parity-host" HOST = "parity-host"
TIMESTAMP = "2026-07-10T00:00:00-07:00" TIMESTAMP = "2026-07-10T00:00:00-07:00"
@ -73,6 +74,7 @@ def main() -> int:
process = dict(raw_process) process = dict(raw_process)
process.setdefault("environ", {}) process.setdefault("environ", {})
process.setdefault("fd_targets", {}) process.setdefault("fd_targets", {})
process["memory_maps"] = [MemoryMap(**mapping) for mapping in process.get("memory_maps", [])]
processes.append(FixtureProcess(**process)) processes.append(FixtureProcess(**process))
sockets = [Socket(**item) for item in fixture_data.get("sockets", [])] sockets = [Socket(**item) for item in fixture_data.get("sockets", [])]
cfg = Config() cfg = Config()
@ -87,6 +89,7 @@ def main() -> int:
alerts.extend(input_snooper.detect(state, cfg)) alerts.extend(input_snooper.detect(state, cfg))
alerts.extend(credential_access.detect(state, cfg)) alerts.extend(credential_access.detect(state, cfg))
alerts.extend(stealth_network.detect(state, cfg)) alerts.extend(stealth_network.detect(state, cfg))
alerts.extend(memory_obfuscation.detect(state, cfg))
alerts.extend(egress.detect(state, cfg)) alerts.extend(egress.detect(state, cfg))
python_alerts = [ python_alerts = [
event.from_alert(alert, host=HOST, timestamp=TIMESTAMP) event.from_alert(alert, host=HOST, timestamp=TIMESTAMP)

View file

@ -8,6 +8,12 @@
{"pid": 301, "comm": "memdrop", "cmdline": "memdrop", "exe": "/memfd:foo (deleted)"}, {"pid": 301, "comm": "memdrop", "cmdline": "memdrop", "exe": "/memfd:foo (deleted)"},
{"pid": 302, "comm": "upgrade", "cmdline": "upgrade", "exe": "/usr/bin/x (deleted)"}, {"pid": 302, "comm": "upgrade", "cmdline": "upgrade", "exe": "/usr/bin/x (deleted)"},
{"pid": 303, "comm": "normal", "cmdline": "normal", "exe": "/usr/bin/x"}, {"pid": 303, "comm": "normal", "cmdline": "normal", "exe": "/usr/bin/x"},
{"pid": 350, "comm": "hoxha", "cmdline": "hoxha", "exe": "/usr/bin/hoxha", "memory_maps": [{"start": 4096, "end": 8192, "perms": "r-xp", "path": "/memfd:stage (deleted)"}]},
{"pid": 351, "comm": "packer", "cmdline": "packer", "exe": "/usr/bin/packer", "memory_maps": [{"start": 12288, "end": 16384, "perms": "rwxp", "path": ""}]},
{"pid": 352, "comm": "bash", "cmdline": "bash", "exe": "/usr/bin/bash", "memory_maps": [{"start": 20480, "end": 24576, "perms": "r-xp", "path": "/usr/lib/libhide.so"}]},
{"pid": 353, "comm": "node", "cmdline": "node", "exe": "/usr/bin/node", "memory_maps": [{"start": 28672, "end": 32768, "perms": "rwxp", "path": ""}]},
{"pid": 354, "comm": "node", "cmdline": "node", "exe": "/usr/bin/node", "memory_maps": [{"start": 36864, "end": 40960, "perms": "r-xp", "path": "/tmp/libhide.so"}]},
{"pid": 355, "comm": "sshd", "cmdline": "sshd", "exe": "/usr/sbin/sshd", "memory_maps": [{"start": 45056, "end": 49152, "perms": "r-xp", "path": "/tmp/libinject.so"}]},
{"pid": 320, "comm": "hoxha", "cmdline": "hoxha", "exe": "/tmp/hoxha", "fd_targets": {"7": "/dev/input/event3"}}, {"pid": 320, "comm": "hoxha", "cmdline": "hoxha", "exe": "/tmp/hoxha", "fd_targets": {"7": "/dev/input/event3"}},
{"pid": 321, "comm": "Xorg", "cmdline": "Xorg", "exe": "/usr/bin/Xorg", "fd_targets": {"8": "/dev/hidraw0"}}, {"pid": 321, "comm": "Xorg", "cmdline": "Xorg", "exe": "/usr/bin/Xorg", "fd_targets": {"8": "/dev/hidraw0"}},
{"pid": 330, "comm": "hoxha", "cmdline": "hoxha", "exe": "/tmp/hoxha", "fd_targets": {"4": "/etc/shadow"}}, {"pid": 330, "comm": "hoxha", "cmdline": "hoxha", "exe": "/tmp/hoxha", "fd_targets": {"4": "/etc/shadow"}},