secmem-proc 0.3.0

Process hardening through system APIs
Documentation
//! Helper functions for interfacing with the windows specific Win32 API, mainly
//! the security base API.
//!
//! Note that Win32 API is not only for 32 bit machines, but also *the* C API of
//! 64 bit windows.

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 {
    // import functions
    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,
    };
    #[cfg(feature = "unstable")]
    pub(super) use windows::Win32::System::Threading::NtSetInformationThread;
    pub(super) use windows::Win32::System::Threading::{
        GetCurrentProcess, GetCurrentThread, OpenProcessToken,
    };

    // import structures
    pub(super) use windows::Win32::Foundation::{BOOL, HANDLE, PSID};
    pub(super) use windows::Win32::Security::Authorization::SE_OBJECT_TYPE;
    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;

    // import constants
    pub(super) use windows::Win32::Security::{
        TokenUser, ACL_REVISION, DACL_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION,
    };

    // define constants
    #[cfg(feature = "unstable")]
    mod unstable {
        use windows::Win32::System::Threading::THREADINFOCLASS;
        // SOURCE:
        // <https://anti-debug.checkpoint.com/techniques/interactive.html#ntsetinformationthread>
        pub(crate) const ThreadHideFromDebugger: THREADINFOCLASS = THREADINFOCLASS(0x11);
    }
    #[cfg(feature = "unstable")]
    pub(super) use unstable::*;
}

/// Process handle.
pub struct ProcessHandle(win::HANDLE);
/// Thread 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
    }
}

/// Creates pseudo-handle to the current process. Needs not be closed.
#[must_use]
pub fn get_process_handle() -> ProcessHandle {
    // calling `GetCurrentProcess` just returns a constant, is safe and cannot fail
    ProcessHandle(unsafe { win::GetCurrentProcess() })
}

/// Creates pseudo-handle to the current thread. Needs not be closed.
#[must_use]
pub fn get_thread_handle() -> ThreadHandle {
    // calling `GetCurrentThread` just returns a constant, is safe and cannot fail
    ThreadHandle(unsafe { win::GetCurrentThread() })
}

/// Checks whether the current process is being traced by a debugger.
#[must_use]
pub fn is_debugger_present() -> bool {
    // always safe to call
    unsafe { win::IsDebuggerPresent() }.as_bool()
}

/// Checks whether process `process_handle` is being traced by a debugger.
///
/// This can be used with a handle to the current process, in addition to
/// checking via [`is_debugger_present`] since they work via very different
/// mechanisms. Therefore it requires more effort for a debugger to work around
/// both techniques.
///
/// # Safety
/// `process_handle` must be a valid process handle
pub unsafe fn is_remote_debugger_present(process_handle: ProcessHandle) -> anyhow::Result<bool> {
    let mut debugger = win::BOOL(0);
    unsafe { win::CheckRemoteDebuggerPresent(process_handle, &mut debugger as *mut _) }
        .ok()
        .map_anyhow()?;
    Ok(debugger.as_bool())
}

/// Hides the thread `thread_handle` from an attached debugger.
///
/// # Safety
/// `thread_handle` must be a valid thread handle.
#[cfg(feature = "unstable")]
pub unsafe fn hide_thread_from_debugger(thread_handle: ThreadHandle) -> anyhow::Result<()> {
    unsafe {
        win::NtSetInformationThread(
            thread_handle,
            win::ThreadHideFromDebugger,
            core::ptr::null(),
            0,
        )
    }
    .map_anyhow()?;
    Ok(())
}

/// Checks whether a debugger is present by reading the `KdDebuggerEnabled`
/// field in the `KUSER_SHARED_DATA` shared kernel table.
///
/// # Compatibility
/// Requires Windows 2000 or later.
#[cfg(feature = "unstable")]
pub unsafe fn is_kernelflag_debugger_present() -> bool {
    let ptr: *mut u8 = 0x7ffe02d4 as *mut u8;
    // SAFETY: this address points into a shared kernel part of the address space,
    // so this pointer can't alias any Rust known/managed memory
    (unsafe { ptr.read_volatile() }) != 0
}

/// Pointer to a SID.
#[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 win::PSID {
    fn from(ptr: &SidPtr) -> Self {
        ptr.0
    }
}

impl SidPtr {
    /// # Safety
    /// Not unsafe in itself to call because functions which take a `SidPtr` are
    /// unsafe, but the returned null-pointer must be used only when a
    /// function allows for a null `SidPtr`.
    #[must_use]
    fn null() -> Self {
        Self(win::PSID(core::ptr::null_mut()))
    }

    /// Checks whether `self` points to a valid SID.
    #[must_use]
    fn is_valid(&self) -> bool {
        if self.0.is_invalid() {
            return false;
        }
        // SAFETY: `IsValidSid` requires non-null `PSID`; this is handled by the
        // `is_invalid` check above
        unsafe { win::IsValidSid(self) }.as_bool()
    }

    /// Returns the length of the SID that `self` points to.
    ///
    /// # Safety
    /// Requires `self` to point to a valid SID.
    #[must_use]
    pub unsafe fn len(&self) -> u32 {
        debug_assert!(self.is_valid());
        unsafe { win::GetLengthSid(self) }
    }
}

/// A pointer to a SID valid for lifetime `'a`.
#[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> {
    /// Get the raw pointer to the SID, for FFI.
    #[must_use]
    fn as_ptr(&self) -> SidPtr {
        debug_assert!(self.is_valid());
        self.ptr
    }

    /// Cast SID pointer into SID reference.
    ///
    /// # Safety
    /// `ptr` must point to a valid SID for at least the lifetime `'a`.
    #[must_use]
    unsafe fn from_ptr(ptr: SidPtr) -> Self {
        debug_assert!(ptr.is_valid());
        Self {
            ptr,
            lifetime: core::marker::PhantomData,
        }
    }

    /// Returns the length of the SID that `self` points to.
    #[must_use]
    pub fn len(&self) -> u32 {
        // SAFETY: `SidRef` must always point to a valid SID
        unsafe { self.ptr.len() }
    }

    /// Checks whether `self` points to a valid SID.
    #[must_use]
    fn is_valid(&self) -> bool {
        self.ptr.is_valid()
    }
}

/// Handle to an access token.
pub struct AccessToken(win::HANDLE);

impl Drop for AccessToken {
    fn drop(&mut self) {
        // SAFETY: safe since `self.0` must be an open (access token) handle
        let res = unsafe { win::CloseHandle(self.0) };
        res.unwrap()
    }
}

impl AccessToken {
    /// Given a process handle, this function returns a pointer to the process
    /// token of this process.
    ///
    /// # Safety
    /// `handle` must be a valid handle to a process.
    pub unsafe fn open_process_token(
        handle: ProcessHandle,
        access: win::TOKEN_ACCESS_MASK,
    ) -> anyhow::Result<Self> {
        let mut token_handle = win::HANDLE(0);
        unsafe { win::OpenProcessToken(handle, access, &mut token_handle as *mut win::HANDLE) }
            .ok()
            .map_anyhow()?;
        Ok(AccessToken(token_handle))
    }

    /// Given a token handle, this function returns the token user structure.
    pub fn get_token_user(&self) -> anyhow::Result<TokenUserBox> {
        // Get the required length of the buffer
        let mut length: u32 = 0;
        unsafe {
            win::GetTokenInformation(self.0, win::TokenUser, None, 0, &mut length as *mut u32);
        }

        // Allocate buffer of this length
        // SAFETY: 4 is power of 2, size is always sufficiently small
        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,
        };

        // Write token user into the allocated buffer
        unsafe {
            win::GetTokenInformation(
                self.0,
                win::TokenUser,
                Some(ptr.cast::<c_void>()),
                length,
                &mut length as *mut u32,
            )
        }
        .ok()
        .map_anyhow()?;

        Ok(token_user)
    }
}

/// Heap allocated structure containing a token user.
pub struct TokenUserBox {
    ptr: NonNull<u8>,
    size: u32,
}

impl Drop for TokenUserBox {
    fn drop(&mut self) {
        // SAFETY: 4 is power of 2, size is always sufficiently small
        let layout = unsafe { Layout::from_size_align_unchecked(self.size as usize, 4) };
        unsafe { alloc::dealloc(self.ptr.as_ptr(), layout) };
    }
}

impl TokenUserBox {
    /// Given a token handle, this function returns the token user structure.
    pub fn from_token(token: &AccessToken) -> anyhow::Result<Self> {
        token.get_token_user()
    }

    /// Given a token user, this function returns a reference to the user SID.
    #[must_use]
    pub fn sid<'a>(&'a self) -> SidRef<'a> {
        let ptr: *mut win::TOKEN_USER = self.ptr.as_ptr().cast();
        // SAFETY: all functions creating `Self` guarantee that the buffer is
        // initialised with a `win::TOKEN_USER` structure at the start of the memory
        // range
        let tokenuser_ref: &'a win::TOKEN_USER = unsafe { ptr.as_mut().unwrap() };
        let sidptr = SidPtr(tokenuser_ref.User.Sid);
        // SAFETY: `sidptr` points to a SID in the `TokenUserBox` allocation, so the SID
        // pointer is valid at least for the borrow lifetime of `self`
        unsafe { SidRef::<'a>::from_ptr(sidptr) }
    }
}

/// Add access allowed ACE to the ACL pointed to by `acl`.
///
/// # Safety
/// - `acl` has to point to a valid ACL with write access
///
/// # Errors
/// Errors if
/// - the ACL pointed to by `acl` doesn't have enough space for the Ace
/// - `revision` is not a valid revision
/// - `access_mask` is not a valid access_mask
unsafe fn add_allowed_ace(
    acl: *mut win::ACL,
    revision: win::ACE_REVISION,
    access_mask: win::PROCESS_ACCESS_RIGHTS,
    sid: SidRef<'_>,
) -> anyhow::Result<()> {
    // SAFETY: uphold by caller
    unsafe { win::AddAccessAllowedAce(acl, revision, access_mask.0, sid.as_ptr()) }
        .ok()
        .map_anyhow()?;
    Ok(())
}

/// Add access denied ACE to the ACL pointed to by `acl`.
///
/// # Safety
/// - `acl` has to point to a valid ACL with write access
///
/// # Errors
/// Errors if
/// - the ACL pointed to by `acl` doesn't have enough space for the Ace
/// - `revision` is not a valid revision
/// - `access_mask` is not a valid access_mask
unsafe fn add_denied_ace(
    acl: *mut win::ACL,
    revision: win::ACE_REVISION,
    access_mask: win::PROCESS_ACCESS_RIGHTS,
    sid: SidRef<'_>,
) -> anyhow::Result<()> {
    // SAFETY: uphold by caller
    unsafe { win::AddAccessDeniedAce(acl, revision, access_mask.0, sid.as_ptr()) }
        .ok()
        .map_anyhow()?;
    Ok(())
}

/// # Safety
/// - `acl` must be valid for a `acl_len` byte write, 4 byte aligned
/// - `acl_len` must be a multiple of 4
unsafe fn initialize_acl(
    acl: *mut win::ACL,
    acl_len: u32,
    revision: win::ACE_REVISION,
) -> anyhow::Result<()> {
    // SAFETY: uphold by caller
    unsafe { win::InitializeAcl(acl, acl_len, revision.0) }
        .ok()
        .map_anyhow()?;
    Ok(())
}

/// # Safety
/// - `handle` must point to a valid object of type `obj_type`
/// - `owner`, `group` must point to valid SIDs, or be null, depending on
///   `sec_info`
/// - `dacl` and `sacl` must point to valid ACLs, or be `None`, depending on
///   `sec_info`
///
/// See <https://docs.microsoft.com/en-us/windows/win32/api/aclapi/nf-aclapi-setsecurityinfo>
/// and <https://docs.microsoft.com/en-us/windows/win32/secauthz/security-information> for more
/// information.
unsafe fn set_security_info(
    handle: 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, obj_type, sec_info.0, owner, group, dacl, sacl) }
        .ok()
        .map_anyhow()?;
    Ok(())
}

/// Heap allocated ACL.
pub struct AclBox {
    // SAFETY INVARIANT: must be 4 byte (`u32`) aligned, valid for `size` byte write
    ptr: NonNull<win::ACL>,
    // SAFETY INVARIANT: must be a multiple of 4, immutable
    size: u32,
}

impl Drop for AclBox {
    fn drop(&mut self) {
        // SAFETY: align is 4 so power of 2; size is multiple of align so rounding
        // doesn't overflow
        let layout = unsafe { Layout::from_size_align_unchecked(self.size as usize, 4) };
        // SAFETY: `AclBox` can only be created through `Self::alloc` so `self.ptr`
        // points to a memory allocation of `self.size` bytes allocated through
        // the global allocator SAFETY: `layout` is identical to the one in
        // `Self::alloc` since `self.size` hasn't changed
        unsafe { alloc::dealloc(self.ptr.as_ptr().cast::<u8>(), layout) }
    }
}

impl AclBox {
    /// Create allocated and initialized empty ACL.
    ///
    /// # Safety
    /// `size` has to be non-zero and a multiple of 4.
    ///
    /// # Errors
    /// Errors when system is out of memory, or when `size` doesn't fit an empty
    /// ACL.
    pub unsafe fn new(size: u32) -> anyhow::Result<Self> {
        let mut allocation = unsafe { Self::alloc(size) }?;
        allocation.initialize()?;
        Ok(allocation)
    }

    /// Create uninitialized ACL of size `size`. This must be initialized before
    /// use.
    ///
    /// # Safety
    /// `size` has to be non-zero and a multiple of 4.
    /// Call `self.initialize()` before using the returned `Self`
    unsafe fn alloc(size: u32) -> anyhow::Result<Self> {
        debug_assert!(size % 4 == 0);
        debug_assert!(size != 0);

        // SAFETY: align is 4 so power of 2; size is multiple of align so rounding
        // doesn't overflow
        let layout = unsafe { Layout::from_size_align_unchecked(size as usize, 4) };
        // SAFETY: `layout` has non-zero size since `size != 0`
        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)),
        }
    }

    /// Initialize uninitialized ACL.
    fn initialize(&mut self) -> anyhow::Result<()> {
        // SAFETY: `self.ptr` is valid for `self.size` byte writes, both are 4 byte
        // aligned by struct safety invariants
        unsafe { initialize_acl(self.ptr.as_ptr(), self.size, win::ACL_REVISION) }
    }

    /// Add allowed ACE to the ACL. `access_mask` must be a valid access mask.
    ///
    /// # Safety
    /// - `self` must be large enough to add this ACE.
    pub unsafe fn add_allowed_ace(
        &mut self,
        access_mask: win::PROCESS_ACCESS_RIGHTS,
        sid: SidRef<'_>,
    ) -> anyhow::Result<()> {
        // SAFETY: `self.ptr` points to a valid ACL since it must have been created by
        // `Self::new` which properly initializes the ACL
        unsafe { add_allowed_ace(self.ptr.as_ptr(), win::ACL_REVISION, access_mask, sid) }
    }

    /// Add denied ACE to the ACL. `access_mask` must be a valid access mask.
    ///
    /// # Safety
    /// - `self` must be large enough to add this ACE.
    pub unsafe fn add_denied_ace(
        &mut self,
        access_mask: win::PROCESS_ACCESS_RIGHTS,
        sid: SidRef<'_>,
    ) -> anyhow::Result<()> {
        // SAFETY: `self.ptr` points to a valid ACL since it must have been created by
        // `Self::new` which properly initializes the ACL
        unsafe { add_denied_ace(self.ptr.as_ptr(), win::ACL_REVISION, access_mask, sid) }
    }

    /// Set this DACL to the object pointed to by `handle` of type `obj_type`.
    /// The DACL is set protected, meaning it doesn't inherit ACEs.
    ///
    /// # Safety
    /// `handle` must point to a valid object of type `obj_type`.
    pub unsafe fn set_protected(
        &self,
        handle: impl Into<win::HANDLE>,
        obj_type: win::SE_OBJECT_TYPE,
    ) -> anyhow::Result<()> {
        // change only DACL, do not inherit ACEs
        let sec_info: win::OBJECT_SECURITY_INFORMATION =
            win::DACL_SECURITY_INFORMATION | win::PROTECTED_DACL_SECURITY_INFORMATION;
        // SAFETY: the `SidPtr`s and (last) SACL pointer can be null since `sec_info`
        // states that we only modify the DACL
        // SAFETY: `handle` validity is uphold by the caller
        unsafe {
            set_security_info(
                handle.into(),
                obj_type,
                sec_info,
                SidPtr::null(),
                SidPtr::null(),
                Some(self.ptr.as_ptr()),
                None,
            )
        }
    }
}

/// Helper type to compute the size necessary for an ACL object, aiding creation
/// of an [`AclBox`].
///
/// # Panics
/// Associated methods panic when the size wraps around a `u32`. This should
/// never happen as having an ACL of that size is not realistic (and also such
/// large ACLs cannot be created in Windows).
#[derive(Clone, Copy, Debug)]
pub struct AclSize(u32);

impl AclSize {
    /// Returns the size as a `u32`.
    ///
    /// # Panics
    /// Panics when rounding the size up to a multiple of 4 wraps a `u32`.
    #[must_use]
    pub fn get_size(self) -> u32 {
        // round to a multiple of 4
        self.0.checked_add(3).unwrap() & !3
    }

    /// Allocate an (empty, but initialised) [`AclBox`] with this size.
    pub fn allocate(self) -> anyhow::Result<AclBox> {
        // SAFETY: `self.get_size()` is non-zero and a multiple of 4
        // SAFETY: `self.get_size()` large enough to hold an empty ACL, as `Self::new`
        // enforces this and is the only constructor, and wrapping always panics
        unsafe { AclBox::new(self.get_size()) }
    }

    /// Create [`AclSize`] for an empty ACL.
    #[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)
    }

    /// Add access allowed ace (size). `sid_size` should be the size of the used
    /// sid.
    ///
    /// # Panics
    /// Panics when adding the ACE size wraps.
    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;

        // add size of ACE minus the sidstart field (u32 -> 4 bytes)
        self.0 = self.0.checked_add(ace_header_size - 4).unwrap();
        // add size of sid
        self.0 = self.0.checked_add(sid_size).unwrap();
    }

    /// Add access denied ace (size). `sid_size` should be the size of the used
    /// sid.
    ///
    /// # Panics
    /// Panics when adding the ACE size wraps.
    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;

        // add size of ACE minus the sidstart field (u32 -> 4 bytes)
        self.0 = self.0.checked_add(ace_header_size - 4).unwrap();
        // add size of sid
        self.0 = self.0.checked_add(sid_size).unwrap();
    }
}

impl Default for AclSize {
    #[must_use]
    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");
        // SAFETY: `get_process_handle()` yields a valid handle to this process
        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();
        // It seems the SID remains valid after closing the process token
        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");
    }
}