How to use Python for privilege escalation in Windows

Penetration testers can use Python to write scripts and services to discover security vulnerabilities. In this walkthrough, learn how to escalate privileges in Windows.

Penetration testers use a variety of offensive security tools to evaluate an organization's security stance and find potential vulnerabilities. While pen testers can use off-the-shelf tools, such as Wireshark or Scapy, to handle such tasks, it's also good to know how to write a custom script. One popular programming language to do this is Python.

With Python, pen testers can create scripts to build network sniffers, exfiltrate data, search for exploits and conduct forensics following a breach.

To help pen testers learn how to use Python for penetration testing, authors Justin Seitz, security practitioner and co-founder of security and intelligence company Dark River Systems, and Tim Arnold, principal software developer at SAS Institute, wrote Black Hat Python: Python Programming for Hackers and Pentesters 2nd Edition. An update of the 2015 release, the 2021 version includes coding with Python 3.

Here, read an excerpt from Chapter 10 of the book. Arnold and Seitz describe how to use Python for Windows privilege escalation attacks, providing provide pen testers with the Python libraries needed and explaining how to create a service to execute scripts.

Download a PDF of Chapter 10, which explores how to run the script created, automate mundane tasks, and inject code to monitor processes and file locations.

black hat python book coverClick here to learn more about
Tim Arnold and Justin Seitz's
Black Hat Python.

More about Black Hat Python

Learn more about using Python for pen testing in this interview with authors Justin Seitz and Tim Arnold.






So you've popped a box inside a nice, juicy Windows network. Maybe you leveraged a remote heap overflow, or you phished your way in. It's time to start looking for ways to escalate privileges.

Even if you're already operating as 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'd 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 causing system instability. Let's explore some other means of acquiring elevated privileges on Windows. System administrators in large enterprises commonly schedule tasks or services that execute child processes, or run VBScript or PowerShell scripts to automate activities. Vendors, too, often have automated, built-in tasks that behave the same way. We'll try to take advantage of any high-privilege processes that handle files or execute binaries that are writable by low-privilege users. There are countless ways for you to try to escalate privileges on Windows, and we'll cover only 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 Management Instrumentation (WMI) programming to create a flexible interface that monitors the creation of new processes. We'll harvest useful data such as the file paths, the user who created the process, and enabled privileges. Then we'll hand off all filepaths to a file-monitoring script that continuously keeps track of any new files created, as well as what gets written to them. This tells us which files the high-privilege processes are accessing. Finally, we'll intercept the file-creation process by injecting our own scripting code into the file and make 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 to write the tooling in this chapter. Execute the following in a cmd.exe shell on Windows:

C:\Users\tim\work> pip install pywin32 wmi pyinstaller

You may have installed pyinstaller when you made your keylogger and screenshot-taker in Chapter 8, but if not, install it now (you can use pip). Next, we'll create the sample service we'll use to test our monitoring scripts.

Creating the Vulnerable BlackHat Service

The service we're creating emulates a set of vulnerabilities commonly found in large enterprise networks. We'll be attacking it later in this chapter. This service will periodically copy a script to a temporary directory and execute it from that directory. Open bhservice.py to get started:

import os
import servicemanager
import shutil
import subprocess
import sys
 
import win32event
import win32service
import win32serviceutil
 
SRCDIR = 'C:\\Users\\tim\\work'
TGTDIR = 'C:\\Windows\\TEMP'

Here, we do our imports, set the source directory for the script file, and then set the target directory where the service will run it. Now, we'll create the actual service using a class:

class BHServerSvc(win32serviceutil.ServiceFramework):
  _svc_name_ = "BlackHatService"
  _svc_display_name_ = "Black Hat Service"
  _svc_description_ = ("Executes VBScripts at regular intervals." +
              " What could possibly go wrong?")
1 def __init__(self,args):
self.vbs = os.path.join(TGTDIR, 'bhservice_task.vbs')
self.timeout = 1000 * 60

    win32serviceutil.ServiceFramework.__init__(self, args)
    self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

2
def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)

3
def SvcDoRun(self): self.ReportServiceStatus(win32service.SERVICE_RUNNING) self.main()

This class is a skeleton of what any service must provide. It inherits from the win32serviceutil.ServiceFramework and defines three methods. In the __init__ method, we initialize the framework, define the location of the script to run, set a time out of one minute, and create the event object 1. In the SvcStop method, we set the service status and stop the service 2. In the SvcDoRun method, we start the service and call the main method in which our tasks will run 3. We define this main method next:

def main(self): 
 1 while True:
         ret_code = win32event.WaitForSingleObject(
         self.hWaitStop, self.timeout)
       2 if ret_code == win32event.WAIT_OBJECT_0:
         servicemanager.LogInfoMsg("Service is stopping")
         break
         src = os.path.join(SRCDIR, 'bhservice_task.vbs')
         shutil.copy(src, self.vbs)
       3 subprocess.call("cscript.exe %s" % self.vbs, shell=False)
         os.unlink(self.vbs)

In main, we set up a loop 1 that runs every minute, because of the self.timeout parameter, until the service receives the stop signal 2. While it's running, we copy the script file to the target directory, execute the script, and remove the file 3.

In the main block, we handle any command line arguments:

if __name__ == '__main__':
  if len(sys.argv) == 1:
    servicemanager.Initialize()
    servicemanager.PrepareToHostSingle(BHServerSvc)
    servicemanager.StartServiceCtrlDispatcher()
  else:
    win32serviceutil.HandleCommandLine(BHServerSvc)

You may sometimes want to create a real service on a victim machine. This skeleton framework gives you the outline for how to structure one.

You can find the bhservice_tasks.vbs script at https://nostarch.com/black-hat-python2E/. Place the file in a directory with bhservice.py and change SRCDIR to point to this directory. Your directory should look like this:

06/22/2020 09:02 AM         <DIR>               .
06/22/2020 09:02 AM         <DIR>               ..
06/22/2020 11:26 AM                   2,099    bhservice.py
06/22/2020 11:08 AM                   2,501    bhservice_task.vbs

Now create the service executable with pyinstaller:

C:\Users\tim\work> pyinstaller -F --hiddenimport win32timezone bhservice.py

This command saves the bservice.exe file in the dist subdirectory. Let's change into that directory to install the service and get it started. As Administrator, run these commands:

C:\Users\tim\work\dist> bhservice.exe install
C:\Users\tim\work\dist> bhservice.exe start

Now, every minute, the service will write the script file into a temporary directory, execute the script, and delete the file. It will do this until you run the stop command:

C:\Users\tim\work\dist> bhservice.exe stop

You can start or stop the service as many times as you like. Keep in mind that if you change the code in bhservice.py, you'll also have to create a new executable with pyinstaller and have Windows reload the service with the bhservice update command. When you've finished playing around with the service in this chapter, remove it with bhservice remove.

You should be good to go. Now let's get on with the fun part!

Creating a Process Monitor

Several years ago, Justin, one of the authors of this book, contributed to El Jefe, a project of the security provider Immunity. At its core, El Jefe is a very simple process-monitoring system. The tool is designed to help people on defensive teams track process creation and the installation of malware.

While consulting one day, his coworker Mark Wuergler suggested that they use El Jefe offensively: with it, they could monitor processes executed as SYSTEM on the target Windows machines. This would provide insight into potentially insecure file handling or child process creation. It worked, and they walked away with numerous privilege escalation bugs, giving them the keys to the kingdom.

The major drawback of the original El Jefe was that it used a DLL, injected into every process, to intercept calls to the native CreateProcess function. It then used a named pipe to communicate with the collection client, which forwarded the details of the process creation to the logging server. Unfortunately, most antivirus software also hooks the CreateProcess calls, so either they view you as malware or you have system instability issues when running El Jefe side by side with the antivirus software.

We'll re-create some of El Jefe's monitoring capabilities in a hookless manner, gearing it toward offensive techniques. This should make our monitoring portable and give us the ability to run it alongside antivirus software without issue.

Process Monitoring with WMI

The Windows Management Instrumentation (WMI) API gives programmers the ability to monitor a system for certain events and then receive callbacks when those events occur. We'll leverage this interface to receive a callback every time a process is created and then log some valuable information: the time the process was created, the user who 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 created by higher- privilege accounts, and in particular, any processes that call external files, such as VBScript or batch scripts. When we have all of this information, we'll also determine the privileges enabled on the process tokens. In certain rare cases, you'll find processes that were created as a regular user but have been granted additional Windows privileges that you can leverage.

Let's begin by writing a very simple monitoring script that provides the basic process information and then build on that to determine the enabled privileges. This code was adapted from the Python WMI page (http://timgolden.me.uk/python/wmi/tutorial.html). 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 Administrator. Start by adding the following code to process_monitor.py:

import os
import sys
import win32api
import win32con
import win32security
import wmi
 
def log_to_file(message):
  with open('process_monitor_log.csv', 'a') as fd:
    fd.write(f'{message}\r\n')
 
def monitor():
  head = 'CommandLine, Time, Executable, Parent PID, PID, User, Privileges'
  log_to_file(head)
1 c = wmi.WMI()
2 process_watcher = c.Win32_Process.watch_for('creation')
 while True:
   try:
 3 new_process = process_watcher()
   cmdline = new_process.CommandLine
   create_date = new_process.CreationDate
   executable = new_process.ExecutablePath
   parent_pid = new_process.ParentProcessId
   pid = new_process.ProcessId
  4 proc_owner = new_process.GetOwner()
 
privileges = 'N/A'
process_log_message = (
  f'{cmdline} , {create_date} , {executable},'
  f'{parent_pid} , {pid} , {proc_owner} , {privileges}'
             )
print(process_log_message)
print()
log_to_file(process_log_message)
     except Exception:
           pass


if __name__ == '__main__':
    monitor()

We start by instantiating the WMI class 1 and tell it to watch for the process creation event 2. We then enter a loop, which blocks until process _watcher returns a new process event 3. The new process event is a WMI class called Win32_Process that contains all of the relevant information we're after (see MSDN documentation online for more information on the Win32 _Process WMI class). One of the class functions is GetOwner, which we call 4 to determine who spawned the process. We collect all of the process information we're looking for, output it to the screen, and log it to a file.

About the authors
Justin Seitz is a renowned cybersecurity and open source intelligence practitioner and co-founder of Dark River Systems Inc., a Canadian security and intelligence company. His work has been featured in Popular Science, Motherboard and Forbes. Seitz has authored two books on developing hacking tools. He created the AutomatingOSINT.com training platform and Hunchly, an open source intelligence collection tool for investigators. Seitz is also a contributor to the citizen journalism site Bellingcat, a member of the International Criminal Court's Technical Advisory Board and a fellow at the Center for Advanced Defense Studies in Washington, D.C.

Tim Arnold is currently a professional Python programmer and statistician. He spent much of his early career at North Carolina State University as a respected international speaker and educator. Among his accomplishments, he has ensured that educational tools are accessible to underserved communities worldwide, including making mathematical documentation accessible to the blind. Arnold has worked at SAS Institute as a principal software developer, designing and implementing a publishing system for technical and mathematical documentation. He has served on the board of the Raleigh Information Systems Security Association and as a consultant to the board of the International Statistical Institute. He enjoys working as an independent educator, making infosec and Python concepts available to new users and elevating those with more advanced skills.

https://nostarch.com/black-hat-python2E

Dig Deeper on Risk management