Playing With Process Handles

Process handle overview

Let’s talk Handles! A process handle is simply an identifier handled by the system which allows Windows processes to reference and manipulate other processes within the OS environment. It’s essentially a pointer or an access token granted by the OS in which process can use to interact with another process. This handle provides a controlled way to access the resources and perform operations on the target process, such as reading or writing memory, terminating the process, or querying its information.

From a “red team” standpoint we can use these handles to perform a wide range of tasks from enumeration to exploitation. For example, we can use handles to query processes to determine all loaded modules, capture process snapshots, monitoring the process’s behavior, or use them to assist in process injection.

From a Red Team standpoint, we can use these process handles to:

  • Access to Process Resources: Allow for access to and manipulate the resources of other processes, which can be crucial for various offensive techniques. This includes reading sensitive information from a process’s memory, injecting code, or even modifying the behavior of the process to escalate privileges or bypass security controls.
  • Process Injection and Manipulation: This is a pretty common practice which involves writing malicious code into the memory space of another process and then executing it, effectively hiding the malicious activity within a legitimate process. Actions performed through the affected process may be attributed to that process, helping to evade detection mechanisms that monitor for suspicious activity.
  • Privilege Escalation: The usage of process handles can be instrumental in privilege escalation techniques especially when paired with a known vulnerability.
  • Bypassing Security Mechanisms: Handles can be used to interact with processes in ways that circumvent security mechanisms. For example, disabling anti-malware features such as AMSI, modify security settings, or unload security related modules.
  • Information Gathering: They are also useful for enumeration and information gathering. Handles can be used to inspect the memory of a process for useful or sensitive information, configuration details, or to understand the environment and software stack of the target system a bit better. This information can be used to further tailor attacks or identify vulnerabilities.

Process handle demonstration – Loaded Module Enumeration

Now, let’s perform some real world demonstrations using PowerShell combined with a few needed .NET assemblies. This will make the code a bit more understandable and allow for easy modification.

The following script is designed to take a process name as input and create a handle for the specified process.

DISCLAIMER: The provided code in this article is for educational purposes and should not be used maliciously.

param (
    [Parameter(Mandatory=$true)]
    [string]$process
)

# Remove the exe extension if present
$process = $process -replace '\.exe$', ''

# Load needed .NET assemblies
$loadAssembly =  @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class ProcessUtils {
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseHandle(IntPtr hObject);

    public static IntPtr GetProcessHandle(int processId, uint processAccess) {
        return OpenProcess(processAccess, false, processId);
    }
}
"@ 

Add-Type -TypeDefinition $loadAssembly -Language CSharp


# Define  the process access flags
$PROCESS_VM_READ = 0x0010 # ReadProcessMemory
$PROCESS_QUERY_INFORMATION = 0x0400 # OpenProcessToken

# Attempt to get the process by name
$targetProc = Get-Process -Name $process -ErrorAction SilentlyContinue

# Throw error if the process is not found
if ($null -ne $targetProc) {
    # Grab the first instance of the discovered process
    $targetProc = $targetProc[0]
    $processHandle = [ProcessUtils]::GetProcessHandle($targetProc.Id, $PROCESS_VM_READ -bor $PROCESS_QUERY_INFORMATION)
    
    if ($processHandle -ne [IntPtr]::Zero) {
        Write-Host -ForegroundColor Yellow "Successfully obtained handle for $process under process ID: $processHandle`n"

        # This is where we can perform our actions on the target process
        # Example: Loaded module enumeration:
        $modules = $targetProc.Modules
        foreach ($module in $modules) {
            Write-Host "Module: $($module.FileName)"
        }

        # Close the handle after use
        [ProcessUtils]::CloseHandle($processHandle)
    } else {
        Write-Error "Failed to obtain the process handle.`n"
    }
} else {
    Write-Error "The specified process ""$process"" was not found.`n"
}

The script basically includes some needed .NET assemblies needed for the script to interact with Windows API functions. This allows us to interact with the process handles.

Next, process access flags are defined which are needed for access rights. The first, (processAccess parameter) is needed in this example based on the operation we’re intending to perform. The example uses “PROCESS_VM_READ” and “PROCESS_QUERY_INFORMATION”.

The script then goes through a few failsafes ensuring that first, the process actually exists or is running, and that the handle was successfully obtained. Once obtained, this is where we can start playing with some of these handle functions!

In the above script, the following foreach loop is used to simply loop through all of the open modules loaded by the specified process:

$modules = $targetProc.Modules
foreach ($module in $modules) {
    Write-Host "Module: $($module.FileName)"
}

When executed against the target process firefox.exe, we get some useful data. A nice list of all open modules by the process:

There is a huge amount of information that can be sourced by simply obtaining a list of DLL modules loaded into the process’s memory. This can be especially useful for identifying process dependencies, discovering the loading of vulnerable components, analyzing potential malicious modules and for forensic analysis or debugging purposes.

Let’s look at a practical example and analyze a running process which can be used to sideload a malicious DLL. A lesser-known example is the Microsoft OneDrive “OneDriveStandaloneUpdater.exe” process which loads “IPHLPAPI.DLL” libary. However, thanks to a DLL Search Order Hijacking vulnerability which exists, we can simply place a malicious “IPHLPAPI.DLL” into the victim process’s path located at %LOCALAPPDATA\Microsoft\OneDrive” and execute the “OneDriveStandaloneUpdater.exe” found within! Our malicious DLL is loaded and executed rather than the expected DLL found in C:\Windows\System32.

We can use our process handle script to look for loaded modules that look out of place. Running it against the open one drive updater process, we get some serious suspiciousness:

As you can see in the figure above, our malicious DLL file got loaded and we were able to confirm it using process handles!

Process handle demonstration – Process Injection

On the flip side, let’s take a look at how leveraging process handles can lead to process injection. This PowerShell snippet works similar to the one above, but with a few added imports and variables to handle the needed functions which enable use to allocate and write to memory.

param (
    [Parameter(Mandatory=$true)]
    [string]$process
)

# Remove the exe extension if present
$process = $process -replace '\.exe$', ''

# Load needed .NET assemblies
$loadAssembly =  @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class ProcessInjection {

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, uint dwSize, out IntPtr lpNumberOfBytesRead);

    [DllImport("kernel32.dll")]
    public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseHandle(IntPtr hObject);

    public const uint PROCESS_VM_OPERATION = 0x0008;
    public const uint PROCESS_VM_WRITE = 0x0020;
    public const uint PROCESS_VM_READ = 0x0010;
    public const uint PAGE_READWRITE = 0x04;
    public const uint MEM_COMMIT = 0x1000;
    public const uint MEM_RESERVE = 0x2000;
}
"@

Add-Type -TypeDefinition $loadAssembly -Language CSharp

$PROCESS_VM_OPERATION = 0x0008;
$PROCESS_VM_WRITE = 0x0020;
$PROCESS_VM_READ = 0x0010;
$PAGE_READWRITE = 0x04;
$MEM_COMMIT = 0x1000;
$MEM_RESERVE = 0x2000;

# Attempt to get the process by name
$targetProc = Get-Process -Name $process -ErrorAction SilentlyContinue
$targetProc = $targetProc[0]

if ($null -ne $targetProc) {

    # Open process with privs
    $processHandle = [ProcessInjection]::OpenProcess($PROCESS_VM_OPERATION -bor $PROCESS_VM_WRITE -bor $PROCESS_VM_READ, $false, $targetProc.Id)

    if ($processHandle -eq [IntPtr]::Zero) {
        Write-Error "Failed to open process.`n"
        exit
    }

    # Allocate memory
    $memoryAddress = [ProcessInjection]::VirtualAllocEx($processHandle, [IntPtr]::Zero, 1024, $MEM_COMMIT -bor $MEM_RESERVE, $PAGE_READWRITE)

    if ($memoryAddress -eq [IntPtr]::Zero) {
        Write-Error "Failed to allocate memory.`n"
        [ProcessInjection]::CloseHandle($processHandle)
        exit
    }

    # Code to inject into memory --
    # Example: send a message string into the process memory space
    $message = [System.Text.Encoding]::Unicode.GetBytes("Hello $process from PowerShell! (⌐ ͡■ ͜ʖ ͡■)`0")
    $bytesWritten = [UIntPtr]::Zero
    $result = [ProcessInjection]::WriteProcessMemory($processHandle, $memoryAddress, $message, $message.Length, [ref] $bytesWritten)

    if (-not $result) {
        Write-Host "Failed to write process memory."
        [ProcessInjection]::CloseHandle($processHandle)
        exit
    }

    Write-Host -ForegroundColor Yellow "Supplied code injected successfully.`n"

    # Cleanup
    [ProcessInjection]::CloseHandle($processHandle)

} else {
    Write-Error "The specified process ""$process"" was not found.`n"
}

While the above script doesn’t technically do anything malicious, it serves to demonstrate the concept of process injection. It specifically targets a running instance of a supplied running process and obtaining a handle with sufficient privileges to perform memory operations.

Similar to the previous example, the script leverages .NET assemblies to call native Windows API functions. This allows it to allocate memory within the the target process’s address space. Once the memory is allocated, the script proceeds to write a benign message into this allocated space based on the message length.

While this simple message could easily be replaced with something more typical of malware such as shellcode, this action exemplifies how a threat actor could potentially inject malicious code into a process to execute arbitrary actions covertly.

This is how typical automated process injection type scripts are generated using tools which generate semi-obfuscated PowerShell code such as msfvenom. If you generate PowerShell based payloads using the “psh-net” format, you should see quite a few similarities between the outputted PowerShell and the example above. The method in which process handles are managed and interacted with should be overall quite similar:

I hope this article was informative! Please feel free to comment below if you have any questions.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *