Skip to main content

memf_linux/
magic_gid.rs

1//! Magic GID detection — identifies processes controlled by LD_PRELOAD rootkits.
2//!
3//! Father rootkit (github.com/mav8557/Father) grants GID 7823 to processes
4//! it controls via its accept() hook. Scanning /proc/<pid>/status for
5//! supplementary GIDs that match known rootkit magic values is a reliable
6//! indicator even when the process is hidden from readdir.
7
8/// Magic GID used by the Father rootkit to mark controlled processes.
9pub const FATHER_MAGIC_GID: u32 = 7823;
10
11/// Known rootkit magic GIDs to watch for.
12///
13/// Each entry is `(gid, rootkit_name)`.
14pub const KNOWN_MAGIC_GIDS: &[(u32, &str)] = &[(7823, "Father")];
15
16/// Returns `Some(rootkit_name)` if the GID is a known rootkit magic GID,
17/// or `None` if it is not recognised.
18pub fn classify_magic_gid(gid: u32) -> Option<&'static str> {
19    KNOWN_MAGIC_GIDS
20        .iter()
21        .find(|&&(known_gid, _)| known_gid == gid)
22        .map(|&(_, name)| name)
23}
24
25/// Returns `true` if any GID in the list is a known rootkit magic GID.
26pub fn has_magic_gid(gids: &[u32]) -> bool {
27    gids.iter().any(|&g| classify_magic_gid(g).is_some())
28}
29
30/// A finding produced when a process carries a known rootkit magic GID.
31#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
32pub struct MagicGidFinding {
33    /// Process ID.
34    pub pid: u32,
35    /// Process name (`task_struct.comm`, max 16 chars).
36    pub comm: String,
37    /// The magic GID that triggered this finding.
38    pub magic_gid: u32,
39    /// Name of the rootkit associated with the magic GID.
40    pub rootkit_name: &'static str,
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn classify_father_magic_gid_returns_some() {
49        assert_eq!(classify_magic_gid(7823), Some("Father"));
50    }
51
52    #[test]
53    fn classify_normal_gid_returns_none() {
54        assert_eq!(classify_magic_gid(1000), None);
55    }
56
57    #[test]
58    fn classify_zero_gid_returns_none() {
59        assert_eq!(classify_magic_gid(0), None);
60    }
61
62    #[test]
63    fn has_magic_gid_true_when_present() {
64        assert!(has_magic_gid(&[1000, 7823]));
65    }
66
67    #[test]
68    fn has_magic_gid_false_when_absent() {
69        assert!(!has_magic_gid(&[1000, 2000]));
70    }
71
72    #[test]
73    fn has_magic_gid_empty_slice_is_false() {
74        assert!(!has_magic_gid(&[]));
75    }
76
77    #[test]
78    fn magic_gid_finding_fields_constructible() {
79        let finding = MagicGidFinding {
80            pid: 1234,
81            comm: "evil".to_string(),
82            magic_gid: 7823,
83            rootkit_name: "Father",
84        };
85        assert_eq!(finding.pid, 1234);
86        assert_eq!(finding.comm, "evil");
87        assert_eq!(finding.magic_gid, 7823);
88        assert_eq!(finding.rootkit_name, "Father");
89    }
90
91    #[test]
92    fn magic_gid_finding_serializes_to_json() {
93        let finding = MagicGidFinding {
94            pid: 42,
95            comm: "rootkit".to_string(),
96            magic_gid: 7823,
97            rootkit_name: "Father",
98        };
99        let json = serde_json::to_string(&finding).unwrap();
100        assert!(json.contains("\"pid\":42"));
101        assert!(json.contains("\"magic_gid\":7823"));
102        assert!(json.contains("\"rootkit_name\":\"Father\""));
103    }
104
105    #[test]
106    fn magic_gid_finding_clone_and_debug() {
107        let finding = MagicGidFinding {
108            pid: 99,
109            comm: "sh".to_string(),
110            magic_gid: 7823,
111            rootkit_name: "Father",
112        };
113        let cloned = finding.clone();
114        let dbg = format!("{cloned:?}");
115        assert!(dbg.contains("Father"));
116    }
117
118    #[test]
119    fn father_magic_gid_constant_matches_known_table() {
120        let found = KNOWN_MAGIC_GIDS
121            .iter()
122            .any(|&(gid, name)| gid == FATHER_MAGIC_GID && name == "Father");
123        assert!(found, "FATHER_MAGIC_GID must appear in KNOWN_MAGIC_GIDS");
124    }
125}