1use memf_core::object_reader::ObjectReader;
8use memf_format::PhysicalMemoryProvider;
9
10use crate::{Error, HiddenModuleInfo, Result};
11
12fn check_module_in_sysfs<P: PhysicalMemoryProvider>(
19 reader: &ObjectReader<P>,
20 mod_addr: u64,
21) -> bool {
22 let mkobj_offset = match reader.symbols().field_offset("module", "mkobj") {
23 Some(off) => off,
24 None => return true, };
26 let kobj_offset = match reader.symbols().field_offset("module_kobject", "kobj") {
27 Some(off) => off,
28 None => return true,
29 };
30 let entry_offset = match reader.symbols().field_offset("kobject", "entry") {
31 Some(off) => off,
32 None => return true,
33 };
34
35 let entry_addr = mod_addr + mkobj_offset + kobj_offset + entry_offset;
36 let next_ptr: u64 = match reader.read_field(entry_addr, "list_head", "next") {
37 Ok(v) => v,
38 Err(_) => return true, };
40
41 next_ptr != 0
43}
44
45pub fn check_hidden_modules<P: PhysicalMemoryProvider>(
51 reader: &ObjectReader<P>,
52) -> Result<Vec<HiddenModuleInfo>> {
53 let modules_addr =
54 reader
55 .symbols()
56 .symbol_address("modules")
57 .ok_or_else(|| Error::MissingKernelSymbol {
58 name: "modules".into(),
59 })?;
60
61 let _list_offset = reader
62 .symbols()
63 .field_offset("module", "list")
64 .ok_or_else(|| Error::MissingField {
65 struct_name: "module".into(),
66 field_name: "list".into(),
67 })?;
68
69 let module_addrs = reader.walk_list(modules_addr, "module", "list")?;
71
72 let mut results = Vec::new();
73
74 for &mod_addr in &module_addrs {
75 let name = reader
76 .read_field_string(mod_addr, "module", "name", 56)
77 .unwrap_or_else(|_| "<unknown>".to_string());
78
79 let base_addr: u64 = reader
80 .read_field(mod_addr, "module", "module_core")
81 .unwrap_or(0);
82
83 let size: u32 = reader
84 .read_field(mod_addr, "module", "core_size")
85 .unwrap_or(0);
86
87 let in_sysfs = check_module_in_sysfs(reader, mod_addr);
92
93 results.push(HiddenModuleInfo {
94 name,
95 base_addr,
96 size: u64::from(size),
97 in_modules_list: true,
98 in_sysfs,
99 });
100 }
101
102 Ok(results)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use memf_core::test_builders::{flags as ptflags, PageTableBuilder, SyntheticPhysMem};
109 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
110 use memf_symbols::isf::IsfResolver;
111 use memf_symbols::test_builders::IsfBuilder;
112
113 fn make_test_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
114 let isf = IsfBuilder::new()
115 .add_struct("module", 256)
116 .add_field("module", "name", 0, "char")
117 .add_field("module", "list", 56, "list_head")
118 .add_field("module", "module_core", 128, "pointer")
119 .add_field("module", "core_size", 136, "unsigned int")
120 .add_field("module", "mkobj", 160, "module_kobject")
121 .add_struct("module_kobject", 64)
122 .add_field("module_kobject", "kobj", 0, "kobject")
123 .add_struct("kobject", 64)
124 .add_field("kobject", "name", 0, "pointer")
125 .add_field("kobject", "entry", 16, "list_head")
126 .add_struct("list_head", 16)
127 .add_field("list_head", "next", 0, "pointer")
128 .add_field("list_head", "prev", 8, "pointer")
129 .add_symbol("modules", vaddr + 0x800)
130 .add_symbol("module_kset", vaddr + 0x900)
131 .build_json();
132
133 let resolver = IsfResolver::from_value(&isf).unwrap();
134 let (cr3, mem) = PageTableBuilder::new()
135 .map_4k(vaddr, paddr, ptflags::WRITABLE)
136 .write_phys(paddr, data)
137 .build();
138 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
139 ObjectReader::new(vas, Box::new(resolver))
140 }
141
142 #[test]
143 fn empty_module_list() {
144 let vaddr: u64 = 0xFFFF_8000_0010_0000;
145 let paddr: u64 = 0x0080_0000;
146 let mut data = vec![0u8; 4096];
147
148 let modules_head = vaddr + 0x800;
150 data[0x800..0x808].copy_from_slice(&modules_head.to_le_bytes());
151 data[0x808..0x810].copy_from_slice(&modules_head.to_le_bytes());
152
153 let reader = make_test_reader(&data, vaddr, paddr);
154 let results = check_hidden_modules(&reader).unwrap();
155
156 assert!(results.is_empty());
157 }
158
159 #[test]
160 fn missing_modules_symbol() {
161 let isf = IsfBuilder::new()
162 .add_struct("module", 64)
163 .add_field("module", "name", 0, "char")
164 .add_field("module", "list", 8, "list_head")
165 .add_struct("list_head", 16)
166 .add_field("list_head", "next", 0, "pointer")
167 .add_field("list_head", "prev", 8, "pointer")
168 .build_json();
169
170 let resolver = IsfResolver::from_value(&isf).unwrap();
171 let (cr3, mem) = PageTableBuilder::new().build();
172 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
173 let reader = ObjectReader::new(vas, Box::new(resolver));
174
175 let result = check_hidden_modules(&reader);
176 assert!(
177 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "modules"),
178 "expected MissingKernelSymbol {{name: \"modules\"}}, got {result:?}"
179 );
180 }
181
182 #[test]
183 fn missing_module_list_field_returns_error() {
184 let isf = IsfBuilder::new()
186 .add_struct("module", 64)
187 .add_field("module", "name", 0, "char")
188 .add_struct("list_head", 16)
190 .add_field("list_head", "next", 0, "pointer")
191 .add_field("list_head", "prev", 8, "pointer")
192 .add_symbol("modules", 0xFFFF_8000_0010_0800)
193 .build_json();
194
195 let resolver = IsfResolver::from_value(&isf).unwrap();
196 let (cr3, mem) = PageTableBuilder::new().build();
197 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
198 let reader = ObjectReader::new(vas, Box::new(resolver));
199
200 let result = check_hidden_modules(&reader);
201 assert!(
202 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "module" && field_name == "list"),
203 "expected MissingField module.list, got {result:?}"
204 );
205 }
206
207 #[test]
208 fn single_module_in_list_with_sysfs() {
209 let vaddr: u64 = 0xFFFF_8000_0010_0000;
214 let paddr: u64 = 0x0080_0000;
215 let mut data = vec![0u8; 4096];
216
217 let module_list_vaddr = vaddr; let module_list_field_vaddr = module_list_vaddr + 56;
219
220 let modules_head = vaddr + 0x800;
221 data[0x800..0x808].copy_from_slice(&module_list_field_vaddr.to_le_bytes());
222 data[0x808..0x810].copy_from_slice(&modules_head.to_le_bytes());
223
224 data[0..8].copy_from_slice(b"rootkit\0");
226 data[56..64].copy_from_slice(&modules_head.to_le_bytes());
228 data[64..72].copy_from_slice(&modules_head.to_le_bytes());
229 let base: u64 = 0xFFFF_C000_0000_0000;
231 data[128..136].copy_from_slice(&base.to_le_bytes());
232 data[136..140].copy_from_slice(&4096u32.to_le_bytes());
234 let kobj_entry_next_sentinel: u64 = 0xFFFF_8000_DEAD_0001;
236 data[176..184].copy_from_slice(&kobj_entry_next_sentinel.to_le_bytes());
237
238 let reader = make_test_reader(&data, vaddr, paddr);
239 let results = check_hidden_modules(&reader).unwrap();
240
241 assert_eq!(results.len(), 1, "should find one module");
242 assert!(
243 results[0].name.starts_with("rootkit"),
244 "name should match: {}",
245 results[0].name
246 );
247 assert_eq!(results[0].base_addr, base);
248 assert_eq!(results[0].size, 4096);
249 assert!(results[0].in_modules_list);
250 assert!(
251 results[0].in_sysfs,
252 "module with non-zero kobj entry.next should be in sysfs"
253 );
254 }
255
256 #[test]
257 fn single_module_not_in_sysfs() {
258 use memf_core::test_builders::{flags as ptflags, PageTableBuilder};
259 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
260 use memf_symbols::isf::IsfResolver;
261
262 let vaddr: u64 = 0xFFFF_8000_0011_0000;
264 let paddr: u64 = 0x0081_0000;
265 let mut data = vec![0u8; 4096];
266
267 let module_list_field_vaddr = vaddr + 56;
268 let modules_head = vaddr + 0x800;
269 data[0x800..0x808].copy_from_slice(&module_list_field_vaddr.to_le_bytes());
270 data[0x808..0x810].copy_from_slice(&modules_head.to_le_bytes());
271
272 data[0..8].copy_from_slice(b"hidden\0\0");
273 data[56..64].copy_from_slice(&modules_head.to_le_bytes());
274 data[64..72].copy_from_slice(&modules_head.to_le_bytes());
275 let base: u64 = 0xFFFF_C001_0000_0000;
276 data[128..136].copy_from_slice(&base.to_le_bytes());
277 data[136..140].copy_from_slice(&4096u32.to_le_bytes());
278 let isf = memf_symbols::test_builders::IsfBuilder::new()
282 .add_struct("module", 256)
283 .add_field("module", "name", 0, "char")
284 .add_field("module", "list", 56, "list_head")
285 .add_field("module", "module_core", 128, "pointer")
286 .add_field("module", "core_size", 136, "unsigned int")
287 .add_field("module", "mkobj", 160, "module_kobject")
288 .add_struct("module_kobject", 64)
289 .add_field("module_kobject", "kobj", 0, "kobject")
290 .add_struct("kobject", 64)
291 .add_field("kobject", "name", 0, "pointer")
292 .add_field("kobject", "entry", 16, "list_head")
293 .add_struct("list_head", 16)
294 .add_field("list_head", "next", 0, "pointer")
295 .add_field("list_head", "prev", 8, "pointer")
296 .add_symbol("modules", vaddr + 0x800)
297 .add_symbol("module_kset", vaddr + 0x900)
298 .build_json();
299
300 let resolver = IsfResolver::from_value(&isf).unwrap();
301 let (cr3, mem) = PageTableBuilder::new()
302 .map_4k(vaddr, paddr, ptflags::WRITABLE)
303 .write_phys(paddr, &data)
304 .build();
305 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
306 let reader = ObjectReader::new(vas, Box::new(resolver));
307
308 let results = check_hidden_modules(&reader).unwrap();
309 assert_eq!(results.len(), 1, "should find one module");
310 assert!(results[0].name.starts_with("hidden"));
311 assert!(results[0].in_modules_list);
312 assert!(
313 !results[0].in_sysfs,
314 "module with kobj entry.next==0 should not be in sysfs"
315 );
316 }
317}