#![allow(unsafe_op_in_unsafe_fn)]
use anyhow::{Result, anyhow};
use std::ffi::c_void;
use std::ptr;
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, HLOCAL, LUID, LocalFree};
use windows_sys::Win32::Security::{
AdjustTokenPrivileges, CopySid,
CreateRestrictedToken, CreateWellKnownSid,
GetLengthSid, GetTokenInformation, LookupPrivilegeValueW,
SID_AND_ATTRIBUTES, SetTokenInformation,
TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_PRIVILEGES,
TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_PRIVILEGES, TOKEN_QUERY,
TokenDefaultDacl, TokenGroups,
};
use windows_sys::Win32::Security::Authorization::{
ConvertStringSidToSidW, EXPLICIT_ACCESS_W, GRANT_ACCESS, SetEntriesInAclW,
TRUSTEE_IS_SID, TRUSTEE_IS_UNKNOWN, TRUSTEE_W,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
const DISABLE_MAX_PRIVILEGE: u32 = 0x01;
const LUA_TOKEN: u32 = 0x04;
const WRITE_RESTRICTED: u32 = 0x08;
const SE_GROUP_LOGON_ID: u32 = 0xC0000000;
const SE_PRIVILEGE_ENABLED: u32 = 0x0000_0002;
const GENERIC_ALL: u32 = 0x1000_0000;
const WIN_WORLD_SID: i32 = 1;
pub struct LocalSid {
psid: *mut c_void,
}
impl LocalSid {
pub fn from_string(sid: &str) -> Result<Self> {
let wide: Vec<u16> = crate::winutil::to_wide(sid);
let mut psid: *mut c_void = ptr::null_mut();
let ok = unsafe { ConvertStringSidToSidW(wide.as_ptr(), &mut psid) };
if ok == 0 || psid.is_null() {
return Err(anyhow!("ConvertStringSidToSidW failed for: {sid}"));
}
Ok(Self { psid })
}
pub fn as_ptr(&self) -> *mut c_void {
self.psid
}
}
impl Drop for LocalSid {
fn drop(&mut self) {
if !self.psid.is_null() {
unsafe { LocalFree(self.psid as HLOCAL); }
}
}
}
unsafe impl Send for LocalSid {}
unsafe fn world_sid() -> Result<Vec<u8>> {
let mut size: u32 = 0;
CreateWellKnownSid(WIN_WORLD_SID, ptr::null_mut(), ptr::null_mut(), &mut size);
let mut buf: Vec<u8> = vec![0u8; size as usize];
let ok = CreateWellKnownSid(
WIN_WORLD_SID,
ptr::null_mut(),
buf.as_mut_ptr() as *mut c_void,
&mut size,
);
if ok == 0 {
return Err(anyhow!("CreateWellKnownSid failed: {}", GetLastError()));
}
Ok(buf)
}
unsafe fn get_current_token() -> Result<HANDLE> {
let desired = TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY
| TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_PRIVILEGES;
let mut h: HANDLE = 0;
let ok = OpenProcessToken(GetCurrentProcess(), desired, &mut h);
if ok == 0 {
return Err(anyhow!("OpenProcessToken failed: {}", GetLastError()));
}
Ok(h)
}
pub(crate) unsafe fn get_logon_sid_bytes(h_token: HANDLE) -> Option<Vec<u8>> {
let mut needed: u32 = 0;
GetTokenInformation(h_token, TokenGroups, ptr::null_mut(), 0, &mut needed);
if needed == 0 {
return None;
}
let mut buf: Vec<u8> = vec![0u8; needed as usize];
let ok = GetTokenInformation(
h_token,
TokenGroups,
buf.as_mut_ptr() as *mut c_void,
needed,
&mut needed,
);
if ok == 0 {
return None;
}
let group_count = ptr::read_unaligned(buf.as_ptr() as *const u32) as usize;
let groups_start = buf.as_ptr().add(std::mem::size_of::<u32>());
for i in 0..group_count {
let offset = i * std::mem::size_of::<SID_AND_ATTRIBUTES>();
let entry_ptr = groups_start.add(offset) as *const SID_AND_ATTRIBUTES;
let entry: SID_AND_ATTRIBUTES = ptr::read_unaligned(entry_ptr);
if (entry.Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID {
let sid_len = GetLengthSid(entry.Sid);
if sid_len == 0 {
continue;
}
let mut out = vec![0u8; sid_len as usize];
if CopySid(sid_len, out.as_mut_ptr() as *mut c_void, entry.Sid) == 0 {
continue;
}
return Some(out);
}
}
None }
unsafe fn set_default_dacl(h_token: HANDLE, sids: &[*mut c_void]) -> Result<()> {
if sids.is_empty() {
return Ok(());
}
let entries: Vec<EXPLICIT_ACCESS_W> = sids
.iter()
.map(|sid| EXPLICIT_ACCESS_W {
grfAccessPermissions: GENERIC_ALL,
grfAccessMode: GRANT_ACCESS,
grfInheritance: 0,
Trustee: TRUSTEE_W {
pMultipleTrustee: ptr::null_mut(),
MultipleTrusteeOperation: 0,
TrusteeForm: TRUSTEE_IS_SID,
TrusteeType: TRUSTEE_IS_UNKNOWN,
ptstrName: *sid as *mut u16,
},
})
.collect();
let mut p_new_dacl: *mut windows_sys::Win32::Security::ACL = ptr::null_mut();
let res = SetEntriesInAclW(
entries.len() as u32,
entries.as_ptr(),
ptr::null_mut(),
&mut p_new_dacl,
);
if res != 0 {
return Err(anyhow!("SetEntriesInAclW failed: {res}"));
}
#[repr(C)]
struct TokenDefaultDaclInfo {
default_dacl: *mut windows_sys::Win32::Security::ACL,
}
let mut info = TokenDefaultDaclInfo {
default_dacl: p_new_dacl,
};
let ok = SetTokenInformation(
h_token,
TokenDefaultDacl,
&mut info as *mut _ as *mut c_void,
std::mem::size_of::<TokenDefaultDaclInfo>() as u32,
);
if ok == 0 {
let err = GetLastError();
if !p_new_dacl.is_null() {
LocalFree(p_new_dacl as HLOCAL);
}
return Err(anyhow!("SetTokenInformation(TokenDefaultDacl) failed: {err}"));
}
if !p_new_dacl.is_null() {
LocalFree(p_new_dacl as HLOCAL);
}
Ok(())
}
unsafe fn enable_privilege(h_token: HANDLE, name: &str) -> Result<()> {
let mut luid = LUID { LowPart: 0, HighPart: 0 };
let wide_name: Vec<u16> = crate::winutil::to_wide(name);
let ok = LookupPrivilegeValueW(ptr::null(), wide_name.as_ptr(), &mut luid);
if ok == 0 {
return Err(anyhow!("LookupPrivilegeValueW failed: {}", GetLastError()));
}
let mut tp: TOKEN_PRIVILEGES = std::mem::zeroed();
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
let ok2 = AdjustTokenPrivileges(h_token, 0, &tp, 0, ptr::null_mut(), ptr::null_mut());
if ok2 == 0 {
return Err(anyhow!("AdjustTokenPrivileges failed: {}", GetLastError()));
}
Ok(())
}
unsafe fn create_token_inner(
cap_sids: &[*mut c_void],
blacklist_sid: Option<*mut c_void>,
) -> Result<HANDLE> {
let base = get_current_token()?;
let mut logon_sid_bytes = get_logon_sid_bytes(base);
let has_logon = logon_sid_bytes.is_some();
let psid_logon = logon_sid_bytes.as_mut().map_or(std::ptr::null_mut(), |b| b.as_mut_ptr() as *mut c_void);
let mut everyone = world_sid()?;
let psid_everyone = everyone.as_mut_ptr() as *mut c_void;
let logon_extra = if has_logon { 1 } else { 0 };
let bl_extra = if blacklist_sid.is_some() { 1 } else { 0 };
let total_entries = cap_sids.len() + 1 + logon_extra + bl_extra;
let mut entries: Vec<SID_AND_ATTRIBUTES> = vec![std::mem::zeroed(); total_entries];
let mut idx = 0;
for &psid in cap_sids {
entries[idx].Sid = psid;
idx += 1;
}
if has_logon {
entries[idx].Sid = psid_logon;
idx += 1;
}
entries[idx].Sid = psid_everyone;
idx += 1;
if let Some(bl_sid) = blacklist_sid {
entries[idx].Sid = bl_sid;
}
let mut new_token: HANDLE = 0;
let flags = DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED;
let ok = CreateRestrictedToken(
base,
flags,
0, ptr::null(),
0, ptr::null(),
entries.len() as u32,
entries.as_ptr(),
&mut new_token,
);
if ok == 0 {
let err = GetLastError();
CloseHandle(base);
return Err(anyhow!("CreateRestrictedToken failed: {err}"));
}
let mut dacl_sids: Vec<*mut c_void> = Vec::with_capacity(cap_sids.len() + 1 + logon_extra);
if has_logon {
dacl_sids.push(psid_logon);
}
dacl_sids.push(psid_everyone);
dacl_sids.extend_from_slice(cap_sids);
if let Err(e) = set_default_dacl(new_token, &dacl_sids) {
CloseHandle(new_token);
CloseHandle(base);
return Err(e);
}
if let Err(e) = enable_privilege(new_token, "SeChangeNotifyPrivilege") {
CloseHandle(new_token);
CloseHandle(base);
return Err(e);
}
CloseHandle(base);
Ok(new_token)
}
pub unsafe fn create_restricted_token(
cap_sids: &[*mut c_void],
blacklist_sid: Option<*mut c_void>,
) -> Result<HANDLE> {
create_token_inner(cap_sids, blacklist_sid)
}
pub unsafe fn create_readonly_restricted_token() -> Result<HANDLE> {
create_token_inner(&[], None)
}