Skip to main content

forensic_mount/
detect.rs

1#![forbid(unsafe_code)]
2
3use std::io::{self, Read, Seek, SeekFrom};
4
5/// A recognized memory-dump container format. These route to the memory mount
6/// path (a `MemoryFs` over memf), not the engine's disk/logical `open()`.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum MemDumpFormat {
9    /// `LiME` (Linux Memory Extractor) — magic `EMiL`.
10    Lime,
11    /// AVML (Acquisition of Volatile Memory for Linux) v2 — magic `AVML`.
12    Avml,
13    /// ELF core dump (`ET_CORE`).
14    ElfCore,
15    /// Windows kernel crash dump (64-bit) — magic `PAGEDU64`.
16    WinCrashDump,
17}
18
19impl std::fmt::Display for MemDumpFormat {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            MemDumpFormat::Lime => write!(f, "lime"),
23            MemDumpFormat::Avml => write!(f, "avml"),
24            MemDumpFormat::ElfCore => write!(f, "elf-core"),
25            MemDumpFormat::WinCrashDump => write!(f, "win-crashdump"),
26        }
27    }
28}
29
30/// Detect a memory-dump container by its header magic.
31///
32/// Returns `Ok(None)` for non-dumps — including raw/headerless dumps, which
33/// carry no signature and must be selected explicitly (`--fs memory`). The seek
34/// position is reset to 0. Magics mirror `memf-format`'s plugins (`LiME`
35/// `0x4C694D45`, AVML `0x4C4D5641`, ELF `ET_CORE`, crash `PAGE` + `DU64`).
36pub fn detect_memory_dump<R: Read + Seek>(source: &mut R) -> io::Result<Option<MemDumpFormat>> {
37    source.seek(SeekFrom::Start(0))?;
38    let mut buf = [0u8; 18];
39    let n = read_fill(source, &mut buf);
40    source.seek(SeekFrom::Start(0))?;
41
42    // LiME header: magic 0x4C694D45 ("EMiL" little-endian) at byte 0.
43    if n >= 4 && &buf[0..4] == b"EMiL" {
44        return Ok(Some(MemDumpFormat::Lime));
45    }
46    // AVML v2: magic 0x4C4D5641 ("AVML" little-endian) at byte 0.
47    if n >= 4 && &buf[0..4] == b"AVML" {
48        return Ok(Some(MemDumpFormat::Avml));
49    }
50    // Windows kernel crash dump (64-bit): "PAGE" + "DU64" = "PAGEDU64" at byte 0.
51    if n >= 8 && &buf[0..8] == b"PAGEDU64" {
52        return Ok(Some(MemDumpFormat::WinCrashDump));
53    }
54    // ELF core dump: ELF magic at byte 0 and e_type == ET_CORE (4) at byte 16.
55    if n >= 18 && buf[0..4] == [0x7F, b'E', b'L', b'F'] {
56        let e_type = u16::from_le_bytes([buf[16], buf[17]]);
57        if e_type == 4 {
58            return Ok(Some(MemDumpFormat::ElfCore));
59        }
60    }
61    Ok(None)
62}
63
64/// Read as many bytes as possible into `buf`, returning total bytes read.
65fn read_fill<R: Read>(source: &mut R, buf: &mut [u8]) -> usize {
66    let mut total = 0;
67    while total < buf.len() {
68        match source.read(&mut buf[total..]) {
69            Ok(0) | Err(_) => break,
70            Ok(n) => total += n,
71        }
72    }
73    total
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use std::io::Cursor;
80
81    #[test]
82    fn detect_mem_lime() {
83        let mut data = vec![0u8; 64];
84        data[0..4].copy_from_slice(b"EMiL"); // LIME_MAGIC 0x4C694D45 little-endian
85        assert_eq!(
86            detect_memory_dump(&mut Cursor::new(data)).unwrap(),
87            Some(MemDumpFormat::Lime)
88        );
89    }
90
91    #[test]
92    fn detect_mem_avml() {
93        let mut data = vec![0u8; 64];
94        data[0..4].copy_from_slice(b"AVML");
95        assert_eq!(
96            detect_memory_dump(&mut Cursor::new(data)).unwrap(),
97            Some(MemDumpFormat::Avml)
98        );
99    }
100
101    #[test]
102    fn detect_mem_elf_core() {
103        let mut data = vec![0u8; 64];
104        data[0..4].copy_from_slice(&[0x7F, b'E', b'L', b'F']);
105        data[16..18].copy_from_slice(&4u16.to_le_bytes()); // e_type = ET_CORE
106        assert_eq!(
107            detect_memory_dump(&mut Cursor::new(data)).unwrap(),
108            Some(MemDumpFormat::ElfCore)
109        );
110    }
111
112    #[test]
113    fn detect_mem_elf_exec_is_not_a_dump() {
114        // A normal ELF executable (ET_EXEC) is not a core dump.
115        let mut data = vec![0u8; 64];
116        data[0..4].copy_from_slice(&[0x7F, b'E', b'L', b'F']);
117        data[16..18].copy_from_slice(&2u16.to_le_bytes()); // ET_EXEC
118        assert_eq!(detect_memory_dump(&mut Cursor::new(data)).unwrap(), None);
119    }
120
121    #[test]
122    fn detect_mem_win_crashdump() {
123        let mut data = vec![0u8; 64];
124        data[0..8].copy_from_slice(b"PAGEDU64");
125        assert_eq!(
126            detect_memory_dump(&mut Cursor::new(data)).unwrap(),
127            Some(MemDumpFormat::WinCrashDump)
128        );
129    }
130
131    #[test]
132    fn detect_mem_none_for_non_dump() {
133        let data = vec![0u8; 64];
134        assert_eq!(detect_memory_dump(&mut Cursor::new(data)).unwrap(), None);
135    }
136}