logitech_cve/
device.rs

1use std::{ffi, mem, ptr};
2
3use windows_sys::{
4    Wdk::{
5        Foundation::OBJECT_ATTRIBUTES,
6        Storage::FileSystem::{FILE_NON_DIRECTORY_FILE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NtCreateFile},
7        System::{IO::NtDeviceIoControlFile, SystemServices::ZwClose},
8    },
9    Win32::{
10        Foundation::{GENERIC_WRITE, HANDLE, NTSTATUS, STATUS_SUCCESS, UNICODE_STRING},
11        Storage::FileSystem::{FILE_ATTRIBUTE_NORMAL, SYNCHRONIZE},
12        System::{IO::IO_STATUS_BLOCK, WindowsProgramming::RtlInitUnicodeString},
13    },
14    core::PCWSTR,
15};
16
17use crate::{KeyboardButton, MouseButton, util::InitializeObjectAttributes};
18
19/// I/O structure used to communicate mouse actions to the device driver.
20#[repr(C)]
21struct MouseIO {
22    button: u8,
23    x: i8,
24    y: i8,
25    wheel: i8,
26    unk1: i8,
27}
28
29impl MouseIO {
30    const fn new(button: u8, x: i8, y: i8, wheel: i8) -> Self {
31        let unk1 = 0;
32        Self {
33            button,
34            x,
35            y,
36            wheel,
37            unk1,
38        }
39    }
40}
41
42/// I/O structure used to communicate keyboard button states to the device driver.
43#[repr(C)]
44struct KeyboardIO {
45    unknown1: u8,
46    unknown2: u8,
47    button1: u8,
48    button2: u8,
49    button3: u8,
50    button4: u8,
51    button5: u8,
52    button6: u8,
53}
54
55impl KeyboardIO {
56    const fn new(button1: u8, button2: u8, button3: u8, button4: u8, button5: u8, button6: u8) -> Self {
57        let unknown1 = 0;
58        let unknown2 = 0;
59        Self {
60            unknown1,
61            unknown2,
62            button1,
63            button2,
64            button3,
65            button4,
66            button5,
67            button6,
68        }
69    }
70}
71
72/// Represents a handle to the virtual input device.
73pub struct Device {
74    filehandle: HANDLE,
75    iostatusblock: IO_STATUS_BLOCK,
76}
77
78impl Drop for Device {
79    fn drop(&mut self) {
80        self.close();
81    }
82}
83
84impl Device {
85    /// Attempts to open the device and return a [`Device`] instance.
86    ///
87    /// # Errors
88    /// Returns an error if the device cannot be opened (e.g., G HUB not installed or incompatible version).
89    pub fn try_new() -> Result<Self, &'static str> {
90        let filehandle = HANDLE::default();
91        let iostatusblock = IO_STATUS_BLOCK::default();
92
93        let mut device = Self {
94            filehandle,
95            iostatusblock,
96        };
97
98        if !device.open() {
99            return Err("Device not found. Consider to download Logitech G HUB 2021.11.1775");
100        }
101
102        Ok(device)
103    }
104
105    /// Sends a mouse command to the device.
106    pub fn send_mouse(&mut self, button: MouseButton, x: i8, y: i8, wheel: i8) {
107        let mut io = MouseIO::new(button.into(), x, y, wheel);
108
109        if !self.call_mouse(&mut io) {
110            self.close();
111            self.open(); // Attempt to re-open if call failed
112        }
113    }
114
115    /// Sends a keyboard command to the device.
116    pub fn send_keyboard(
117        &mut self,
118        button1: KeyboardButton,
119        button2: KeyboardButton,
120        button3: KeyboardButton,
121        button4: KeyboardButton,
122        button5: KeyboardButton,
123        button6: KeyboardButton,
124    ) {
125        let mut buffer = KeyboardIO::new(
126            button1.into(),
127            button2.into(),
128            button3.into(),
129            button4.into(),
130            button5.into(),
131            button6.into(),
132        );
133
134        if !self.call_keyboard(&mut buffer) {
135            self.close();
136            self.open(); // Attempt to re-open if call failed
137        }
138    }
139
140    /// Tries to open the device by testing multiple known device paths.
141    ///
142    /// # Returns
143    /// `true` if a device was successfully opened, `false` otherwise.
144    fn open(&mut self) -> bool {
145        let buffers: [Vec<u16>; 2] = [
146            "\\??\\ROOT#SYSTEM#0001#{1abc05c0-c378-41b9-9cef-df1aba82b015}\0"
147                .encode_utf16()
148                .collect(),
149            "\\??\\ROOT#SYSTEM#0002#{1abc05c0-c378-41b9-9cef-df1aba82b015}\0"
150                .encode_utf16()
151                .collect(),
152        ];
153
154        for buffer in buffers {
155            if self.device_initialize(buffer.as_ptr()) == STATUS_SUCCESS {
156                return true;
157            }
158        }
159
160        false
161    }
162
163    /// Initializes the device by opening a handle to it.
164    ///
165    /// # Arguments
166    /// * `device_name` - A `PCWSTR` representing the path to the device.
167    ///
168    /// # Returns
169    /// An `NTSTATUS` indicating the success or failure of the operation.
170    fn device_initialize(&mut self, device_name: PCWSTR) -> NTSTATUS {
171        let mut name = UNICODE_STRING::default();
172        let mut attr = OBJECT_ATTRIBUTES::default();
173
174        unsafe {
175            RtlInitUnicodeString(&raw mut name, device_name);
176            InitializeObjectAttributes(&mut attr, &raw const name, 0, ptr::null_mut(), ptr::null());
177
178            NtCreateFile(
179                &raw mut self.filehandle,
180                GENERIC_WRITE | SYNCHRONIZE,
181                &raw const attr,
182                &raw mut self.iostatusblock,
183                ptr::null::<i64>(), // AllocationSize (optional)
184                FILE_ATTRIBUTE_NORMAL,
185                0,
186                FILE_OPEN_IF, // CreateDisposition (OPEN_EXISTING)
187                FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
188                ptr::null(),
189                0,
190            )
191        }
192    }
193
194    /// Calls the device IOCTL.
195    ///
196    /// # Arguments
197    /// * `buffer` - A mutable reference to a `MouseIO` struct containing the mouse action data.
198    ///
199    /// # Returns
200    /// `true` if the IOCTL call was successful, `false` otherwise.
201    fn call_mouse(&self, buffer: &mut MouseIO) -> bool {
202        #[allow(clippy::cast_possible_truncation)] // MouseIO is only 5 bytes
203        const INPUTBUFFERLENGTH: u32 = mem::size_of::<MouseIO>() as u32;
204
205        let mut block = IO_STATUS_BLOCK::default();
206
207        let status = unsafe {
208            NtDeviceIoControlFile(
209                self.filehandle,
210                ptr::null_mut(),
211                None,
212                ptr::null(),
213                &raw mut block,
214                0x002A_2010,
215                ptr::from_mut(buffer).cast::<ffi::c_void>(),
216                INPUTBUFFERLENGTH,
217                ptr::null_mut(),
218                0,
219            )
220        };
221        status == STATUS_SUCCESS
222    }
223
224    /// Calls the device IOCTL.
225    ///
226    /// # Arguments
227    /// * `buffer` - A mutable reference to a `KeyboardIO` struct containing the keyboard action data.
228    ///
229    /// # Returns
230    /// `true` if the IOCTL call was successful, `false` otherwise.
231    fn call_keyboard(&self, buffer: &mut KeyboardIO) -> bool {
232        #[allow(clippy::cast_possible_truncation)] // KeyboardIO is only 8 bytes
233        const INPUTBUFFERLENGTH: u32 = mem::size_of::<KeyboardIO>() as u32;
234
235        let mut block = IO_STATUS_BLOCK::default();
236
237        let status = unsafe {
238            NtDeviceIoControlFile(
239                self.filehandle,
240                ptr::null_mut(),
241                None,
242                ptr::null(),
243                &raw mut block,
244                0x002A_200C,
245                ptr::from_mut(buffer).cast::<ffi::c_void>(),
246                INPUTBUFFERLENGTH,
247                ptr::null_mut(),
248                0,
249            )
250        };
251        status == STATUS_SUCCESS
252    }
253
254    /// Closes the handle to the device.
255    fn close(&mut self) {
256        unsafe {
257            if !self.filehandle.is_null() {
258                let _ = ZwClose(self.filehandle);
259                self.filehandle = ptr::null_mut();
260            }
261        }
262    }
263}
264
265mod tests {
266    #[allow(unused_imports)]
267    use super::*;
268
269    #[test]
270    fn test_open_close() {
271        let mut device = Device {
272            filehandle: HANDLE::default(),
273            iostatusblock: IO_STATUS_BLOCK::default(),
274        };
275        assert!(device.open(), "Device not opened");
276        device.close();
277        assert!(device.filehandle.is_null());
278    }
279
280    #[test]
281    fn test_call_mouse() {
282        let device = Device::try_new().unwrap();
283        let mut buffer = MouseIO::new(0, 0, 0, 0);
284        assert!(device.call_mouse(&mut buffer));
285    }
286
287    #[test]
288    fn test_call_keyboard() {
289        let device = Device::try_new().unwrap();
290        let mut buffer = KeyboardIO::new(0, 0, 0, 0, 0, 0);
291        assert!(device.call_keyboard(&mut buffer));
292    }
293}