memf_linux/
keyboard_notifiers.rs1use memf_core::object_reader::ObjectReader;
9use memf_format::PhysicalMemoryProvider;
10
11use crate::Result;
12
13#[derive(Debug, Clone, serde::Serialize)]
15pub struct KeyboardNotifierInfo {
16 pub address: u64,
18 pub notifier_call: u64,
20 pub priority: i32,
22 pub is_suspicious: bool,
24}
25
26pub fn walk_keyboard_notifiers<P: PhysicalMemoryProvider>(
38 reader: &ObjectReader<P>,
39) -> Result<Vec<KeyboardNotifierInfo>> {
40 const MAX_NOTIFIERS: usize = 1_000;
41 let Some(head_addr) = reader.symbols().symbol_address("keyboard_notifier_list") else {
42 return Ok(Vec::new());
43 };
44
45 let stext = reader.symbols().symbol_address("_stext").unwrap_or(0);
46 let etext = reader
47 .symbols()
48 .symbol_address("_etext")
49 .unwrap_or(u64::MAX);
50
51 let first_nb = match reader.read_bytes(head_addr, 8) {
53 Ok(b) if b.len() == 8 => b.try_into().map_or(0, u64::from_le_bytes),
54 _ => return Ok(Vec::new()),
55 };
56
57 let mut notifiers = Vec::new();
58 let mut current = first_nb;
59
60 for _ in 0..MAX_NOTIFIERS {
61 if current == 0 {
62 break;
63 }
64
65 let notifier_call = match reader.read_bytes(current, 8) {
67 Ok(b) if b.len() == 8 => b.try_into().map_or(0, u64::from_le_bytes),
68 _ => break,
69 };
70
71 let next = match reader.read_bytes(current + 8, 8) {
73 Ok(b) if b.len() == 8 => b.try_into().map_or(0, u64::from_le_bytes),
74 _ => 0,
75 };
76
77 let priority = match reader.read_bytes(current + 16, 4) {
79 Ok(b) if b.len() == 4 => b.try_into().map_or(0, i32::from_le_bytes),
80 _ => 0,
81 };
82
83 let is_suspicious = classify_notifier(notifier_call, stext, etext);
84
85 notifiers.push(KeyboardNotifierInfo {
86 address: current,
87 notifier_call,
88 priority,
89 is_suspicious,
90 });
91
92 current = next;
93 }
94
95 Ok(notifiers)
96}
97
98pub use crate::heuristics::classify_notifier;
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
105 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
106 use memf_symbols::isf::IsfResolver;
107 use memf_symbols::test_builders::IsfBuilder;
108
109 fn make_no_symbol_reader() -> ObjectReader<SyntheticPhysMem> {
110 let isf = IsfBuilder::new().build_json();
111 let resolver = IsfResolver::from_value(&isf).unwrap();
112 let (cr3, mem) = PageTableBuilder::new().build();
113 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
114 ObjectReader::new(vas, Box::new(resolver))
115 }
116
117 #[test]
118 fn no_symbol_returns_empty() {
119 let reader = make_no_symbol_reader();
120 let result = walk_keyboard_notifiers(&reader).unwrap();
121 assert!(
122 result.is_empty(),
123 "no keyboard_notifier_list symbol → empty vec"
124 );
125 }
126
127 #[test]
128 fn classify_in_kernel_benign() {
129 let stext = 0xFFFF_FFFF_8100_0000_u64;
130 let etext = 0xFFFF_FFFF_8200_0000_u64;
131 let call = 0xFFFF_FFFF_8150_0000_u64;
132 assert!(
133 !classify_notifier(call, stext, etext),
134 "in-kernel notifier_call should be benign"
135 );
136 }
137
138 #[test]
139 fn classify_out_of_kernel_suspicious() {
140 let stext = 0xFFFF_FFFF_8100_0000_u64;
141 let etext = 0xFFFF_FFFF_8200_0000_u64;
142 let call = 0x0000_7FFF_1234_5678_u64; assert!(
144 classify_notifier(call, stext, etext),
145 "out-of-kernel notifier_call should be suspicious"
146 );
147 }
148
149 #[test]
151 fn walk_keyboard_notifiers_with_symbol_returns_entry() {
152 let head_vaddr: u64 = 0xFFFF_8000_0010_0000; let head_paddr: u64 = 0x0080_0000;
162 let nb_vaddr: u64 = 0xFFFF_8000_0010_1000;
163 let nb_paddr: u64 = 0x0081_0000;
164
165 let notifier_call: u64 = 0xFFFF_FFFF_8155_0000; let priority: i32 = 10;
167
168 let mut head_data = [0u8; 0x1000];
170 head_data[0..8].copy_from_slice(&nb_vaddr.to_le_bytes());
171
172 let mut nb_data = [0u8; 0x1000];
177 nb_data[0..8].copy_from_slice(¬ifier_call.to_le_bytes());
178 nb_data[8..16].copy_from_slice(&0u64.to_le_bytes()); nb_data[16..20].copy_from_slice(&priority.to_le_bytes());
180
181 let stext: u64 = 0xFFFF_FFFF_8100_0000;
182 let etext: u64 = 0xFFFF_FFFF_8200_0000;
183
184 let isf = IsfBuilder::new()
185 .add_symbol("keyboard_notifier_list", head_vaddr)
186 .add_symbol("_stext", stext)
187 .add_symbol("_etext", etext)
188 .build_json();
189
190 let resolver = IsfResolver::from_value(&isf).unwrap();
191 let (cr3, mut mem) = PageTableBuilder::new()
192 .map_4k(head_vaddr, head_paddr, flags::PRESENT | flags::WRITABLE)
193 .map_4k(nb_vaddr, nb_paddr, flags::PRESENT | flags::WRITABLE)
194 .build();
195 mem.write_bytes(head_paddr, &head_data);
196 mem.write_bytes(nb_paddr, &nb_data);
197
198 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
199 let reader = ObjectReader::new(vas, Box::new(resolver));
200
201 let notifiers = walk_keyboard_notifiers(&reader).unwrap();
202 assert_eq!(notifiers.len(), 1, "should find one notifier_block");
203 assert_eq!(notifiers[0].notifier_call, notifier_call);
204 assert_eq!(notifiers[0].priority, priority);
205 assert!(
206 !notifiers[0].is_suspicious,
207 "in-kernel call should be benign"
208 );
209 }
210}