1use memf_core::object_reader::ObjectReader;
21use memf_format::PhysicalMemoryProvider;
22
23use crate::Result;
24
25const MAX_IDT_ENTRIES: usize = 256;
27
28const GATE_DESC_SIZE: usize = 16;
30
31#[derive(Debug, Clone, serde::Serialize)]
33pub struct IdtEntryInfo {
34 pub vector: u8,
36 pub handler_addr: u64,
38 pub segment: u16,
40 pub gate_type: String,
42 pub is_hooked: bool,
44}
45
46pub use crate::heuristics::classify_idt_entry;
52
53pub fn gate_type_name(type_attr: u8) -> String {
60 let gate_nibble = type_attr & 0x0F;
61 match gate_nibble {
62 0xE => "Interrupt Gate".to_string(),
63 0xF => "Trap Gate".to_string(),
64 other => format!("Unknown(0x{other:02X})"),
65 }
66}
67
68pub fn walk_check_idt<P: PhysicalMemoryProvider>(
75 reader: &ObjectReader<P>,
76) -> Result<Vec<IdtEntryInfo>> {
77 let symbols = reader.symbols();
78
79 let Some(kernel_start) = symbols.symbol_address("_stext") else {
81 return Ok(Vec::new());
82 };
83 let Some(kernel_end) = symbols.symbol_address("_etext") else {
84 return Ok(Vec::new());
85 };
86
87 let Some(idt_base) = symbols.symbol_address("idt_table") else {
89 return Ok(Vec::new());
90 };
91
92 let mut results = Vec::new();
93
94 for vector in 0..MAX_IDT_ENTRIES {
95 let entry_addr = idt_base + (vector as u64) * (GATE_DESC_SIZE as u64);
96
97 let raw = match reader.read_bytes(entry_addr, GATE_DESC_SIZE) {
99 Ok(bytes) => bytes,
100 Err(_) => continue,
101 };
102
103 let offset_low = u16::from_le_bytes([raw[0], raw[1]]);
112 let segment = u16::from_le_bytes([raw[2], raw[3]]);
113 let type_attr = raw[5];
114 let offset_mid = u16::from_le_bytes([raw[6], raw[7]]);
115 let offset_high = u32::from_le_bytes([raw[8], raw[9], raw[10], raw[11]]);
116
117 let handler_addr =
118 u64::from(offset_high) << 32 | u64::from(offset_mid) << 16 | u64::from(offset_low);
119
120 if handler_addr == 0 {
122 continue;
123 }
124
125 let is_hooked = classify_idt_entry(handler_addr, kernel_start, kernel_end);
126 let gate_type = gate_type_name(type_attr);
127
128 results.push(IdtEntryInfo {
129 vector: vector as u8,
130 handler_addr,
131 segment,
132 gate_type,
133 is_hooked,
134 });
135 }
136
137 Ok(results)
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use memf_core::test_builders::PageTableBuilder;
144 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
145 use memf_symbols::isf::IsfResolver;
146 use memf_symbols::test_builders::IsfBuilder;
147
148 #[test]
153 fn classify_hooked_outside_kernel() {
154 let kernel_start = 0xFFFF_8000_0000_0000u64;
155 let kernel_end = 0xFFFF_8000_00FF_FFFFu64;
156
157 assert!(classify_idt_entry(
159 0xFFFF_C900_DEAD_BEEF,
160 kernel_start,
161 kernel_end
162 ));
163 assert!(classify_idt_entry(
165 kernel_start - 1,
166 kernel_start,
167 kernel_end
168 ));
169 assert!(classify_idt_entry(kernel_end + 1, kernel_start, kernel_end));
171 }
172
173 #[test]
174 fn classify_benign_inside_kernel() {
175 let kernel_start = 0xFFFF_8000_0000_0000u64;
176 let kernel_end = 0xFFFF_8000_00FF_FFFFu64;
177
178 assert!(!classify_idt_entry(kernel_start, kernel_start, kernel_end));
180 assert!(!classify_idt_entry(
182 kernel_start + 0x1000,
183 kernel_start,
184 kernel_end
185 ));
186 assert!(!classify_idt_entry(kernel_end, kernel_start, kernel_end));
188 }
189
190 #[test]
191 fn classify_null_benign() {
192 let kernel_start = 0xFFFF_8000_0000_0000u64;
193 let kernel_end = 0xFFFF_8000_00FF_FFFFu64;
194
195 assert!(!classify_idt_entry(0, kernel_start, kernel_end));
197 }
198
199 #[test]
200 fn gate_type_interrupt() {
201 assert_eq!(gate_type_name(0x8E), "Interrupt Gate");
202 assert_eq!(gate_type_name(0x0E), "Interrupt Gate");
204 }
205
206 #[test]
207 fn gate_type_trap() {
208 assert_eq!(gate_type_name(0x8F), "Trap Gate");
209 assert_eq!(gate_type_name(0x0F), "Trap Gate");
210 }
211
212 #[test]
213 fn gate_type_unknown() {
214 assert_eq!(gate_type_name(0x00), "Unknown(0x00)");
215 assert_eq!(gate_type_name(0x85), "Unknown(0x05)");
216 assert_eq!(gate_type_name(0xAC), "Unknown(0x0C)");
217 }
218
219 #[test]
220 fn walk_no_symbol_returns_empty() {
221 let isf = IsfBuilder::new()
224 .add_struct("task_struct", 64)
225 .add_field("task_struct", "pid", 0, "int")
226 .add_symbol("_stext", 0xFFFF_8000_0000_0000)
227 .add_symbol("_etext", 0xFFFF_8000_00FF_FFFF)
228 .build_json();
229
230 let resolver = IsfResolver::from_value(&isf).unwrap();
231 let (cr3, mem) = PageTableBuilder::new().build();
232 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
233 let reader = ObjectReader::new(vas, Box::new(resolver));
234
235 let results = walk_check_idt(&reader).unwrap();
236 assert!(
237 results.is_empty(),
238 "expected empty results when idt_table symbol is missing"
239 );
240 }
241
242 #[test]
243 fn walk_missing_stext_returns_empty() {
244 let isf = IsfBuilder::new()
246 .add_symbol("_etext", 0xFFFF_8000_00FF_FFFF)
248 .add_symbol("idt_table", 0xFFFF_8000_0020_0000)
249 .build_json();
250
251 let resolver = IsfResolver::from_value(&isf).unwrap();
252 let (cr3, mem) = PageTableBuilder::new().build();
253 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
254 let reader = ObjectReader::new(vas, Box::new(resolver));
255
256 let results = walk_check_idt(&reader).unwrap();
257 assert!(results.is_empty(), "missing _stext should yield empty vec");
258 }
259
260 #[test]
261 fn walk_missing_etext_returns_empty() {
262 let isf = IsfBuilder::new()
264 .add_symbol("_stext", 0xFFFF_8000_0000_0000)
265 .add_symbol("idt_table", 0xFFFF_8000_0020_0000)
267 .build_json();
268
269 let resolver = IsfResolver::from_value(&isf).unwrap();
270 let (cr3, mem) = PageTableBuilder::new().build();
271 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
272 let reader = ObjectReader::new(vas, Box::new(resolver));
273
274 let results = walk_check_idt(&reader).unwrap();
275 assert!(results.is_empty(), "missing _etext should yield empty vec");
276 }
277
278 #[test]
283 fn walk_check_idt_symbol_present_all_zero_entries() {
284 let idt_vaddr: u64 = 0xFFFF_8800_0070_0000;
287 let idt_paddr: u64 = 0x0080_0000;
288 let kernel_start: u64 = 0xFFFF_8000_0000_0000;
289 let kernel_end: u64 = 0xFFFF_8000_00FF_FFFF;
290
291 let page = [0u8; 4096];
293
294 let isf = IsfBuilder::new()
295 .add_symbol("_stext", kernel_start)
296 .add_symbol("_etext", kernel_end)
297 .add_symbol("idt_table", idt_vaddr)
298 .build_json();
299
300 let resolver = IsfResolver::from_value(&isf).unwrap();
301 let (cr3, mem) = PageTableBuilder::new()
302 .map_4k(
303 idt_vaddr,
304 idt_paddr,
305 memf_core::test_builders::flags::WRITABLE,
306 )
307 .write_phys(idt_paddr, &page)
308 .build();
309 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
310 let reader = ObjectReader::new(vas, Box::new(resolver));
311
312 let results = walk_check_idt(&reader).unwrap_or_default();
313 assert!(
314 results.is_empty(),
315 "all-zero IDT entries (handler==0) should all be skipped"
316 );
317 }
318
319 #[test]
320 fn gate_type_all_nibbles_covered() {
321 for nibble in 0u8..=0x0F {
323 let name = gate_type_name(nibble);
324 match nibble {
325 0xE => assert_eq!(name, "Interrupt Gate"),
326 0xF => assert_eq!(name, "Trap Gate"),
327 _ => assert!(name.starts_with("Unknown("), "nibble {nibble:#x}: {name}"),
328 }
329 }
330 }
331
332 #[test]
333 fn classify_idt_entry_at_boundaries() {
334 let ks: u64 = 0xFFFF_8000_0000_0000;
335 let ke: u64 = 0xFFFF_8000_00FF_FFFF;
336 assert!(!classify_idt_entry(ks, ks, ke));
338 assert!(!classify_idt_entry(ke, ks, ke));
340 assert!(classify_idt_entry(ks - 1, ks, ke));
342 assert!(classify_idt_entry(ke + 1, ks, ke));
344 }
345
346 #[test]
348 fn walk_check_idt_benign_and_hooked_entries() {
349 use memf_core::test_builders::flags as ptf;
350
351 let kernel_start: u64 = 0xFFFF_8000_0000_0000;
352 let kernel_end: u64 = 0xFFFF_8000_00FF_FFFF;
353
354 let idt_vaddr: u64 = 0xFFFF_8800_0072_0000;
356 let idt_paddr: u64 = 0x0072_0000;
357
358 let mut idt_page = [0u8; 4096];
361
362 let h0: u64 = kernel_start + 0x1000;
364 let h0_low = (h0 & 0xFFFF) as u16;
365 let h0_mid = ((h0 >> 16) & 0xFFFF) as u16;
366 let h0_high = ((h0 >> 32) & 0xFFFF_FFFF) as u32;
367 let off0 = 0usize;
368 idt_page[off0..off0 + 2].copy_from_slice(&h0_low.to_le_bytes());
369 idt_page[off0 + 2..off0 + 4].copy_from_slice(&0x0010u16.to_le_bytes()); idt_page[off0 + 5] = 0x8E; idt_page[off0 + 6..off0 + 8].copy_from_slice(&h0_mid.to_le_bytes());
372 idt_page[off0 + 8..off0 + 12].copy_from_slice(&h0_high.to_le_bytes());
373
374 let h1: u64 = 0xFFFF_C000_DEAD_0000;
376 let h1_low = (h1 & 0xFFFF) as u16;
377 let h1_mid = ((h1 >> 16) & 0xFFFF) as u16;
378 let h1_high = ((h1 >> 32) & 0xFFFF_FFFF) as u32;
379 let off1 = 16usize;
380 idt_page[off1..off1 + 2].copy_from_slice(&h1_low.to_le_bytes());
381 idt_page[off1 + 2..off1 + 4].copy_from_slice(&0x0010u16.to_le_bytes());
382 idt_page[off1 + 5] = 0x8F; idt_page[off1 + 6..off1 + 8].copy_from_slice(&h1_mid.to_le_bytes());
384 idt_page[off1 + 8..off1 + 12].copy_from_slice(&h1_high.to_le_bytes());
385
386 let isf = IsfBuilder::new()
387 .add_symbol("_stext", kernel_start)
388 .add_symbol("_etext", kernel_end)
389 .add_symbol("idt_table", idt_vaddr)
390 .build_json();
391
392 let resolver = IsfResolver::from_value(&isf).unwrap();
393 let (cr3, mem) = PageTableBuilder::new()
394 .map_4k(idt_vaddr, idt_paddr, ptf::WRITABLE)
395 .write_phys(idt_paddr, &idt_page)
396 .build();
397 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
398 let reader = ObjectReader::new(vas, Box::new(resolver));
399
400 let results = walk_check_idt(&reader).unwrap();
401 assert_eq!(results.len(), 2, "two non-zero entries expected");
402
403 assert_eq!(results[0].vector, 0);
405 assert_eq!(results[0].handler_addr, h0);
406 assert_eq!(results[0].gate_type, "Interrupt Gate");
407 assert!(
408 !results[0].is_hooked,
409 "entry 0 is inside kernel text → not hooked"
410 );
411
412 assert_eq!(results[1].vector, 1);
414 assert_eq!(results[1].handler_addr, h1);
415 assert_eq!(results[1].gate_type, "Trap Gate");
416 assert!(
417 results[1].is_hooked,
418 "entry 1 is outside kernel text → hooked"
419 );
420 }
421
422 #[test]
424 fn idt_entry_info_debug_clone_serialize() {
425 use super::IdtEntryInfo;
426 let entry = IdtEntryInfo {
427 vector: 0x21,
428 handler_addr: 0xFFFF_8000_0001_0000,
429 segment: 0x10,
430 gate_type: "Interrupt Gate".to_string(),
431 is_hooked: false,
432 };
433 let cloned = entry.clone();
434 let dbg = format!("{cloned:?}");
435 assert!(dbg.contains("Interrupt Gate"));
436 let json = serde_json::to_string(&entry).unwrap();
437 assert!(json.contains("\"vector\":33"));
438 assert!(json.contains("\"is_hooked\":false"));
439 }
440}