├── .gitignore ├── Cargo.toml └── src ├── allocator.rs ├── basedef.rs ├── kernel.rs ├── lib.rs ├── log.rs ├── ntstatus.rs ├── process.rs ├── string.rs ├── util.rs └── vsb.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "winkernel" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | log = "0.4.14" 10 | ntapi = { version = "0.3.6", features = ["kernel", "impl-default", "nightly"] } 11 | winapi = { version = "0.3.9", features = ["ntdef", "ntstatus", "basetsd", "winnt"] } 12 | libc = "0.2.106" 13 | cstr_core = "0.2.4" 14 | -------------------------------------------------------------------------------- /src/allocator.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::{GlobalAlloc, Layout}; 2 | use core::ffi::c_void; 3 | 4 | #[repr(C)] 5 | pub enum PoolType { 6 | NonPagedPool, 7 | NonPagedPoolExecute, 8 | } 9 | 10 | #[link(name = "ntoskrnl")] 11 | extern "system" { 12 | pub fn ExAllocatePoolWithTag(pool_type: PoolType, number_of_bytes: usize, tag: u32) -> *mut c_void; 13 | pub fn ExFreePoolWithTag(pool: *mut c_void, tag: u32); 14 | } 15 | 16 | static ALLOC_TAG: u32 = u32::from_le_bytes(*b"krnl"); 17 | 18 | /// The global kernel allocator structure. 19 | pub struct KernelAlloc; 20 | 21 | unsafe impl GlobalAlloc for KernelAlloc { 22 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 23 | let pool = ExAllocatePoolWithTag(PoolType::NonPagedPool, layout.size() as _, ALLOC_TAG); 24 | 25 | 26 | if pool.is_null() { 27 | panic!("Failed to allocate pool"); 28 | } 29 | 30 | pool as _ 31 | } 32 | 33 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 34 | ExFreePoolWithTag(ptr as _, ALLOC_TAG); 35 | } 36 | } 37 | 38 | #[alloc_error_handler] 39 | #[cfg(not(test))] 40 | fn alloc_error(layout: Layout) -> ! { 41 | panic!("{:?} alloc memory error", layout); 42 | } 43 | -------------------------------------------------------------------------------- /src/basedef.rs: -------------------------------------------------------------------------------- 1 | //! Kernel-Mode Types. 2 | #![allow(non_camel_case_types)] 3 | 4 | pub use winapi::shared::ntdef::*; 5 | use core::ffi::c_void; 6 | pub use winapi::shared::ntstatus; 7 | pub use winapi; 8 | pub use ntapi; 9 | 10 | pub type PEPROCESS = *mut c_void; 11 | pub type PMDL = *mut c_void; 12 | 13 | /// Processor modes. 14 | #[repr(u8)] 15 | #[derive(Copy, Clone)] 16 | pub enum KProcessorMode 17 | { 18 | KernelMode, 19 | UserMode, 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/kernel.rs: -------------------------------------------------------------------------------- 1 | use winapi::ctypes::c_void; 2 | use core::ptr::{null_mut, NonNull}; 3 | use crate::basedef::*; 4 | use crate::ntstatus::NtStatus; 5 | use ntapi::ntexapi::{SYSTEM_INFORMATION_CLASS, SystemModuleInformation, SystemProcessInformation, SYSTEM_THREAD_INFORMATION}; 6 | use crate::vsb::VariableSizedBox; 7 | use ntapi::ntzwapi::ZwQuerySystemInformation; 8 | use alloc::prelude::v1::*; 9 | use ntapi::ntldr::RTL_PROCESS_MODULES; 10 | use core::{slice, mem}; 11 | use ntapi::ntapi_base::KPRIORITY; 12 | use winapi::shared::basetsd::{ULONG_PTR, SIZE_T, PSIZE_T}; 13 | use crate::string::UnicodeString; 14 | use ntapi::ntrtl::RtlFindExportedRoutineByName; 15 | use cstr_core::CString; 16 | use crate::basedef::ntapi::ntobapi::POBJECT_NAME_INFORMATION; 17 | use ntapi::ntobapi::OBJECT_NAME_INFORMATION; 18 | use alloc::string::FromUtf16Error; 19 | use winapi::um::winnt::PAGE_READWRITE; 20 | use crate::process::PeProcess; 21 | use alloc::prelude::v1::*; 22 | use alloc::vec; 23 | 24 | #[allow(non_camel_case_types)] 25 | #[repr(i32)] 26 | pub enum LOCK_OPERATION { 27 | IoReadAccess = 0, 28 | IoWriteAccess = 1, 29 | IoModifyAccess = 2, 30 | } 31 | 32 | #[allow(non_camel_case_types)] 33 | #[repr(i32)] 34 | pub enum MEMORY_CACHING_TYPE { 35 | MmNonCached = 0, 36 | MmCached = 1, 37 | MmWriteCombined = 2, 38 | MmHardwareCoherentCached = 3, 39 | MmNonCachedUnordered = 4, 40 | MmUSWCCached = 5, 41 | MmMaximumCacheType = 6, 42 | MmNotMapped = -1, 43 | } 44 | 45 | extern "system" { 46 | fn IoAllocateMdl( 47 | virtual_address: *mut c_void, 48 | length: u32, 49 | secondary_buffer: u8, 50 | charge_quota: u8, 51 | irp: *mut c_void, 52 | ) -> PMDL; 53 | 54 | pub fn MmProbeAndLockPages( 55 | memory_descriptor_list: PMDL, 56 | access_mode: KProcessorMode, 57 | operation: LOCK_OPERATION, 58 | ); 59 | 60 | pub fn MmMapLockedPagesSpecifyCache( 61 | memory_descriptor_list: PMDL, 62 | access_mode: KProcessorMode, 63 | cache_type: MEMORY_CACHING_TYPE, 64 | requested_address: PVOID, 65 | bug_check_on_failure: ULONG, 66 | priority: ULONG, 67 | ) -> PVOID; 68 | 69 | pub fn MmProtectMdlSystemAddress(memory_descriptor_list: PMDL, new_protect: ULONG) -> NtStatus; 70 | 71 | pub fn MmUnmapLockedPages(base_address: PVOID, memory_descriptor_list: PMDL); 72 | pub fn MmUnlockPages(memory_descriptor_list: PMDL); 73 | pub fn IoFreeMdl(mdl: PMDL); 74 | } 75 | 76 | pub unsafe fn safe_copy(src: *const u8, dst: *mut u8, len: usize) -> Result<(), NTSTATUS> { 77 | let mdl = IoAllocateMdl(dst as _, len as _, 0, 0, null_mut()); 78 | if mdl.is_null() { 79 | return Err(ntstatus::STATUS_ACCESS_DENIED); 80 | } 81 | 82 | MmProbeAndLockPages(mdl, KProcessorMode::KernelMode, LOCK_OPERATION::IoReadAccess); 83 | let map = MmMapLockedPagesSpecifyCache( 84 | mdl, 85 | KProcessorMode::KernelMode, 86 | MEMORY_CACHING_TYPE::MmNonCached, 87 | null_mut(), 88 | FALSE as u32, 89 | 16, // NormalPagePriority 90 | ); 91 | 92 | MmProtectMdlSystemAddress(mdl, 0x04 /* PAGE_READWRITE */).to_result()?; 93 | 94 | core::ptr::copy_nonoverlapping(src, map as _, len); 95 | 96 | MmUnmapLockedPages(map, mdl); 97 | MmUnlockPages(mdl); 98 | IoFreeMdl(mdl); 99 | 100 | Ok(()) 101 | } 102 | 103 | pub unsafe fn query_system_information(class: SYSTEM_INFORMATION_CLASS) -> Result, NTSTATUS> { 104 | let mut size = 0; 105 | let status = ZwQuerySystemInformation( 106 | class, 107 | null_mut(), 108 | size, 109 | &mut size, 110 | ); 111 | 112 | if size == 0 { 113 | return Err(ntstatus::STATUS_UNSUCCESSFUL); 114 | } 115 | 116 | let mut buf: VariableSizedBox = VariableSizedBox::new(size as _); 117 | 118 | let status = ZwQuerySystemInformation( 119 | class, 120 | buf.as_mut_ptr() as _, 121 | size, 122 | &mut size, 123 | ); 124 | NtStatus(status).to_result()?; 125 | 126 | Ok(buf) 127 | } 128 | 129 | #[repr(C)] 130 | #[derive(Copy, Clone)] 131 | pub struct ProcessModuleInformation { 132 | pub section: HANDLE, 133 | pub mapped_base: usize, 134 | pub image_base: usize, 135 | pub image_size: ULONG, 136 | pub flags: ULONG, 137 | pub load_order_index: USHORT, 138 | pub init_order_index: USHORT, 139 | pub load_count: USHORT, 140 | pub offset_to_file_name: USHORT, 141 | pub full_path_name: [UCHAR; 256], 142 | } 143 | 144 | impl ProcessModuleInformation { 145 | pub unsafe fn full_path(&self) -> &str { 146 | crate::util::str_from_slice_unchecked(self.full_path_name.as_slice()) 147 | } 148 | 149 | pub unsafe fn get_export(&self, func_name: &str) -> Option> { 150 | get_kernel_export(self.image_base, func_name) 151 | } 152 | } 153 | 154 | pub unsafe fn get_kernel_modules() -> Result, NTSTATUS> { 155 | let buf = query_system_information::(SystemModuleInformation)?; 156 | let modules = slice::from_raw_parts(buf.as_ref().Modules.as_ptr() as *const ProcessModuleInformation, buf.as_ref().NumberOfModules as usize); 157 | Ok(modules.to_vec()) 158 | } 159 | 160 | pub unsafe fn get_kernel_export(module_base: usize, func_name: &str) -> Option> { 161 | let func_name = CString::new(func_name).unwrap(); 162 | NonNull::new(RtlFindExportedRoutineByName(module_base as _, func_name.as_ptr() as _)) 163 | } 164 | 165 | #[repr(C)] 166 | #[derive(Copy, Clone)] 167 | pub struct SystemProcessInformation { 168 | pub next_entry_offset: ULONG, 169 | pub number_of_threads: ULONG, 170 | pub working_set_private_size: i64, 171 | pub hard_fault_count: ULONG, 172 | pub number_of_threads_high_watermark: ULONG, 173 | pub cycle_time: ULONGLONG, 174 | pub create_time: i64, 175 | pub user_time: i64, 176 | pub kernel_time: i64, 177 | pub image_name: UnicodeString, 178 | pub base_priority: KPRIORITY, 179 | pub unique_process_id: HANDLE, 180 | pub inherited_from_unique_process_id: HANDLE, 181 | pub handle_count: ULONG, 182 | pub session_id: ULONG, 183 | pub unique_process_key: ULONG_PTR, 184 | pub peak_virtual_size: SIZE_T, 185 | pub virtual_size: SIZE_T, 186 | pub page_fault_count: ULONG, 187 | pub peak_working_set_size: SIZE_T, 188 | pub working_set_size: SIZE_T, 189 | pub quota_peak_paged_pool_usage: SIZE_T, 190 | pub quota_paged_pool_usage: SIZE_T, 191 | pub quota_peak_non_paged_pool_usage: SIZE_T, 192 | pub quota_non_paged_pool_usage: SIZE_T, 193 | pub pagefile_usage: SIZE_T, 194 | pub peak_pagefile_usage: SIZE_T, 195 | pub private_page_count: SIZE_T, 196 | pub read_operation_count: i64, 197 | pub write_operation_count: i64, 198 | pub other_operation_count: i64, 199 | pub read_transfer_count: i64, 200 | pub write_transfer_count: i64, 201 | pub other_transfer_count: i64, 202 | pub threads: [SYSTEM_THREAD_INFORMATION; 1], 203 | } 204 | 205 | impl SystemProcessInformation { 206 | pub unsafe fn to_process(&self) -> Option { 207 | PeProcess::by_pid(self.unique_process_id as _) 208 | } 209 | } 210 | 211 | pub unsafe fn get_process_list() -> Result, NTSTATUS> { 212 | let buf = query_system_information::(SystemProcessInformation)?; 213 | 214 | let mut info = buf.as_ptr(); 215 | let mut structs = Vec::new(); 216 | 217 | loop { 218 | structs.push(*info); 219 | 220 | match (*info).next_entry_offset { 221 | 0 => break, 222 | offset => info = (info as usize + offset as usize) as _ 223 | } 224 | } 225 | 226 | Ok(structs) 227 | } 228 | 229 | #[repr(u32)] 230 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 231 | pub enum RegNotifyClass { 232 | RegNtPreDeleteKey = 0, 233 | RegNtPreSetValueKey = 1, 234 | RegNtPreDeleteValueKey = 2, 235 | RegNtPreSetInformationKey = 3, 236 | RegNtPreRenameKey = 4, 237 | RegNtPreEnumerateKey = 5, 238 | RegNtPreEnumerateValueKey = 6, 239 | RegNtPreQueryKey = 7, 240 | RegNtPreQueryValueKey = 8, 241 | RegNtPreQueryMultipleValueKey = 9, 242 | RegNtPreCreateKey = 10, 243 | RegNtPostCreateKey = 11, 244 | RegNtPreOpenKey = 12, 245 | RegNtPostOpenKey = 13, 246 | RegNtPreKeyHandleClose = 14, 247 | RegNtPostDeleteKey = 15, 248 | RegNtPostSetValueKey = 16, 249 | RegNtPostDeleteValueKey = 17, 250 | RegNtPostSetInformationKey = 18, 251 | RegNtPostRenameKey = 19, 252 | RegNtPostEnumerateKey = 20, 253 | RegNtPostEnumerateValueKey = 21, 254 | RegNtPostQueryKey = 22, 255 | RegNtPostQueryValueKey = 23, 256 | RegNtPostQueryMultipleValueKey = 24, 257 | RegNtPostKeyHandleClose = 25, 258 | RegNtPreCreateKeyEx = 26, 259 | RegNtPostCreateKeyEx = 27, 260 | RegNtPreOpenKeyEx = 28, 261 | RegNtPostOpenKeyEx = 29, 262 | RegNtPreFlushKey = 30, 263 | RegNtPostFlushKey = 31, 264 | RegNtPreLoadKey = 32, 265 | RegNtPostLoadKey = 33, 266 | RegNtPreUnLoadKey = 34, 267 | RegNtPostUnLoadKey = 35, 268 | RegNtPreQueryKeySecurity = 36, 269 | RegNtPostQueryKeySecurity = 37, 270 | RegNtPreSetKeySecurity = 38, 271 | RegNtPostSetKeySecurity = 39, 272 | RegNtCallbackObjectContextCleanup = 40, 273 | MaxRegNtNotifyClass = 41, 274 | } 275 | 276 | extern "system" { 277 | pub fn CmRegisterCallback( 278 | func: *mut c_void, 279 | context: *mut c_void, 280 | cookie: *mut u64, 281 | ) -> NtStatus; 282 | 283 | pub fn CmUnRegisterCallback(cookie: u64) -> NtStatus; 284 | } 285 | 286 | pub type RegistryCallbackFunc = extern "C" fn(callback_context: &mut T, class: RegNotifyClass, operation: *mut c_void) -> NTSTATUS; 287 | 288 | pub unsafe fn create_registry_callback(func: RegistryCallbackFunc, context: &'static mut T) -> Result { 289 | let mut cookie = 0; 290 | CmRegisterCallback(func as _, context as *mut T as _, &mut cookie).to_result()?; 291 | Ok(RegistryCallback(cookie)) 292 | } 293 | 294 | #[derive(Copy, Clone, Eq, PartialEq)] 295 | pub struct RegistryCallback(pub u64); 296 | 297 | impl RegistryCallback { 298 | pub unsafe fn unregister(&self) -> Result<(), NTSTATUS> { 299 | CmUnRegisterCallback(self.0).to_result() 300 | } 301 | } 302 | 303 | #[repr(C)] 304 | #[derive(Debug)] 305 | pub struct RegSetValueKeyInformation { 306 | pub object: PVOID, 307 | pub value_name: &'static UnicodeString, 308 | pub title_index: ULONG, 309 | pub reg_type: ULONG, 310 | pub data: PVOID, 311 | pub data_size: ULONG, 312 | pub call_context: PVOID, 313 | pub object_context: PVOID, 314 | pub reserved: PVOID, 315 | } 316 | 317 | impl RegSetValueKeyInformation { 318 | pub fn data(&self) -> &[u8] { 319 | unsafe { core::slice::from_raw_parts(self.data as *const u8, self.data_size as _) } 320 | } 321 | } 322 | 323 | extern "system" { 324 | fn ObQueryNameString( 325 | object: PVOID, 326 | object_name_info: POBJECT_NAME_INFORMATION, 327 | length: ULONG, 328 | return_length: PULONG, 329 | ) -> NtStatus; 330 | } 331 | 332 | pub unsafe fn get_object_name(object: PVOID) -> Result { 333 | if object.is_null() { 334 | return Err(ntstatus::STATUS_NOT_FOUND); 335 | } 336 | 337 | let mut len = 0; 338 | let result = ObQueryNameString(object, null_mut(), 0, &mut len); 339 | if result.0 != ntstatus::STATUS_INFO_LENGTH_MISMATCH { 340 | return Err(ntstatus::STATUS_NOT_FOUND); 341 | } 342 | 343 | let mut name_info = VariableSizedBox::new(len as usize); 344 | ObQueryNameString(object, name_info.as_mut_ptr(), len, &mut len).to_result()?; 345 | 346 | let name: UnicodeString = name_info.as_ref().Name.into(); 347 | match name.try_to_string() { 348 | Ok(s) => Ok(s), 349 | Err(_) => Err(ntstatus::STATUS_UNSUCCESSFUL) 350 | } 351 | } 352 | 353 | extern "system" { 354 | pub fn MmCopyMemory( 355 | target: *mut u8, 356 | copy_address: i64, 357 | size: usize, 358 | flags: u32, 359 | bytes_transferred: *mut usize, 360 | ) -> NtStatus; 361 | } 362 | 363 | const MM_COPY_MEMORY_PHYSICAL: u32 = 0x1; 364 | const MM_COPY_MEMORY_VIRTUAL: u32 = 0x2; 365 | 366 | pub unsafe fn read_physical_memory(physical_address: u64, buf: &mut [u8]) -> Result<(), (NTSTATUS, usize)> { 367 | let mut bytes_transferred = 0; 368 | let mut intermediate_buf = vec![0u8; buf.len()]; 369 | MmCopyMemory(intermediate_buf.as_mut_ptr(), physical_address as _, intermediate_buf.len(), MM_COPY_MEMORY_PHYSICAL, &mut bytes_transferred).to_result().map_err(|e| (e, bytes_transferred))?; 370 | buf.copy_from_slice(&intermediate_buf); 371 | if bytes_transferred != intermediate_buf.len() { 372 | return Err((ntstatus::STATUS_PARTIAL_COPY, bytes_transferred)); 373 | } 374 | Ok(()) 375 | } 376 | 377 | extern "system" { 378 | pub fn MmMapIoSpaceEx(physical_address: i64, len: usize, protect: u32) -> *mut c_void; 379 | pub fn MmUnmapIoSpace(base_address: *mut c_void, len: usize); 380 | } 381 | 382 | pub struct PhysicalMap { 383 | buf: *mut u8, 384 | len: usize, 385 | } 386 | 387 | impl PhysicalMap { 388 | pub unsafe fn new(physical_address: u64, len: usize) -> Option { 389 | let map = MmMapIoSpaceEx(physical_address as _, len, PAGE_READWRITE); 390 | if map.is_null() { 391 | return None; 392 | } 393 | let buf = map as *mut u8; 394 | 395 | Some(Self { buf, len }) 396 | } 397 | 398 | pub unsafe fn as_slice(&self) -> &[u8] { 399 | core::slice::from_raw_parts(self.buf, self.len) 400 | } 401 | 402 | pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] { 403 | core::slice::from_raw_parts_mut(self.buf, self.len) 404 | } 405 | } 406 | 407 | impl core::ops::Deref for PhysicalMap { 408 | type Target = [u8]; 409 | 410 | fn deref(&self) -> &Self::Target { 411 | unsafe { self.as_slice() } 412 | } 413 | } 414 | 415 | impl core::ops::DerefMut for PhysicalMap { 416 | fn deref_mut(&mut self) -> &mut Self::Target { 417 | unsafe { self.as_mut_slice() } 418 | } 419 | } 420 | 421 | impl Drop for PhysicalMap { 422 | fn drop(&mut self) { 423 | unsafe { 424 | MmUnmapIoSpace(self.buf as _, self.len); 425 | } 426 | } 427 | } 428 | 429 | #[link(name = "hal")] 430 | extern "system" { 431 | pub fn KeQueryPerformanceCounter(performance_frequency: *mut i64) -> u64; 432 | } 433 | 434 | pub unsafe fn query_performance_counter() -> u64 { 435 | KeQueryPerformanceCounter(null_mut()) 436 | } 437 | 438 | extern "system" { 439 | pub fn MmIsAddressValid(virtual_address: *mut c_void) -> BOOLEAN; 440 | } 441 | 442 | pub unsafe fn is_address_valid(address: usize) -> bool { 443 | MmIsAddressValid(address as _) == 1 444 | } 445 | 446 | pub unsafe fn is_valid_ptr(ptr: *const T) -> bool { 447 | is_address_valid(ptr as _) 448 | } 449 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(alloc_error_handler)] 3 | #![feature(alloc_prelude)] 4 | #![feature(core_intrinsics)] 5 | #![allow(clippy::missing_safety_doc)] 6 | 7 | extern crate alloc; 8 | 9 | pub mod allocator; 10 | pub mod log; 11 | pub mod string; 12 | pub mod kernel; 13 | pub mod basedef; 14 | pub mod ntstatus; 15 | pub mod process; 16 | pub mod vsb; 17 | pub mod util; -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use log::{Level, LevelFilter, Metadata, Record, SetLoggerError}; 2 | use alloc::string::String; 3 | 4 | extern "cdecl" { 5 | pub fn DbgPrintEx(component_id: u32, level: u32, fmt: *const u8, ...) -> i32; 6 | } 7 | 8 | /// Prints a string using DbgPrintEx. Automatically adds a null terminator 9 | pub fn __kernel_print(mut text: String) { 10 | text.push('\n'); 11 | text.push('\0'); 12 | unsafe { DbgPrintEx(0, 0, text.as_ptr()) }; 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! println { 17 | ($($arg:tt)*) => ({ 18 | ::winkernel::log::__kernel_print(alloc::format!($($arg)*)); 19 | }) 20 | } 21 | 22 | #[macro_export] 23 | macro_rules! dbg { 24 | () => { 25 | ::winkernel::println!("[{}:{}]", $crate::file!(), $crate::line!()); 26 | }; 27 | ($val:expr $(,)?) => { 28 | // Use of `match` here is intentional because it affects the lifetimes 29 | // of temporaries - https://stackoverflow.com/a/48732525/1063961 30 | match $val { 31 | tmp => { 32 | ::winkernel::println!("[{}:{}] {} = {:#?}", 33 | core::file!(), core::line!(), core::stringify!($val), &tmp); 34 | tmp 35 | } 36 | } 37 | }; 38 | ($($val:expr),+ $(,)?) => { 39 | ($($crate::dbg!($val)),+,) 40 | }; 41 | } 42 | 43 | pub struct KernelLogger { 44 | pub prefix: &'static str, 45 | } 46 | 47 | static mut LOGGER: KernelLogger = KernelLogger { prefix: "" }; 48 | 49 | impl KernelLogger { 50 | pub fn init(level: LevelFilter, prefix: &'static str) -> Result<(), SetLoggerError> { 51 | unsafe { 52 | LOGGER.prefix = prefix; 53 | log::set_logger(&LOGGER) 54 | .map(|()| log::set_max_level(level)) 55 | } 56 | } 57 | } 58 | 59 | impl log::Log for KernelLogger { 60 | fn enabled(&self, _metadata: &Metadata) -> bool { 61 | true 62 | } 63 | 64 | fn log(&self, record: &Record) { 65 | if !self.enabled(record.metadata()) { 66 | return; 67 | } 68 | 69 | let prefix = match record.level() { 70 | Level::Error => "[ERROR]", 71 | Level::Warn => "[!]", 72 | Level::Info => "[+]", 73 | Level::Debug => "[*]", 74 | Level::Trace => "[?]", 75 | }; 76 | 77 | __kernel_print(alloc::format!("[{}] {} {}", self.prefix, prefix, record.args())); 78 | } 79 | 80 | fn flush(&self) {} 81 | } 82 | -------------------------------------------------------------------------------- /src/ntstatus.rs: -------------------------------------------------------------------------------- 1 | use crate::basedef::NTSTATUS; 2 | 3 | #[repr(transparent)] 4 | pub struct NtStatus(pub NTSTATUS); 5 | 6 | #[derive(Copy, Clone, Eq, PartialEq)] 7 | pub enum NtStatusType { 8 | Success, 9 | Information, 10 | Warning, 11 | Error, 12 | } 13 | 14 | impl NtStatus { 15 | pub fn get_type(&self) -> NtStatusType { 16 | match self.0 { 17 | s if nt_information(s) => NtStatusType::Information, 18 | s if nt_success(s) => NtStatusType::Success, 19 | s if nt_warning(s) => NtStatusType::Warning, 20 | s if nt_error(s) => NtStatusType::Error, 21 | _ => unreachable!() 22 | } 23 | } 24 | 25 | pub fn is_success(&self) -> bool { 26 | self.get_type() == NtStatusType::Success 27 | } 28 | 29 | pub fn is_warning(&self) -> bool { 30 | self.get_type() == NtStatusType::Warning 31 | } 32 | 33 | pub fn is_error(&self) -> bool { 34 | self.get_type() == NtStatusType::Error 35 | } 36 | 37 | pub fn to_result(&self) -> Result<(), NTSTATUS> { 38 | match self.get_type() { 39 | NtStatusType::Error => Err(self.0), 40 | _ => Ok(()) 41 | } 42 | } 43 | 44 | pub fn to_result_with_value(&self, value: T) -> Result { 45 | match self.get_type() { 46 | NtStatusType::Error => Err(self.0), 47 | _ => Ok(value) 48 | } 49 | } 50 | } 51 | 52 | impl From for NtStatus { 53 | fn from(s: NTSTATUS) -> Self { 54 | Self(s) 55 | } 56 | } 57 | 58 | /// Evaluates to TRUE if the return value specified by Status is a success type (0 − 0x3FFFFFFF) or an informational type (0x40000000 − 0x7FFFFFFF). 59 | pub fn nt_success(status: NTSTATUS) -> bool { 60 | (0..=0x7FFFFFFF).contains(&(status as u32)) 61 | } 62 | 63 | /// Evaluates to TRUE if the return value specified by Status is an informational type (0x40000000 − 0x7FFFFFFF). 64 | pub fn nt_information(status: NTSTATUS) -> bool { 65 | (0x40000000..=0x7FFFFFFF).contains(&(status as u32)) 66 | } 67 | 68 | /// Evaluates to TRUE if the return value specified by Status is a warning type (0x80000000 − 0xBFFFFFFF). 69 | pub fn nt_warning(status: NTSTATUS) -> bool { 70 | (0x80000000..=0xBFFFFFFF).contains(&(status as u32)) 71 | } 72 | 73 | /// Evaluates to TRUE if the return value specified by Status is an error type (0xC0000000 - 0xFFFFFFFF). 74 | pub fn nt_error(status: NTSTATUS) -> bool { 75 | (0xC0000000..=0xFFFFFFFF).contains(&(status as u32)) 76 | } -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | use crate::basedef::*; 3 | use core::{mem, ptr}; 4 | use crate::ntstatus::NtStatus; 5 | use ntapi::ntpebteb::PPEB; 6 | use cstr_core::CStr; 7 | 8 | extern "system" { 9 | pub fn PsLookupProcessByProcessId(process_id: HANDLE, process: *mut PeProcess) -> NtStatus; 10 | pub fn PsGetProcessPeb(process: PeProcess) -> PPEB; 11 | pub fn IoGetCurrentProcess() -> PeProcess; 12 | pub fn PsGetProcessImageFileName(process: PeProcess) -> *const u8; 13 | pub fn MmCopyVirtualMemory(from_process: PeProcess, from_address: *mut c_void, to_process: PeProcess, to_address: *mut c_void, size: usize, previous_mode: KProcessorMode, bytes_copied: &mut usize) -> NtStatus; 14 | } 15 | 16 | #[repr(transparent)] 17 | #[derive(Copy, Clone, Debug)] 18 | pub struct PeProcess(PEPROCESS); 19 | 20 | impl PeProcess { 21 | pub fn from_peprocess(proc: PEPROCESS) -> Self { 22 | Self(proc) 23 | } 24 | 25 | pub unsafe fn current() -> Self { 26 | IoGetCurrentProcess() 27 | } 28 | 29 | pub unsafe fn file_name(&self) -> &str { 30 | let buf = PsGetProcessImageFileName(*self); 31 | CStr::from_ptr(buf as _).to_str().unwrap() 32 | } 33 | 34 | pub unsafe fn by_pid(pid: u64) -> Option { 35 | let mut proc: PeProcess = mem::zeroed(); 36 | PsLookupProcessByProcessId(pid as HANDLE, &mut proc) 37 | .to_result_with_value(proc) 38 | .ok() 39 | } 40 | 41 | pub unsafe fn peb(&self) -> PPEB { 42 | PsGetProcessPeb(*self) 43 | } 44 | 45 | pub unsafe fn read_memory(&self, address: u64, buf: &mut [u8]) -> Result<(), NTSTATUS> { 46 | let mut bytes_copied = 0; 47 | MmCopyVirtualMemory(*self, address as _, Self::current(), buf.as_mut_ptr() as _, buf.len(), KProcessorMode::KernelMode, &mut bytes_copied); 48 | if bytes_copied < buf.len() { 49 | return Err(ntstatus::STATUS_UNSUCCESSFUL); 50 | } 51 | 52 | Ok(()) 53 | } 54 | 55 | pub unsafe fn write_memory(&self, address: u64, buf: &[u8]) -> Result<(), NTSTATUS> { 56 | let mut bytes_copied = 0; 57 | MmCopyVirtualMemory(Self::current(), buf.as_ptr() as _, *self, address as _, buf.len(), KProcessorMode::KernelMode, &mut bytes_copied); 58 | if bytes_copied < buf.len() { 59 | return Err(ntstatus::STATUS_UNSUCCESSFUL); 60 | } 61 | 62 | Ok(()) 63 | } 64 | } -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | use alloc::prelude::v1::*; 2 | use alloc::string::FromUtf16Error; 3 | use core::fmt::Debug; 4 | use crate::basedef::ntapi::_core::fmt::Formatter; 5 | use crate::basedef::UNICODE_STRING; 6 | 7 | /// A counted Unicode string. 8 | #[repr(C)] 9 | #[derive(Copy, Clone)] 10 | pub struct UnicodeString 11 | { 12 | /// The length in **bytes** of the string stored in `Buffer`. 13 | pub length: u16, 14 | /// The length in **bytes** of `Buffer`. 15 | pub maximum_length: u16, 16 | /// Pointer to a buffer used to contain a string of wide characters. 17 | pub buffer: *const u16, 18 | } 19 | 20 | impl UnicodeString { 21 | pub fn try_to_string(&self) -> Result { 22 | unsafe { String::from_utf16(self.as_slice()) } 23 | } 24 | 25 | pub unsafe fn as_slice(&self) -> &[u16] { 26 | core::slice::from_raw_parts(self.buffer, (self.length / 2) as _) 27 | } 28 | 29 | pub fn from_slice(slice: &[u16]) -> Self { 30 | Self { 31 | length: slice.len() as _, 32 | maximum_length: slice.len() as _, 33 | buffer: slice.as_ptr(), 34 | } 35 | } 36 | } 37 | 38 | impl From for UnicodeString { 39 | fn from(s: UNICODE_STRING) -> Self { 40 | Self { length: s.Length, maximum_length: s.MaximumLength, buffer: s.Buffer } 41 | } 42 | } 43 | 44 | impl Debug for UnicodeString { 45 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 46 | write!(f, "{:?}", self.try_to_string()) 47 | } 48 | } -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | pub unsafe fn str_from_slice_unchecked<'a>(slice: &'a [u8]) -> &'a str { 2 | let mut len = libc::strlen(slice.as_ptr() as _); 3 | if len > slice.len() { 4 | len = slice.len(); 5 | } 6 | core::str::from_utf8_unchecked(&slice[0..len]) 7 | } -------------------------------------------------------------------------------- /src/vsb.rs: -------------------------------------------------------------------------------- 1 | // From https://github.com/retep998/wio-rs/blob/master/src/vsb.rs 2 | 3 | use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error, realloc}; 4 | // Licensed under the Apache License, Version 2.0 5 | // or the MIT license 6 | // , at your option. 7 | // All files in the project carrying such notice may not be copied, modified, or distributed 8 | // except according to those terms. 9 | use core::{ 10 | alloc::Layout, 11 | marker::PhantomData, 12 | mem::{align_of, size_of}, 13 | ptr::{self, NonNull}, 14 | slice::{from_raw_parts, from_raw_parts_mut}, 15 | }; 16 | use core::ops::{Deref, DerefMut}; 17 | 18 | /// This is a smart pointer type for holding FFI types whose size varies. 19 | /// Most commonly this is with an array member as the last field whose size is specified 20 | /// by either another field, or an external source of information. 21 | pub struct VariableSizedBox { 22 | size: usize, 23 | data: NonNull, 24 | pd: PhantomData, 25 | } 26 | 27 | impl VariableSizedBox { 28 | /// The size is specified in bytes. The data is zeroed. 29 | pub fn new(size: usize) -> VariableSizedBox { 30 | if size == 0 { 31 | return VariableSizedBox::default(); 32 | } 33 | let layout = Layout::from_size_align(size, align_of::()).unwrap(); 34 | if let Some(data) = NonNull::new(unsafe { alloc_zeroed(layout) }) { 35 | VariableSizedBox { 36 | size, 37 | data: data.cast(), 38 | pd: PhantomData, 39 | } 40 | } else { 41 | handle_alloc_error(layout) 42 | } 43 | } 44 | /// Use this to get a pointer to pass to FFI functions. 45 | pub fn as_ptr(&self) -> *const T { 46 | if self.size == 0 { 47 | ptr::null() 48 | } else { 49 | self.data.as_ptr() 50 | } 51 | } 52 | /// Use this to get a pointer to pass to FFI functions. 53 | pub fn as_mut_ptr(&mut self) -> *mut T { 54 | if self.size == 0 { 55 | ptr::null_mut() 56 | } else { 57 | self.data.as_ptr() 58 | } 59 | } 60 | /// This is used to more safely access the fixed size fields. 61 | /// # Safety 62 | /// The current data must be valid for an instance of `T`. 63 | pub unsafe fn as_ref(&self) -> &T { 64 | assert!(self.size >= size_of::()); 65 | self.data.as_ref() 66 | } 67 | /// This is used to more safely access the fixed size fields. 68 | /// # Safety 69 | /// The current data must be valid for an instance of `T`. 70 | pub unsafe fn as_mut_ref(&mut self) -> &mut T { 71 | assert!(self.size >= size_of::()); 72 | self.data.as_mut() 73 | } 74 | /// The size is specified in bytes. 75 | /// If this grows the allocation, the extra bytes will be zeroed. 76 | pub fn resize(&mut self, size: usize) { 77 | if size == 0 || self.size == 0 { 78 | *self = VariableSizedBox::new(size); 79 | } else if size > self.size { 80 | let new = VariableSizedBox::::new(size); 81 | unsafe { 82 | self.data 83 | .as_ptr() 84 | .cast::() 85 | .copy_to(new.data.as_ptr().cast(), self.size.min(size)); 86 | } 87 | *self = new; 88 | } else if size < self.size { 89 | let layout = Layout::from_size_align(size, align_of::()).unwrap(); 90 | if let Some(data) = 91 | NonNull::new(unsafe { realloc(self.as_mut_ptr().cast(), layout, size) }) 92 | { 93 | self.data = data.cast(); 94 | self.size = size; 95 | } else { 96 | handle_alloc_error(layout) 97 | } 98 | } 99 | } 100 | /// The length of the allocation specified in bytes. 101 | pub fn len(&self) -> usize { 102 | self.size 103 | } 104 | /// Given a pointer to a specific field, upgrades the provenance of the pointer to the entire 105 | /// allocation to work around stacked borrows. 106 | /// # Safety 107 | /// `o` must be a valid pointer within the allocation contained by this box. 108 | pub unsafe fn sanitize_ptr(&self, ptr: *const U) -> *const U { 109 | let offset = ptr as usize - self.as_ptr() as usize; 110 | (self.as_ptr() as *const u8).add(offset).cast() 111 | } 112 | /// Given a pointer to a specific field, upgrades the provenance of the pointer to the entire 113 | /// allocation to work around stacked borrows. 114 | /// # Safety 115 | /// `o` must be a valid pointer within the allocation contained by this box. 116 | pub unsafe fn sanitize_mut_ptr(&mut self, ptr: *mut U) -> *mut U { 117 | let offset = ptr as usize - self.as_ptr() as usize; 118 | (self.as_mut_ptr() as *mut u8).add(offset).cast() 119 | } 120 | /// Given a pointer to a variable sized array field and the length of the array in elements, 121 | /// returns a slice to the entire variable sized array. 122 | /// Will return `None` if the slice is not entirely within the allocation. 123 | /// # Safety 124 | /// The data must be valid for the specified type. 125 | pub unsafe fn try_slice_from_count(&self, ptr: *const U, count: usize) -> Option<&[U]> { 126 | if ptr >= self.as_ptr().cast() 127 | && count.checked_mul(size_of::()).unwrap() <= self.size 128 | && ptr.wrapping_add(count) >= ptr 129 | && ptr.wrapping_add(count) <= self.as_ptr().cast::().add(self.size).cast() 130 | { 131 | Some(from_raw_parts(self.sanitize_ptr(ptr), count)) 132 | } else { 133 | None 134 | } 135 | } 136 | /// Given a pointer to a variable sized array field and the length of the array in elements, 137 | /// returns a slice to the entire variable sized array. 138 | /// Will panic if the slice is not entirely within the allocation. 139 | /// # Safety 140 | /// The data must be valid for the specified type. 141 | pub unsafe fn slice_from_count(&self, ptr: *const U, count: usize) -> &[U] { 142 | self.try_slice_from_count(ptr, count).unwrap() 143 | } 144 | /// Given a pointer to a variable sized array field and the length of the array in elements, 145 | /// returns a mutable slice to the entire variable sized array. 146 | /// Will return `None` if the slice is not entirely within the allocation. 147 | /// # Safety 148 | /// The data must be valid for the specified type. 149 | pub unsafe fn try_slice_from_count_mut( 150 | &mut self, 151 | ptr: *mut U, 152 | count: usize, 153 | ) -> Option<&mut [U]> { 154 | if ptr >= self.as_mut_ptr().cast() 155 | && count.checked_mul(size_of::()).unwrap() <= self.size 156 | && ptr.wrapping_add(count) >= ptr 157 | && ptr.wrapping_add(count) <= self.as_mut_ptr().cast::().add(self.size).cast() 158 | { 159 | Some(from_raw_parts_mut(self.sanitize_mut_ptr(ptr), count)) 160 | } else { 161 | None 162 | } 163 | } 164 | /// Given a pointer to a variable sized array field and the length of the array in elements, 165 | /// returns a mutable slice to the entire variable sized array. 166 | /// Will panic if the slice is not entirely within the allocation. 167 | /// # Safety 168 | /// The data must be valid for the specified type. 169 | pub unsafe fn slice_from_count_mut(&mut self, ptr: *mut U, count: usize) -> &mut [U] { 170 | self.try_slice_from_count_mut(ptr, count).unwrap() 171 | } 172 | /// Given a pointer to a variable sized array field and the length of the array in bytes, 173 | /// returns a slice to the entire variable sized array. 174 | /// Will return `None` if the slice is not entirely within the allocation. 175 | /// # Safety 176 | /// The data must be valid for the specified type. 177 | pub unsafe fn try_slice_from_bytes(&self, ptr: *const U, bytes: usize) -> Option<&[U]> { 178 | let count = bytes / size_of::(); 179 | self.try_slice_from_count(ptr, count) 180 | } 181 | /// Given a pointer to a variable sized array field and the length of the array in bytes, 182 | /// returns a slice to the entire variable sized array. 183 | /// Will panic if the slice is not entirely within the allocation. 184 | /// # Safety 185 | /// The data must be valid for the specified type. 186 | pub unsafe fn slice_from_bytes(&self, ptr: *const U, bytes: usize) -> &[U] { 187 | let count = bytes / size_of::(); 188 | self.slice_from_count(ptr, count) 189 | } 190 | /// Given a pointer to a variable sized array field and the length of the array in bytes, 191 | /// returns a mutable slice to the entire variable sized array. 192 | /// Will return `None` if the slice is not entirely within the allocation. 193 | /// # Safety 194 | /// The data must be valid for the specified type. 195 | pub unsafe fn try_slice_from_bytes_mut( 196 | &mut self, 197 | ptr: *mut U, 198 | bytes: usize, 199 | ) -> Option<&mut [U]> { 200 | let count = bytes / size_of::(); 201 | self.try_slice_from_count_mut(ptr, count) 202 | } 203 | /// Given a pointer to a variable sized array field and the length of the array in bytes, 204 | /// returns a mutable slice to the entire variable sized array. 205 | /// Will panic if the slice is not entirely within the allocation. 206 | /// # Safety 207 | /// The data must be valid for the specified type. 208 | pub unsafe fn slice_from_bytes_mut(&mut self, ptr: *mut U, bytes: usize) -> &mut [U] { 209 | let count = bytes / size_of::(); 210 | self.slice_from_count_mut(ptr, count) 211 | } 212 | /// Given a pointer to a variable sized array field and the size of the entire struct in bytes 213 | /// including the size of the array, returns a slice to the entire variable sized array. 214 | /// Will return `None` if the slice is not entirely within the allocation. 215 | /// # Safety 216 | /// The data must be valid for the specified type. 217 | pub unsafe fn try_slice_from_total_bytes( 218 | &self, 219 | ptr: *const U, 220 | total_bytes: usize, 221 | ) -> Option<&[U]> { 222 | let bytes = total_bytes - (ptr as usize - self.as_ptr() as usize); 223 | self.try_slice_from_bytes(ptr, bytes) 224 | } 225 | /// Given a pointer to a variable sized array field and the size of the entire struct in bytes 226 | /// including the size of the array, returns a slice to the entire variable sized array. 227 | /// Will panic if the slice is not entirely within the allocation. 228 | /// # Safety 229 | /// The data must be valid for the specified type. 230 | pub unsafe fn slice_from_total_bytes(&self, ptr: *const U, total_bytes: usize) -> &[U] { 231 | let bytes = total_bytes - (ptr as usize - self.as_ptr() as usize); 232 | self.slice_from_bytes(ptr, bytes) 233 | } 234 | /// Given a pointer to a variable sized array field and the size of the entire struct in bytes 235 | /// including the size of the array, returns a mutable slice to the entire variable sized 236 | /// array. 237 | /// Will return `None` if the slice is not entirely within the allocation. 238 | /// # Safety 239 | /// The data must be valid for the specified type. 240 | pub unsafe fn try_slice_from_total_bytes_mut( 241 | &mut self, 242 | ptr: *mut U, 243 | total_bytes: usize, 244 | ) -> Option<&mut [U]> { 245 | let bytes = total_bytes - (ptr as usize - self.as_ptr() as usize); 246 | self.try_slice_from_bytes_mut(ptr, bytes) 247 | } 248 | /// Given a pointer to a variable sized array field and the size of the entire struct in bytes 249 | /// including the size of the array, returns a mutable slice to the entire variable sized 250 | /// array. 251 | /// Will panic if the slice is not entirely within the allocation. 252 | /// # Safety 253 | /// The data must be valid for the specified type. 254 | pub unsafe fn slice_from_total_bytes_mut( 255 | &mut self, 256 | ptr: *mut U, 257 | total_bytes: usize, 258 | ) -> &mut [U] { 259 | let bytes = total_bytes - (ptr as usize - self.as_ptr() as usize); 260 | self.slice_from_bytes_mut(ptr, bytes) 261 | } 262 | } 263 | 264 | impl Drop for VariableSizedBox { 265 | fn drop(&mut self) { 266 | if self.size == 0 { 267 | return; 268 | } 269 | let layout = Layout::from_size_align(self.size, align_of::()).unwrap(); 270 | unsafe { dealloc(self.as_mut_ptr().cast(), layout) } 271 | } 272 | } 273 | 274 | impl Default for VariableSizedBox { 275 | fn default() -> Self { 276 | VariableSizedBox { 277 | size: 0, 278 | data: NonNull::dangling(), 279 | pd: PhantomData, 280 | } 281 | } 282 | } 283 | --------------------------------------------------------------------------------