├── .gitignore
├── utils
├── lift.ico
├── lift.png
├── manifest.manifest
├── Cargo.toml
├── build.rs
├── resource.rc
└── bin
│ ├── eledo.rs
│ ├── normdo.rs
│ └── ptybridge.rs
├── Cargo.toml
├── deelevate
├── examples
│ ├── show.rs
│ ├── spawn.rs
│ └── impersonate.rs
├── src
│ ├── lib.rs
│ ├── procthreadattr.rs
│ ├── process.rs
│ ├── sid.rs
│ ├── psuedocon.rs
│ ├── spawn.rs
│ ├── pipe.rs
│ ├── command.rs
│ ├── bridge.rs
│ └── token.rs
└── Cargo.toml
├── .github
└── workflows
│ ├── rust.yml
│ └── continuous.yml
├── LICENSE.md
├── spawn-output.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .*.sw*
2 | /target
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/utils/lift.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wez/EleDo/HEAD/utils/lift.ico
--------------------------------------------------------------------------------
/utils/lift.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wez/EleDo/HEAD/utils/lift.png
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "utils",
4 | "deelevate"
5 | ]
6 |
--------------------------------------------------------------------------------
/utils/manifest.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | UTF-8
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/deelevate/examples/show.rs:
--------------------------------------------------------------------------------
1 | //! This example prints the effective privilege output from
2 | //! `whoami` as well as our understanding of that level
3 | use deelevate::Token;
4 |
5 | fn whoami() {
6 | let output = std::process::Command::new("whoami.exe")
7 | .arg("/groups")
8 | .output()
9 | .expect("failed to run whoami");
10 | let stdout = String::from_utf8_lossy(&output.stdout);
11 | println!("{}", stdout);
12 | }
13 |
14 | fn main() {
15 | whoami();
16 |
17 | let token = Token::with_current_process().unwrap();
18 | let level = token.privilege_level().unwrap();
19 | println!("priv level is {:?}", level);
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | pull_request:
5 | branches: [ master ]
6 |
7 | jobs:
8 | build:
9 | runs-on: "vs2017-win2016"
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Build
13 | run: cargo build --release --verbose
14 | - name: Run tests
15 | run: cargo test --release --verbose
16 | - name: "Capture utilities"
17 | shell: bash
18 | run: |
19 | mkdir pkg_
20 | mv target/release/eledo.exe target/release/eledo-pty-bridge.exe target/release/normdo.exe pkg_/
21 | cp README.md LICENSE.md pkg_
22 | - name: "Upload artifact"
23 | uses: actions/upload-artifact@master
24 | with:
25 | name: "eledo"
26 | path: "pkg_"
27 |
--------------------------------------------------------------------------------
/deelevate/examples/spawn.rs:
--------------------------------------------------------------------------------
1 | //! This example prints the effective privilege output from
2 | //! `whoami` as well as our understanding of that level
3 | use deelevate::{spawn_with_normal_privileges, Token};
4 |
5 | fn whoami() {
6 | let output = std::process::Command::new("whoami.exe")
7 | .arg("/groups")
8 | .output()
9 | .expect("failed to run whoami");
10 | let stdout = String::from_utf8_lossy(&output.stdout);
11 | println!("{}", stdout);
12 | }
13 |
14 | fn main() {
15 | whoami();
16 |
17 | let token = Token::with_current_process().unwrap();
18 | let level = token.privilege_level().unwrap();
19 | println!("priv level is {:?}", level);
20 |
21 | spawn_with_normal_privileges().unwrap();
22 | println!("now I'm safe to proceed with reduced privs");
23 | }
24 |
--------------------------------------------------------------------------------
/utils/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "utils"
3 | version = "0.1.1"
4 | authors = ["Wez Furlong"]
5 | edition = "2018"
6 | description = "Drop privileges on Windows"
7 | license = "MIT"
8 | documentation = "https://docs.rs/deelevate"
9 | readme = "../README.md"
10 | keywords = ["UAC", "elevate", "privileges"]
11 | build = "build.rs"
12 |
13 | [[bin]]
14 | name = "eledo"
15 | path = "bin/eledo.rs"
16 |
17 | [[bin]]
18 | name = "normdo"
19 | path = "bin/normdo.rs"
20 |
21 | [[bin]]
22 | name = "eledo-pty-bridge"
23 | path = "bin/ptybridge.rs"
24 |
25 | [build-dependencies]
26 | embed-resource = "1.7"
27 | vergen = "3"
28 | cc = "1.0"
29 |
30 | [dependencies]
31 | structopt = "0.3"
32 | pathsearch = "0.2"
33 | deelevate = { path = "../deelevate" }
34 | winapi = { version = "0.3", features = [
35 | "wincon",
36 | "winnls",
37 | ]}
38 |
--------------------------------------------------------------------------------
/deelevate/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::OsStr;
2 | use std::io::Error as IoError;
3 | use std::os::windows::ffi::OsStrExt;
4 |
5 | mod bridge;
6 | mod command;
7 | mod pipe;
8 | mod process;
9 | mod procthreadattr;
10 | mod psuedocon;
11 | mod sid;
12 | mod spawn;
13 | mod token;
14 |
15 | pub use bridge::{BridgePtyClient, BridgeServer};
16 | pub use command::Command;
17 | #[doc(hidden)]
18 | pub use pipe::PipeHandle;
19 | pub use spawn::{spawn_with_elevated_privileges, spawn_with_normal_privileges};
20 | pub use token::PrivilegeLevel;
21 | pub use token::Token;
22 |
23 | fn win32_error_with_context(context: &str, err: IoError) -> IoError {
24 | IoError::new(err.kind(), format!("{}: {}", context, err))
25 | }
26 |
27 | fn os_str_to_null_terminated_vec(s: &OsStr) -> Vec {
28 | s.encode_wide().chain(std::iter::once(0)).collect()
29 | }
30 |
--------------------------------------------------------------------------------
/utils/build.rs:
--------------------------------------------------------------------------------
1 | use vergen::{generate_cargo_keys, ConstantsFlags};
2 | fn main() {
3 | let mut flags = ConstantsFlags::all();
4 | flags.remove(ConstantsFlags::SEMVER_FROM_CARGO_PKG);
5 | generate_cargo_keys(ConstantsFlags::all()).expect("Unable to generate the cargo keys!");
6 | println!("cargo:rerun-if-changed=resource.rc");
7 |
8 | // Obtain MSVC environment so that the rc compiler can find the right headers.
9 | // https://github.com/nabijaczleweli/rust-embed-resource/issues/11#issuecomment-603655972
10 | let target = std::env::var("TARGET").unwrap();
11 | if let Some(tool) = cc::windows_registry::find_tool(target.as_str(), "cl.exe") {
12 | for (key, value) in tool.env() {
13 | std::env::set_var(key, value);
14 | }
15 | }
16 |
17 | embed_resource::compile("resource.rc");
18 | }
19 |
--------------------------------------------------------------------------------
/deelevate/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "deelevate"
3 | version = "0.2.0"
4 | authors = ["Wez Furlong"]
5 | edition = "2018"
6 | description = "Drop privileges on Windows"
7 | license = "MIT"
8 | documentation = "https://docs.rs/deelevate"
9 | readme = "../README.md"
10 | keywords = ["UAC", "elevate", "privileges"]
11 |
12 | [lib]
13 | crate-type = ["lib", "staticlib"]
14 |
15 | [dependencies]
16 | pathsearch = "0.2"
17 | lazy_static = "1.4"
18 | rand = "0.8"
19 | shared_library = "0.1"
20 | termwiz = "0.15"
21 | winapi = { version = "0.3", features = [
22 | "accctrl",
23 | "aclapi",
24 | "combaseapi",
25 | "consoleapi",
26 | "errhandlingapi",
27 | "fileapi",
28 | "handleapi",
29 | "ioapiset",
30 | "namedpipeapi",
31 | "objbase",
32 | "processenv",
33 | "processthreadsapi",
34 | "securitybaseapi",
35 | "shellapi",
36 | "synchapi",
37 | "userenv",
38 | "winbase",
39 | "wincontypes",
40 | "winerror",
41 | "winsafer",
42 | "winuser",
43 | ]}
44 |
--------------------------------------------------------------------------------
/.github/workflows/continuous.yml:
--------------------------------------------------------------------------------
1 | name: Continuous
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 |
7 | jobs:
8 | build:
9 | runs-on: "vs2017-win2016"
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Build
13 | run: cargo build --release --verbose
14 | - name: "Build Release"
15 | shell: bash
16 | run: |
17 | mkdir EleDo
18 | mv target/release/eledo.exe target/release/eledo-pty-bridge.exe target/release/normdo.exe EleDo/
19 | cp README.md LICENSE.md EleDo
20 | cd EleDo
21 | 7z a -tzip ../EleDo.zip *
22 | - name: "Upload artifact"
23 | uses: actions/upload-artifact@master
24 | with:
25 | name: "EleDo"
26 | path: "EleDo.zip"
27 | - name: "Upload to Continuous Release"
28 | uses: wez/upload-release-assets@releases/v1
29 | with:
30 | files: "EleDo.zip"
31 | release-tag: "Continuous"
32 | repo-token: "${{ secrets.GITHUB_TOKEN }}"
33 |
34 |
--------------------------------------------------------------------------------
/utils/resource.rc:
--------------------------------------------------------------------------------
1 | #include
2 | #define IDI_ICON 0x101
3 | IDI_ICON ICON "lift.ico"
4 | APP_MANIFEST RT_MANIFEST manifest.manifest
5 | VS_VERSION_INFO VERSIONINFO
6 | FILEVERSION 1,0,0,0
7 | PRODUCTVERSION 1,0,0,0
8 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
9 | FILEFLAGS 0
10 | FILEOS VOS__WINDOWS32
11 | FILETYPE VFT_APP
12 | FILESUBTYPE VFT2_UNKNOWN
13 | BEGIN
14 | BLOCK "StringFileInfo"
15 | BEGIN
16 | BLOCK "040904E4"
17 | BEGIN
18 | VALUE "CompanyName", "Wez Furlong\0"
19 | VALUE "FileDescription", "Elevated-Do Helper\0"
20 | VALUE "FileVersion", "1.0\0"
21 | VALUE "LegalCopyright", "Wez Furlong, MIT licensed\0"
22 | VALUE "InternalName", "\0"
23 | VALUE "OriginalFilename", "\0"
24 | VALUE "ProductName", "Elevated-Do\0"
25 | VALUE "ProductVersion", "1.0\0"
26 | END
27 | END
28 | BLOCK "VarFileInfo"
29 | BEGIN
30 | VALUE "Translation", 0x409, 1252
31 | END
32 | END
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Wez Furlong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/deelevate/examples/impersonate.rs:
--------------------------------------------------------------------------------
1 | //! This example demonstrates that impersonation isn't sufficient
2 | //! to effectively reduce privileges. It does this by running
3 | //! `whoami` before and after an impersonation request and showing
4 | //! that the effective prileges remain the same.
5 |
6 | use deelevate::Token;
7 |
8 | fn whoami() {
9 | let output = std::process::Command::new("whoami.exe")
10 | .arg("/groups")
11 | .output()
12 | .expect("failed to run whoami");
13 | let stdout = String::from_utf8_lossy(&output.stdout);
14 | println!("{}", stdout);
15 | }
16 |
17 | fn main() {
18 | whoami();
19 |
20 | let token = Token::with_current_process().unwrap();
21 | let level = token.privilege_level().unwrap();
22 | println!("priv level is {:?}", level);
23 |
24 | let medium = token
25 | .as_medium_integrity_safer_token()
26 | .expect("failed to make medium token");
27 |
28 | medium
29 | .impersonate()
30 | .expect("failed to impersonate self with medium token");
31 |
32 | println!("impersonation successful, but note that it isn't effective in changing whoami!");
33 |
34 | whoami();
35 | }
36 |
--------------------------------------------------------------------------------
/deelevate/src/procthreadattr.rs:
--------------------------------------------------------------------------------
1 | use crate::psuedocon::HPCON;
2 | use crate::win32_error_with_context;
3 | use std::io::{Error as IoError, Result as IoResult};
4 | use std::mem;
5 | use std::ptr;
6 | use winapi::shared::minwindef::DWORD;
7 | use winapi::um::processthreadsapi::*;
8 |
9 | const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: usize = 0x00020016;
10 |
11 | pub struct ProcThreadAttributeList {
12 | data: Vec,
13 | }
14 |
15 | impl ProcThreadAttributeList {
16 | pub fn with_capacity(num_attributes: DWORD) -> IoResult {
17 | let mut bytes_required: usize = 0;
18 | unsafe {
19 | InitializeProcThreadAttributeList(
20 | ptr::null_mut(),
21 | num_attributes,
22 | 0,
23 | &mut bytes_required,
24 | )
25 | };
26 | let mut data = Vec::with_capacity(bytes_required);
27 | // We have the right capacity, so force the vec to consider itself
28 | // that length. The contents of those bytes will be maintained
29 | // by the win32 apis used in this impl.
30 | unsafe { data.set_len(bytes_required) };
31 |
32 | let attr_ptr = data.as_mut_slice().as_mut_ptr() as *mut _;
33 | let res = unsafe {
34 | InitializeProcThreadAttributeList(attr_ptr, num_attributes, 0, &mut bytes_required)
35 | };
36 | if res == 0 {
37 | Err(win32_error_with_context(
38 | "InitializeProcThreadAttributeList failed",
39 | IoError::last_os_error(),
40 | ))
41 | } else {
42 | Ok(Self { data })
43 | }
44 | }
45 |
46 | pub fn as_mut_ptr(&mut self) -> LPPROC_THREAD_ATTRIBUTE_LIST {
47 | self.data.as_mut_slice().as_mut_ptr() as *mut _
48 | }
49 |
50 | pub fn set_pty(&mut self, con: HPCON) -> IoResult<()> {
51 | let res = unsafe {
52 | UpdateProcThreadAttribute(
53 | self.as_mut_ptr(),
54 | 0,
55 | PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
56 | con,
57 | mem::size_of::(),
58 | ptr::null_mut(),
59 | ptr::null_mut(),
60 | )
61 | };
62 | if res == 0 {
63 | Err(win32_error_with_context(
64 | "UpdateProcThreadAttribute failed",
65 | IoError::last_os_error(),
66 | ))
67 | } else {
68 | Ok(())
69 | }
70 | }
71 | }
72 |
73 | impl Drop for ProcThreadAttributeList {
74 | fn drop(&mut self) {
75 | unsafe { DeleteProcThreadAttributeList(self.as_mut_ptr()) };
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/deelevate/src/process.rs:
--------------------------------------------------------------------------------
1 | //! Working with process handles
2 | use crate::win32_error_with_context;
3 | use std::io::{Error as IoError, Result as IoResult};
4 | use winapi::shared::minwindef::DWORD;
5 | use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
6 | use winapi::um::processthreadsapi::{GetExitCodeProcess, OpenProcess};
7 | use winapi::um::synchapi::WaitForSingleObject;
8 | use winapi::um::winbase::{INFINITE, WAIT_FAILED};
9 | use winapi::um::winnt::HANDLE;
10 |
11 | /// An owning wrapper around handles that represent processes
12 | pub struct Process(HANDLE);
13 | /// The compiler thinks it isn't send because HANDLE is a pointer
14 | /// type. We happen to know that moving the handle between threads
15 | /// is totally fine, hence this impl.
16 | unsafe impl Send for Process {}
17 |
18 | impl Drop for Process {
19 | fn drop(&mut self) {
20 | unsafe {
21 | CloseHandle(self.0);
22 | }
23 | }
24 | }
25 |
26 | impl Process {
27 | /// Delegates to the OpenProcess win32 API
28 | pub fn with_process_id(
29 | desired_access: DWORD,
30 | inherit_handles: bool,
31 | pid: DWORD,
32 | ) -> IoResult {
33 | let proc = unsafe { OpenProcess(desired_access, inherit_handles as _, pid) };
34 | if proc == INVALID_HANDLE_VALUE {
35 | Err(win32_error_with_context(
36 | "OpenProcess",
37 | IoError::last_os_error(),
38 | ))
39 | } else {
40 | Ok(Self(proc))
41 | }
42 | }
43 |
44 | /// Returns the underlying raw handle value
45 | pub fn as_handle(&self) -> HANDLE {
46 | self.0
47 | }
48 |
49 | /// Takes ownership of the provided handle and will close
50 | /// it when this Process instance is dropped!
51 | pub fn with_handle(proc: HANDLE) -> Self {
52 | Self(proc)
53 | }
54 |
55 | /// Wait for the specified duration (in milliseconds!) to pass.
56 | /// Use None to wait forever.
57 | pub fn wait_for(&self, duration: Option) -> IoResult {
58 | let res = unsafe { WaitForSingleObject(self.0, duration.unwrap_or(INFINITE)) };
59 | if res == WAIT_FAILED {
60 | Err(win32_error_with_context(
61 | "WaitForSingleObject(process)",
62 | IoError::last_os_error(),
63 | ))
64 | } else {
65 | Ok(res)
66 | }
67 | }
68 |
69 | /// Retrieves the exit code from the process
70 | pub fn exit_code(&self) -> IoResult {
71 | let mut exit_code = 0;
72 | if unsafe { GetExitCodeProcess(self.0, &mut exit_code) } != 0 {
73 | Ok(exit_code)
74 | } else {
75 | Err(win32_error_with_context(
76 | "GetExitCodeProcess",
77 | IoError::last_os_error(),
78 | ))
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/utils/bin/eledo.rs:
--------------------------------------------------------------------------------
1 | use deelevate::{BridgeServer, Command, PrivilegeLevel, Token};
2 | use pathsearch::find_executable_in_path;
3 | use std::ffi::OsString;
4 | use structopt::*;
5 |
6 | /// EleDo - "Do" a command with Elevated privileges
7 | ///
8 | /// EleDo will check to see if the current context has admin privileges.
9 | /// If it does then it will execute the requested program directly,
10 | /// returning the exit status from that program.
11 | ///
12 | /// Otherwise, EleDo will arrange to run the program with an elevated
13 | /// PTY that is bridged to the current terminal session. Elevation
14 | /// requires that the current process be able to communicate with the
15 | /// shell in the current desktop session and will typically trigger
16 | /// a UAC prompt for that user to confirm that elevation should occur.
17 | ///
18 | /// Example:
19 | /// `eledo whoami /groups`
20 | #[derive(StructOpt)]
21 | #[structopt(
22 | about = "EleDo - \"Do\" a command with Elevated privileges",
23 | author = "Wez Furlong",
24 | setting(clap::AppSettings::TrailingVarArg),
25 | setting(clap::AppSettings::ArgRequiredElseHelp),
26 | version = env!("VERGEN_SEMVER_LIGHTWEIGHT")
27 | )]
28 | #[derive(Debug)]
29 | struct Opt {
30 | #[structopt(value_name("PROGRAM"), parse(from_os_str))]
31 | args: Vec,
32 | }
33 |
34 | fn main() -> std::io::Result<()> {
35 | let mut opt = Opt::from_args();
36 |
37 | let token = Token::with_current_process()?;
38 | let level = token.privilege_level()?;
39 |
40 | opt.args[0] = match find_executable_in_path(&opt.args[0]) {
41 | Some(path) => path.into(),
42 | None => {
43 | eprintln!("Unable to find {:?} in path", opt.args[0]);
44 | std::process::exit(1);
45 | }
46 | };
47 |
48 | let target_token = match level {
49 | PrivilegeLevel::NotPrivileged | PrivilegeLevel::HighIntegrityAdmin => {
50 | token.as_medium_integrity_safer_token()?
51 | }
52 | PrivilegeLevel::Elevated => Token::with_shell_process()?,
53 | };
54 |
55 | let mut command = Command::with_environment_for_token(&target_token)?;
56 |
57 | let exit_code = match level {
58 | PrivilegeLevel::Elevated | PrivilegeLevel::HighIntegrityAdmin => {
59 | // We already have privs, so just run it directly
60 | command.set_argv(opt.args);
61 | let proc = command.spawn()?;
62 | let _ = proc.wait_for(None);
63 | proc.exit_code()?
64 | }
65 | PrivilegeLevel::NotPrivileged => {
66 | let mut server = BridgeServer::new();
67 |
68 | let mut bridge_cmd = server.start_for_command(&mut opt.args, &target_token)?;
69 |
70 | let proc = bridge_cmd.shell_execute("runas")?;
71 | server.serve(proc)?
72 | }
73 | };
74 | std::process::exit(exit_code as _);
75 | }
76 |
--------------------------------------------------------------------------------
/utils/bin/normdo.rs:
--------------------------------------------------------------------------------
1 | use deelevate::{BridgeServer, Command, PrivilegeLevel, Token};
2 | use pathsearch::find_executable_in_path;
3 | use std::ffi::OsString;
4 | use structopt::*;
5 |
6 | /// NormDo - "Do" a command with Normal privileges
7 | ///
8 | /// NormDo will check to see if the current context has admin privileges.
9 | /// If it doesn't then it will execute the requested program directly,
10 | /// returning the exit status from that program.
11 | ///
12 | /// Otherwise, NormDo will arrange to run the program with a Normal
13 | /// user token with Medium integrity level, dropping/denying the local
14 | /// administrator group from the token. The program will be run in a
15 | /// PTY that is bridged to the current terminal session.
16 | ///
17 | /// Example:
18 | /// `normdo whoami /groups`
19 | #[derive(StructOpt)]
20 | #[structopt(
21 | about = "NormDo - \"Do\" a command with Normal privileges",
22 | author = "Wez Furlong",
23 | setting(clap::AppSettings::TrailingVarArg),
24 | setting(clap::AppSettings::ArgRequiredElseHelp),
25 | version = env!("VERGEN_SEMVER_LIGHTWEIGHT")
26 | )]
27 | #[derive(Debug)]
28 | struct Opt {
29 | #[structopt(value_name("PROGRAM"), parse(from_os_str))]
30 | args: Vec,
31 | }
32 |
33 | fn main() -> std::io::Result<()> {
34 | let mut opt = Opt::from_args();
35 |
36 | let token = Token::with_current_process()?;
37 | let level = token.privilege_level()?;
38 |
39 | opt.args[0] = match find_executable_in_path(&opt.args[0]) {
40 | Some(path) => path.into(),
41 | None => {
42 | eprintln!("Unable to find {:?} in path", opt.args[0]);
43 | std::process::exit(1);
44 | }
45 | };
46 |
47 | let target_token = match level {
48 | PrivilegeLevel::NotPrivileged => token,
49 | PrivilegeLevel::HighIntegrityAdmin => token.as_medium_integrity_safer_token()?,
50 | PrivilegeLevel::Elevated => Token::with_shell_process()?,
51 | };
52 |
53 | let mut command = Command::with_environment_for_token(&target_token)?;
54 |
55 | let exit_code = match level {
56 | PrivilegeLevel::NotPrivileged => {
57 | // We're already normal, so just run it directly
58 | command.set_argv(opt.args);
59 | let proc = command.spawn()?;
60 | let _ = proc.wait_for(None);
61 | proc.exit_code()?
62 | }
63 | PrivilegeLevel::HighIntegrityAdmin | PrivilegeLevel::Elevated => {
64 | let mut server = BridgeServer::new();
65 |
66 | let mut bridge_cmd = server.start_for_command(&mut opt.args, &target_token)?;
67 |
68 | let proc = match level {
69 | PrivilegeLevel::Elevated => bridge_cmd.spawn_with_token(&target_token)?,
70 | PrivilegeLevel::NotPrivileged | PrivilegeLevel::HighIntegrityAdmin => {
71 | bridge_cmd.spawn_as_user(&target_token)?
72 | }
73 | };
74 |
75 | server.serve(proc)?
76 | }
77 | };
78 |
79 | std::process::exit(exit_code as _);
80 | }
81 |
--------------------------------------------------------------------------------
/deelevate/src/sid.rs:
--------------------------------------------------------------------------------
1 | use std::io::{Error as IoError, Result as IoResult};
2 | use winapi::shared::minwindef::DWORD;
3 | use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER;
4 | use winapi::um::errhandlingapi::GetLastError;
5 | use winapi::um::securitybaseapi::{CreateWellKnownSid, GetLengthSid, IsWellKnownSid};
6 | use winapi::um::winnt::SID;
7 | use winapi::um::winnt::WELL_KNOWN_SID_TYPE;
8 |
9 | /// A little helper trait to make it easier to operate on SIDs
10 | /// that reside in various storage
11 | pub trait AsSid {
12 | fn as_sid(self) -> *const SID;
13 | }
14 |
15 | impl AsSid for *const SID {
16 | fn as_sid(self) -> *const SID {
17 | self
18 | }
19 | }
20 |
21 | /// Return true if sid matches the requested well known sid type
22 | pub fn is_well_known(sid: S, sid_type: WELL_KNOWN_SID_TYPE) -> bool {
23 | let result = unsafe { IsWellKnownSid(sid.as_sid() as *mut _, sid_type) };
24 | result == 1
25 | }
26 |
27 | pub fn get_length_sid(sid: S) -> DWORD {
28 | unsafe { GetLengthSid(sid.as_sid() as *mut _) }
29 | }
30 |
31 | /// Stores the data for a well known sid instance
32 | pub struct WellKnownSid {
33 | data: Vec,
34 | }
35 |
36 | impl WellKnownSid {
37 | /// Construct a Sid from a well known sid identifier
38 | pub fn with_well_known(sid_type: WELL_KNOWN_SID_TYPE) -> IoResult {
39 | // Measure storage requirements by doing a dry run with no buffer
40 | let mut size: DWORD = 0;
41 | let err;
42 | unsafe {
43 | CreateWellKnownSid(
44 | sid_type,
45 | std::ptr::null_mut(),
46 | std::ptr::null_mut(),
47 | &mut size,
48 | );
49 | err = GetLastError();
50 | };
51 | // The call should have failed and told us we need more space
52 | if err != ERROR_INSUFFICIENT_BUFFER {
53 | return Err(IoError::last_os_error());
54 | }
55 |
56 | // Allocate and zero out the storage
57 | let mut data = vec![0u8; size as usize];
58 |
59 | // and now populate the sid for real
60 | unsafe {
61 | if CreateWellKnownSid(
62 | sid_type,
63 | std::ptr::null_mut(),
64 | data.as_mut_ptr() as _,
65 | &mut size,
66 | ) == 0
67 | {
68 | return Err(IoError::last_os_error());
69 | }
70 | }
71 |
72 | Ok(Self { data })
73 | }
74 | }
75 |
76 | impl AsSid for &WellKnownSid {
77 | fn as_sid(self) -> *const SID {
78 | self.data.as_ptr() as *const SID
79 | }
80 | }
81 |
82 | #[cfg(test)]
83 | mod tests {
84 | use super::*;
85 | use winapi::um::winnt::{WinBuiltinAdministratorsSid, WinBuiltinUsersSid};
86 |
87 | #[test]
88 | fn sid_well_known() {
89 | let sid = WellKnownSid::with_well_known(WinBuiltinAdministratorsSid).unwrap();
90 | assert!(is_well_known(&sid, WinBuiltinAdministratorsSid));
91 | assert!(!is_well_known(&sid, WinBuiltinUsersSid));
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/deelevate/src/psuedocon.rs:
--------------------------------------------------------------------------------
1 | use crate::pipe::PipeHandle;
2 | use lazy_static::lazy_static;
3 | use shared_library::shared_library;
4 | use std::io::{Error as IoError, Result as IoResult};
5 | use std::os::windows::io::AsRawHandle;
6 | use std::path::Path;
7 | use winapi::shared::minwindef::DWORD;
8 | use winapi::shared::winerror::{HRESULT, S_OK};
9 | use winapi::um::handleapi::*;
10 | use winapi::um::wincon::COORD;
11 | use winapi::um::winnt::HANDLE;
12 |
13 | pub type HPCON = HANDLE;
14 |
15 | shared_library!(ConPtyFuncs,
16 | pub fn CreatePseudoConsole(
17 | size: COORD,
18 | hInput: HANDLE,
19 | hOutput: HANDLE,
20 | flags: DWORD,
21 | hpc: *mut HPCON
22 | ) -> HRESULT,
23 | pub fn ResizePseudoConsole(hpc: HPCON, size: COORD) -> HRESULT,
24 | pub fn ClosePseudoConsole(hpc: HPCON),
25 | );
26 |
27 | fn load_conpty() -> ConPtyFuncs {
28 | // If the kernel doesn't export these functions then their system is
29 | // too old and we cannot run.
30 | let kernel = ConPtyFuncs::open(Path::new("kernel32.dll")).expect(
31 | "this system does not support conpty. Windows 10 October 2018 or newer is required",
32 | );
33 |
34 | // We prefer to use a sideloaded conpty.dll and openconsole.exe host deployed
35 | // alongside the application. We check for this after checking for kernel
36 | // support so that we don't try to proceed and do something crazy.
37 | if let Ok(sideloaded) = ConPtyFuncs::open(Path::new("conpty.dll")) {
38 | sideloaded
39 | } else {
40 | kernel
41 | }
42 | }
43 |
44 | lazy_static! {
45 | static ref CONPTY: ConPtyFuncs = load_conpty();
46 | }
47 |
48 | pub struct PsuedoCon {
49 | pub(crate) con: HPCON,
50 | }
51 |
52 | unsafe impl Send for PsuedoCon {}
53 | unsafe impl Sync for PsuedoCon {}
54 |
55 | impl Drop for PsuedoCon {
56 | fn drop(&mut self) {
57 | unsafe { (CONPTY.ClosePseudoConsole)(self.con) };
58 | }
59 | }
60 |
61 | impl PsuedoCon {
62 | pub fn new(size: COORD, input: PipeHandle, output: PipeHandle) -> IoResult {
63 | let mut con: HPCON = INVALID_HANDLE_VALUE;
64 | let result = unsafe {
65 | (CONPTY.CreatePseudoConsole)(
66 | size,
67 | input.as_raw_handle() as _,
68 | output.as_raw_handle() as _,
69 | 0,
70 | &mut con,
71 | )
72 | };
73 | if result != S_OK {
74 | Err(IoError::new(
75 | std::io::ErrorKind::Other,
76 | format!("failed to create psuedo console: HRESULT {}", result),
77 | ))
78 | } else {
79 | Ok(Self { con })
80 | }
81 | }
82 |
83 | pub fn resize(&self, size: COORD) -> IoResult<()> {
84 | let result = unsafe { (CONPTY.ResizePseudoConsole)(self.con, size) };
85 | if result != S_OK {
86 | Err(IoError::new(
87 | std::io::ErrorKind::Other,
88 | format!(
89 | "failed to resize console to {}x{}: HRESULT: {}",
90 | size.X, size.Y, result
91 | ),
92 | ))
93 | } else {
94 | Ok(())
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/utils/bin/ptybridge.rs:
--------------------------------------------------------------------------------
1 | use deelevate::{BridgePtyClient, Command, PipeHandle, Token};
2 | use std::convert::TryInto;
3 | use std::ffi::OsString;
4 | use std::path::PathBuf;
5 | use structopt::*;
6 | use winapi::um::wincon::{SetConsoleCP, SetConsoleCursorPosition, SetConsoleOutputCP, COORD};
7 | use winapi::um::winnls::CP_UTF8;
8 |
9 | /// A helper program for `eledo` and `normdo` that is used to
10 | /// bridge pty and pipes between the different privilege levels.
11 | /// This utility is not intended to be run by humans.
12 | #[derive(StructOpt)]
13 | #[structopt(
14 | version = env!("VERGEN_SEMVER_LIGHTWEIGHT")
15 | )]
16 | struct Opt {
17 | #[structopt(long, parse(from_os_str))]
18 | stdin: Option,
19 | #[structopt(long, parse(from_os_str))]
20 | stdout: Option,
21 | #[structopt(long, parse(from_os_str))]
22 | stderr: Option,
23 | #[structopt(long, parse(from_os_str))]
24 | conin: Option,
25 | #[structopt(long, parse(from_os_str))]
26 | conout: Option,
27 |
28 | #[structopt(long)]
29 | width: Option,
30 | #[structopt(long)]
31 | height: Option,
32 |
33 | #[structopt(long)]
34 | cursor_x: Option,
35 | #[structopt(long)]
36 | cursor_y: Option,
37 |
38 | #[structopt(parse(from_os_str))]
39 | args: Vec,
40 | }
41 |
42 | fn main() -> std::io::Result<()> {
43 | let mut opt = Opt::from_args();
44 |
45 | unsafe {
46 | SetConsoleCP(CP_UTF8);
47 | SetConsoleOutputCP(CP_UTF8);
48 | }
49 |
50 | let token = Token::with_current_process()?;
51 |
52 | if let Some(conin) = opt.conin {
53 | let pty_client = BridgePtyClient::with_params(
54 | &conin,
55 | &opt.conout.unwrap(),
56 | opt.width.unwrap(),
57 | opt.height.unwrap(),
58 | )?;
59 |
60 | let mut args: Vec = vec![std::env::current_exe()?.into()];
61 |
62 | if let Some(stdin) = opt.stdin {
63 | args.push("--stdin".into());
64 | args.push(stdin.into());
65 | }
66 | if let Some(stdout) = opt.stdout {
67 | args.push("--stdout".into());
68 | args.push(stdout.into());
69 | }
70 | if let Some(stderr) = opt.stderr {
71 | args.push("--stderr".into());
72 | args.push(stderr.into());
73 | }
74 | if let Some(cursor_x) = opt.cursor_x {
75 | args.push("--cursor-x".into());
76 | args.push(cursor_x.to_string().into());
77 | }
78 | if let Some(cursor_y) = opt.cursor_x {
79 | args.push("--cursor-y".into());
80 | args.push(cursor_y.to_string().into());
81 | }
82 |
83 | args.push("--".into());
84 | args.append(&mut opt.args);
85 |
86 | let mut cmd = Command::with_environment_for_token(&token)?;
87 | cmd.set_argv(args);
88 |
89 | let exit_code = pty_client.run(cmd)?;
90 | std::process::exit(exit_code as _);
91 | } else {
92 | let mut cmd = Command::with_environment_for_token(&token)?;
93 | cmd.set_argv(opt.args);
94 |
95 | if let Some(stdin) = opt.stdin {
96 | cmd.set_stdin(PipeHandle::open_pipe(stdin)?)?;
97 | }
98 | if let Some(stdout) = opt.stdout {
99 | cmd.set_stdout(PipeHandle::open_pipe(stdout)?)?;
100 | }
101 | if let Some(stderr) = opt.stderr {
102 | cmd.set_stderr(PipeHandle::open_pipe(stderr)?)?;
103 | }
104 |
105 | if let Some(cursor_x) = opt.cursor_x {
106 | let conout = PipeHandle::open_pipe("CONOUT$")?;
107 | unsafe {
108 | SetConsoleCursorPosition(
109 | conout.as_handle(),
110 | COORD {
111 | X: cursor_x.try_into().unwrap(),
112 | Y: opt.cursor_y.unwrap().try_into().unwrap(),
113 | },
114 | );
115 | }
116 | }
117 |
118 | let proc = cmd.spawn()?;
119 | let _ = proc.wait_for(None)?;
120 | let exit_code = proc.exit_code()?;
121 | std::process::exit(exit_code as _);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/deelevate/src/spawn.rs:
--------------------------------------------------------------------------------
1 | use crate::bridge::BridgeServer;
2 | use crate::command::*;
3 | use crate::{PrivilegeLevel, Token};
4 | use std::convert::TryInto;
5 | use std::io::Result as IoResult;
6 |
7 | /// Spawn a copy of the current process using the provided token.
8 | /// The existing streams are passed through to the child.
9 | /// On success, does not return to the caller; it will terminate
10 | /// the current process and assign the exit status from the child.
11 | fn spawn_with_current_io_streams(token: &Token) -> IoResult<()> {
12 | let mut cmd = Command::with_environment_for_token(token)?;
13 | cmd.set_command_from_current_process()?;
14 | let proc = cmd.spawn_as_user(token)?;
15 |
16 | proc.wait_for(None)?;
17 |
18 | let exit_code = proc.exit_code()?;
19 | std::process::exit(exit_code.try_into().unwrap());
20 | }
21 |
22 | /// If the token is PrivilegeLevel::NotPrivileged then this function
23 | /// will return `Ok` and the intent is that the host program continue
24 | /// with its normal operation.
25 | ///
26 | /// Otherwise, assuming no errors were detected, this function will
27 | /// not return to the caller. Instead a reduced privilege token
28 | /// will be created and used to spawn a copy of the host program,
29 | /// passing through the arguments from the current process.
30 | /// *This* process will remain running to bridge pipes for the stdio
31 | /// streams to the new process and to wait for the child process
32 | /// and then terminate *this* process and exit with the exit code
33 | /// from the child.
34 | pub fn spawn_with_normal_privileges() -> IoResult<()> {
35 | let token = Token::with_current_process()?;
36 | let level = token.privilege_level()?;
37 |
38 | match level {
39 | PrivilegeLevel::NotPrivileged => Ok(()),
40 | PrivilegeLevel::Elevated => {
41 | let target_token = Token::with_shell_process()?;
42 | let mut server = BridgeServer::new();
43 | let mut argv = std::env::args_os().collect();
44 | let mut bridge_cmd = server.start_for_command(&mut argv, &target_token)?;
45 | let proc = bridge_cmd.spawn_with_token(&target_token)?;
46 | std::process::exit(server.serve(proc)? as _);
47 | }
48 | PrivilegeLevel::HighIntegrityAdmin => {
49 | let medium_token = token.as_medium_integrity_safer_token()?;
50 | spawn_with_current_io_streams(&medium_token)
51 | }
52 | }
53 | }
54 |
55 | /// If the token is NOT PrivilegeLevel::NotPrivileged then this function
56 | /// will return `Ok` and the intent is that the host program continue
57 | /// with its normal operation.
58 | ///
59 | /// Otherwise, assuming no errors were detected, this function will
60 | /// not return to the caller. Instead an elevated privilege token
61 | /// will be created and used to spawn a copy of the host program,
62 | /// passing through the arguments from the current process.
63 | /// *This* process will remain running to bridge pipes for the stdio
64 | /// streams to the new process and to wait for the child process
65 | /// and then terminate *this* process and exit with the exit code
66 | /// from the child.
67 | pub fn spawn_with_elevated_privileges() -> IoResult<()> {
68 | let token = Token::with_current_process()?;
69 | let level = token.privilege_level()?;
70 |
71 | let target_token = match level {
72 | PrivilegeLevel::NotPrivileged => token.as_medium_integrity_safer_token()?,
73 | PrivilegeLevel::HighIntegrityAdmin | PrivilegeLevel::Elevated => return Ok(()),
74 | };
75 |
76 | let mut server = BridgeServer::new();
77 | let mut argv = std::env::args_os().collect();
78 | let mut bridge_cmd = server.start_for_command(&mut argv, &target_token)?;
79 | let proc = bridge_cmd.spawn_with_token(&target_token)?;
80 | std::process::exit(server.serve(proc)? as _);
81 | }
82 |
83 | /// This function is for use by C/C++ code that wants to test whether the
84 | /// current session is elevated. The return value is 0 for a non-privileged
85 | /// process and non-zero for a privileged process.
86 | /// If an error occurs while obtaining this information, the program will
87 | /// terminate.
88 | #[no_mangle]
89 | pub extern "C" fn deelevate_is_privileged_process() -> i32 {
90 | match Token::with_current_process().and_then(|token| token.privilege_level()) {
91 | Ok(PrivilegeLevel::Elevated) | Ok(PrivilegeLevel::HighIntegrityAdmin) => 1,
92 | Ok(PrivilegeLevel::NotPrivileged) => 0,
93 | Err(e) => {
94 | eprintln!(
95 | "An error occurred while determining the privilege level: {}",
96 | e
97 | );
98 | std::process::exit(1);
99 | }
100 | }
101 | }
102 |
103 | /// This function is for use by C/C++ code that wants to ensure that execution
104 | /// will only continue if the current token has a Normal privilege level.
105 | /// This function will attempt to re-execute the program in the appropriate
106 | /// context.
107 | /// This function will only return if the current context has normal privs.
108 | #[no_mangle]
109 | pub extern "C" fn deelevate_requires_normal_privileges() {
110 | if let Err(e) = spawn_with_normal_privileges() {
111 | eprintln!(
112 | "This program requires running with Normal privileges and \
113 | encountered an issue while attempting to run in that context: {}",
114 | e
115 | );
116 | std::process::exit(1);
117 | }
118 | }
119 |
120 | /// This function is for use by C/C++ code that wants to ensure that execution
121 | /// will only continue if the current token has an Elevated privilege level.
122 | /// This function will attempt to re-execute the program in the appropriate
123 | /// context.
124 | /// This function will only return if the current context has Elevated or
125 | /// High Integrity Admin privs.
126 | #[no_mangle]
127 | pub extern "C" fn deelevate_requires_elevated_privileges() {
128 | if let Err(e) = spawn_with_elevated_privileges() {
129 | eprintln!(
130 | "This program requires running with Elevated privileges and \
131 | encountered an issue while attempting to run in that context: {}",
132 | e
133 | );
134 | std::process::exit(1);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/spawn-output.md:
--------------------------------------------------------------------------------
1 | ## local session not elevated:
2 |
3 | ```
4 | > cargo run --example spawn
5 | Finished dev [unoptimized + debuginfo] target(s) in 0.05s
6 | Running `target\debug\examples\spawn.exe`
7 |
8 | GROUP INFORMATION
9 | -----------------
10 |
11 | Group Name Type SID Attributes
12 | ============================================================= ================ ============ ==================================================
13 | Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
14 | NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Group used for deny only
15 | BUILTIN\Administrators Alias S-1-5-32-544 Group used for deny only
16 | BUILTIN\Performance Log Users Alias S-1-5-32-559 Mandatory group, Enabled by default, Enabled group
17 | BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
18 | NT AUTHORITY\INTERACTIVE Well-known group S-1-5-4 Mandatory group, Enabled by default, Enabled group
19 | CONSOLE LOGON Well-known group S-1-2-1 Mandatory group, Enabled by default, Enabled group
20 | NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
21 | NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
22 | NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
23 | LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group
24 | NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
25 | Mandatory Label\Medium Mandatory Level Label S-1-16-8192
26 |
27 | priv level is NotPrivileged
28 | now I'm safe to proceed with reduced privs
29 |
30 | ```
31 |
32 | ## elevated powershell:
33 |
34 | ```
35 | PS C:\Users\wez\wez-personal\deelevate> .\target\debug\examples\spawn.exe
36 |
37 | GROUP INFORMATION
38 | -----------------
39 |
40 | Group Name Type SID Attributes
41 | ============================================================= ================ ============ ===============================================================
42 | Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
43 | NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Mandatory group, Enabled by default, Enabled group
44 | BUILTIN\Administrators Alias S-1-5-32-544 Mandatory group, Enabled by default, Enabled group, Group owner
45 | BUILTIN\Performance Log Users Alias S-1-5-32-559 Mandatory group, Enabled by default, Enabled group
46 | BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
47 | NT AUTHORITY\INTERACTIVE Well-known group S-1-5-4 Mandatory group, Enabled by default, Enabled group
48 | CONSOLE LOGON Well-known group S-1-2-1 Mandatory group, Enabled by default, Enabled group
49 | NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
50 | NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
51 | NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
52 | LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group
53 | NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
54 | Mandatory Label\High Mandatory Level Label S-1-16-12288
55 |
56 |
57 | priv level is Elevated
58 |
59 | GROUP INFORMATION
60 | -----------------
61 |
62 | Group Name Type SID Attributes
63 | ============================================================= ================ ============ ==================================================
64 | Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
65 | NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Group used for deny only
66 | BUILTIN\Administrators Alias S-1-5-32-544 Group used for deny only
67 | BUILTIN\Performance Log Users Alias S-1-5-32-559 Mandatory group, Enabled by default, Enabled group
68 | BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
69 | NT AUTHORITY\INTERACTIVE Well-known group S-1-5-4 Mandatory group, Enabled by default, Enabled group
70 | CONSOLE LOGON Well-known group S-1-2-1 Mandatory group, Enabled by default, Enabled group
71 | NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
72 | NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
73 | NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
74 | LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group
75 | NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
76 |
77 | Mandatory Label\Medium Mandatory Level Label S-1-16-8192
78 |
79 |
80 | priv level is NotPrivileged
81 | now I'm safe to proceed with reduced privs
82 | ```
83 |
84 | ## ssh session:
85 |
86 | ```
87 | C:\Users\wez\wez-personal\deelevate>target\debug\examples\spawn.exe
88 |
89 | GROUP INFORMATION
90 | -----------------
91 |
92 | Group Name Type SID Attributes
93 | ============================================================= ================ ============ ===============================================================
94 | Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
95 | NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Mandatory group, Enabled by default, Enabled group
96 | BUILTIN\Administrators Alias S-1-5-32-544 Mandatory group, Enabled by default, Enabled group, Group owner
97 | BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
98 | NT AUTHORITY\NETWORK Well-known group S-1-5-2 Mandatory group, Enabled by default, Enabled group
99 | NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
100 | NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
101 | NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
102 | NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
103 | Mandatory Label\High Mandatory Level Label S-1-16-12288
104 |
105 | priv level is HighIntegrityAdmin
106 |
107 | GROUP INFORMATION
108 | -----------------
109 |
110 | Group Name Type SID Attributes
111 | ============================================================= ================ ============ ==================================================
112 | Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
113 | NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Mandatory group, Enabled by default, Enabled group
114 | BUILTIN\Administrators Alias S-1-5-32-544 Group used for deny only
115 | BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
116 | NT AUTHORITY\NETWORK Well-known group S-1-5-2 Mandatory group, Enabled by default, Enabled group
117 | NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
118 | NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
119 | NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
120 | NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
121 | Mandatory Label\Medium Mandatory Level Label S-1-16-8192
122 |
123 | priv level is NotPrivileged
124 | now I'm safe to proceed with reduced privs
125 |
126 | ```
127 |
128 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EleDo - Elevated-Do
2 |
3 | [Download EleDo.zip](https://github.com/wez/deelevate/releases/download/Continuous/EleDo.zip)
4 |
5 | This repo is the home of EleDo (and NormDo), utilities that allow switching
6 | privilege levels from the command line on Windows 10 and later.
7 |
8 | The core privilege shifting code is also available as a Rust crate and a
9 | small C library to make it possible for an console based application to
10 | to detect and adjust its privilege level.
11 |
12 | While this repo is named for the privilege elevation aspect, it originally
13 | started life with the goal of reducing privileges to "normal" levels.
14 |
15 | ## Why Elevate?
16 |
17 | In some cases you need a higher level of access than is normal; for example, to
18 | install software or make system configuration. This is not new; most users
19 | will use the "Run as Administrator" option for a powershell session and run
20 | their commands that way, leaving the privileged session open for convenience,
21 | mixing commands and running most of them with higher privileges than are
22 | strictly required.
23 |
24 | Users coming from unix systems generally prefer not to do this and instead use
25 | a utility known as `sudo` (Super-User Do) to run a specific command with
26 | increased privileges.
27 |
28 | This repo provides `eledo.exe` as a workalike to `sudo`; it will attempt to
29 | increase privileges using the User Account Control mechanism built in to
30 | Windows to prompt the user to confirm that it should run with increased
31 | privileges.
32 |
33 | This repo also provides `normdo.exe` (Normal User Do) that works in the
34 | opposite way, dropping privileges back to normal levels to run a command.
35 | This functionality is important for security-minded code that wishes to
36 | run with lower (if not least!) privilege regardless of the privilege level
37 | of the code that invoked it.
38 |
39 | ## How do I use the Rust crate?
40 |
41 | There are two logical halves to this crate;
42 |
43 | * Detecting the privilege level, including both *Elevation* and *High Integrity
44 | Administrative* privs, so that the embedding application can choose whether
45 | to surface this as an error, or to continue with the second half of the crate...
46 |
47 | ```rust
48 | use deelevate::{Token, PrivilegeLevel};
49 |
50 | let token = Token::with_current_process()?;
51 | match token.privilege_level()? {
52 | PrivilegeLevel::NotPrivileged => {
53 | // No special privs
54 | }
55 | PrivilegeLevel::Elevated => {
56 | // Invoked via runas
57 | }
58 | PrivilegeLevel::HighIntegrityAdmin => {
59 | // Some other kind of admin priv.
60 | // For example: ssh session to Windows 10 SSH server
61 | }
62 | }
63 | ```
64 |
65 | * Re-executing the application with altered privs, while passing the stdio
66 | streams and process exit status back to the original parent.
67 |
68 | ```rust
69 | use deelevate::spawn_with_normal_privileges;
70 | use deelevate::spawn_with_elevated_privileges;
71 |
72 | // If we have admin privs, this next line will either spawn a version
73 | // of the current process with reduced privs, or yield an error trying
74 | // to do that.
75 | // The spawn_with_elevated_privileges function works similarly, except
76 | // that it will only return when the calling process has elevated
77 | // privs.
78 | spawn_with_normal_privileges()?;
79 |
80 | // If we reach this line it is because we don't have any special privs
81 | // and we can therefore continue with our normal operation.
82 | ```
83 |
84 | The `show` example demonstrates testing for the privilege level.
85 |
86 | The `spawn` example demonstrates re-executing the process at a lower priv level.
87 |
88 | ## Caveats?
89 |
90 | There are some privilege levels that are not mapped as privileged from the
91 | perspective of this crate. The rationale for this is that those levels are
92 | unusual enough that they are probably not humans and probably should not have
93 | this crate adjusting the privilege level.
94 |
95 | It may feel like this might be a security concern, but its worth noting that:
96 |
97 | * The calling code already has equal or higher privilege (so no escalation is possible)
98 | * This crate is intended for convenience and consistency for human users
99 |
100 | ## Utilities
101 |
102 | This crate provides `normdo.exe` for running a command with normal privileges,
103 | and `eledo.exe` for running a command with elevated privileges. Unlike other
104 | elevation solutions, both of these utilities are designed to run from inside
105 | a console and to keep the output from the target application in that console.
106 | In addition, these tools use the PTY APIs in order to support running terminal
107 | applications such as pagers and editors (vim.exe!) correctly!
108 |
109 | Both of these tools require that the `eledo-pty-bridge.exe` be installed
110 | alongside them, or otherwise be in the PATH. The bridge process is required
111 | to host the PTY and spawn the program in the alternatively privileged
112 | context.
113 |
114 | ### `eledo.exe`
115 |
116 | *Runs a program with elevated privs*
117 |
118 | ```
119 | eledo.exe PROGRAM [ARGUMENTS]
120 | ```
121 |
122 | `eledo.exe` will check to see if the current context has admin privileges;
123 | if it does then it will execute the requested `PROGRAM` directly, returning
124 | its exit status.
125 |
126 | Otherwise, `eledo.exe` will arrange to run the program with an elevated PTY
127 | that is bridged to the current terminal session. Elevation requires that the
128 | current process be able to communicate with the shell in the current desktop
129 | session, and will typically trigger a UAC prompt for that user.
130 |
131 | ```
132 | > eledo.exe whoami /groups
133 |
134 | GROUP INFORMATION
135 | -----------------
136 |
137 | Group Name Type SID Attributes
138 | ============================================================= ================ ============ ===============================================================
139 | Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
140 | NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Mandatory group, Enabled by default, Enabled group
141 | BUILTIN\Administrators Alias S-1-5-32-544 Mandatory group, Enabled by default, Enabled group, Group owner
142 | BUILTIN\Performance Log Users Alias S-1-5-32-559 Mandatory group, Enabled by default, Enabled group
143 | BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
144 | NT AUTHORITY\INTERACTIVE Well-known group S-1-5-4 Mandatory group, Enabled by default, Enabled group
145 | CONSOLE LOGON Well-known group S-1-2-1 Mandatory group, Enabled by default, Enabled group
146 | NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
147 | NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
148 | NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
149 | LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group
150 | NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
151 | Mandatory Label\High Mandatory Level Label S-1-16-12288
152 | ```
153 |
154 | ### `normdo.exe`
155 |
156 | *Runs a program with normal privs*
157 |
158 | ```
159 | normdo.exe PROGRAM [ARGUMENTS]
160 | ```
161 |
162 | `normdo.exe` will check to see if the current context has admin privileges;
163 | if it does *not* then it will execute the requested `PROGRAM` directly, returning
164 | its exit status.
165 |
166 | Otherwise, `eledo.exe` will arrange to run the program with a Normal user token
167 | with Medium integrity level, dropping/denying the local administrator group
168 | from the current token. The program will be run in a PTY that is bridged to
169 | the current terminal session.
170 |
171 | ```
172 | > normdo.exe whoami /groups
173 |
174 | GROUP INFORMATION
175 | -----------------
176 |
177 | Group Name Type SID Attributes
178 | ============================================================= ================ ============ ==================================================
179 | Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
180 | NT AUTHORITY\Local account and member of Administrators group Well-known group S-1-5-114 Group used for deny only
181 | BUILTIN\Administrators Alias S-1-5-32-544 Group used for deny only
182 | BUILTIN\Performance Log Users Alias S-1-5-32-559 Mandatory group, Enabled by default, Enabled group
183 | BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
184 | NT AUTHORITY\INTERACTIVE Well-known group S-1-5-4 Mandatory group, Enabled by default, Enabled group
185 | CONSOLE LOGON Well-known group S-1-2-1 Mandatory group, Enabled by default, Enabled group
186 | NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
187 | NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
188 | NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
189 | LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group
190 | NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
191 | Mandatory Label\Medium Mandatory Level Label S-1-16-8192
192 | ```
193 |
194 | ## Thanks
195 |
196 | The elevator icons embedded into the utilities were made by Pixel perfect from www.flaticon.com
197 |
--------------------------------------------------------------------------------
/deelevate/src/pipe.rs:
--------------------------------------------------------------------------------
1 | use crate::{os_str_to_null_terminated_vec, win32_error_with_context, Token};
2 | use std::io::{Error as IoError, Result as IoResult};
3 | use std::os::windows::prelude::*;
4 | use std::path::{Path, PathBuf};
5 | use std::ptr::null_mut;
6 | use std::sync::atomic::AtomicUsize;
7 | use winapi::shared::winerror::ERROR_PIPE_CONNECTED;
8 | use winapi::um::errhandlingapi::GetLastError;
9 | use winapi::um::fileapi::{CreateFileW, FlushFileBuffers, ReadFile, WriteFile, OPEN_EXISTING};
10 | use winapi::um::handleapi::{
11 | CloseHandle, DuplicateHandle, SetHandleInformation, INVALID_HANDLE_VALUE,
12 | };
13 | use winapi::um::minwinbase::SECURITY_ATTRIBUTES;
14 | use winapi::um::namedpipeapi::{ConnectNamedPipe, CreateNamedPipeW, CreatePipe};
15 | use winapi::um::processthreadsapi::GetCurrentProcess;
16 | use winapi::um::processthreadsapi::GetCurrentProcessId;
17 | use winapi::um::winbase::*;
18 | use winapi::um::winnt::{DUPLICATE_SAME_ACCESS, GENERIC_READ, GENERIC_WRITE, HANDLE};
19 |
20 | /// A little container type for holding a pipe file handle
21 | #[derive(Debug)]
22 | pub struct PipeHandle(HANDLE);
23 | /// The compiler thinks it isn't send because HANDLE is a pointer
24 | /// type. We happen to know that moving the handle between threads
25 | /// is totally fine, hence this impl.
26 | unsafe impl Send for PipeHandle {}
27 |
28 | impl PipeHandle {
29 | pub fn make_inheritable(&self) -> IoResult<()> {
30 | let res = unsafe { SetHandleInformation(self.0, HANDLE_FLAG_INHERIT, 1) };
31 | if res != 1 {
32 | Err(win32_error_with_context(
33 | "SetHandleInformation HANDLE_FLAG_INHERIT",
34 | IoError::last_os_error(),
35 | ))
36 | } else {
37 | Ok(())
38 | }
39 | }
40 |
41 | pub fn as_handle(&self) -> HANDLE {
42 | self.0
43 | }
44 |
45 | pub fn create_named_pipe_byte_mode_for_token>(
46 | name: P,
47 | token: &Token,
48 | ) -> IoResult {
49 | let descriptor = token.create_security_descriptor()?;
50 |
51 | let path = os_str_to_null_terminated_vec(name.as_ref().as_os_str());
52 | let max_instances = 1;
53 | let buf_size = 4096;
54 | let default_timeout_ms = 100;
55 | let mut security_attr = SECURITY_ATTRIBUTES {
56 | nLength: std::mem::size_of::() as _,
57 | lpSecurityDescriptor: descriptor.0,
58 | bInheritHandle: 0,
59 | };
60 |
61 | let handle = unsafe {
62 | CreateNamedPipeW(
63 | path.as_ptr(),
64 | PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
65 | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
66 | max_instances,
67 | buf_size,
68 | buf_size,
69 | default_timeout_ms,
70 | &mut security_attr,
71 | )
72 | };
73 | if handle != INVALID_HANDLE_VALUE {
74 | Ok(Self(handle))
75 | } else {
76 | Err(win32_error_with_context(
77 | "CreateNamedPipeW",
78 | IoError::last_os_error(),
79 | ))
80 | }
81 | }
82 |
83 | /// Wait for a short period for a client to connect to
84 | /// this pipe instance.
85 | pub fn wait_for_pipe_client(&self) -> IoResult<()> {
86 | // One does not simply do non-blocking pipe work.
87 | // We spawn a thread that will cancel all IO on this pipe if
88 | // we don't send it a message within the timeout.
89 | use std::sync::mpsc::channel;
90 | let (tx, rx) = channel();
91 |
92 | // A little helper for safely passing the HANDLE
93 | // pointer to another thread
94 | struct HandleHolder(HANDLE);
95 | unsafe impl Send for HandleHolder {}
96 | let handle = HandleHolder(self.0);
97 |
98 | // This thread will cancel all IO on self.0 if not signalled
99 | // in time to stop it.
100 | std::thread::spawn(move || {
101 | if rx
102 | .recv_timeout(std::time::Duration::from_millis(2500))
103 | .is_err()
104 | {
105 | unsafe { winapi::um::ioapiset::CancelIoEx(handle.0, null_mut()) };
106 | }
107 | });
108 |
109 | // Wrap up the connect operation in a lambda so that we can
110 | // ensure that we send a message to the timeout thread before
111 | // we unwind.
112 | let res = (move || {
113 | let res = unsafe { ConnectNamedPipe(self.0, null_mut()) };
114 | let err = unsafe { GetLastError() };
115 | if res == 0 && err != ERROR_PIPE_CONNECTED {
116 | Err(win32_error_with_context(
117 | "ConnectNamedPipe",
118 | IoError::last_os_error(),
119 | ))
120 | } else {
121 | Ok(())
122 | }
123 | })();
124 | let _ = tx.send(());
125 |
126 | res
127 | }
128 |
129 | pub fn duplicate(&self) -> IoResult {
130 | let proc = unsafe { GetCurrentProcess() };
131 | let mut duped = INVALID_HANDLE_VALUE;
132 | let inheritable = false;
133 | let access = 0;
134 | let res = unsafe {
135 | DuplicateHandle(
136 | proc,
137 | self.0,
138 | proc,
139 | &mut duped,
140 | access,
141 | inheritable as _,
142 | DUPLICATE_SAME_ACCESS,
143 | )
144 | };
145 | if res == 0 {
146 | Err(win32_error_with_context(
147 | "DuplicateHandle",
148 | IoError::last_os_error(),
149 | ))
150 | } else {
151 | Ok(Self(duped))
152 | }
153 | }
154 |
155 | pub fn open_pipe>(name: P) -> IoResult {
156 | let path = os_str_to_null_terminated_vec(name.as_ref().as_os_str());
157 | let share_mode = 0;
158 | let mut security_attr = SECURITY_ATTRIBUTES {
159 | nLength: std::mem::size_of::() as _,
160 | lpSecurityDescriptor: null_mut(),
161 | bInheritHandle: 0,
162 | };
163 |
164 | let flags = 0;
165 | let template_file = null_mut();
166 | let handle = unsafe {
167 | CreateFileW(
168 | path.as_ptr(),
169 | GENERIC_READ | GENERIC_WRITE,
170 | share_mode,
171 | &mut security_attr,
172 | OPEN_EXISTING,
173 | flags,
174 | template_file,
175 | )
176 | };
177 | if handle != INVALID_HANDLE_VALUE {
178 | Ok(Self(handle))
179 | } else {
180 | let err = IoError::last_os_error();
181 | Err(win32_error_with_context(
182 | &format!("CreateFileW: {}", name.as_ref().display()),
183 | err,
184 | ))
185 | }
186 | }
187 | }
188 |
189 | impl AsRawHandle for PipeHandle {
190 | fn as_raw_handle(&self) -> RawHandle {
191 | self.as_handle() as RawHandle
192 | }
193 | }
194 |
195 | impl Drop for PipeHandle {
196 | fn drop(&mut self) {
197 | unsafe {
198 | CloseHandle(self.0);
199 | }
200 | }
201 | }
202 |
203 | impl std::io::Read for PipeHandle {
204 | fn read(&mut self, buf: &mut [u8]) -> IoResult {
205 | let mut num_read = 0;
206 | let ok = unsafe {
207 | ReadFile(
208 | self.0,
209 | buf.as_mut_ptr() as *mut _,
210 | buf.len() as _,
211 | &mut num_read,
212 | null_mut(),
213 | )
214 | };
215 | if ok == 0 {
216 | let err = IoError::last_os_error();
217 | Err(win32_error_with_context("ReadFile", err))
218 | /*
219 | if err.kind() == std::io::ErrorKind::BrokenPipe {
220 | Ok(0)
221 | } else {
222 | Err(win32_error_with_context("ReadFile", err))
223 | }
224 | */
225 | } else {
226 | Ok(num_read as usize)
227 | }
228 | }
229 | }
230 |
231 | impl std::io::Write for PipeHandle {
232 | fn write(&mut self, buf: &[u8]) -> IoResult {
233 | let mut num_wrote = 0;
234 | let ok = unsafe {
235 | WriteFile(
236 | self.0,
237 | buf.as_ptr() as *const _,
238 | buf.len() as u32,
239 | &mut num_wrote,
240 | null_mut(),
241 | )
242 | };
243 | if ok == 0 {
244 | Err(win32_error_with_context(
245 | "WriteFile",
246 | IoError::last_os_error(),
247 | ))
248 | } else {
249 | Ok(num_wrote as usize)
250 | }
251 | }
252 |
253 | fn flush(&mut self) -> IoResult<()> {
254 | if unsafe { FlushFileBuffers(self.0) } != 1 {
255 | Err(win32_error_with_context(
256 | "FlushFileBuffers",
257 | IoError::last_os_error(),
258 | ))
259 | } else {
260 | Ok(())
261 | }
262 | }
263 | }
264 |
265 | pub struct NamedPipeServer {
266 | pub pipe: PipeHandle,
267 | pub path: PathBuf,
268 | }
269 |
270 | impl NamedPipeServer {
271 | pub fn for_token(token: &Token) -> IoResult {
272 | static ID: AtomicUsize = AtomicUsize::new(1);
273 | let path: PathBuf = format!(
274 | "\\\\.\\pipe\\eledo-bridge-{:x}-{:x}-{:x}",
275 | unsafe { GetCurrentProcessId() },
276 | ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
277 | rand::random::()
278 | )
279 | .into();
280 | let pipe = PipeHandle::create_named_pipe_byte_mode_for_token(&path, token)?;
281 | Ok(Self { pipe, path })
282 | }
283 | }
284 |
285 | /// A little helper for creating a pipe
286 | pub struct PipePair {
287 | pub read: PipeHandle,
288 | pub write: PipeHandle,
289 | }
290 |
291 | impl PipePair {
292 | /// Create a new pipe
293 | #[allow(unused)]
294 | pub fn new() -> IoResult {
295 | let mut sa = SECURITY_ATTRIBUTES {
296 | nLength: std::mem::size_of::() as u32,
297 | lpSecurityDescriptor: null_mut(),
298 | bInheritHandle: 0,
299 | };
300 | let mut read: HANDLE = INVALID_HANDLE_VALUE as _;
301 | let mut write: HANDLE = INVALID_HANDLE_VALUE as _;
302 | if unsafe { CreatePipe(&mut read, &mut write, &mut sa, 0) } == 0 {
303 | return Err(win32_error_with_context(
304 | "CreatePipe",
305 | IoError::last_os_error(),
306 | ));
307 | }
308 | Ok(Self {
309 | read: PipeHandle(read),
310 | write: PipeHandle(write),
311 | })
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/deelevate/src/command.rs:
--------------------------------------------------------------------------------
1 | use crate::pipe::*;
2 | use crate::process::Process;
3 | use crate::procthreadattr::ProcThreadAttributeList;
4 | use crate::psuedocon::PsuedoCon;
5 | use crate::{os_str_to_null_terminated_vec, win32_error_with_context, Token};
6 | use std::ffi::{OsStr, OsString};
7 | use std::io::{Error as IoError, Result as IoResult};
8 | use std::os::windows::ffi::OsStrExt;
9 | use std::path::PathBuf;
10 | use std::ptr::null_mut;
11 | use winapi::shared::minwindef::{BOOL, DWORD, LPVOID};
12 | use winapi::um::combaseapi::CoInitializeEx;
13 | use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
14 | use winapi::um::objbase::{COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE};
15 | use winapi::um::processthreadsapi::{
16 | CreateProcessAsUserW, CreateProcessW, PROCESS_INFORMATION, STARTUPINFOW,
17 | };
18 | use winapi::um::shellapi::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW};
19 | use winapi::um::userenv::{CreateEnvironmentBlock, DestroyEnvironmentBlock};
20 | use winapi::um::winbase::{
21 | CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP,
22 | CREATE_UNICODE_ENVIRONMENT, EXTENDED_STARTUPINFO_PRESENT, STARTF_USESHOWWINDOW,
23 | STARTF_USESTDHANDLES, STARTUPINFOEXW,
24 | };
25 | use winapi::um::winnt::{HANDLE, LPCWSTR, LPWSTR};
26 | use winapi::um::winuser::{SW_HIDE, SW_SHOWNORMAL};
27 |
28 | extern "system" {
29 | /// This is missing from the currently available versions of the winapi crate.
30 | fn CreateProcessWithTokenW(
31 | hToken: HANDLE,
32 | dwLogonFlags: DWORD,
33 | lpApplicationName: LPCWSTR,
34 | lpCommandLine: LPWSTR,
35 | dwCreationFlags: DWORD,
36 | lpEnvironment: LPVOID,
37 | lpCurrentDirectory: LPCWSTR,
38 | lpStartupInfo: *mut STARTUPINFOW,
39 | lpProcessInformation: *mut PROCESS_INFORMATION,
40 | ) -> BOOL;
41 | }
42 |
43 | pub struct EnvironmentBlock(pub LPVOID);
44 | impl Drop for EnvironmentBlock {
45 | fn drop(&mut self) {
46 | unsafe {
47 | DestroyEnvironmentBlock(self.0);
48 | }
49 | }
50 | }
51 |
52 | impl EnvironmentBlock {
53 | /// Create a copy of the current environment, but do so for the provided token.
54 | /// We have to do this explicitly as some of the CreateProcessAsXXX
55 | /// calls will default to a different process environment otherwise!
56 | pub fn with_token(token: &Token) -> IoResult {
57 | let mut block = null_mut();
58 | let inherit = true;
59 | if unsafe { CreateEnvironmentBlock(&mut block, token.token, inherit as _) } != 1 {
60 | Err(win32_error_with_context(
61 | "CreateEnvironmentBlock",
62 | IoError::last_os_error(),
63 | ))
64 | } else {
65 | Ok(Self(block))
66 | }
67 | }
68 |
69 | pub fn as_vec(&self) -> Vec {
70 | // This is safe because we know that the block was created by
71 | // CreateEnvironmentBlock and that it is well formed.
72 | // An environment block has the form:
73 | // key=value\0
74 | // ...
75 | // key=value\0
76 | // \0
77 | // So when we find the sequence \0\0 then we have found the extent
78 | // of the block.
79 | unsafe {
80 | let mut ptr = self.0 as *const u16;
81 | let mut size = 0;
82 | loop {
83 | let next = ptr.add(1);
84 | if ptr.read() == 0 {
85 | if next.read() == 0 {
86 | // We found the double-null terminator
87 | size += 2;
88 | break;
89 | }
90 | }
91 | ptr = next;
92 | size += 1;
93 | }
94 |
95 | let slice = std::slice::from_raw_parts(self.0 as *const u16, size);
96 | slice.to_vec()
97 | }
98 | }
99 | }
100 |
101 | /// Helper for ensuring that handles from a spawned
102 | /// process are closed
103 | struct ProcInfo(PROCESS_INFORMATION);
104 | impl Drop for ProcInfo {
105 | fn drop(&mut self) {
106 | unsafe {
107 | if !self.0.hProcess.is_null() {
108 | CloseHandle(self.0.hProcess);
109 | }
110 | if !self.0.hThread.is_null() {
111 | CloseHandle(self.0.hThread);
112 | }
113 | }
114 | }
115 | }
116 |
117 | impl ProcInfo {
118 | pub fn new() -> Self {
119 | Self(unsafe { std::mem::zeroed() })
120 | }
121 |
122 | /// Take ownership of the process handle
123 | pub fn process(&mut self) -> Option {
124 | if self.0.hProcess.is_null() {
125 | None
126 | } else {
127 | let proc = Process::with_handle(self.0.hProcess);
128 | self.0.hProcess = null_mut();
129 | Some(proc)
130 | }
131 | }
132 | }
133 |
134 | pub struct Command {
135 | args: Vec,
136 | env: Vec,
137 | cwd: PathBuf,
138 | hide_window: bool,
139 | stdin: Option,
140 | stdout: Option,
141 | stderr: Option,
142 | }
143 |
144 | impl Command {
145 | pub fn with_environment_for_token(token: &Token) -> IoResult {
146 | let env = EnvironmentBlock::with_token(token)?.as_vec();
147 | let cwd = std::env::current_dir()?;
148 | Ok(Self {
149 | args: vec![],
150 | env,
151 | cwd,
152 | stdin: None,
153 | stdout: None,
154 | stderr: None,
155 | hide_window: false,
156 | })
157 | }
158 |
159 | pub fn set_command_from_current_process(&mut self) -> IoResult<()> {
160 | self.args = std::env::args_os().collect();
161 | Ok(())
162 | }
163 |
164 | pub fn hide_window(&mut self) {
165 | self.hide_window = true;
166 | }
167 |
168 | pub fn set_argv(&mut self, argv: Vec) {
169 | self.args = argv;
170 | }
171 |
172 | fn executable_and_command_line(&self, skip: usize) -> IoResult<(Vec, Vec)> {
173 | let exe_path = PathBuf::from(self.args[0].clone());
174 | let exe_path = if !exe_path.has_root() {
175 | pathsearch::find_executable_in_path(&exe_path).ok_or_else(|| {
176 | std::io::Error::new(
177 | std::io::ErrorKind::Other,
178 | format!("{:?} not found in the path", exe_path),
179 | )
180 | })?
181 | } else {
182 | exe_path
183 | };
184 |
185 | let executable = os_str_to_null_terminated_vec(&exe_path.as_os_str());
186 |
187 | let mut cmdline = Vec::::new();
188 | for arg in self.args.iter().skip(skip) {
189 | if !cmdline.is_empty() {
190 | cmdline.push(' ' as u16);
191 | }
192 | append_quoted(&arg, &mut cmdline);
193 | }
194 | cmdline.push(0);
195 |
196 | Ok((executable, cmdline))
197 | }
198 |
199 | pub fn set_stdin(&mut self, p: PipeHandle) -> IoResult<()> {
200 | p.make_inheritable()?;
201 | self.stdin.replace(p);
202 | Ok(())
203 | }
204 |
205 | pub fn set_stdout(&mut self, p: PipeHandle) -> IoResult<()> {
206 | p.make_inheritable()?;
207 | self.stdout.replace(p);
208 | Ok(())
209 | }
210 |
211 | pub fn set_stderr(&mut self, p: PipeHandle) -> IoResult<()> {
212 | p.make_inheritable()?;
213 | self.stderr.replace(p);
214 | Ok(())
215 | }
216 |
217 | fn make_startup_info(&self) -> STARTUPINFOW {
218 | let mut si: STARTUPINFOW = unsafe { std::mem::zeroed() };
219 | si.cb = std::mem::size_of::() as u32;
220 | si.dwFlags = 0;
221 |
222 | if self.hide_window {
223 | si.dwFlags |= STARTF_USESHOWWINDOW;
224 | si.wShowWindow = SW_HIDE as _;
225 | }
226 |
227 | if self.stdin.is_some() || self.stdout.is_some() || self.stderr.is_some() {
228 | si.dwFlags |= STARTF_USESTDHANDLES;
229 | si.hStdInput = INVALID_HANDLE_VALUE;
230 | si.hStdOutput = INVALID_HANDLE_VALUE;
231 | si.hStdError = INVALID_HANDLE_VALUE;
232 | }
233 |
234 | if let Some(pipe) = self.stdin.as_ref() {
235 | si.hStdInput = pipe.as_handle();
236 | }
237 | if let Some(pipe) = self.stdout.as_ref() {
238 | si.hStdOutput = pipe.as_handle();
239 | }
240 | if let Some(pipe) = self.stderr.as_ref() {
241 | si.hStdError = pipe.as_handle();
242 | }
243 |
244 | si
245 | }
246 |
247 | pub fn shell_execute(&mut self, verb: &str) -> IoResult {
248 | unsafe {
249 | CoInitializeEx(
250 | null_mut(),
251 | COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE,
252 | )
253 | };
254 | let (exe, params) = self.executable_and_command_line(1)?;
255 | let cwd = os_str_to_null_terminated_vec(self.cwd.as_os_str());
256 | let verb = os_str_to_null_terminated_vec(OsStr::new(verb));
257 |
258 | let mut info = SHELLEXECUTEINFOW {
259 | cbSize: std::mem::size_of::() as u32,
260 | fMask: SEE_MASK_NOCLOSEPROCESS,
261 | hwnd: null_mut(),
262 | lpVerb: verb.as_ptr(),
263 | lpFile: exe.as_ptr(),
264 | lpParameters: params.as_ptr(),
265 | lpDirectory: cwd.as_ptr(),
266 | nShow: if self.hide_window {
267 | SW_HIDE
268 | } else {
269 | SW_SHOWNORMAL
270 | },
271 | hInstApp: null_mut(),
272 | lpIDList: null_mut(),
273 | lpClass: null_mut(),
274 | hkeyClass: null_mut(),
275 | hMonitor: null_mut(),
276 | dwHotKey: 0,
277 | hProcess: null_mut(),
278 | };
279 |
280 | let res = unsafe { ShellExecuteExW(&mut info) };
281 |
282 | if res == 0 {
283 | Err(win32_error_with_context(
284 | "ShellExecuteExW",
285 | IoError::last_os_error(),
286 | ))
287 | } else {
288 | Ok(Process::with_handle(info.hProcess))
289 | }
290 | }
291 |
292 | pub fn spawn_with_pty(&mut self, psuedocon: &PsuedoCon) -> IoResult {
293 | let mut si = STARTUPINFOEXW {
294 | StartupInfo: self.make_startup_info(),
295 | lpAttributeList: null_mut(),
296 | };
297 | si.StartupInfo.cb = std::mem::size_of::() as u32;
298 |
299 | let mut attrs = ProcThreadAttributeList::with_capacity(1)?;
300 | attrs.set_pty(psuedocon.con)?;
301 | si.lpAttributeList = attrs.as_mut_ptr();
302 |
303 | let mut pi = ProcInfo::new();
304 | let (mut exe, mut command_line) = self.executable_and_command_line(0)?;
305 | let mut cwd = os_str_to_null_terminated_vec(self.cwd.as_os_str());
306 |
307 | let proc_attributes = null_mut();
308 | let thread_attributes = null_mut();
309 | let inherit_handles = true;
310 |
311 | let res = unsafe {
312 | CreateProcessW(
313 | exe.as_mut_ptr(),
314 | command_line.as_mut_ptr(),
315 | proc_attributes,
316 | thread_attributes,
317 | inherit_handles as _,
318 | EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
319 | self.env.as_mut_ptr() as *mut _,
320 | cwd.as_mut_ptr(),
321 | &mut si.StartupInfo,
322 | &mut pi.0,
323 | )
324 | };
325 | if res == 0 {
326 | Err(win32_error_with_context(
327 | "CreateProcessAsUserW",
328 | IoError::last_os_error(),
329 | ))
330 | } else {
331 | Ok(pi.process().unwrap())
332 | }
333 | }
334 |
335 | pub fn spawn(&mut self) -> IoResult {
336 | let mut si = self.make_startup_info();
337 | let mut pi = ProcInfo::new();
338 | let (mut exe, mut command_line) = self.executable_and_command_line(0)?;
339 | let mut cwd = os_str_to_null_terminated_vec(self.cwd.as_os_str());
340 |
341 | let proc_attributes = null_mut();
342 | let thread_attributes = null_mut();
343 | let inherit_handles = true;
344 |
345 | let res = unsafe {
346 | CreateProcessW(
347 | exe.as_mut_ptr(),
348 | command_line.as_mut_ptr(),
349 | proc_attributes,
350 | thread_attributes,
351 | inherit_handles as _,
352 | CREATE_UNICODE_ENVIRONMENT,
353 | self.env.as_mut_ptr() as *mut _,
354 | cwd.as_mut_ptr(),
355 | &mut si,
356 | &mut pi.0,
357 | )
358 | };
359 | if res != 1 {
360 | Err(win32_error_with_context(
361 | "CreateProcessW",
362 | IoError::last_os_error(),
363 | ))
364 | } else {
365 | Ok(pi.process().unwrap())
366 | }
367 | }
368 |
369 | pub fn spawn_as_user(&mut self, token: &Token) -> IoResult {
370 | let mut si = self.make_startup_info();
371 | let mut pi = ProcInfo::new();
372 |
373 | let (mut exe, mut command_line) = self.executable_and_command_line(0)?;
374 | let mut cwd = os_str_to_null_terminated_vec(self.cwd.as_os_str());
375 |
376 | let proc_attributes = null_mut();
377 | let thread_attributes = null_mut();
378 | let inherit_handles = true;
379 |
380 | let res = unsafe {
381 | CreateProcessAsUserW(
382 | token.token,
383 | exe.as_mut_ptr(),
384 | command_line.as_mut_ptr(),
385 | proc_attributes,
386 | thread_attributes,
387 | inherit_handles as _,
388 | CREATE_UNICODE_ENVIRONMENT,
389 | self.env.as_mut_ptr() as *mut _,
390 | cwd.as_mut_ptr(),
391 | &mut si,
392 | &mut pi.0,
393 | )
394 | };
395 | if res != 1 {
396 | Err(win32_error_with_context(
397 | "CreateProcessAsUserW",
398 | IoError::last_os_error(),
399 | ))
400 | } else {
401 | Ok(pi.process().unwrap())
402 | }
403 | }
404 |
405 | pub fn spawn_with_token(&mut self, token: &Token) -> IoResult {
406 | let mut si = self.make_startup_info();
407 |
408 | let mut pi = ProcInfo::new();
409 | let (mut exe, mut command_line) = self.executable_and_command_line(0)?;
410 | let mut cwd = os_str_to_null_terminated_vec(self.cwd.as_os_str());
411 |
412 | let logon_flags = 0;
413 |
414 | let res = unsafe {
415 | CreateProcessWithTokenW(
416 | token.token,
417 | logon_flags,
418 | exe.as_mut_ptr(),
419 | command_line.as_mut_ptr(),
420 | CREATE_UNICODE_ENVIRONMENT|
421 | // Note that these flags are unconditionally or'd
422 | // in by CreateProcessWithTokenW: they're included
423 | // here to make it more obvious that these apply:
424 | CREATE_DEFAULT_ERROR_MODE|
425 | CREATE_NEW_CONSOLE|
426 | CREATE_NEW_PROCESS_GROUP,
427 | self.env.as_mut_ptr() as *mut _,
428 | cwd.as_mut_ptr(),
429 | &mut si,
430 | &mut pi.0,
431 | )
432 | };
433 | if res != 1 {
434 | Err(win32_error_with_context(
435 | "CreateProcessWithTokenW",
436 | IoError::last_os_error(),
437 | ))
438 | } else {
439 | Ok(pi.process().unwrap())
440 | }
441 | }
442 | }
443 |
444 | // Borrowed from https://github.com/wez/wezterm/blob/65707aba56f940f8c370f0465f0f3f2a6303a9cc/pty/src/cmdbuilder.rs#L313
445 | // and thus from https://github.com/hniksic/rust-subprocess/blob/873dfed165173e52907beb87118b2c0c05d8b8a1/src/popen.rs#L1117
446 | // which in turn was translated from ArgvQuote at http://tinyurl.com/zmgtnls
447 | fn append_quoted(arg: &OsStr, cmdline: &mut Vec) {
448 | if !arg.is_empty()
449 | && !arg.encode_wide().any(|c| {
450 | c == ' ' as u16
451 | || c == '\t' as u16
452 | || c == '\n' as u16
453 | || c == '\x0b' as u16
454 | || c == '\"' as u16
455 | })
456 | {
457 | cmdline.extend(arg.encode_wide());
458 | return;
459 | }
460 | cmdline.push('"' as u16);
461 |
462 | let arg: Vec<_> = arg.encode_wide().collect();
463 | let mut i = 0;
464 | while i < arg.len() {
465 | let mut num_backslashes = 0;
466 | while i < arg.len() && arg[i] == '\\' as u16 {
467 | i += 1;
468 | num_backslashes += 1;
469 | }
470 |
471 | if i == arg.len() {
472 | for _ in 0..num_backslashes * 2 {
473 | cmdline.push('\\' as u16);
474 | }
475 | break;
476 | } else if arg[i] == b'"' as u16 {
477 | for _ in 0..num_backslashes * 2 + 1 {
478 | cmdline.push('\\' as u16);
479 | }
480 | cmdline.push(arg[i]);
481 | } else {
482 | for _ in 0..num_backslashes {
483 | cmdline.push('\\' as u16);
484 | }
485 | cmdline.push(arg[i]);
486 | }
487 | i += 1;
488 | }
489 | cmdline.push('"' as u16);
490 | }
491 |
--------------------------------------------------------------------------------
/deelevate/src/bridge.rs:
--------------------------------------------------------------------------------
1 | use crate::command::Command;
2 | use crate::pipe::*;
3 | use crate::process::Process;
4 | use crate::psuedocon::PsuedoCon;
5 | use crate::win32_error_with_context;
6 | use crate::Token;
7 | use std::ffi::{OsStr, OsString};
8 | use std::io::{Error as IoError, Read, Result as IoResult, Write};
9 | use std::os::windows::prelude::*;
10 | use std::path::{Path, PathBuf};
11 | use winapi::shared::minwindef::DWORD;
12 | use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
13 | use winapi::um::consoleapi::{ReadConsoleW, WriteConsoleW};
14 | use winapi::um::fileapi::GetFileType;
15 | use winapi::um::winbase::FILE_TYPE_CHAR;
16 | use winapi::um::wincon::{
17 | GetConsoleScreenBufferInfo, CONSOLE_SCREEN_BUFFER_INFO, DISABLE_NEWLINE_AUTO_RETURN,
18 | ENABLE_PROCESSED_OUTPUT, ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
19 | ENABLE_WRAP_AT_EOL_OUTPUT,
20 | };
21 | use winapi::um::wincontypes::COORD;
22 |
23 | pub struct BridgePtyClient {
24 | con: PsuedoCon,
25 | }
26 |
27 | impl BridgePtyClient {
28 | pub fn with_params(conin: &Path, conout: &Path, width: usize, height: usize) -> IoResult {
29 | let client_to_server = PipeHandle::open_pipe(conout)?;
30 | let server_to_client = PipeHandle::open_pipe(conin)?;
31 |
32 | let con = PsuedoCon::new(
33 | COORD {
34 | X: width as i16,
35 | Y: height as i16,
36 | },
37 | server_to_client,
38 | client_to_server,
39 | )?;
40 |
41 | Ok(Self { con })
42 | }
43 |
44 | pub fn run(&self, mut command: Command) -> IoResult {
45 | let proc = command.spawn_with_pty(&self.con)?;
46 | proc.wait_for(None)?;
47 | // Well, this is a bit awkward.
48 | // If we kill the pty immediately when the child process exits,
49 | // it will fracture the associated pipes and any buffered
50 | // output will be lost.
51 | // There doesn't seem to be a reasonable way to wait for that
52 | // flush to occur from here, so we just use a short sleep.
53 | // This is gross and I wonder if this is even long enough
54 | // for every case?
55 | std::thread::sleep(std::time::Duration::from_millis(300));
56 | proc.exit_code()
57 | }
58 | }
59 |
60 | #[allow(unused)]
61 | fn join_with_timeout(join_handle: std::thread::JoinHandle<()>, timeout: std::time::Duration) {
62 | use std::sync::mpsc::channel;
63 | let (tx, rx) = channel();
64 | std::thread::spawn(move || {
65 | let _ = join_handle.join();
66 | let _ = tx.send(());
67 | });
68 | let _ = rx.recv_timeout(timeout);
69 | }
70 |
71 | /// The bridge server is the originator of the spawned command.
72 | /// It owns the server end of the connection and awaits the
73 | /// bridge client connection.
74 | pub struct BridgeServer {
75 | stdin_is_pty: bool,
76 | stdout_is_pty: bool,
77 | stderr_is_pty: bool,
78 |
79 | stdin: Option,
80 | stdout: Option,
81 | stderr: Option,
82 |
83 | conin: Option,
84 | conin_pipe: Option,
85 | conout: Option,
86 | conout_pipe: Option,
87 |
88 | input_mode: Option,
89 | output_mode: Option,
90 | }
91 |
92 | impl Drop for BridgeServer {
93 | fn drop(&mut self) {
94 | if let Some(mode) = self.output_mode {
95 | if let Ok(mut conout) = PipeHandle::open_pipe("CONOUT$") {
96 | // Emit a soft reset
97 | let _ = write!(&mut conout, "\x1b[!p");
98 | // Restore mode
99 | let _ = set_console_mode(&conout, mode);
100 | }
101 | }
102 | if let Some(mode) = self.input_mode {
103 | if let Ok(conin) = PipeHandle::open_pipe("CONIN$") {
104 | let _ = set_console_mode(&conin, mode);
105 | }
106 | }
107 | }
108 | }
109 |
110 | fn get_console_mode(pipe: &PipeHandle) -> IoResult {
111 | let mut mode = 0;
112 | let res = unsafe { GetConsoleMode(pipe.as_handle(), &mut mode) };
113 | if res == 0 {
114 | Err(win32_error_with_context(
115 | "GetConsoleMode",
116 | IoError::last_os_error(),
117 | ))
118 | } else {
119 | Ok(mode)
120 | }
121 | }
122 |
123 | fn set_console_mode(pipe: &PipeHandle, mode: DWORD) -> IoResult<()> {
124 | let res = unsafe { SetConsoleMode(pipe.as_handle(), mode) };
125 | if res == 0 {
126 | Err(win32_error_with_context(
127 | "SetConsoleMode",
128 | IoError::last_os_error(),
129 | ))
130 | } else {
131 | Ok(())
132 | }
133 | }
134 |
135 | // Due to https://github.com/microsoft/terminal/issues/4551
136 | // we cannot simply write bytes to the console, we have to
137 | // use WriteConsoleW to send the unicode data through, otherwise
138 | // we end up with problems with mismatching codepages.
139 | fn write_console(out: &mut PipeHandle, s: &str) -> IoResult<()> {
140 | let c: Vec = OsStr::new(s).encode_wide().collect();
141 | let mut wrote = 0;
142 | let res = unsafe {
143 | WriteConsoleW(
144 | out.as_handle(),
145 | c.as_ptr() as *const _,
146 | c.len() as _,
147 | &mut wrote,
148 | std::ptr::null_mut(),
149 | )
150 | };
151 | if res == 0 {
152 | Err(IoError::last_os_error())
153 | } else {
154 | Ok(())
155 | }
156 | }
157 |
158 | fn is_pty_stream(f: &F) -> bool {
159 | let handle = f.as_raw_handle();
160 | unsafe { GetFileType(handle as _) == FILE_TYPE_CHAR }
161 | }
162 |
163 | impl BridgeServer {
164 | pub fn new() -> Self {
165 | let stdin_is_pty = is_pty_stream(&std::io::stdin());
166 | let stdout_is_pty = is_pty_stream(&std::io::stdout());
167 | let stderr_is_pty = is_pty_stream(&std::io::stderr());
168 |
169 | Self {
170 | stdin_is_pty,
171 | stdout_is_pty,
172 | stderr_is_pty,
173 | conin: None,
174 | conout: None,
175 | conin_pipe: None,
176 | conout_pipe: None,
177 | input_mode: None,
178 | output_mode: None,
179 | stderr: None,
180 | stdout: None,
181 | stdin: None,
182 | }
183 | }
184 |
185 | pub fn start_for_command(
186 | &mut self,
187 | argv: &mut Vec,
188 | target_token: &Token,
189 | ) -> IoResult {
190 | let bridge_path = locate_pty_bridge()?;
191 | let mut bridge_args = self.start(target_token)?;
192 |
193 | bridge_args.insert(0, bridge_path.into_os_string());
194 | bridge_args.push("--".into());
195 | bridge_args.append(argv);
196 |
197 | let mut bridge_cmd = Command::with_environment_for_token(&target_token)?;
198 | bridge_cmd.set_argv(bridge_args);
199 | bridge_cmd.hide_window();
200 |
201 | Ok(bridge_cmd)
202 | }
203 |
204 | /// Creates the server pipe and returns the name of the pipe
205 | /// so that it can be passed to the client process
206 | pub fn start(&mut self, token: &Token) -> IoResult> {
207 | let mut args = vec![];
208 |
209 | if !self.stdin_is_pty {
210 | let pipe = NamedPipeServer::for_token(token)?;
211 | self.stdin.replace(pipe.pipe);
212 | args.push("--stdin".into());
213 | args.push(pipe.path.into());
214 | }
215 |
216 | if !self.stdout_is_pty {
217 | let pipe = NamedPipeServer::for_token(token)?;
218 | self.stdout.replace(pipe.pipe);
219 | args.push("--stdout".into());
220 | args.push(pipe.path.into());
221 | }
222 |
223 | if !self.stderr_is_pty {
224 | let pipe = NamedPipeServer::for_token(token)?;
225 | self.stderr.replace(pipe.pipe);
226 | args.push("--stderr".into());
227 | args.push(pipe.path.into());
228 | }
229 |
230 | if let Ok(conin) = PipeHandle::open_pipe("CONIN$") {
231 | self.input_mode.replace(get_console_mode(&conin)?);
232 | let pipe = NamedPipeServer::for_token(token)?;
233 | self.conin_pipe.replace(pipe.pipe);
234 |
235 | args.push("--conin".into());
236 | args.push(pipe.path.into());
237 |
238 | set_console_mode(
239 | &conin,
240 | // ENABLE_PROCESSED_OUTPUT | FIXME: CTRl-C handling?
241 | ENABLE_VIRTUAL_TERMINAL_INPUT,
242 | )?;
243 | self.conin.replace(conin);
244 | }
245 |
246 | if let Ok(conout) = PipeHandle::open_pipe("CONOUT$") {
247 | self.output_mode.replace(get_console_mode(&conout)?);
248 | let pipe = NamedPipeServer::for_token(token)?;
249 | self.conout_pipe.replace(pipe.pipe);
250 |
251 | args.push("--conout".into());
252 | args.push(pipe.path.into());
253 |
254 | let mut console_info: CONSOLE_SCREEN_BUFFER_INFO = unsafe { std::mem::zeroed() };
255 | let res = unsafe { GetConsoleScreenBufferInfo(conout.as_handle(), &mut console_info) };
256 |
257 | if res == 0 {
258 | return Err(win32_error_with_context(
259 | "GetConsoleScreenBufferInfo",
260 | IoError::last_os_error(),
261 | ));
262 | }
263 |
264 | // The console info describes the buffer dimensions.
265 | // We need to do a little bit of math to obtain the viewport dimensions!
266 | let width = console_info
267 | .srWindow
268 | .Right
269 | .saturating_sub(console_info.srWindow.Left) as usize
270 | + 1;
271 |
272 | args.push("--width".into());
273 | args.push(width.to_string().into());
274 |
275 | let height = console_info
276 | .srWindow
277 | .Bottom
278 | .saturating_sub(console_info.srWindow.Top) as usize
279 | + 1;
280 |
281 | args.push("--height".into());
282 | args.push(height.to_string().into());
283 |
284 | let cursor_x = console_info.dwCursorPosition.X as usize;
285 | let cursor_y = console_info
286 | .dwCursorPosition
287 | .Y
288 | .saturating_sub(console_info.srWindow.Top) as usize;
289 |
290 | args.push("--cursor-x".into());
291 | args.push(cursor_x.to_string().into());
292 |
293 | args.push("--cursor-y".into());
294 | args.push(cursor_y.to_string().into());
295 |
296 | set_console_mode(
297 | &conout,
298 | ENABLE_PROCESSED_OUTPUT
299 | | ENABLE_WRAP_AT_EOL_OUTPUT
300 | | ENABLE_VIRTUAL_TERMINAL_PROCESSING
301 | | DISABLE_NEWLINE_AUTO_RETURN,
302 | )?;
303 |
304 | self.conout.replace(conout);
305 | }
306 |
307 | Ok(args)
308 | }
309 |
310 | pub fn serve(mut self, proc: Process) -> IoResult {
311 | if let Some(conin) = self.conin.take() {
312 | let mut conin_dest = self.conin_pipe.take().unwrap();
313 | conin_dest.wait_for_pipe_client()?;
314 | std::thread::spawn(move || -> IoResult<()> {
315 | let mut buf = [0u16; 8192];
316 | let mut num_read = 0;
317 | loop {
318 | let res = unsafe {
319 | ReadConsoleW(
320 | conin.as_handle(),
321 | buf.as_mut_ptr() as *mut _,
322 | buf.len() as _,
323 | &mut num_read,
324 | std::ptr::null_mut(),
325 | )
326 | };
327 |
328 | if res == 0 {
329 | return Err(IoError::last_os_error());
330 | }
331 |
332 | let s = OsString::from_wide(&buf[0..num_read as usize]);
333 | let utf8 = s.to_string_lossy();
334 |
335 | conin_dest.write_all(utf8.as_bytes())?;
336 | }
337 | });
338 | }
339 |
340 | // Start up the console output processing thread.
341 | // This is ostensibly just a matter of taking the output
342 | // from the pty created by the bridge executable and piping it
343 | // into our own CONOUT$ stream, but it is made a little bit
344 | // more complicated because the Windows console APIs emit
345 | // some slightly hostile initialization sequences when creating
346 | // a fresh PTY and launching a process inside it: it will emit
347 | // sequences that move the cursor, clear the screen and change
348 | // the window title sequence.
349 | // For our embedding use case those are distinctly unwanted.
350 | // In order to deal with this, we need to parse the terminal
351 | // output so that we can filter them out.
352 | // The approach is simple: until we spot that initial title
353 | // change, we'll filter out CSI and OSC sequences.
354 | // Just in case the behavior changes in the future, we'll
355 | // also disable suppression if we see any other kind of
356 | // output from the pty stream.
357 | let conout_thread = self.conout.take().map(|mut conout| {
358 | let mut conout_src = self.conout_pipe.take().unwrap();
359 | let _ = conout_src.wait_for_pipe_client();
360 | std::thread::spawn(move || -> IoResult<()> {
361 | use termwiz::escape::osc::OperatingSystemCommand;
362 | use termwiz::escape::parser::Parser;
363 | use termwiz::escape::Action;
364 |
365 | let mut parser = Parser::new();
366 |
367 | let mut buf = [0u8; 4096];
368 | let mut suppress_control = true;
369 |
370 | loop {
371 | let len = conout_src.read(&mut buf)?;
372 | if len == 0 {
373 | return Ok(());
374 | }
375 |
376 | let mut error = None;
377 | let mut callback = |action: Action| -> IoResult<()> {
378 | match action {
379 | Action::OperatingSystemCommand(osc) => {
380 | match *osc {
381 | OperatingSystemCommand::SetIconNameAndWindowTitle(_) => {
382 | if suppress_control {
383 | // We're now sync'd up with the new pty instance.
384 | // We ignore this first title change request because
385 | // it is going to be the uninteresting bridge exe
386 | suppress_control = false;
387 | Ok(())
388 | } else {
389 | write_console(&mut conout, &format!("{}", osc))
390 | }
391 | }
392 | _ => write_console(&mut conout, &format!("{}", osc)),
393 | }
394 | }
395 | Action::CSI(c) => {
396 | if !suppress_control {
397 | write_console(&mut conout, &format!("{}", c))
398 | } else {
399 | Ok(())
400 | }
401 | }
402 | _ => {
403 | suppress_control = false;
404 | write_console(&mut conout, &format!("{}", action))
405 | }
406 | }
407 | };
408 |
409 | parser.parse(&buf[0..len], |action| {
410 | if let Err(e) = callback(action) {
411 | error.replace(e);
412 | }
413 | });
414 |
415 | if let Some(e) = error.take() {
416 | return Err(e);
417 | }
418 | }
419 | })
420 | });
421 |
422 | if let Some(mut stdin_dest) = self.stdin.take() {
423 | stdin_dest.wait_for_pipe_client()?;
424 | std::thread::spawn(move || {
425 | let mut stdin = std::io::stdin();
426 | let _ = std::io::copy(&mut stdin, &mut stdin_dest);
427 | });
428 | }
429 |
430 | let stdout_thread = self.stdout.take().map(|mut stdout_src| {
431 | let _ = stdout_src.wait_for_pipe_client();
432 | std::thread::spawn(move || {
433 | let mut stdout = std::io::stdout();
434 | let _ = std::io::copy(&mut stdout_src, &mut stdout);
435 | })
436 | });
437 | let stderr_thread = self.stderr.take().map(|mut stderr_src| {
438 | let _ = stderr_src.wait_for_pipe_client();
439 | std::thread::spawn(move || {
440 | let mut stderr = std::io::stderr();
441 | let _ = std::io::copy(&mut stderr_src, &mut stderr);
442 | })
443 | });
444 |
445 | let _ = proc.wait_for(None)?;
446 |
447 | stdout_thread.map(|t| t.join());
448 | stderr_thread.map(|t| t.join());
449 | conout_thread.map(|t| t.join());
450 |
451 | let exit_code = proc.exit_code()?;
452 | Ok(exit_code)
453 | }
454 | }
455 |
456 | fn locate_pty_bridge() -> IoResult {
457 | let bridge_name = "eledo-pty-bridge.exe";
458 | let bridge_path = std::env::current_exe()?
459 | .parent()
460 | .ok_or_else(|| {
461 | std::io::Error::new(
462 | std::io::ErrorKind::Other,
463 | "current exe has no containing dir while locating pty bridge!?",
464 | )
465 | })?
466 | .join(bridge_name);
467 | if bridge_path.exists() {
468 | Ok(bridge_path)
469 | } else {
470 | pathsearch::find_executable_in_path(bridge_name).ok_or_else(|| {
471 | std::io::Error::new(
472 | std::io::ErrorKind::Other,
473 | format!(
474 | "{} not found alongside executable or in the path",
475 | bridge_name
476 | ),
477 | )
478 | })
479 | }
480 | }
481 |
--------------------------------------------------------------------------------
/deelevate/src/token.rs:
--------------------------------------------------------------------------------
1 | use crate::process::Process;
2 | use crate::sid::{get_length_sid, is_well_known, AsSid, WellKnownSid};
3 | use crate::win32_error_with_context;
4 | use std::io::{Error as IoError, Result as IoResult};
5 | use std::ptr::null_mut;
6 | use winapi::shared::minwindef::{BOOL, DWORD};
7 | use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS};
8 | use winapi::um::accctrl::{
9 | EXPLICIT_ACCESSW, NO_INHERITANCE, NO_MULTIPLE_TRUSTEE, SET_ACCESS, TRUSTEE_IS_SID,
10 | TRUSTEE_IS_USER, TRUSTEE_W,
11 | };
12 | use winapi::um::aclapi::SetEntriesInAclW;
13 | use winapi::um::errhandlingapi::GetLastError;
14 | use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
15 | use winapi::um::minwinbase::LPTR;
16 | use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
17 | use winapi::um::securitybaseapi::{
18 | CheckTokenMembership, DuplicateTokenEx, GetTokenInformation, ImpersonateLoggedOnUser,
19 | InitializeSecurityDescriptor, SetSecurityDescriptorDacl, SetTokenInformation,
20 | };
21 | use winapi::um::winbase::{LocalAlloc, LocalFree};
22 | use winapi::um::winnt::{
23 | SecurityImpersonation, TokenElevationType, TokenElevationTypeFull, TokenImpersonation,
24 | TokenIntegrityLevel, TokenPrimary, TokenUser, WinBuiltinAdministratorsSid, WinHighLabelSid,
25 | WinMediumLabelSid, GENERIC_READ, GENERIC_WRITE, HANDLE, PACL, PROCESS_QUERY_INFORMATION,
26 | PSECURITY_DESCRIPTOR, SECURITY_DESCRIPTOR_MIN_LENGTH, SECURITY_DESCRIPTOR_REVISION,
27 | SE_GROUP_INTEGRITY, SID, SID_AND_ATTRIBUTES, TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_SESSIONID,
28 | TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_ELEVATION_TYPE, TOKEN_IMPERSONATE,
29 | TOKEN_MANDATORY_LABEL, TOKEN_QUERY, TOKEN_TYPE,
30 | };
31 | use winapi::um::winsafer::{
32 | SaferCloseLevel, SaferComputeTokenFromLevel, SaferCreateLevel, SAFER_LEVELID_NORMALUSER,
33 | SAFER_LEVEL_HANDLE, SAFER_LEVEL_OPEN, SAFER_SCOPEID_USER,
34 | };
35 | use winapi::um::winuser::{GetShellWindow, GetWindowThreadProcessId};
36 |
37 | /// Indicates the effective level of privileges held by the token
38 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
39 | pub enum PrivilegeLevel {
40 | /// The token isn't privileged OR may be privileged in
41 | /// an unusual way that we don't know how to guarantee
42 | /// that we can/should reduce privilege or do so successfully.
43 | NotPrivileged,
44 | /// The token is an elevated token produced via runas/UAC.
45 | /// It may be a different administrative user than the
46 | /// interactive desktop user!
47 | Elevated,
48 | /// The token isn't an elevated token but it does have
49 | /// high integrity privileges, such as those produced
50 | /// by sshing in to a Windows 10 system.
51 | HighIntegrityAdmin,
52 | }
53 |
54 | /// A helper that wraps a TOKEN_MANDATORY_LABEL struct.
55 | /// That struct holds a SID and some attribute flags.
56 | /// Its use in this module is to query the integrity level
57 | /// of the token, so we have a very targeted set of accessors
58 | /// for that purpose.
59 | /// The integrity level is a single SID that represents the
60 | /// degree of trust that the token has.
61 | /// A normal user is typically running with Medium integrity,
62 | /// whereas an elevated session is typically running with High
63 | /// integrity.
64 | struct TokenIntegrityLevel {
65 | data: Vec,
66 | }
67 |
68 | impl TokenIntegrityLevel {
69 | fn as_label(&self) -> &TOKEN_MANDATORY_LABEL {
70 | // This is safe because we cannot construct an invalid instance
71 | unsafe { &*(self.data.as_ptr() as *const TOKEN_MANDATORY_LABEL) }
72 | }
73 |
74 | fn sid(&self) -> *const SID {
75 | // For whatever reason, the PSID type in the SDK is defined
76 | // as void* and that is the type of Label.Sid, rather than
77 | // SID*, so we get to cast it here.
78 | self.as_label().Label.Sid as *const SID
79 | }
80 |
81 | /// Return true if this is a high integrity level label
82 | pub fn is_high(&self) -> bool {
83 | is_well_known(self.sid(), WinHighLabelSid)
84 | }
85 | }
86 |
87 | /// `Token` represents a set of credentials and privileges. A process
88 | /// typically inherits the token of its parent process for its primary
89 | /// token, and Windows allows for threads to create/obtain impersonation
90 | /// tokens so that a thread can run with a different identity for a
91 | /// while.
92 | ///
93 | /// For the purposes of this crate, we are concerned with reducing
94 | /// the scope of the privileges in a given Token.
95 | pub struct Token {
96 | pub(crate) token: HANDLE,
97 | }
98 |
99 | impl Drop for Token {
100 | fn drop(&mut self) {
101 | unsafe {
102 | CloseHandle(self.token);
103 | }
104 | }
105 | }
106 |
107 | pub(crate) struct SecurityDescriptor(pub PSECURITY_DESCRIPTOR);
108 | impl Drop for SecurityDescriptor {
109 | fn drop(&mut self) {
110 | unsafe {
111 | LocalFree(self.0);
112 | }
113 | }
114 | }
115 |
116 | impl Token {
117 | /// Obtain a handle to the primary token for this process
118 | pub fn with_current_process() -> IoResult {
119 | let mut token: HANDLE = INVALID_HANDLE_VALUE;
120 | let res = unsafe {
121 | OpenProcessToken(
122 | GetCurrentProcess(),
123 | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
124 | &mut token,
125 | )
126 | };
127 | if res != 1 {
128 | Err(win32_error_with_context(
129 | "OpenProcessToken(GetCurrentProcess))",
130 | IoError::last_os_error(),
131 | ))
132 | } else {
133 | Ok(Self { token })
134 | }
135 | }
136 |
137 | pub(crate) fn create_security_descriptor(&self) -> IoResult {
138 | let user = self.user()?;
139 |
140 | let mut ea = EXPLICIT_ACCESSW {
141 | grfAccessPermissions: GENERIC_READ | GENERIC_WRITE,
142 | grfAccessMode: SET_ACCESS,
143 | grfInheritance: NO_INHERITANCE,
144 | Trustee: TRUSTEE_W {
145 | TrusteeForm: TRUSTEE_IS_SID,
146 | TrusteeType: TRUSTEE_IS_USER,
147 | MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE,
148 | ptstrName: user.sid() as *mut _,
149 | pMultipleTrustee: null_mut(),
150 | },
151 | };
152 |
153 | let mut acl: PACL = null_mut();
154 | let res = unsafe { SetEntriesInAclW(1, &mut ea, null_mut(), &mut acl) };
155 | if res != ERROR_SUCCESS {
156 | return Err(win32_error_with_context(
157 | "SetEntriesInAcl",
158 | IoError::last_os_error(),
159 | ));
160 | }
161 |
162 | let sd = SecurityDescriptor(unsafe { LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH) });
163 | let res = unsafe { InitializeSecurityDescriptor(sd.0, SECURITY_DESCRIPTOR_REVISION) };
164 | if res == 0 {
165 | return Err(win32_error_with_context(
166 | "InitializeSecurityDescriptor",
167 | IoError::last_os_error(),
168 | ));
169 | }
170 |
171 | let dacl_present = true as _;
172 | let default_dacl = false as _;
173 | let res = unsafe { SetSecurityDescriptorDacl(sd.0, dacl_present, acl, default_dacl) };
174 | if res == 0 {
175 | return Err(win32_error_with_context(
176 | "SetSecurityDescriptorDacl",
177 | IoError::last_os_error(),
178 | ));
179 | }
180 | Ok(sd)
181 | }
182 |
183 | /// Obtain the token from the shell process as a primary token.
184 | /// This can fail if there is no shell accessible to the process,
185 | /// for example if the process was spawned by an ssh session.
186 | /// Why might we want this token? We can't directly
187 | /// de-elevate a token so we need to obtain a non-elevated
188 | /// token from a well known source.
189 | pub fn with_shell_process() -> IoResult {
190 | let shell_window = unsafe { GetShellWindow() };
191 | if shell_window.is_null() {
192 | return Err(IoError::new(
193 | std::io::ErrorKind::NotFound,
194 | "there is no shell window",
195 | ));
196 | }
197 |
198 | let mut shell_pid: DWORD = 0;
199 | let _thread_id = unsafe { GetWindowThreadProcessId(shell_window, &mut shell_pid) };
200 |
201 | let proc = Process::with_process_id(PROCESS_QUERY_INFORMATION, false, shell_pid)?;
202 |
203 | let mut token: HANDLE = INVALID_HANDLE_VALUE;
204 | let res = unsafe { OpenProcessToken(proc.as_handle(), TOKEN_DUPLICATE, &mut token) };
205 | if res != 1 {
206 | Err(win32_error_with_context(
207 | "OpenProcessToken(shell process)",
208 | IoError::last_os_error(),
209 | ))
210 | } else {
211 | let token = Self { token };
212 |
213 | // And now that we have it, make a primary token from it!
214 | token.duplicate_as_primary_token()
215 | }
216 | }
217 |
218 | /// Build a medium integrity level normal user access token
219 | /// from the current token.
220 | /// This is most suitable in the case where you have a
221 | /// HighIntegrityAdmin privilege level and want to proceed
222 | /// with a normal privilege token.
223 | pub fn as_medium_integrity_safer_token(&self) -> IoResult {
224 | let mut level: SAFER_LEVEL_HANDLE = null_mut();
225 | let res = unsafe {
226 | SaferCreateLevel(
227 | SAFER_SCOPEID_USER,
228 | SAFER_LEVELID_NORMALUSER,
229 | SAFER_LEVEL_OPEN,
230 | &mut level,
231 | null_mut(),
232 | )
233 | };
234 | if res != 1 {
235 | return Err(win32_error_with_context(
236 | "SaferCreateLevel",
237 | IoError::last_os_error(),
238 | ));
239 | }
240 |
241 | struct SaferHandle(SAFER_LEVEL_HANDLE);
242 | impl Drop for SaferHandle {
243 | fn drop(&mut self) {
244 | unsafe { SaferCloseLevel(self.0) };
245 | }
246 | }
247 | let level = SaferHandle(level);
248 |
249 | let mut token = INVALID_HANDLE_VALUE;
250 | let res =
251 | unsafe { SaferComputeTokenFromLevel(level.0, self.token, &mut token, 0, null_mut()) };
252 | if res != 1 {
253 | return Err(win32_error_with_context(
254 | "SaferComputeTokenFromLevel",
255 | IoError::last_os_error(),
256 | ));
257 | }
258 |
259 | let token = Self { token };
260 | token.set_medium_integrity()?;
261 | Ok(token)
262 | }
263 |
264 | fn set_medium_integrity(&self) -> IoResult<()> {
265 | let medium = WellKnownSid::with_well_known(WinMediumLabelSid)?;
266 | let mut tml = TOKEN_MANDATORY_LABEL {
267 | Label: SID_AND_ATTRIBUTES {
268 | Attributes: SE_GROUP_INTEGRITY,
269 | Sid: medium.as_sid() as *mut _,
270 | },
271 | };
272 |
273 | let res = unsafe {
274 | SetTokenInformation(
275 | self.token,
276 | TokenIntegrityLevel,
277 | &mut tml as *mut TOKEN_MANDATORY_LABEL as *mut _,
278 | std::mem::size_of_val(&tml) as u32 + get_length_sid(&medium),
279 | )
280 | };
281 | if res != 1 {
282 | Err(win32_error_with_context(
283 | "SetTokenInformation(TokenIntegrityLevel Medium)",
284 | IoError::last_os_error(),
285 | ))
286 | } else {
287 | Ok(())
288 | }
289 | }
290 |
291 | /// Attempt to duplicate this token as one that is suitable
292 | /// for use in impersonation related APIs, which includes the
293 | /// check_membership method.
294 | fn duplicate_as_impersonation_token(&self) -> IoResult {
295 | self.duplicate(TokenImpersonation)
296 | }
297 |
298 | fn duplicate_as_primary_token(&self) -> IoResult {
299 | self.duplicate(TokenPrimary)
300 | }
301 |
302 | fn duplicate(&self, token_type: TOKEN_TYPE) -> IoResult {
303 | let mut dup: HANDLE = INVALID_HANDLE_VALUE;
304 | let res = unsafe {
305 | DuplicateTokenEx(
306 | self.token,
307 | TOKEN_ADJUST_SESSIONID
308 | | TOKEN_ADJUST_DEFAULT
309 | | TOKEN_ASSIGN_PRIMARY
310 | | TOKEN_IMPERSONATE
311 | | TOKEN_DUPLICATE
312 | | TOKEN_QUERY,
313 | null_mut(),
314 | SecurityImpersonation,
315 | token_type,
316 | &mut dup,
317 | )
318 | };
319 | if res != 1 {
320 | Err(win32_error_with_context(
321 | "DuplicateTokenEx",
322 | IoError::last_os_error(),
323 | ))
324 | } else {
325 | Ok(Self { token: dup })
326 | }
327 | }
328 |
329 | /// Returns true if `sid` is an enabled group on this token.
330 | /// The token must be an impersonation token, so you may need
331 | /// to use duplicate_as_impersonation_token() to obtain one.
332 | fn check_membership(&self, sid: S) -> IoResult {
333 | let mut is_member: BOOL = 0;
334 | let res =
335 | unsafe { CheckTokenMembership(self.token, sid.as_sid() as *mut _, &mut is_member) };
336 | if res != 1 {
337 | Err(win32_error_with_context(
338 | "CheckTokenMembership",
339 | IoError::last_os_error(),
340 | ))
341 | } else {
342 | Ok(is_member == 1)
343 | }
344 | }
345 |
346 | /// A convenience wrapper around check_membership that tests for
347 | /// being a member of the builtin administrators group
348 | fn check_administrators_membership(&self) -> IoResult {
349 | let admins = WellKnownSid::with_well_known(WinBuiltinAdministratorsSid)?;
350 | self.check_membership(&admins)
351 | }
352 |
353 | /// Retrieve the user SID and attributes from the token.
354 | /// This is encoded in the same way as TokenIntegrityLevel,
355 | /// hence the return type.
356 | /// This method is used to obtain the SID in order to
357 | /// construct a DACL for a named pipe.
358 | fn user(&self) -> IoResult {
359 | let mut size: DWORD = 0;
360 | let err;
361 |
362 | unsafe {
363 | GetTokenInformation(self.token, TokenUser, null_mut(), 0, &mut size);
364 | err = GetLastError();
365 | };
366 |
367 | // The call should have failed and told us we need more space
368 | if err != ERROR_INSUFFICIENT_BUFFER {
369 | return Err(win32_error_with_context(
370 | "GetTokenInformation TokenUser unexpected failure",
371 | IoError::last_os_error(),
372 | ));
373 | }
374 |
375 | // Allocate and zero out the storage
376 | let mut data = vec![0u8; size as usize];
377 |
378 | unsafe {
379 | if GetTokenInformation(
380 | self.token,
381 | TokenUser,
382 | data.as_mut_ptr() as *mut _,
383 | size,
384 | &mut size,
385 | ) == 0
386 | {
387 | return Err(win32_error_with_context(
388 | "GetTokenInformation TokenUser",
389 | IoError::last_os_error(),
390 | ));
391 | }
392 | };
393 |
394 | Ok(TokenIntegrityLevel { data })
395 | }
396 |
397 | /// Retrieve the integrity level label of the process.
398 | fn integrity_level(&self) -> IoResult {
399 | let mut size: DWORD = 0;
400 | let err;
401 |
402 | unsafe {
403 | GetTokenInformation(self.token, TokenIntegrityLevel, null_mut(), 0, &mut size);
404 | err = GetLastError();
405 | };
406 |
407 | // The call should have failed and told us we need more space
408 | if err != ERROR_INSUFFICIENT_BUFFER {
409 | return Err(win32_error_with_context(
410 | "GetTokenInformation TokenIntegrityLevel unexpected failure",
411 | IoError::last_os_error(),
412 | ));
413 | }
414 |
415 | // Allocate and zero out the storage
416 | let mut data = vec![0u8; size as usize];
417 |
418 | unsafe {
419 | if GetTokenInformation(
420 | self.token,
421 | TokenIntegrityLevel,
422 | data.as_mut_ptr() as *mut _,
423 | size,
424 | &mut size,
425 | ) == 0
426 | {
427 | return Err(win32_error_with_context(
428 | "GetTokenInformation TokenIntegrityLevel",
429 | IoError::last_os_error(),
430 | ));
431 | }
432 | };
433 |
434 | Ok(TokenIntegrityLevel { data })
435 | }
436 |
437 | /// Return an enum value that indicates the degree of elevation
438 | /// applied to the current token; this can be one of:
439 | /// TokenElevationTypeDefault, TokenElevationTypeFull,
440 | /// TokenElevationTypeLimited.
441 | fn elevation_type(&self) -> IoResult {
442 | let mut ele_type: TOKEN_ELEVATION_TYPE = 0;
443 | let mut size: DWORD = 0;
444 | let res = unsafe {
445 | GetTokenInformation(
446 | self.token,
447 | TokenElevationType,
448 | &mut ele_type as *mut TOKEN_ELEVATION_TYPE as *mut _,
449 | std::mem::size_of_val(&ele_type) as u32,
450 | &mut size,
451 | )
452 | };
453 | if res != 1 {
454 | Err(win32_error_with_context(
455 | "GetTokenInformation TOKEN_ELEVATION_TYPE",
456 | IoError::last_os_error(),
457 | ))
458 | } else {
459 | Ok(ele_type)
460 | }
461 | }
462 |
463 | /// Determine the effective privilege level of the token
464 | pub fn privilege_level(&self) -> IoResult {
465 | let ele_type = self.elevation_type()?;
466 | if ele_type == TokenElevationTypeFull {
467 | return Ok(PrivilegeLevel::Elevated);
468 | }
469 |
470 | let level = self.integrity_level()?;
471 | if !level.is_high() {
472 | return Ok(PrivilegeLevel::NotPrivileged);
473 | }
474 |
475 | let imp_token = self.duplicate_as_impersonation_token()?;
476 | if imp_token.check_administrators_membership()? {
477 | Ok(PrivilegeLevel::HighIntegrityAdmin)
478 | } else {
479 | Ok(PrivilegeLevel::NotPrivileged)
480 | }
481 | }
482 |
483 | /// Impersonate applies the token to the current thread only.
484 | /// This isn't a supported API: it is present to furnish an
485 | /// example that shows that it doesn't behave how you might
486 | /// expect!
487 | #[doc(hidden)]
488 | pub fn impersonate(&self) -> IoResult<()> {
489 | let res = unsafe { ImpersonateLoggedOnUser(self.token) };
490 | if res != 1 {
491 | Err(win32_error_with_context(
492 | "ImpersonateLoggedOnUser",
493 | IoError::last_os_error(),
494 | ))
495 | } else {
496 | Ok(())
497 | }
498 | }
499 | }
500 |
501 | #[cfg(test)]
502 | mod test {
503 | use super::*;
504 |
505 | #[test]
506 | fn get_own_token() {
507 | let token = Token::with_current_process().unwrap();
508 | let level = token.privilege_level().unwrap();
509 | // We can't make any assertions about what the level is,
510 | // but we can at least assume that we should be able
511 | // to successfully reach this point
512 | eprintln!("priv level is {:?}", level);
513 |
514 | // Verify that we can build a medium token from this,
515 | // and that the medium token doesn't show as privileged
516 | let medium = token.as_medium_integrity_safer_token().unwrap();
517 | let level = medium.privilege_level().unwrap();
518 | assert_eq!(level, PrivilegeLevel::NotPrivileged);
519 | }
520 |
521 | #[test]
522 | fn get_shell_token() {
523 | // We should either successfully obtain the shell token (if we're
524 | // connected to a desktop with a shell), or get a NotFound error.
525 | // We treat any other error as a test failure.
526 | match Token::with_shell_process() {
527 | Ok(_) => eprintln!("got shell token!"),
528 | Err(err) => match err.kind() {
529 | std::io::ErrorKind::NotFound => eprintln!("There is no shell"),
530 | _ => panic!("failed to get shell token: {:?}", err),
531 | },
532 | }
533 | }
534 | }
535 |
--------------------------------------------------------------------------------