use crate::error::private::{alloc_err_from_size_align, ResultExt};
use alloc::alloc;
use core::alloc::Layout;
use core::ffi::c_void;
use core::ptr::NonNull;
#[allow(non_upper_case_globals)]
mod win {
#[cfg(feature = "unstable")]
pub(super) use windows::Wdk::System::Threading::NtSetInformationThread;
pub(super) use windows::Win32::Foundation::CloseHandle;
pub(super) use windows::Win32::Security::Authorization::SetSecurityInfo;
pub(super) use windows::Win32::Security::{
AddAccessAllowedAce, AddAccessDeniedAce, GetLengthSid, GetTokenInformation, InitializeAcl,
IsValidSid,
};
pub(super) use windows::Win32::System::Diagnostics::Debug::{
CheckRemoteDebuggerPresent, IsDebuggerPresent,
};
pub(super) use windows::Win32::System::Threading::{
GetCurrentProcess, GetCurrentThread, OpenProcessToken,
};
pub(super) use windows::core::BOOL;
pub(super) use windows::Win32::Foundation::HANDLE;
pub(super) use windows::Win32::Security::Authorization::SE_OBJECT_TYPE;
pub(super) use windows::Win32::Security::PSID;
pub(super) use windows::Win32::Security::{
ACCESS_ALLOWED_ACE, ACCESS_DENIED_ACE, ACE_REVISION, ACL, OBJECT_SECURITY_INFORMATION,
TOKEN_ACCESS_MASK, TOKEN_USER,
};
pub(super) use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
#[cfg(feature = "unstable")]
pub(super) use windows::Wdk::System::Threading::ThreadHideFromDebugger;
pub(super) use windows::Win32::Security::{
TokenUser, ACL_REVISION, DACL_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION,
};
}
pub struct ProcessHandle(win::HANDLE);
pub struct ThreadHandle(win::HANDLE);
impl From<ProcessHandle> for win::HANDLE {
fn from(handle: ProcessHandle) -> Self {
handle.0
}
}
impl From<ThreadHandle> for win::HANDLE {
fn from(handle: ThreadHandle) -> Self {
handle.0
}
}
#[must_use]
pub fn get_process_handle() -> ProcessHandle {
ProcessHandle(unsafe { win::GetCurrentProcess() })
}
#[must_use]
pub fn get_thread_handle() -> ThreadHandle {
ThreadHandle(unsafe { win::GetCurrentThread() })
}
#[must_use]
pub fn is_debugger_present() -> bool {
unsafe { win::IsDebuggerPresent() }.as_bool()
}
pub unsafe fn is_remote_debugger_present(process_handle: ProcessHandle) -> anyhow::Result<bool> {
let mut debugger = win::BOOL(0);
unsafe { win::CheckRemoteDebuggerPresent(process_handle.into(), &mut debugger as *mut _) }
.map_anyhow()?;
Ok(debugger.as_bool())
}
#[cfg(feature = "unstable")]
pub unsafe fn hide_thread_from_debugger(thread_handle: ThreadHandle) -> anyhow::Result<()> {
unsafe {
win::NtSetInformationThread(
thread_handle.into(),
win::ThreadHideFromDebugger,
core::ptr::null(),
0,
)
}
.ok()
.map_anyhow()?;
Ok(())
}
#[cfg(feature = "unstable")]
pub unsafe fn is_kernelflag_debugger_present() -> bool {
let ptr: *mut u8 = 0x7ffe02d4 as *mut u8;
(unsafe { ptr.read_volatile() }) != 0
}
#[derive(Copy, Clone, Debug)]
pub struct SidPtr(win::PSID);
impl From<SidPtr> for win::PSID {
fn from(ptr: SidPtr) -> Self {
ptr.0
}
}
impl From<SidPtr> for Option<win::PSID> {
fn from(ptr: SidPtr) -> Self {
if ptr.0 .0.is_null() {
None
} else {
Some(ptr.0)
}
}
}
impl SidPtr {
#[must_use]
fn null() -> Self {
Self(win::PSID(core::ptr::null_mut()))
}
#[must_use]
fn is_valid(self) -> bool {
if self.0.is_invalid() {
return false;
}
unsafe { win::IsValidSid(self.into()) }.as_bool()
}
#[must_use]
pub unsafe fn len(self) -> u32 {
debug_assert!(self.is_valid());
unsafe { win::GetLengthSid(self.into()) }
}
}
#[allow(clippy::len_without_is_empty)]
#[derive(Copy, Clone, Debug)]
pub struct SidRef<'a> {
ptr: SidPtr,
lifetime: core::marker::PhantomData<&'a [u8]>,
}
impl<'a> SidRef<'a> {
#[must_use]
fn as_ptr(&self) -> SidPtr {
debug_assert!(self.is_valid());
self.ptr
}
#[must_use]
unsafe fn from_ptr(ptr: SidPtr) -> Self {
debug_assert!(ptr.is_valid());
Self {
ptr,
lifetime: core::marker::PhantomData,
}
}
#[must_use]
pub fn len(&self) -> u32 {
unsafe { self.ptr.len() }
}
#[must_use]
fn is_valid(&self) -> bool {
self.ptr.is_valid()
}
}
pub struct AccessToken(win::HANDLE);
impl Drop for AccessToken {
fn drop(&mut self) {
let res = unsafe { win::CloseHandle(self.0) };
res.unwrap()
}
}
impl AccessToken {
pub unsafe fn open_process_token(
handle: ProcessHandle,
access: win::TOKEN_ACCESS_MASK,
) -> anyhow::Result<Self> {
let mut token_handle = win::HANDLE(core::ptr::null_mut());
unsafe {
win::OpenProcessToken(handle.into(), access, &mut token_handle as *mut win::HANDLE)
}
.map_anyhow()?;
Ok(AccessToken(token_handle))
}
pub fn get_token_user(&self) -> anyhow::Result<TokenUserBox> {
let mut length: u32 = 0;
let _ = unsafe {
win::GetTokenInformation(self.0, win::TokenUser, None, 0, &mut length as *mut u32)
};
let layout = unsafe { Layout::from_size_align_unchecked(length as usize, 4) };
let ptr = unsafe { alloc::alloc(layout) };
let buf_ptr = match NonNull::new(ptr) {
Some(ptr) => ptr,
None => return Err(alloc_err_from_size_align(length as usize, 4)),
};
let token_user = TokenUserBox {
ptr: buf_ptr,
size: length,
};
unsafe {
win::GetTokenInformation(
self.0,
win::TokenUser,
Some(ptr.cast::<c_void>()),
length,
&mut length as *mut u32,
)
}
.map_anyhow()?;
Ok(token_user)
}
}
pub struct TokenUserBox {
ptr: NonNull<u8>,
size: u32,
}
impl Drop for TokenUserBox {
fn drop(&mut self) {
let layout = unsafe { Layout::from_size_align_unchecked(self.size as usize, 4) };
unsafe { alloc::dealloc(self.ptr.as_ptr(), layout) };
}
}
impl TokenUserBox {
pub fn from_token(token: &AccessToken) -> anyhow::Result<Self> {
token.get_token_user()
}
#[must_use]
pub fn sid<'a>(&'a self) -> SidRef<'a> {
let ptr: *mut win::TOKEN_USER = self.ptr.as_ptr().cast();
let tokenuser_ref: &'a win::TOKEN_USER = unsafe { ptr.as_mut().unwrap() };
let sidptr = SidPtr(tokenuser_ref.User.Sid);
unsafe { SidRef::<'a>::from_ptr(sidptr) }
}
}
unsafe fn add_allowed_ace(
acl: *mut win::ACL,
revision: win::ACE_REVISION,
access_mask: win::PROCESS_ACCESS_RIGHTS,
sid: SidRef<'_>,
) -> anyhow::Result<()> {
unsafe { win::AddAccessAllowedAce(acl, revision, access_mask.0, sid.as_ptr().into()) }
.map_anyhow()?;
Ok(())
}
unsafe fn add_denied_ace(
acl: *mut win::ACL,
revision: win::ACE_REVISION,
access_mask: win::PROCESS_ACCESS_RIGHTS,
sid: SidRef<'_>,
) -> anyhow::Result<()> {
unsafe { win::AddAccessDeniedAce(acl, revision, access_mask.0, sid.as_ptr().into()) }
.map_anyhow()?;
Ok(())
}
unsafe fn initialize_acl(
acl: *mut win::ACL,
acl_len: u32,
revision: win::ACE_REVISION,
) -> anyhow::Result<()> {
unsafe { win::InitializeAcl(acl, acl_len, revision) }.map_anyhow()?;
Ok(())
}
unsafe fn set_security_info(
handle: impl Into<win::HANDLE>,
obj_type: win::SE_OBJECT_TYPE,
sec_info: win::OBJECT_SECURITY_INFORMATION,
owner: SidPtr,
group: SidPtr,
dacl: Option<*const win::ACL>,
sacl: Option<*const win::ACL>,
) -> anyhow::Result<()> {
unsafe {
win::SetSecurityInfo(
handle.into(),
obj_type,
sec_info,
owner.into(),
group.into(),
dacl,
sacl,
)
}
.ok()
.map_anyhow()?;
Ok(())
}
pub struct AclBox {
ptr: NonNull<win::ACL>,
size: u32,
}
impl Drop for AclBox {
fn drop(&mut self) {
let layout = unsafe { Layout::from_size_align_unchecked(self.size as usize, 4) };
unsafe { alloc::dealloc(self.ptr.as_ptr().cast::<u8>(), layout) }
}
}
impl AclBox {
pub unsafe fn new(size: u32) -> anyhow::Result<Self> {
let mut allocation = unsafe { Self::alloc(size) }?;
allocation.initialize()?;
Ok(allocation)
}
unsafe fn alloc(size: u32) -> anyhow::Result<Self> {
debug_assert!(size % 4 == 0);
debug_assert!(size != 0);
let layout = unsafe { Layout::from_size_align_unchecked(size as usize, 4) };
let ptr = unsafe { alloc::alloc(layout) }.cast::<win::ACL>();
match NonNull::new(ptr) {
Some(ptr) => Ok(Self { ptr, size }),
None => Err(alloc_err_from_size_align(size as usize, 4)),
}
}
fn initialize(&mut self) -> anyhow::Result<()> {
unsafe { initialize_acl(self.ptr.as_ptr(), self.size, win::ACL_REVISION) }
}
pub unsafe fn add_allowed_ace(
&mut self,
access_mask: win::PROCESS_ACCESS_RIGHTS,
sid: SidRef<'_>,
) -> anyhow::Result<()> {
unsafe { add_allowed_ace(self.ptr.as_ptr(), win::ACL_REVISION, access_mask, sid) }
}
pub unsafe fn add_denied_ace(
&mut self,
access_mask: win::PROCESS_ACCESS_RIGHTS,
sid: SidRef<'_>,
) -> anyhow::Result<()> {
unsafe { add_denied_ace(self.ptr.as_ptr(), win::ACL_REVISION, access_mask, sid) }
}
pub unsafe fn set_protected(
&self,
handle: impl Into<win::HANDLE>,
obj_type: win::SE_OBJECT_TYPE,
) -> anyhow::Result<()> {
let sec_info: win::OBJECT_SECURITY_INFORMATION =
win::DACL_SECURITY_INFORMATION | win::PROTECTED_DACL_SECURITY_INFORMATION;
unsafe {
set_security_info(
handle,
obj_type,
sec_info,
SidPtr::null(),
SidPtr::null(),
Some(self.ptr.as_ptr()),
None,
)
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct AclSize(u32);
impl AclSize {
#[must_use]
pub fn get_size(self) -> u32 {
self.0.checked_add(3).unwrap() & !3
}
pub fn allocate(self) -> anyhow::Result<AclBox> {
unsafe { AclBox::new(self.get_size()) }
}
#[must_use]
pub fn new() -> Self {
#[allow(clippy::cast_possible_truncation)]
let empty_size = core::mem::size_of::<win::ACL>() as u32;
Self(empty_size)
}
pub fn add_allowed_ace(&mut self, sid_size: u32) {
#[allow(clippy::cast_possible_truncation)]
let ace_header_size = core::mem::size_of::<win::ACCESS_ALLOWED_ACE>() as u32;
self.0 = self.0.checked_add(ace_header_size - 4).unwrap();
self.0 = self.0.checked_add(sid_size).unwrap();
}
pub fn add_denied_ace(&mut self, sid_size: u32) {
#[allow(clippy::cast_possible_truncation)]
let ace_header_size = core::mem::size_of::<win::ACCESS_DENIED_ACE>() as u32;
self.0 = self.0.checked_add(ace_header_size - 4).unwrap();
self.0 = self.0.checked_add(sid_size).unwrap();
}
}
impl Default for AclSize {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use windows::Win32::Security::Authorization::SE_KERNEL_OBJECT;
use windows::Win32::Security::TOKEN_QUERY;
use windows::Win32::System::Threading::{
PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE,
};
#[test]
fn test_create_drop_aclbox() {
let size = AclSize::new();
let _acl: AclBox = size.allocate().expect("could not create ACL");
}
#[test]
fn test_set_empty_acl() {
let size = AclSize::new();
let acl: AclBox = size.allocate().expect("could not create ACL");
unsafe { acl.set_protected(get_process_handle(), SE_KERNEL_OBJECT) }
.expect("could not set ACL");
}
#[test]
fn test_open_process_token() {
let _process_tok =
unsafe { AccessToken::open_process_token(get_process_handle(), TOKEN_QUERY) }
.expect("could not open process token");
}
#[test]
fn test_get_process_user_sid() {
let process_tok =
unsafe { AccessToken::open_process_token(get_process_handle(), TOKEN_QUERY) }
.expect("could not open process token");
let tok_user = process_tok
.get_token_user()
.expect("could not retrieve token user");
let sid = tok_user.sid();
core::mem::drop(process_tok);
assert!(sid.is_valid());
}
#[test]
fn test_aclbox_allowed_ace() {
let process_tok =
unsafe { AccessToken::open_process_token(get_process_handle(), TOKEN_QUERY) }
.expect("could not open process token");
let tok_user = process_tok
.get_token_user()
.expect("could not retrieve token user");
let sid = tok_user.sid();
assert!(sid.is_valid());
let mut size = AclSize::new();
size.add_allowed_ace(sid.len());
let mut acl: AclBox = size.allocate().expect("could not create ACL");
unsafe {
acl.add_allowed_ace(
PROCESS_SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE,
sid,
)
}
.expect("could not add ACE to ACL");
}
}