fsfilter_rs/
shared_def.rs

1//! Contains all definitions shared between this user-mode app and the minifilter in order to
2//! communicate properly. Those are C-representation of structures sent or received from the minifilter.
3
4use std::cmp::Ordering;
5use std::fmt;
6use std::os::raw::{c_uchar, c_ulong, c_ulonglong, c_ushort};
7use std::path::PathBuf;
8use std::time::SystemTime;
9
10use num_derive::FromPrimitive;
11use serde::{Deserialize, Serialize};
12use wchar::wchar_t;
13use windows::Win32::Foundation::{CloseHandle, GetLastError};
14use windows::Win32::Storage::FileSystem::FILE_ID_INFO;
15use windows::Win32::System::ProcessStatus::K32GetProcessImageFileNameA;
16use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
17
18/// See [`IOMessage`] struct. Used with [`IrpSetInfo`](crate::driver_comm::IrpMajorOp::IrpSetInfo)
19#[derive(FromPrimitive)]
20#[repr(C)]
21pub enum FileChangeInfo {
22    FileChangeNotSet,
23    FileOpenDirectory,
24    FileChangeWrite,
25    FileChangeNewFile,
26    FileChangeRenameFile,
27    FileChangeExtensionChanged,
28    FileChangeDeleteFile,
29    /// Temp file: created and deleted on close
30    FileChangeDeleteNewFile,
31    FileChangeOverwriteFile,
32}
33
34/// See [`IOMessage`] struct.
35#[derive(FromPrimitive)]
36#[repr(C)]
37pub enum FileLocationInfo {
38    FileNotProtected,
39    FileProtected,
40    FileMovedIn,
41    FileMovedOut,
42}
43
44/// Low-level C-like object to communicate with the minifilter.
45/// The minifilter yields `ReplyIrp` objects (retrieved by [`get_irp`](crate::driver_comm::Driver::get_irp) to
46/// manage the fixed size of the *data buffer.
47/// In other words, a `ReplyIrp` is a collection of [`CDriverMsg`] with a capped size.
48#[derive(Debug, Copy, Clone)]
49#[repr(C)]
50pub struct ReplyIrp {
51    /// The size od the collection.
52    pub data_size: c_ulonglong,
53    /// The C pointer to the buffer containing the [`CDriverMsg`] events.
54    pub data: *const CDriverMsg,
55    /// The number of different operations in this collection.
56    pub num_ops: u64,
57}
58
59impl ReplyIrp {
60    /// Iterate through `self.data` and returns the collection of [`CDriverMsg`]
61    #[inline]
62    fn unpack_drivermsg(&self) -> Vec<&CDriverMsg> {
63        let mut res = vec![];
64        unsafe {
65            let mut msg = &*self.data;
66            res.push(msg);
67            for _ in 0..(self.num_ops) {
68                if msg.next.is_null() {
69                    break;
70                }
71                msg = &*msg.next;
72                res.push(msg);
73            }
74        }
75        res
76    }
77}
78
79/// This class is the straight Rust translation of the Win32 API
80/// [`UNICODE_STRING`](https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_unicode_string),
81/// returned by the driver.
82#[derive(Debug, Copy, Clone)]
83#[repr(C)]
84pub struct UnicodeString {
85    pub length: c_ushort,
86    pub maximum_length: c_ushort,
87    pub buffer: *const wchar_t,
88}
89
90impl UnicodeString {
91    /*
92    pub fn to_string(&self) -> String {
93        unsafe {
94            let str_slice = std::slice::from_raw_parts(self.buffer, self.length as usize);
95            let mut first_zero_index = 0;
96            for (i, c) in str_slice.iter().enumerate() {
97                if *c == 0 {
98                    first_zero_index = i;
99                    break;
100                }
101            }
102            String::from_utf16_lossy(&str_slice[..first_zero_index])
103        }
104    }
105    */
106
107    /// Get the file path from the `UnicodeString` path and the extension returned by the driver.
108    #[inline]
109    #[must_use]
110    pub fn to_string_ext(&self, extension: [wchar_t; 12]) -> String {
111        unsafe {
112            let str_slice = std::slice::from_raw_parts(self.buffer, self.length as usize);
113            let mut first_zero_index = 0;
114            let mut last_dot_index = 0;
115            let mut first_zero_index_ext = 0;
116
117            // Filepath
118            for (i, c) in str_slice.iter().enumerate() {
119                if *c == 46 {
120                    last_dot_index = i + 1;
121                }
122                if *c == 0 {
123                    first_zero_index = i;
124                    break;
125                }
126            }
127
128            if first_zero_index_ext > 0 && last_dot_index > 0 {
129                // Extension
130                for (i, c) in extension.iter().enumerate() {
131                    if *c == 0 {
132                        first_zero_index_ext = i;
133                        break;
134                    } else if *c != str_slice[last_dot_index + i] {
135                        first_zero_index_ext = 0;
136                        break;
137                    }
138                }
139                String::from_utf16_lossy(
140                    &[
141                        &str_slice[..last_dot_index],
142                        &extension[..first_zero_index_ext],
143                    ]
144                    .concat(),
145                )
146            } else {
147                String::from_utf16_lossy(&str_slice[..first_zero_index])
148            }
149        }
150    }
151}
152
153impl fmt::Display for UnicodeString {
154    #[inline]
155    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156        unsafe {
157            let str_slice = std::slice::from_raw_parts(self.buffer, self.length as usize);
158            let mut first_zero_index = 0;
159            for (i, c) in str_slice.iter().enumerate() {
160                if *c == 0 {
161                    first_zero_index = i;
162                    break;
163                }
164            }
165            write!(
166                f,
167                "{}",
168                String::from_utf16_lossy(&str_slice[..first_zero_index])
169            )
170        }
171    }
172}
173
174/// Represents a driver message.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176#[repr(C)]
177pub struct IOMessage {
178    /// The file extension
179    pub extension: [wchar_t; 12],
180    /// Hard Disk Volume Serial Number where the file is saved (from [`FILE_ID_INFO`])
181    pub file_id_vsn: c_ulonglong,
182    /// File ID on the disk ([`FILE_ID_INFO`])
183    pub file_id_id: [u8; 16],
184    /// Number of bytes transferred (`IO_STATUS_BLOCK.Information`)
185    pub mem_sized_used: c_ulonglong,
186    /// (Optional) File Entropy calculated by the driver
187    pub entropy: f64,
188    /// Pid responsible for this io activity
189    pub pid: c_ulong,
190    /// Windows IRP Type caught by the minifilter:
191    /// - NONE (0)
192    /// - READ (1)
193    /// - WRITE (2)
194    /// - SETINFO (3)
195    /// - CREATE (4)
196    /// - CLEANUP (5)
197    pub irp_op: c_uchar,
198    /// Is the entropy calculated?
199    pub is_entropy_calc: u8,
200    /// Type of i/o operation:
201    /// - FILE_CHANGE_NOT_SET (0)
202    /// - FILE_OPEN_DIRECTORY (1)
203    /// - FILE_CHANGE_WRITE (2)
204    /// - FILE_CHANGE_NEW_FILE (3)
205    /// - FILE_CHANGE_RENAME_FILE (4)
206    /// - FILE_CHANGE_EXTENSION_CHANGED (5)
207    /// - FILE_CHANGE_DELETE_FILE (6)
208    /// - FILE_CHANGE_DELETE_NEW_FILE (7)
209    /// - FILE_CHANGE_OVERWRITE_FILE (8)
210    pub file_change: c_uchar,
211    /// The driver has the ability to monitor specific directories only (feature currently not used):
212    /// - FILE_NOT_PROTECTED (0): Monitored dirs do not contained this file
213    /// - FILE_PROTECTED (1)
214    /// - FILE_MOVED_IN (2)
215    /// - FILE_MOVED_OUT (3)
216    pub file_location_info: c_uchar,
217    /// File path on the disk
218    pub filepathstr: String,
219    /// Group Identifier (maintained by the minifilter) of the operation
220    pub gid: c_ulonglong,
221    /// see class [`RuntimeFeatures`]
222    pub runtime_features: RuntimeFeatures,
223    /// Size of the file. Can be equal to -1 if the file path is not found.
224    pub file_size: i64,
225    /// Rough time at which the IRP was created
226    pub time: SystemTime,
227}
228
229impl IOMessage {
230    /// Make a new [`IOMessage`] from a received [`CDriverMsg`]
231    #[inline]
232    #[must_use]
233    pub fn from(c_drivermsg: &CDriverMsg) -> Self {
234        Self {
235            extension: c_drivermsg.extension,
236            file_id_vsn: c_drivermsg.file_id.VolumeSerialNumber,
237            file_id_id: c_drivermsg.file_id.FileId.Identifier,
238            mem_sized_used: c_drivermsg.mem_sized_used,
239            entropy: c_drivermsg.entropy,
240            pid: c_drivermsg.pid,
241            irp_op: c_drivermsg.irp_op,
242            is_entropy_calc: c_drivermsg.is_entropy_calc,
243            file_change: c_drivermsg.file_change,
244            file_location_info: c_drivermsg.file_location_info,
245            filepathstr: c_drivermsg.filepath.to_string_ext(c_drivermsg.extension),
246            gid: c_drivermsg.gid,
247            runtime_features: RuntimeFeatures::new(),
248            file_size: match PathBuf::from(
249                &c_drivermsg.filepath.to_string_ext(c_drivermsg.extension),
250            )
251            .metadata()
252            {
253                Ok(f) => f.len() as i64,
254                Err(_e) => -1,
255            },
256            time: SystemTime::now(),
257        }
258    }
259
260    /// Opens an existing local process object to retrieve the name of the executable file for the
261    /// specified process.
262    #[inline]
263    pub fn exepath(&mut self) {
264        let pid = self.pid;
265        unsafe {
266            let r_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
267            if let Ok(handle) = r_handle {
268                if !(handle.is_invalid() || handle.0 == 0) {
269                    let mut buffer: Vec<u8> = Vec::new();
270                    buffer.resize(1024, 0);
271                    let res = K32GetProcessImageFileNameA(handle, buffer.as_mut_slice());
272
273                    CloseHandle(handle);
274                    if res == 0 {
275                        let _errorcode = GetLastError().0;
276                    } else {
277                        let pathbuf = PathBuf::from(
278                            String::from_utf8_unchecked(buffer).trim_matches(char::from(0)),
279                        );
280                        self.runtime_features.exe_still_exists = true;
281                        self.runtime_features.exepath = pathbuf.file_name().map_or_else(
282                            || PathBuf::from(r"DEFAULT"),
283                            |filename| PathBuf::from(filename.to_string_lossy().to_string()),
284                        );
285                    }
286                    // dbg!(is_closed_handle);
287                }
288            }
289        }
290    }
291}
292
293impl Eq for IOMessage {}
294
295impl Ord for IOMessage {
296    fn cmp(&self, other: &Self) -> Ordering {
297        self.time.cmp(&other.time)
298    }
299}
300
301impl PartialOrd for IOMessage {
302    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
303        Some(self.time.cmp(&other.time))
304    }
305}
306
307impl PartialEq for IOMessage {
308    fn eq(&self, other: &Self) -> bool {
309        self.time == other.time
310    }
311}
312
313/// Stores runtime features that come from our application (and not the minifilter).
314#[derive(Debug, Clone, Serialize, Deserialize)]
315#[repr(C)]
316pub struct RuntimeFeatures {
317    /// The path of the gid root process
318    pub exepath: PathBuf,
319    ///  Did the root exe file still existed (at the moment of this specific *DriverMessage* operation)?
320    pub exe_still_exists: bool,
321}
322
323impl RuntimeFeatures {
324    /// Make a new [`RuntimeFeatures`]
325    #[inline]
326    #[must_use]
327    pub fn new() -> Self {
328        Self {
329            exepath: PathBuf::new(),
330            exe_still_exists: true,
331        }
332    }
333}
334
335impl Default for RuntimeFeatures {
336    #[inline]
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342/// The C object returned by the minifilter, available through [`ReplyIrp`].
343/// It is low level and use C pointers logic which is not always compatible with RUST (in particular
344/// the lifetime of `*next`). That's why we convert it asap to a plain Rust [`IOMessage`] object.
345///
346/// `next` is null `(0x0)` when there is no [`IOMessage`] remaining.
347#[derive(Debug, Copy, Clone)]
348#[repr(C)]
349pub struct CDriverMsg {
350    pub extension: [wchar_t; 12],
351    pub file_id: FILE_ID_INFO,
352    pub mem_sized_used: c_ulonglong,
353    pub entropy: f64,
354    pub pid: c_ulong,
355    pub irp_op: c_uchar,
356    pub is_entropy_calc: u8,
357    pub file_change: c_uchar,
358    pub file_location_info: c_uchar,
359    pub filepath: UnicodeString,
360    pub gid: c_ulonglong,
361    /// null (0x0) when there is no [`IOMessage`] remaining
362    pub next: *const CDriverMsg,
363}
364
365/// To iterate easily over a collection of [`IOMessage`] received from the minifilter, before they are
366/// converted to [`IOMessage`].
367#[repr(C)]
368pub struct CDriverMsgs<'a> {
369    drivermsgs: Vec<&'a CDriverMsg>,
370    index: usize,
371}
372
373impl CDriverMsgs<'_> {
374    /// Make a new [`CDriverMsgs`] from a received [`ReplyIrp`]
375    #[inline]
376    #[must_use]
377    pub fn new(irp: &ReplyIrp) -> CDriverMsgs {
378        CDriverMsgs {
379            drivermsgs: irp.unpack_drivermsg(),
380            index: 0,
381        }
382    }
383}
384
385impl Iterator for CDriverMsgs<'_> {
386    type Item = CDriverMsg;
387
388    #[inline]
389    fn next(&mut self) -> Option<Self::Item> {
390        if self.index == self.drivermsgs.len() {
391            None
392        } else {
393            let res = *self.drivermsgs[self.index];
394            self.index += 1;
395            Some(res)
396        }
397    }
398}