use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct KField {
pub offset: u64,
pub width: u8,
pub name: &'static str,
pub ty: &'static str,
}
macro_rules! fields {
($($off:expr, $width:expr, $name:literal, $ty:literal);+ $(;)?) => {{
&[ $( KField { offset: $off, width: $width, name: $name, ty: $ty }, )+ ]
}};
}
#[allow(dead_code)] pub static UNICODE_STRING: &[KField] = fields![
0x00, 2, "Length", "u16";
0x02, 2, "MaximumLength", "u16";
0x08, 8, "Buffer", "PWSTR";
];
pub static LIST_ENTRY: &[KField] = fields![
0x00, 8, "Flink", "PLIST_ENTRY";
0x08, 8, "Blink", "PLIST_ENTRY";
];
#[allow(dead_code)] pub static DRIVER_OBJECT: &[KField] = fields![
0x00, 2, "Type", "u16";
0x02, 2, "Size", "u16";
0x08, 8, "DeviceObject", "PDEVICE_OBJECT";
0x10, 4, "Flags", "u32";
0x18, 8, "DriverStart", "ptr";
0x20, 4, "DriverSize", "u32";
0x28, 8, "DriverSection", "ptr";
0x30, 8, "DriverExtension", "PDRIVER_EXTENSION";
0x38, 16, "DriverName", "UNICODE_STRING";
0x48, 8, "HardwareDatabase", "PUNICODE_STRING";
0x50, 8, "FastIoDispatch", "PFAST_IO_DISPATCH";
0x58, 8, "DriverInit", "PDRIVER_INITIALIZE";
0x60, 8, "DriverStartIo", "PDRIVER_STARTIO";
0x68, 8, "DriverUnload", "PDRIVER_UNLOAD";
0x70, 224, "MajorFunction", "PDRIVER_DISPATCH[28]";
];
pub static IO_STACK_LOCATION: &[KField] = fields![
0x00, 1, "MajorFunction", "u8";
0x01, 1, "MinorFunction", "u8";
0x02, 1, "Flags", "u8";
0x03, 1, "Control", "u8";
0x08, 4, "Parameters.DeviceIoControl.OutputBufferLength", "u32";
0x0c, 4, "Parameters.DeviceIoControl.InputBufferLength", "u32";
0x10, 4, "Parameters.DeviceIoControl.IoControlCode", "u32";
0x18, 8, "Parameters.DeviceIoControl.Type3InputBuffer", "ptr";
];
pub const MAJOR_BASES: [u64; 1] = [0x70];
pub const FAST_IO_DISPATCH_OFFSET: u64 = 0x50;
pub fn field(ty: &'static [KField], offset: u64) -> Option<&'static KField> {
ty.iter().find(|f| f.offset == offset)
}
#[allow(dead_code)] pub fn dispatch_slot(offset: u64) -> Option<String> {
for base in MAJOR_BASES {
if offset >= base && (offset - base).is_multiple_of(8) {
let idx = (offset - base) / 8;
if idx < 28 {
return Some(format!("MajorFunction[{idx}] /* {} */", irp(idx)));
}
}
}
None
}
pub fn irp(index: u64) -> &'static str {
const N: [&str; 28] = [
"IRP_MJ_CREATE",
"IRP_MJ_CREATE_NAMED_PIPE",
"IRP_MJ_CLOSE",
"IRP_MJ_READ",
"IRP_MJ_WRITE",
"IRP_MJ_QUERY_INFORMATION",
"IRP_MJ_SET_INFORMATION",
"IRP_MJ_QUERY_EA",
"IRP_MJ_SET_EA",
"IRP_MJ_FLUSH_BUFFERS",
"IRP_MJ_QUERY_VOLUME_INFORMATION",
"IRP_MJ_SET_VOLUME_INFORMATION",
"IRP_MJ_DIRECTORY_CONTROL",
"IRP_MJ_FILE_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CONTROL",
"IRP_MJ_INTERNAL_DEVICE_CONTROL",
"IRP_MJ_SHUTDOWN",
"IRP_MJ_LOCK_CONTROL",
"IRP_MJ_CLEANUP",
"IRP_MJ_CREATE_MAILSLOT",
"IRP_MJ_QUERY_SECURITY",
"IRP_MJ_SET_SECURITY",
"IRP_MJ_POWER",
"IRP_MJ_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CHANGE",
"IRP_MJ_QUERY_QUOTA",
"IRP_MJ_SET_QUOTA",
"IRP_MJ_PNP",
];
N.get(index as usize).copied().unwrap_or("?")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_layouts_match_expected_offsets() {
assert_eq!(field(UNICODE_STRING, 0x08).map(|f| f.name), Some("Buffer"));
assert_eq!(
field(IO_STACK_LOCATION, 0x10).map(|f| f.name),
Some("Parameters.DeviceIoControl.IoControlCode")
);
assert!(!MAJOR_BASES.is_empty());
}
#[test]
fn dispatch_slots_render_by_major() {
assert_eq!(
dispatch_slot(0x70 + 8 * 14),
Some("MajorFunction[14] /* IRP_MJ_DEVICE_CONTROL */".into())
);
assert_eq!(dispatch_slot(FAST_IO_DISPATCH_OFFSET), None);
assert_eq!(dispatch_slot(0x20), None);
}
}