vmi_core/os/
struct_reader.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use isr_macros::Field;

use crate::{AccessContext, VmiCore, VmiDriver, VmiError};

/// A handler for reading structured data from guest memory.
///
/// Provides safe access to structured data (like C structs) read from guest memory,
/// with proper bounds checking and endianness handling. It reads the data as a byte buffer
/// and provides methods to safely access fields at specific offsets and sizes.
///
/// # Examples
///
/// ```no_run
/// # use isr_macros::{offsets, Field};
/// # use vmi_core::{AccessContext, VmiCore, VmiDriver, VmiError};
/// # use vmi_core::os::StructReader;
///
/// offsets! {
///     #[derive(Debug)]
///     pub struct Offsets {
///         struct _UNICODE_STRING {
///             Length: Field,          // USHORT
///             MaximumLength: Field,   // USHORT
///             Buffer: Field,          // PWSTR
///         }
///     }
/// }
///
/// # fn example<Driver: VmiDriver>(
/// #     vmi: &VmiCore<Driver>,
/// #     ctx: impl Into<AccessContext>,
/// # ) -> Result<(), VmiError> {
/// # let profile = unimplemented!();
///
/// let offsets = Offsets::new(profile)?;
/// let UNICODE_STRING = &offsets._UNICODE_STRING;
///
/// // Read the structure from memory.
/// let us = StructReader::new(vmi, ctx, UNICODE_STRING.effective_len())?;
///
/// // Access the field values.
/// let length = us.read(UNICODE_STRING.Length)?;
/// let buffer = us.read(UNICODE_STRING.Buffer)?;
///
/// # Ok(())
/// # }
/// ```
pub struct StructReader(Vec<u8>);

impl StructReader {
    /// Creates a new structure reader.
    ///
    /// Reads `len` bytes from the guest memory at the specified address into
    /// a new `StructReader` instance. The data can then be accessed using the
    /// [`read`] method with appropriate field descriptors.
    ///
    /// [`read`]: Self::read
    pub fn new<Driver>(
        vmi: &VmiCore<Driver>,
        ctx: impl Into<AccessContext>,
        len: usize,
    ) -> Result<Self, VmiError>
    where
        Driver: VmiDriver,
    {
        let mut buffer = vec![0u8; len];
        vmi.read(ctx, &mut buffer)?;
        Ok(Self(buffer))
    }

    /// Reads a field value from the data buffer.
    ///
    /// Extracts a value from the buffer using the provided field descriptor,
    /// which specifies the offset and size of the field.
    /// The value is interpreted as a little-endian integer of the appropriate
    /// size and returned as a `u64`.
    ///
    /// # Endianness
    ///
    /// Values are always read as little-endian integers. The returned `u64`
    /// will contain the zero-extended value.
    pub fn read(&self, field: Field) -> Result<u64, VmiError> {
        let offset = field.offset as usize;
        let size = field.size as usize;

        let offset_end = match offset.checked_add(size) {
            Some(offset_end) => offset_end,
            None => return Err(VmiError::OutOfBounds),
        };

        if offset_end > self.0.len() {
            return Err(VmiError::OutOfBounds);
        }

        let data = &self.0[offset..offset_end];

        match size {
            1 => Ok(data[0] as u64),
            2 => Ok(u16::from_le_bytes([data[0], data[1]]) as u64),
            4 => Ok(u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as u64),
            8 => Ok(u64::from_le_bytes([
                data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
            ])),
            _ => Err(VmiError::OutOfBounds),
        }
    }
}