How do I determine if I have admin rights on Windows

Using Rust, how do I code to determine if I have admin rights or not for Windows?

Hello,
You must check if the current user have admin with the following :
https://docs.microsoft.com/fr-fr/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
Use winapi crate or just code good old FFI :wink:
Nota: It's not recommended to use this:
https://docs.microsoft.com/fr-fr/windows/win32/api/shlobj_core/nf-shlobj_core-isuseranadmin?redirectedfrom=MSDN

1 Like

Hey man I don't quite understand what I need to do.

Lol I think I would just stick with using the winapi crate.

I just want to do a simple check if I am admin or not, do you know how to do this?

The function that @JLalu linked is available in the winapi crate here. You can call it like you would if you were coding in C, and it is unsafe as it calls a non-rust function.

Using winapi correctly can be a bit difficult, but if you were to translate this verbatim, I think it'd become something like this:

use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::processthreadsapi::OpenProcessToken;
use winapi::um::securitybaseapi::GetTokenInformation;
use winapi::um::winnt::HANDLE;
use winapi::um::winnt::TOKEN_ELEVATION;
use winapi::um::winnt::TOKEN_QUERY;
use winapi::um::winnt::TokenElevation;
use std::ptr::null_mut;

fn is_elevated() -> bool {
    let mut result = false;
    let mut handle: HANDLE = null_mut();
    if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle) {
        let elevation = TOKEN_ELEVATION::default();
        let size = std::mem::size_of::<TOKEN_ELEVATION>();
        let mut ret_size = size;
        if GetTokenInformation(handle, TokenElevation, &mut elevation, size, &mut ret_size) {
            result = elevation.TokenIsElevated;
        }
    }
    if !handle.is_null() {
        CloseHandle(handle);
    }
    result
}

Unfortunately I don't have a windows machine, so I can't test if it's correct or if it even compiles, but it should at least give some idea of how to approach the issue.

2 Likes

alice that was great, thank you! It didn't work as is, so I figured I'd hack around with it as it gave me a start.

@Joe232 I'm not a programmer (coding enthusiast in the infosec field) but I get correct results based on whether I run it in an admin console or not.

I'd love a rust programmer to look this over and give me pointers on what I could do better.

In your cargo.toml
[dependencies]
winapi = "0.3.8"
libc = "0.2.66"

[target.'cfg(windows)'.dependencies]
winapi = {version = "0.3.8", features = ["winuser", "handleapi", "processthreadsapi", "securitybaseapi"]}

In main.rs

use std::ptr::null_mut;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::processthreadsapi::OpenProcessToken;
use winapi::um::securitybaseapi::GetTokenInformation;
use winapi::um::winnt::TokenElevation;
use winapi::um::winnt::HANDLE;
use winapi::um::winnt::TOKEN_ELEVATION;

use libc;
use std::mem;
use winapi::ctypes::c_void;
use winapi::um::winnt::TOKEN_QUERY;

fn is_elevated() -> bool {

    let mut handle: HANDLE = null_mut();
    unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle) };

    let elevation = unsafe { libc::malloc(mem::size_of::<TOKEN_ELEVATION>()) as *mut c_void };
    let size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
    let mut ret_size = size;
    unsafe {
        GetTokenInformation(
            handle,
            TokenElevation,
            elevation,
            size as u32,
            &mut ret_size,
        )
    };
    let elevation_struct: TOKEN_ELEVATION = unsafe{ *(elevation as *mut TOKEN_ELEVATION)};

    if !handle.is_null() {
        unsafe {
            CloseHandle(handle);
        }
    }

    elevation_struct.TokenIsElevated == 1

}

fn main() {
    println!("I'm an admin? {:#?}", is_elevated());
}

The things I notice are:

  1. You are ignoring the return value of both OpenProcessToken and GetTokenInformation which is invalid if they fail for some reason.
  2. There's no reason to allocate the elevation parameter on the heap. You can simply use a pointer to a stack variable like I did in my example.
  3. Since you are not deallocating the elevation pointer, your function has a memory leak.
  4. You are casting the size to u32 twice for some reason.

If the only mistakes in my example was lack of unsafe block, TokenIsElevated being an integer and lack of mut marker on the elevation variable, I'd recommend keeping the structure of my example to ensure correct error handling.

One note, you'll need to cast to a c_void pointer for this to work. You can let the compiler figure out the correct type. Something like:

&mut elevation as *mut _ as *mut _

You'll also need to add "impl-default" to the winapi feature list to get the Default::default implementation (in addition to the features mentioned by ClintRajaniemi).

To be honest I'd be tempted to create a safe AccessToken type to wrap these functions. It would ensure all the error cases can be handled. As a bonus it would also ease future extension to other queries on the token (although that might not be a concern here).

Ok I quickly hacked something together on my Windows PC using all the information posted above. You'll need this in your cargo.toml:

[dependencies.winapi]
version = "0.3.8"
features = ["handleapi", "processthreadsapi", "winnt", "securitybaseapi", "impl-default"]

Then create a new file in your src. I've called it windows.rs.

// Use std::io::Error::last_os_error for errors.
// NOTE: For this example I'm simple passing on the OS error.
// However, customising the error could provide more context 
use std::io::Error;
use std::ptr;

use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{ GetCurrentProcess, OpenProcessToken };
use winapi::um::securitybaseapi::GetTokenInformation;
use winapi::um::winnt::{HANDLE, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation};

/// Returns true if the current process has admin rights, otherwise false.
pub fn is_app_elevated() -> bool {
    _is_app_elevated().unwrap_or(false)
}

/// On success returns a bool indicating if the current process has admin rights.
/// Otherwise returns an OS error.
/// 
/// This is unlikely to fail but if it does it's even more unlikely that you have admin permissions anyway.
/// Therefore the public function above simply eats the error and returns a bool.
fn _is_app_elevated() -> Result<bool, Error> {
    let token = QueryAccessToken::from_current_process()?;
    token.is_elevated()
}

/// A safe wrapper around querying Windows access tokens.
pub struct QueryAccessToken(HANDLE);
impl QueryAccessToken {
    pub fn from_current_process() -> Result<Self, Error> {
        unsafe {
            let mut handle: HANDLE = ptr::null_mut();
            if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle) != 0 {
                Ok ( Self(handle) )
            } else {
                Err(Error::last_os_error())
            }
        }
    }
    
    /// On success returns a bool indicating if the access token has elevated privilidges.
    /// Otherwise returns an OS error.
    pub fn is_elevated(&self) -> Result<bool, Error> {
        unsafe {
            let mut elevation = TOKEN_ELEVATION::default();
            let size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
            let mut ret_size = size;
            // The weird looking repetition of `as *mut _` is casting the reference to a c_void pointer.
            if GetTokenInformation(self.0, TokenElevation, &mut elevation as *mut _ as *mut _, size, &mut ret_size ) != 0 {
                Ok(elevation.TokenIsElevated != 0)
            } else {
                Err(Error::last_os_error())
            }
        }
    }
}
impl Drop for QueryAccessToken {
    fn drop(&mut self) {
        if !self.0.is_null() {
            unsafe { CloseHandle(self.0) };
        }
    }
}

The meat of it is the QueryAccessToken struct but there is a is_app_elevated function for convenience.

Here's a simple "hello world" style main.rs:

mod windows; // load the module with our code

fn main() {
    if windows::is_app_elevated() {
        println!("I've got admin!");
    } else {
        println!("Non admin permissions.");
    }
}

Your millage may vary but I think it's worth getting in the habit of creating a proper wrapper for Windows types and functions because they can be a pain to deal with otherwise. You can of course refactor the QueryAccessToken struct as a single function if you'd prefer.

  1. Great point and I went back to my example to fix it and check for 0 values for failures as documented in MSDN
  2. The truth is I couldn't figure out how to cast the TOKEN_ELEVATION struct to winapi::c_types::c_void (as required for GetTokenInformation())
  3. Great point. I went back and freed it with libc::free (if it was not null)
  4. Yep, I was trying out different places to see where it was better to read. I failed to remove one of them.

The other things I ran into was the return type mismatch between OpenProcessToken (i32) and GetTokenInformation (i32) with the bool type of is_elevated(). As well, the GetTokenInformation was expecting u32 for size and ret_size but they are usize.

Trying to learn the language, I was aiming to stay within the confines of the function prototype. This is exactly the kind of feedback I was hoping for. Thank you.

I thought I was going crazy when I was trying to figure out why TOKEN_ELEVATION::default() was erroring out on "default() not found in scope" (or something like that.) when the documentation clearly shows it.

chris, I appreciate the wrapper code, I've been struggling to figure out how that's done. I'd found a few examples, but they seem to be for far simpler use cases than this. I'm definitely going to play around with it for a bit.

Of course, if malloc returned a null pointer, you should detect that before trying to use it as an argument to GetTokenInformation as that would be invalid. Note that it's okay to pass a null pointer to free as it simply does nothing in that case.

Of course, this issue disappears if you just use a stack pointer with the appropriate casts as @chrisd described.

I never could remember about free'ing null pointers. Anyway, it's been fun (and eye-opening) and I'll be moving away from my code at this point and digging into the wrapped code provided above.