Skip to content

Commit d6fac6d

Browse files
committed
Implement kernel driver enumeration via NtQuerySystemInformation and remove module lookup panic
1 parent 0948727 commit d6fac6d

3 files changed

Lines changed: 162 additions & 33 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ procfs = { version = "=0.15.1", features = ["backtrace"] }
3333

3434
[target.'cfg(target_os = "windows")'.dependencies]
3535
windows = { version = "0.61", features = [
36+
"Wdk_System_SystemInformation",
3637
"Wdk_System_Threading",
3738
"Win32_Foundation",
3839
"Win32_Security",

src/windows/mod.rs

Lines changed: 159 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ use memflow::os::process::*;
22
use memflow::prelude::v1::*;
33

44
use windows::core::PCSTR;
5+
use windows::Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS};
56
use windows::Win32::Foundation::{
6-
CloseHandle, HANDLE, NTSTATUS, STATUS_ACCESS_DENIED, STATUS_INSUFFICIENT_RESOURCES,
7-
STATUS_INVALID_HANDLE, STATUS_INVALID_INFO_CLASS, STATUS_INVALID_PARAMETER,
8-
STATUS_NOT_IMPLEMENTED, STATUS_NO_MEMORY, STATUS_PRIVILEGE_NOT_HELD,
7+
CloseHandle, HANDLE, NTSTATUS, STATUS_ACCESS_DENIED, STATUS_INFO_LENGTH_MISMATCH,
8+
STATUS_INSUFFICIENT_RESOURCES, STATUS_INVALID_HANDLE, STATUS_INVALID_INFO_CLASS,
9+
STATUS_INVALID_PARAMETER, STATUS_NOT_IMPLEMENTED, STATUS_NO_MEMORY, STATUS_PRIVILEGE_NOT_HELD,
910
};
1011

1112
use windows::Win32::System::Diagnostics::ToolHelp::{
@@ -19,21 +20,43 @@ use windows::Win32::Security::{
1920
TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES,
2021
};
2122

22-
use core::mem::{size_of, MaybeUninit};
23+
use core::mem::{align_of, size_of, MaybeUninit};
2324
use core::ptr;
2425
use std::ffi::OsString;
2526
use std::os::windows::ffi::OsStringExt;
2627

27-
pub mod mem;
28-
use mem::ProcessVirtualMemory;
28+
const SYSTEM_MODULE_INFORMATION_CLASS: SYSTEM_INFORMATION_CLASS = SYSTEM_INFORMATION_CLASS(11);
29+
const MODULE_QUERY_INITIAL_CAPACITY: usize = 256 * 1024;
30+
const MODULE_QUERY_MAX_RETRIES: usize = 6;
2931

32+
pub mod mem;
3033
pub mod process;
3134
pub use process::WindowsProcess;
3235

3336
pub mod keyboard;
3437
pub use keyboard::{WindowsKeyboard, WindowsKeyboardState};
3538

36-
struct KernelModule {}
39+
struct KernelModule {
40+
base: Address,
41+
size: umem,
42+
path: ReprCString,
43+
name: ReprCString,
44+
}
45+
46+
#[repr(C)]
47+
#[allow(non_snake_case)]
48+
struct RtlProcessModuleInformation {
49+
Section: *mut core::ffi::c_void,
50+
MappedBase: *mut core::ffi::c_void,
51+
ImageBase: *mut core::ffi::c_void,
52+
ImageSize: u32,
53+
Flags: u32,
54+
LoadOrderIndex: u16,
55+
InitOrderIndex: u16,
56+
LoadCount: u16,
57+
OffsetToFileName: u16,
58+
FullPathName: [u8; 256],
59+
}
3760

3861
pub(crate) struct Handle(HANDLE);
3962

@@ -65,17 +88,50 @@ pub fn conv_err(_err: windows::core::Error) -> Error {
6588

6689
pub(crate) fn conv_ntstatus(status: NTSTATUS) -> Error {
6790
let kind = match status {
68-
STATUS_ACCESS_DENIED | STATUS_PRIVILEGE_NOT_HELD => ErrorKind::Permissions,
91+
STATUS_ACCESS_DENIED | STATUS_PRIVILEGE_NOT_HELD => ErrorKind::NotSupported,
6992
STATUS_INVALID_PARAMETER => ErrorKind::ArgValidation,
7093
STATUS_NOT_IMPLEMENTED | STATUS_INVALID_INFO_CLASS => ErrorKind::NotSupported,
71-
STATUS_INSUFFICIENT_RESOURCES | STATUS_NO_MEMORY => ErrorKind::OutOfMemory,
94+
STATUS_INSUFFICIENT_RESOURCES | STATUS_NO_MEMORY => ErrorKind::Unknown,
7295
STATUS_INVALID_HANDLE => ErrorKind::ProcessNotFound,
7396
_ => ErrorKind::Unknown,
7497
};
7598

7699
Error(ErrorOrigin::OsLayer, kind)
77100
}
78101

102+
fn split_basename(path: &str) -> &str {
103+
path.rsplit_once('\\')
104+
.or_else(|| path.rsplit_once('/'))
105+
.map(|(_, name)| name)
106+
.unwrap_or(path)
107+
}
108+
109+
fn parse_module_path(info: &RtlProcessModuleInformation) -> (ReprCString, ReprCString) {
110+
let path_end = info
111+
.FullPathName
112+
.iter()
113+
.position(|&c| c == 0)
114+
.unwrap_or(info.FullPathName.len());
115+
116+
let path_raw = &info.FullPathName[..path_end];
117+
let path = String::from_utf8_lossy(path_raw);
118+
let path = path.as_ref();
119+
120+
let name = if (info.OffsetToFileName as usize) < path_end {
121+
let name_raw = &info.FullPathName[info.OffsetToFileName as usize..path_end];
122+
let parsed = String::from_utf8_lossy(name_raw);
123+
if parsed.is_empty() {
124+
split_basename(path).to_owned()
125+
} else {
126+
parsed.into_owned()
127+
}
128+
} else {
129+
split_basename(path).to_owned()
130+
};
131+
132+
(path.into(), name.into())
133+
}
134+
79135
unsafe fn enable_debug_privilege() -> Result<()> {
80136
let process = GetCurrentProcess();
81137
let mut token = HANDLE::default();
@@ -129,6 +185,85 @@ impl WindowsOs {
129185

130186
Ok(Default::default())
131187
}
188+
189+
fn query_kernel_modules() -> Result<Vec<KernelModule>> {
190+
let mut buffer = vec![0u8; MODULE_QUERY_INITIAL_CAPACITY];
191+
192+
for _ in 0..MODULE_QUERY_MAX_RETRIES {
193+
let mut return_len = 0u32;
194+
let status = unsafe {
195+
NtQuerySystemInformation(
196+
SYSTEM_MODULE_INFORMATION_CLASS,
197+
buffer.as_mut_ptr().cast(),
198+
buffer.len() as u32,
199+
&mut return_len,
200+
)
201+
};
202+
203+
if status == STATUS_INFO_LENGTH_MISMATCH {
204+
let table_off = align_of::<RtlProcessModuleInformation>();
205+
let next_len = usize::max(
206+
buffer.len().saturating_mul(2),
207+
(return_len as usize).saturating_add(table_off),
208+
);
209+
buffer.resize(next_len, 0);
210+
continue;
211+
}
212+
213+
if status.is_err() {
214+
return Err(conv_ntstatus(status));
215+
}
216+
217+
let valid_len = if return_len as usize > buffer.len() {
218+
buffer.len()
219+
} else {
220+
return_len as usize
221+
};
222+
223+
let data = &buffer[..valid_len];
224+
let table_off = align_of::<RtlProcessModuleInformation>();
225+
226+
if data.len() < table_off {
227+
return Err(Error(ErrorOrigin::OsLayer, ErrorKind::Unknown));
228+
}
229+
230+
let mut header_buf = [0u8; core::mem::size_of::<u32>()];
231+
header_buf.copy_from_slice(&data[..core::mem::size_of::<u32>()]);
232+
let header_number = u32::from_ne_bytes(header_buf);
233+
let entry_size = core::mem::size_of::<RtlProcessModuleInformation>();
234+
let entries_len = header_number as usize;
235+
let table_len = entries_len
236+
.checked_mul(entry_size)
237+
.ok_or(Error(ErrorOrigin::OsLayer, ErrorKind::Unknown))?;
238+
let table_end = table_off
239+
.checked_add(table_len)
240+
.ok_or(Error(ErrorOrigin::OsLayer, ErrorKind::Unknown))?;
241+
242+
if table_end > data.len() {
243+
return Err(Error(ErrorOrigin::OsLayer, ErrorKind::ArgValidation));
244+
}
245+
246+
let mut parsed = Vec::with_capacity(entries_len);
247+
for idx in 0..entries_len {
248+
let module_off = table_off + idx * entry_size;
249+
let module = unsafe {
250+
(data.as_ptr().add(module_off) as *const RtlProcessModuleInformation)
251+
.read_unaligned()
252+
};
253+
let (path, name) = parse_module_path(&module);
254+
parsed.push(KernelModule {
255+
base: Address::from(module.ImageBase as umem),
256+
size: module.ImageSize as umem,
257+
path,
258+
name,
259+
});
260+
}
261+
262+
return Ok(parsed);
263+
}
264+
265+
Err(Error(ErrorOrigin::OsLayer, ErrorKind::Unknown))
266+
}
132267
}
133268

134269
impl Clone for WindowsOs {
@@ -244,6 +379,8 @@ impl Os for WindowsOs {
244379
/// # Arguments
245380
/// * `callback` - where to pass each matching module to. This is an opaque callback.
246381
fn module_address_list_callback(&mut self, mut callback: AddressCallback) -> Result<()> {
382+
self.cached_modules = Self::query_kernel_modules()?;
383+
247384
(0..self.cached_modules.len())
248385
.map(Address::from)
249386
.take_while(|a| callback.call(*a))
@@ -256,29 +393,19 @@ impl Os for WindowsOs {
256393
///
257394
/// # Arguments
258395
/// * `address` - address where module's information resides in
259-
fn module_by_address(&mut self, _address: Address) -> Result<ModuleInfo> {
260-
/*self.cached_modules
261-
.iter()
262-
.skip(address.to_umem() as usize)
263-
.next()
264-
.map(|km| ModuleInfo {
265-
address,
266-
size: km.size as umem,
267-
base: Address::NULL,
268-
name: km
269-
.name
270-
.split("/")
271-
.last()
272-
.or(Some(""))
273-
.map(ReprCString::from)
274-
.unwrap(),
275-
arch: self.info.arch,
276-
path: km.name.clone().into(),
277-
parent_process: Address::INVALID,
278-
})
279-
.ok_or(Error(ErrorOrigin::OsLayer, ErrorKind::NotFound))*/
280-
281-
todo!()
396+
fn module_by_address(&mut self, address: Address) -> Result<ModuleInfo> {
397+
self.cached_modules
398+
.get(address.to_umem() as usize)
399+
.map(|km| ModuleInfo {
400+
address,
401+
size: km.size,
402+
base: km.base,
403+
name: km.name.clone(),
404+
arch: self.info.arch,
405+
path: km.path.clone(),
406+
parent_process: Address::INVALID,
407+
})
408+
.ok_or(Error(ErrorOrigin::OsLayer, ErrorKind::NotFound))
282409
}
283410

284411
/// Retrieves address of the primary module structure of the process

src/windows/process.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use memflow::os::process::*;
33
use memflow::prelude::v1::*;
44
use memflow::types::gap_remover::GapRemover;
55

6-
use super::{conv_err, conv_ntstatus, ProcessVirtualMemory};
6+
use super::{conv_err, conv_ntstatus};
7+
use crate::windows::mem::ProcessVirtualMemory;
78

89
use windows::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation};
910
use windows::Win32::Foundation::{HINSTANCE, HMODULE, STILL_ACTIVE};

0 commit comments

Comments
 (0)