#![cfg(feature = "ntdll")]
mod types;
use super::macros::{check_ptr, err};
use super::process::Process;
use super::{get_windir, predicate, str_from_wide_str};
use crate::Result;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::mem::MaybeUninit;
use core::sync::atomic::Ordering;
use ntapi::ntpsapi::PROCESSINFOCLASS;
use ntapi::ntwow64::LDR_DATA_TABLE_ENTRY32;
use pelite::Wrap;
pub use types::LDR_DATA_TABLE_ENTRY64;
use winapi::shared::minwindef::{HMODULE, PULONG, ULONG};
use winapi::shared::ntdef::{NTSTATUS, PVOID};
use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryW};
use winapi::um::winnt::{
HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ,
};
#[derive(Debug)]
pub(crate) struct NTDLL {
pub(self) handle: usize,
}
impl NTDLL {
pub(crate) fn new() -> Result<&'static Self> {
static INST: once_cell::race::OnceBox<NTDLL> = once_cell::race::OnceBox::new();
const NTDLL: &widestring::U16CStr = widestring::u16cstr!("NTDLL.dll");
debug_assert!(
NTDLL.as_slice_with_nul().ends_with(&[0]),
"{:x?}",
NTDLL.as_slice_with_nul()
);
INST.get_or_try_init(|| {
let handle = check_ptr!(LoadLibraryW(NTDLL.as_ptr())) as usize;
Ok(Box::new(NTDLL { handle }))
})
}
#[cfg_attr(not(target_arch = "x64"), allow(unused))]
pub(crate) fn get_ntdll_base_addr(
&self,
explicit_x86: bool,
proc: &Process,
) -> Result<(String, u64)> {
let ntdll = get_windir()?.clone()
+ if !explicit_x86 {
"\\System32\\ntdll.dll"
} else {
"\\SysWOW64\\ntdll.dll"
};
let ntdll = ntdll.to_lowercase();
unsafe {
self.get_module_in_proc(
proc,
predicate(
|m: Wrap<LDR_DATA_TABLE_ENTRY32, LDR_DATA_TABLE_ENTRY64>| match m {
Wrap::T32(v) => v.DllBase as u64,
Wrap::T64(v) => v.DllBase as u64,
},
|s| s.to_lowercase().ends_with(ntdll.as_str()),
),
)
}
}
pub(crate) unsafe fn get_module_in_proc<F, R>(&self, proc: &Process, predicate: F) -> Result<R>
where
F: Fn(
Wrap<ntapi::ntwow64::LDR_DATA_TABLE_ENTRY32, types::LDR_DATA_TABLE_ENTRY64>,
&[u16],
) -> Option<R>,
{
proc.err_pseudo_handle()?;
let pid_under_wow = proc.is_under_wow()?;
crate::info!("pid is under wow:{}", pid_under_wow);
let peb_addr: u64 = {
if pid_under_wow {
let pbi: ntapi::ntpsapi::PROCESS_BASIC_INFORMATION =
self.query_process_information(proc, ntapi::ntpsapi::ProcessBasicInformation)?;
pbi.PebBaseAddress as u64
} else {
let pbi: types::PROCESS_BASIC_INFORMATION_WOW64 =
self.query_process_information(proc, ntapi::ntpsapi::ProcessBasicInformation)?;
pbi.PebBaseAddress as u64
}
};
crate::debug!("Peb addr is {:x?}", peb_addr);
let ldr_addr;
{
type PEB32 = ntapi::ntwow64::PEB32;
type PEB64 = types::PEB64;
ldr_addr = if pid_under_wow {
let peb = *self.read_virtual_mem::<PEB32>(proc, peb_addr)?;
peb.Ldr as u64
} else {
let peb = *self.read_virtual_mem::<PEB64>(proc, peb_addr)?;
if *(&peb as *const PEB64 as *const u8 as *const u64).add(3) != peb.Ldr {
return Err(crate::error::CustomError::InvalidStructure)?;
}
peb.Ldr as u64
};
if ldr_addr == 0 {
return Err(crate::error::CustomError::LDRUninit)?;
}
crate::debug!("Ldr Address is {:x}.", ldr_addr);
}
let mut modlist_addr;
{
#[allow(non_camel_case_types)]
type PEB_LDR_DATA32 = ntapi::ntwow64::PEB_LDR_DATA32;
#[allow(non_camel_case_types)]
type PEB_LDR_DATA64 = types::PEB_LDR_DATA64;
modlist_addr = if pid_under_wow {
let ldr = *self.read_virtual_mem::<PEB_LDR_DATA32>(proc, ldr_addr)?;
ldr.InLoadOrderModuleList.Flink as u64
} else {
let ldr = *self.read_virtual_mem::<PEB_LDR_DATA64>(proc, ldr_addr)?;
ldr.InLoadOrderModuleList.Flink as u64
};
crate::debug!("Ldr InLoadOrderModuleList Address is {:x}", modlist_addr);
}
let first_modlist_addr = modlist_addr;
loop {
#[allow(non_camel_case_types)]
type LDR_DATA_TABLE_ENTRY32 = ntapi::ntwow64::LDR_DATA_TABLE_ENTRY32;
#[allow(non_camel_case_types)]
type LDR_DATA_TABLE_ENTRY64 = types::LDR_DATA_TABLE_ENTRY64;
let ldr_entry_data: Wrap<LDR_DATA_TABLE_ENTRY32, LDR_DATA_TABLE_ENTRY64>;
if pid_under_wow {
let entry = *self.read_virtual_mem(proc, modlist_addr as u64)?;
crate::debug!("Read the LDR_DATA_Table {:#x}", modlist_addr);
ldr_entry_data = Wrap::T32(entry);
} else {
let entry = *self.read_virtual_mem(proc, modlist_addr as u64)?;
crate::debug!("Read the LDR_DATA_Table {:#x}", modlist_addr);
ldr_entry_data = Wrap::T64(entry);
}
{
let dll_win_string_length;
let dll_win_string_buffer;
match ldr_entry_data {
Wrap::T32(ldr_entry) => {
dll_win_string_buffer = ldr_entry.FullDllName.Buffer as u64;
dll_win_string_length = ldr_entry.FullDllName.Length;
modlist_addr = ldr_entry.InLoadOrderLinks.Flink as u64;
}
Wrap::T64(ldr_entry) => {
dll_win_string_buffer = ldr_entry.FullDllName.Buffer as u64;
dll_win_string_length = ldr_entry.FullDllName.Length;
modlist_addr = ldr_entry.InLoadOrderLinks.Flink as u64
}
};
if modlist_addr == first_modlist_addr {
let s = crate::error::CustomError::ModuleListLoop;
crate::warn!("{}", s);
return Err(s)?;
}
let dll_path_buf = self.read_virtual_mem_fn(
proc,
dll_win_string_buffer,
(dll_win_string_length) as u32,
)?;
let dll_path_old = dll_path_buf.as_slice();
let dll_path = core::slice::from_raw_parts(
dll_path_old.as_ptr() as *const u16,
dll_path_old.len() / 2,
);
let addr = match ldr_entry_data {
Wrap::T32(v) => v.DllBase as u64,
pelite::Wrap::T64(v) => v.DllBase as u64,
};
match str_from_wide_str(dll_path) {
Ok(v) => {
crate::debug!("dll_name is {},{:x}", v, addr);
}
Err(e) => {
crate::debug!("dll_name could not be printed. os_string is {:?}", e);
}
}
if let Some(val) = predicate(ldr_entry_data, dll_path) {
return Ok(val);
}
}
}
}
pub(in super::super) unsafe fn read_virtual_mem<T>(
&self,
proc: &Process,
addr: u64,
) -> Result<*mut T> {
let v = self.read_virtual_mem_fn(proc, addr, core::mem::size_of::<T>() as u32)?;
Ok(v.leak().as_ptr() as *mut T)
}
pub(in super::super) unsafe fn read_virtual_mem_fn(
&self,
proc: &Process,
addr: u64,
size: u32,
) -> Result<Vec<u8>> {
proc.err_pseudo_handle()?;
if !proc.has_perm(PROCESS_VM_READ) {
return Err(crate::error::CustomError::PermissionDenied.into());
}
static FNS: once_cell::race::OnceBox<FnNtdllWOW> = once_cell::race::OnceBox::new();
let fns: Result<&FnNtdllWOW, crate::error::Error> = FNS.get_or_try_init(|| {
Ok(Box::new(FnNtdllWOW::new(
b"NtReadVirtualMemory\0",
b"NtWow64ReadVirtualMemory64\0",
)?))
});
let fns = fns?;
let mut buf: Vec<u8> = Vec::with_capacity(size as usize);
crate::trace!("reading at address {:x?} {} bytes", addr, size);
let mut i: u64 = 0;
let func = if proc.is_under_wow()? {
fns.get_fn()?
} else {
fns.get_wow64()?
};
let status = match func {
NtdllFn::Normal(v) => {
let cfn: types::FnNtReadVirtualMemory = core::mem::transmute(v);
cfn(
proc.get_proc(),
addr as PVOID,
buf.as_mut_ptr() as PVOID,
size as u32,
&mut i as *mut u64 as *mut u32,
)
}
#[cfg(target_pointer_width = "32")]
NtdllFn::WOW(v) => {
let cfn: types::FnNtWOW64ReadVirtualMemory64 = core::mem::transmute(v);
cfn(
proc.get_proc(),
addr,
buf.as_mut_ptr() as PVOID,
size as u64,
&mut i as *mut u64,
)
}
};
let status = crate::error::Ntdll::new(status);
crate::trace!("rvm {},{}/{}", status, i, size);
if status.is_warning() || status.is_error() {
return Err(status.into());
}
assert!(
i <= size as u64,
"{:#x} bytes were read into a {:#x} byte buffer.",
i,
size
);
buf.set_len(i as usize);
buf.shrink_to_fit();
Ok(buf)
}
unsafe fn query_process_information<T>(
&self,
proc: &Process,
pic: ntapi::ntpsapi::PROCESSINFOCLASS,
) -> Result<T>
where
T: Copy,
{
let size = core::mem::size_of::<T>();
let r = self.query_process_information_raw(proc, pic, size)?;
assert!(
r.len() >= core::mem::size_of::<T>(),
"Not enough bytes, to construct T"
);
let mut t: T = MaybeUninit::zeroed().assume_init();
core::ptr::copy_nonoverlapping(
r.as_ptr(),
&mut t as *mut T as *mut u8,
core::cmp::min(r.len(), size),
);
Ok(t)
}
unsafe fn query_process_information_raw(
&self,
proc: &Process,
pic: ntapi::ntpsapi::PROCESSINFOCLASS,
size: usize,
) -> Result<Vec<u8>> {
proc.err_pseudo_handle()?;
if !proc.has_perm(PROCESS_QUERY_INFORMATION)
&& !proc.has_perm(PROCESS_QUERY_LIMITED_INFORMATION)
{
return Err(crate::error::CustomError::PermissionDenied.into());
}
type FnNtQueryInformationProcess = extern "stdcall" fn(
HANDLE,
ntapi::ntpsapi::PROCESSINFOCLASS,
PVOID,
ULONG,
PULONG,
) -> NTSTATUS;
let cfn = {
static FNS: once_cell::race::OnceBox<FnNtdllWOW> = once_cell::race::OnceBox::new();
let fns: Result<&FnNtdllWOW, crate::error::Error> = FNS.get_or_try_init(|| {
Ok(Box::new(FnNtdllWOW::new(
b"NtQueryInformationProcess\0",
b"NtWow64QueryInformationProcess64\0",
)?))
});
let fns = fns?;
let cfn =
if super::process::Process::self_proc().is_under_wow()? && !proc.is_under_wow()? {
crate::trace!("Trying to get wow64 fn");
fns.get_wow64()
} else {
crate::trace!("Trying to get regular fn");
fns.get_fn()
}?
.take();
let cfn: FnNtQueryInformationProcess = core::mem::transmute(cfn);
cfn
};
let mut i = 0u32;
let i_ptr = &mut i as *mut u32;
let mut buf: Vec<u8> = Vec::with_capacity(size);
buf.resize(size, 0);
crate::trace!("Running NtQueryInformationProcess with fnptr:{:x?} proc:{:x?},pic:{:x}. Size is {}, buf is {:x?}",cfn as usize,proc.get_proc(),pic,size, buf);
unsafe extern "system" fn call(
proc: HANDLE,
pic: PROCESSINFOCLASS,
buf: PVOID,
size: u32,
i: *mut u32,
fnc: FnNtQueryInformationProcess,
) -> NTSTATUS {
fnc(proc, pic, buf, size, i)
}
let status = crate::error::Ntdll::new(call(
proc.get_proc(),
pic,
buf.as_mut_ptr() as PVOID,
size as u32,
i_ptr,
cfn,
));
if status.is_error() || status.is_warning() {
return Err(status.into());
}
assert!(i<= size as u32, "Detected buffer overflow. Stopping here, due to unknown or possibly malicious side-effects.");
if i == 0 && size != 0 {
return Err(crate::error::CustomError::ZeroBytes)?;
}
//Truncate vec, to only use initialized memory.
// buf.set_len(i as usize);
{
while buf.len() < i as usize {
//We do not need to check for none here, since it is guaranteed that: buf.len()=>i
assert!(buf.pop().is_some(),"Buffer was shorter than be expected. earlier asserts should have assured this?");
}
}
buf.shrink_to_fit();
crate::trace!(
"qip {:x},0x{:x}/0x{:x} buf is {:x?}",
status.get_status(),
i,
size,
buf
);
Ok(buf)
}
}
///Helps to differentiate between two function types
enum NtdllFn<T> {
#[cfg(target_pointer_width = "32")]
WOW(T),
Normal(T),
}
impl<T> NtdllFn<T> {
///Destroys the enum, and gives the contained data
pub fn take(self) -> T {
match self {
#[cfg(target_pointer_width = "32")]
NtdllFn::WOW(t) => t,
NtdllFn::Normal(t) => t,
}
}
}
///This holds an abstraction, for functions that are twice inside of NTDLL
///Once for regular interfacing, and a second for specifically querying 64-bit info from inside WOW
struct FnNtdllWOW<'a, 'b, 'c> {
ntdll: &'c NTDLL,
#[cfg(target_pointer_width = "32")]
wow64name: &'b [u8],
#[cfg(target_pointer_width = "64")]
_phantom: core::marker::PhantomData<&'b [u8]>,
name: &'a [u8],
#[cfg(target_pointer_width = "32")]
wowfn: core::sync::atomic::AtomicPtr<core::ffi::c_void>,
namefn: core::sync::atomic::AtomicPtr<core::ffi::c_void>,
}
impl<'a, 'b, 'c> FnNtdllWOW<'a, 'b, 'c> {
///Constructs D
pub(self) fn new(
name: &'a [u8],
#[cfg_attr(target_pointer_width = "64", allow(unused))] wow64name: &'b [u8],
) -> Result<Self> {
Ok(FnNtdllWOW {
ntdll: NTDLL::new()?,
#[cfg(target_pointer_width = "32")]
wow64name,
#[cfg(target_pointer_width = "64")]
_phantom: core::marker::PhantomData::default(),
name,
#[cfg(target_pointer_width = "32")]
wowfn: core::sync::atomic::AtomicPtr::default(),
namefn: core::sync::atomic::AtomicPtr::default(),
})
}
///returns a function which is the wow64name function inside NTDLL, if we were compiled in 32-bit mode (so could be running under wow).
///If we are not running inside of WOW, this function will return the result of [get_fn].
pub(self) unsafe fn get_wow64(&self) -> Result<NtdllFn<*mut core::ffi::c_void>> {
#[cfg(target_pointer_width = "64")]
{
self.get_fn()
}
#[cfg(target_pointer_width = "32")]
{
crate::trace!("wow64 fn");
let mut name = self.wowfn.load(Ordering::Acquire);
if name.is_null() {
let tmp = check_ptr!(GetProcAddress(
self.ntdll.handle as HMODULE,
self.wow64name.as_ptr() as *const i8
)) as *mut c_void;
self.wowfn.store(tmp, Ordering::Release);
name = tmp;
}
Ok(NtdllFn::WOW(name))
}
}
///returns a function which is the self.name function inside NTDLL
pub(self) unsafe fn get_fn(&self) -> Result<NtdllFn<*mut core::ffi::c_void>> {
crate::trace!("regular fn");
let mut name = self.namefn.load(Ordering::Acquire);
if name.is_null() {
let tmp = check_ptr!(GetProcAddress(
self.ntdll.handle as HMODULE,
self.name.as_ptr() as *const i8
)) as *mut c_void;
self.namefn.store(tmp, Ordering::Release);
name = tmp;
}
Ok(NtdllFn::Normal(name))
}
}
impl Drop for NTDLL {
///Decrement Ntdll module use counter.
///Also invalidates handle.
fn drop(&mut self) {
if unsafe { FreeLibrary(self.handle as HMODULE) } == 0 {
crate::warn!("Error whilst unloading NTDLL.dll. This is not actually that bad, since it is present in every Process anyways.");
err("FreeLibrary");
};
}
}
#[cfg(test)]
//Fixme: regression(stdless): Wierdness is happening again. Help?!?
pub mod test {
extern crate std;
use alloc::vec::Vec;
use std::prelude::rust_2021;
use super::NTDLL;
use crate::platforms::windows::ntdll::types;
use crate::Result;
use winapi::um::winnt::PROCESS_ALL_ACCESS;
#[test]
fn new() -> Result<()> {
let ntdll = NTDLL::new();
assert!(ntdll.is_ok(), "{}", ntdll.unwrap_err());
Ok(())
}
#[test]
fn get_module_in_proc() -> Result<()> {
let ntdll = NTDLL::new()?;
let test = |proc: &super::super::process::Process| unsafe {
ntdll.get_module_in_proc(
&proc,
super::predicate(
|x: pelite::Wrap<
ntapi::ntwow64::LDR_DATA_TABLE_ENTRY32,
super::types::LDR_DATA_TABLE_ENTRY64,
>| match x {
pelite::Wrap::T32(x) => x.DllBase as u64,
pelite::Wrap::T64(x) => x.DllBase as u64,
},
|x| {
super::super::cmp(crate::Data::Str("ntdll.dll"))(crate::Data::Str(
x.as_str(),
))
},
),
)
};
//test self real-handle
{
let proc = super::super::process::Process::new(std::process::id(), PROCESS_ALL_ACCESS)?;
test(&proc)?;
}
//test foreign
{
let (mut c, proc) = super::super::test::create_cmd();
let r = test(&proc);
c.kill().unwrap();
assert!(r.is_ok(), "other: {}", r.unwrap_err());
}
Ok(())
}
#[test]
fn no_find_get_module_in_proc() -> Result<()> {
let proc = super::super::process::Process::new(std::process::id(), PROCESS_ALL_ACCESS)?;
let ntdll = NTDLL::new()?;
let x = unsafe { ntdll.get_module_in_proc(&proc, |_, _| None::<()>) };
assert!(x.is_err(),"get_module_in_proc found something, eventhough it shouldn't have. We asked for NOTHING.");
assert_eq!(
x.unwrap_err(),
crate::error::Error::InjectLib(crate::error::CustomError::ModuleListLoop)
);
Ok(())
}
#[test]
fn read_memory() -> Result<()> {
//test read_mem on self
{
let buf: Vec<u8> = (0..255).collect();
let s = buf.as_slice();
let ntdll = NTDLL::new()?;
//Test self proc
{
let self_proc = super::super::process::Process::self_proc();
let re = unsafe {
ntdll.read_virtual_mem_fn(&self_proc, s.as_ptr() as u64, buf.len() as u32)
};
assert!(
re.is_err(),
"read_virtual_mem_fn does apparently not work without a real handle"
);
}
//test proc real handle self
{
let proc =
super::super::process::Process::new(std::process::id(), PROCESS_ALL_ACCESS)?;
let r = unsafe {
ntdll.read_virtual_mem_fn(&proc, s.as_ptr() as u64, buf.len() as u32)
}?;
assert_eq!(r, buf);
}
//test foreign process
{
let (mut c, proc) = super::super::test::create_cmd();
let mut mem = super::super::mem::MemPage::new(&proc, buf.len(), false)?;
let s = mem.write(buf.as_slice())?;
let r = unsafe {
ntdll.read_virtual_mem_fn(&proc, mem.get_address() as u64, s as u32)
}?;
c.kill().unwrap();
assert_eq!(r, buf, "Foreign read failed");
}
}
Ok(())
}
#[test]
fn query_process_information_self() -> Result<()> {
simple_logger::init().ok();
let ntdll = super::NTDLL::new()?;
//test real handle self
{
let proc = super::super::process::Process::new(std::process::id(), PROCESS_ALL_ACCESS)?;
let r = unsafe {
ntdll.query_process_information::<ntapi::ntpsapi::PROCESS_BASIC_INFORMATION>(
&proc,
ntapi::ntpsapi::ProcessBasicInformation,
)
};
assert!(r.is_ok(), "self query process information failed");
}
//test foreign process
{
let (mut c, proc) = super::super::test::create_cmd();
let r = unsafe {
ntdll.query_process_information::<types::PROCESS_BASIC_INFORMATION_WOW64>(
&proc,
ntapi::ntpsapi::ProcessBasicInformation,
)
};
c.kill().unwrap();
assert!(
r.is_ok(),
"foreign query process information failed,{}",
r.unwrap_err()
);
}
//test pseudo handle self
{
let proc = super::super::process::Process::self_proc();
let r = unsafe {
ntdll.query_process_information::<ntapi::ntpsapi::PROCESS_BASIC_INFORMATION>(
&proc,
ntapi::ntpsapi::ProcessBasicInformation,
)
};
assert!(r.is_err(), "Pseudo-Handles do not work on ntdll?");
let r = unsafe { r.unwrap_err_unchecked() }; //Safety is checked above.
assert_eq!(
r,
crate::error::Error::InjectLib(crate::error::CustomError::InvalidInput)
);
}
Ok(())
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_size() {
assert_eq!(
core::mem::size_of::<ntapi::ntpsapi::PROCESS_BASIC_INFORMATION>(),
core::mem::size_of::<types::PROCESS_BASIC_INFORMATION_WOW64>()
)
}
}