Skip to main content

memflow_lime/
lib.rs

1use binread::{BinRead, BinReaderExt};
2
3use memflow::connector::fileio::{CloneFile, FileIoMemory};
4use memflow::prelude::v1::*;
5
6use std::fs::File;
7use std::io;
8use std::io::{Cursor, Read, Seek, SeekFrom};
9
10/// Header defined by the `LiME` file format, version 1
11///
12/// source: [LiME Memory Range Header Version 1 Specification](https://github.com/504ensicsLabs/LiME/blob/master/doc/README.md#Spec)
13#[derive(Debug, BinRead)]
14#[br(magic = 0x4C69_4D45_u32)] //LiME
15struct LimeHeader {
16    /// Header version number
17    #[br(assert(version == 1, "Unsupported LiME version: {}", version))]
18    #[allow(dead_code)]
19    version: u32,
20    /// Starting address of physical RAM range
21    s_addr: u64,
22    /// Ending address of physical RAM range
23    #[br(assert(e_addr >= s_addr, "End address can not be lower than start address"))]
24    e_addr: u64,
25    /// Currently all zeros
26    #[br(assert(reserved == [0; 8], "Unsupported LiME reserved fields values"))]
27    #[allow(dead_code)]
28    reserved: [u8; 8],
29}
30
31impl LimeHeader {
32    /// Size in bytes of `LimeHeader`
33    const HEADER_SIZE_IN_BYTES: usize = 32;
34
35    /// Get the `LiME` header from file.
36    ///
37    /// Returns `Ok(None)` if the End Of File is reached\
38    /// Returns `Ok(Some(...))` if the `LimeHeader` is parsed correctly\
39    ///
40    /// # Arguments
41    ///
42    /// * `lime_dump` - file to read from, the seek of the file  must be already at the start of the header or at EOF.
43    ///
44    /// # Errors
45    ///
46    /// Returns `Err` if an error occurred while reading the file or parsing the header
47    ///
48    fn next_header_from_file(lime_dump: &mut File) -> Result<Option<LimeHeader>> {
49        let mut buff = [0u8; LimeHeader::HEADER_SIZE_IN_BYTES];
50
51        match lime_dump.read_exact(&mut buff) {
52            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(None),
53            Err(_) => Err(Error(ErrorOrigin::Connector, ErrorKind::UnableToReadFile)),
54            Ok(()) => {
55                let header = Cursor::new(&buff).read_le().map_err(|_| {
56                    Error(ErrorOrigin::Connector, ErrorKind::UnableToReadFile)
57                        .log_error("Unable to parse the LiME file.")
58                })?;
59
60                Ok(Some(header))
61            }
62        }
63    }
64
65    /// Size in bytes of the memory represented by this header
66    const fn mem_section_size(&self) -> u64 {
67        self.e_addr - self.s_addr + 1
68    }
69}
70
71/// Create connector to a `LiME` file.
72///
73/// # Arguments
74///
75/// * `args` - the target field may contain the `LiME` file path
76///
77/// # Errors
78///
79/// Returns `Err` if an error occurred while reading or parsing the file
80///
81#[connector(name = "lime", help_fn = "help")]
82pub fn create_connector(args: &ConnectorArgs) -> Result<FileIoMemory<CloneFile>> {
83    let mut lime_dump = File::open(
84        args.target
85            .as_ref()
86            .ok_or(
87                Error(ErrorOrigin::Connector, ErrorKind::UnableToReadFile)
88                    .log_error("LiME file path not specified"),
89            )?
90            .as_ref(),
91    )
92    .map_err(|_| Error(ErrorOrigin::Connector, ErrorKind::UnableToReadFile))?;
93
94    let mut map = MemoryMap::new();
95    let mut offset = 0;
96
97    while let Some(header) = LimeHeader::next_header_from_file(&mut lime_dump)? {
98        offset += LimeHeader::HEADER_SIZE_IN_BYTES as u64;
99
100        map.push_remap(
101            header.s_addr.into(),
102            header.mem_section_size(),
103            offset.into(),
104        );
105        offset = lime_dump
106            .seek(SeekFrom::Current(header.mem_section_size() as i64))
107            .map_err(|_| {
108                Error(ErrorOrigin::Connector, ErrorKind::UnableToSeekFile)
109                    .log_error("Corrupted LiME file")
110            })?;
111    }
112
113    lime_dump.seek(SeekFrom::Start(0)).map_err(|_| {
114        Error(ErrorOrigin::Connector, ErrorKind::UnableToSeekFile)
115            .log_error("Unable to seek back to the beginning of the file")
116    })?;
117    FileIoMemory::with_mem_map(lime_dump.into(), map)
118}
119
120/// Retrieve the help text for the `LiME` Connector.
121pub fn help() -> String {
122    "\
123The `lime` connector implements the LiME file format parser.
124
125The `target` argument specifies the filename of the file to be opened.
126    "
127    .to_string()
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use std::fs;
134    use std::fs::OpenOptions;
135    use std::io::{Seek, SeekFrom, Write};
136
137    #[test]
138    fn unspecified_file_causes_error() {
139        let connector_args = ConnectorArgs::default();
140        let connector = create_connector(&connector_args);
141        assert_eq!(
142            connector.err().unwrap(),
143            Error(ErrorOrigin::Connector, ErrorKind::UnableToReadFile)
144        );
145    }
146
147    #[test]
148    fn header_parser_works() {
149        let raw_header: [u8; LimeHeader::HEADER_SIZE_IN_BYTES] = [
150            69, 77, 105, 76, 1, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 255, 255, 207, 251, 0, 0, 0, 0,
151            0, 0, 0, 0, 0, 0, 0, 0,
152        ];
153        let tmp_file_path = "./test_header.tmp";
154        let mut tmp_file = OpenOptions::new()
155            .read(true)
156            .write(true)
157            .create(true)
158            .open(tmp_file_path)
159            .unwrap();
160
161        tmp_file.write(&raw_header).unwrap();
162        tmp_file.seek(SeekFrom::Start(0)).unwrap();
163
164        let header = LimeHeader::next_header_from_file(&mut tmp_file)
165            .unwrap()
166            .unwrap();
167
168        fs::remove_file(tmp_file_path).unwrap();
169
170        assert_eq!(header.version, 1);
171        assert_eq!(header.s_addr, 0x40000000);
172        assert_eq!(header.e_addr, 0xFBD00000 - 1);
173        assert_eq!(header.reserved, [0; 8]);
174    }
175}