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
|
|
@ -7,13 +7,14 @@ behavioral oracle until every subsystem reaches fixture and red-team parity.
|
|||
Current scope:
|
||||
|
||||
- standard-library-only flat TOML loading for the settings used so far;
|
||||
- an injectable `/proc` process/file-descriptor snapshot plus fail-open `ss`
|
||||
socket collection;
|
||||
- an injectable `/proc` process/file-descriptor/memory-map snapshot plus
|
||||
fail-open `ss` socket collection;
|
||||
- a cancellable sweep loop that emits JSONL `enodia.event.v1` records;
|
||||
- exact `enodia.alert.v1` parity for `ld_preload` (SID `100011`),
|
||||
`deleted_exe` (`100012`), `input_snooper` (`100032`), and
|
||||
`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
|
||||
claims;
|
||||
- a shared fixture checked against the Python detector by
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ func (a *Agent) Sweep() ([]map[string]any, error) {
|
|||
if a.Config.Enabled("stealth_network") {
|
||||
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") {
|
||||
alerts = append(alerts, detectors.Egress(state, a.Config)...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,19 +44,27 @@ var defaultStealthNetworkAllowComms = []string{
|
|||
"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
|
||||
// fields are added as their detectors are ported.
|
||||
type Config struct {
|
||||
SampleInterval time.Duration
|
||||
HeartbeatMaxAge int
|
||||
Detectors map[string]bool
|
||||
InputSnooperAllowComms map[string]bool
|
||||
CredentialAccessAllowComms map[string]bool
|
||||
CredentialAccessExtraPaths []string
|
||||
Interpreters map[string]bool
|
||||
EgressAllowCIDRs []string
|
||||
StealthNetworkAllowComms map[string]bool
|
||||
StealthNetworkAllowKinds map[string]bool
|
||||
SampleInterval time.Duration
|
||||
HeartbeatMaxAge int
|
||||
Detectors map[string]bool
|
||||
InputSnooperAllowComms map[string]bool
|
||||
CredentialAccessAllowComms map[string]bool
|
||||
CredentialAccessExtraPaths []string
|
||||
Interpreters map[string]bool
|
||||
EgressAllowCIDRs []string
|
||||
StealthNetworkAllowComms map[string]bool
|
||||
StealthNetworkAllowKinds map[string]bool
|
||||
MemoryObfuscationAllowComms map[string]bool
|
||||
MemoryObfuscationAllowPaths []string
|
||||
}
|
||||
|
||||
// Default returns Python-compatible defaults for the fields currently used.
|
||||
|
|
@ -66,16 +74,18 @@ func Default() Config {
|
|||
detectors[name] = true
|
||||
}
|
||||
return Config{
|
||||
SampleInterval: 4 * time.Second,
|
||||
HeartbeatMaxAge: 120,
|
||||
Detectors: detectors,
|
||||
InputSnooperAllowComms: stringSet(defaultInputSnooperAllowComms),
|
||||
CredentialAccessAllowComms: stringSet(defaultCredentialAccessAllowComms),
|
||||
CredentialAccessExtraPaths: []string{},
|
||||
Interpreters: stringSet(defaultInterpreters),
|
||||
EgressAllowCIDRs: []string{},
|
||||
StealthNetworkAllowComms: stringSet(defaultStealthNetworkAllowComms),
|
||||
StealthNetworkAllowKinds: map[string]bool{},
|
||||
SampleInterval: 4 * time.Second,
|
||||
HeartbeatMaxAge: 120,
|
||||
Detectors: detectors,
|
||||
InputSnooperAllowComms: stringSet(defaultInputSnooperAllowComms),
|
||||
CredentialAccessAllowComms: stringSet(defaultCredentialAccessAllowComms),
|
||||
CredentialAccessExtraPaths: []string{},
|
||||
Interpreters: stringSet(defaultInterpreters),
|
||||
EgressAllowCIDRs: []string{},
|
||||
StealthNetworkAllowComms: stringSet(defaultStealthNetworkAllowComms),
|
||||
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)
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ interpreters = ["bash", "python3"]
|
|||
egress_allow_cidrs = ["203.0.113.0/24"]
|
||||
stealth_network_allow_comms = ["tcpdump"]
|
||||
stealth_network_allow_kinds = ["mptcp"]
|
||||
memory_obfuscation_allow_comms = ["jit-runtime"]
|
||||
memory_obfuscation_allow_paths = ["/tmp/known-profiler/"]
|
||||
unknown_future_key = true
|
||||
`)
|
||||
if err := os.WriteFile(path, raw, 0o600); err != nil {
|
||||
|
|
@ -64,6 +66,11 @@ unknown_future_key = true
|
|||
t.Fatalf("unexpected stealth tuning: %#v %#v",
|
||||
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) {
|
||||
|
|
|
|||
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>"
|
||||
}
|
||||
69
go-agent/internal/detectors/memory_obfuscation_test.go
Normal file
69
go-agent/internal/detectors/memory_obfuscation_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,23 @@ package model
|
|||
// Process is the subset of /proc process state needed by the first ported
|
||||
// poll detectors. Fields are added as more Python detectors move across.
|
||||
type Process struct {
|
||||
PID int `json:"pid"`
|
||||
Comm string `json:"comm"`
|
||||
Cmdline string `json:"cmdline"`
|
||||
Exe string `json:"exe"`
|
||||
Environ map[string]string `json:"environ"`
|
||||
FDTargets map[string]string `json:"fd_targets"`
|
||||
PID int `json:"pid"`
|
||||
Comm string `json:"comm"`
|
||||
Cmdline string `json:"cmdline"`
|
||||
Exe string `json:"exe"`
|
||||
Environ map[string]string `json:"environ"`
|
||||
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
|
||||
|
|
|
|||
|
|
@ -78,12 +78,13 @@ func CaptureWithPaths(paths Paths) (model.State, error) {
|
|||
}
|
||||
base := filepath.Join(procRoot, entry.Name())
|
||||
processes = append(processes, model.Process{
|
||||
PID: pid,
|
||||
Comm: strings.TrimSpace(readText(filepath.Join(base, "comm"))),
|
||||
Cmdline: strings.TrimSpace(strings.ReplaceAll(readText(filepath.Join(base, "cmdline")), "\x00", " ")),
|
||||
Exe: readLink(filepath.Join(base, "exe")),
|
||||
Environ: parseEnviron(readText(filepath.Join(base, "environ"))),
|
||||
FDTargets: readFDTargets(filepath.Join(base, "fd")),
|
||||
PID: pid,
|
||||
Comm: strings.TrimSpace(readText(filepath.Join(base, "comm"))),
|
||||
Cmdline: strings.TrimSpace(strings.ReplaceAll(readText(filepath.Join(base, "cmdline")), "\x00", " ")),
|
||||
Exe: readLink(filepath.Join(base, "exe")),
|
||||
Environ: parseEnviron(readText(filepath.Join(base, "environ"))),
|
||||
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 })
|
||||
|
|
@ -193,6 +194,49 @@ func readFDTargets(path string) map[string]string {
|
|||
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 {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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 {
|
||||
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" {
|
||||
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" {
|
||||
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) {
|
||||
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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue