Skip to content

Commit

Permalink
feat(pedm): netmgmt wrapper to add/remove local users from local grou…
Browse files Browse the repository at this point in the history
…ps (#1099)
  • Loading branch information
thenextman authored Nov 11, 2024
1 parent caa5ffa commit 3c3254b
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
24 changes: 24 additions & 0 deletions crates/devolutions-pedm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::path::Path;
use tracing::info;
use win_api_wrappers::identity::account::Account;
use win_api_wrappers::identity::sid::Sid;
use win_api_wrappers::netmgmt::{get_local_admin_group_members, get_local_group_members};
use win_api_wrappers::process::{create_process_as_user, ProcessInformation, StartupInfo};
use win_api_wrappers::raw::Win32::Foundation::{GENERIC_ALL, GENERIC_READ};
use win_api_wrappers::raw::Win32::Security::Authorization::SE_FILE_OBJECT;
Expand Down Expand Up @@ -60,6 +61,29 @@ pub(crate) fn start_process(
)
}

fn is_member_of_administrators_group_directly(user_sid: &Sid) -> anyhow::Result<bool> {
Ok(get_local_admin_group_members()?.contains(user_sid))
}

fn is_member_of_administrators(user_token: &Token) -> anyhow::Result<bool> {
if is_member_of_administrators_group_directly(&user_token.sid_and_attributes()?.sid)? {
return Ok(true);
}

let local_admin_sids = get_local_admin_group_members()?;
let group_sids_and_attributes = user_token.groups()?.0;

for user_group_sid_and_attributes in group_sids_and_attributes {
for admin_sid in &local_admin_sids {
if admin_sid == &user_group_sid_and_attributes.sid {
return Ok(true);
}
}
}

Ok(false)
}

#[derive(Default)]
pub(crate) struct MultiHasher {
sha1: Sha1,
Expand Down
1 change: 1 addition & 0 deletions crates/win-api-wrappers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod lib_win {
pub mod event;
pub mod handle;
pub mod identity;
pub mod netmgmt;
pub mod process;
pub mod security;
pub mod session;
Expand Down
108 changes: 108 additions & 0 deletions crates/win-api-wrappers/src/netmgmt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::ptr::null_mut;

use anyhow::bail;
use windows::Win32::Foundation::WIN32_ERROR;

use crate::identity::account::Account;
use crate::identity::sid::{RawSid, Sid};
use crate::raw::Win32::NetworkManagement::NetManagement::{
NERR_Success, NetApiBufferFree, NetLocalGroupAddMembers, NetLocalGroupDelMembers, NetLocalGroupGetMembers,
LOCALGROUP_MEMBERS_INFO_0, MAX_PREFERRED_LENGTH,
};
use crate::raw::Win32::Security::WinBuiltinAdministratorsSid;
use crate::utils::WideString;
use crate::Error;

pub fn add_local_group_member(group_name: &str, security_identifier: &Sid) -> anyhow::Result<()> {
// SAFETY: It is safe to zero out the structure as it is a simple POD type.
let mut group_info = unsafe { core::mem::zeroed::<LOCALGROUP_MEMBERS_INFO_0>() };

let group_name = WideString::from(group_name);

let user_sid = RawSid::try_from(security_identifier)?;
group_info.lgrmi0_sid = user_sid.as_psid();

// SAFETY: All buffers are valid.
// WideString holds a null-terminated UTF-16 string, and as_pcwstr() returns a valid pointer to it.
let rc =
unsafe { NetLocalGroupAddMembers(None, group_name.as_pcwstr(), 0, &group_info as *const _ as *const u8, 1) };

if rc != NERR_Success {
bail!(Error::from_win32(WIN32_ERROR(rc)))
}

Ok(())
}

pub fn remove_local_group_member(group_name: &str, security_identifier: &Sid) -> anyhow::Result<()> {
// SAFETY: It is safe to zero out the structure as it is a simple POD type.
let mut group_info = unsafe { core::mem::zeroed::<LOCALGROUP_MEMBERS_INFO_0>() };

let group_name = WideString::from(group_name);

let user_sid = RawSid::try_from(security_identifier)?;
group_info.lgrmi0_sid = user_sid.as_psid();

// SAFETY: All buffers are valid.
// WideString holds a null-terminated UTF-16 string, and as_pcwstr() returns a valid pointer to it.
let rc =
unsafe { NetLocalGroupDelMembers(None, group_name.as_pcwstr(), 0, &group_info as *const _ as *const u8, 1) };

if rc != NERR_Success {
bail!(Error::from_win32(WIN32_ERROR(rc)))
}

Ok(())
}

pub fn get_local_admin_group_members() -> anyhow::Result<Vec<Sid>> {
let local_admin_group_sid = Sid::from_well_known(WinBuiltinAdministratorsSid, None)?;
let local_admin_group_account = local_admin_group_sid.account(None)?;
get_local_group_members(local_admin_group_account.account_name)
}

pub fn get_local_group_members(group_name: String) -> anyhow::Result<Vec<Sid>> {
let group_name = WideString::from(group_name);
let mut buffer = null_mut();
let mut entries_read = 0;
let mut total_entries = 0;

// SAFETY: All buffers are valid.
// WideString holds a null-terminated UTF-16 string, and as_pcwstr() returns a valid pointer to it.
// `buffer` must be freed by `NetApiBufferFree.
// Specifying `MAX_PREFERRED_LENGTH` allocates the required amount of memory for the data, and
// the function will not return `ERROR_MORE_DATA`
let group_members_slice = unsafe {
let rc = NetLocalGroupGetMembers(
None,
group_name.as_pcwstr(),
0,
&mut buffer,
MAX_PREFERRED_LENGTH,
&mut entries_read,
&mut total_entries,
None,
);

if rc != NERR_Success {
bail!(Error::from_win32(WIN32_ERROR(rc)))
}

std::slice::from_raw_parts(
buffer as *const u8 as *const LOCALGROUP_MEMBERS_INFO_0,
entries_read as usize,
)
};

let mut group_members = Vec::<Sid>::with_capacity(group_members_slice.len());
for member in group_members_slice {
group_members.push(Sid::try_from(member.lgrmi0_sid)?);
}

// SAFETY: `buffer` is valid and points to memory allocated by `NetLocalGroupGetMembers`
unsafe {
NetApiBufferFree(Some(buffer.cast()));
}

Ok(group_members)
}

0 comments on commit 3c3254b

Please sign in to comment.