In January of this year, Peleg Hadar (@peleghd) and Tomer Bar reported to Microsoft, without publicly releasing any details, a Windows Print Spooler vulnerability that could allow privilege elevation from Windows 7 onwards. The security updates intended for that vulnerability (CVE-2020-1048) were released by Microsoft on May 12.
On the same day, Alex Ionescu (@aionescu) and Yarden Shafir (@yarden_shafir), who collided discovering the vulnerability, published a very detailed blog post about it: PrintDemon: Print Spooler Privilege Escalation, Persistence & Stealth (CVE-2020-1048 & more). Using this knowledge and after some tests, I was able to find a bypass for the security updates released by Microsoft mainly via junction points and symlinks (kudos to James Forshaw @tiraniddo).
In this post I’ll try to summarize the CVE-2020-1048 vulnerability exploitation process and the subsequent bypass found by me, plus some detailed references to read.
As I explained above, on May 12 of this same year Microsoft released the CVE-2020-1048 security updates:
An elevation of privilege vulnerability exists when the Windows Print Spooler service improperly allows arbitrary writing to the file system. An attacker who successfully exploited this vulnerability could run arbitrary code with elevated system privileges. An attacker could then install programs; view, change, or delete data; or create new accounts with full user rights.
To exploit this vulnerability, an attacker would have to log on to an affected system and run a specially crafted script or application.
The update addresses the vulnerability by correcting how the Windows Print Spooler Component writes to the file system.
Yarden Shafir and Alex Ionescu published his blogpost PrintDemon: Print Spooler Privilege Escalation, Persistence & Stealth (CVE-2020-1048 & more) giving detailed clues about the vulnerability and its exploitation without releasing a functional PoC, or at least not “as is”.
The exploitation process requires to create a specially crafted local printer port and a virtual printer with a generic driver. The printer port has to point to a file in a restricted path, i.e.
C:\Windows\system32\evil.dll, because during its creation Windows doesn’t check the user privileges to write in this path. If a standard user launches a print job later through this virtual printer, an error will be prompted because the user is not allowed to write in that path; the printing job is executed via impersoation and that is the normal behavior, isn’t it?
Note that to point the local pointer to the
C:\Windows\system32 path and later be able to create the virtual printer using this port with a generic driver it has to be done via PowerShell or programmatically; using the UI printer wizard will cause an Access denied error to be returned because the system will check the privileges before even writing the file.
The blog post, that I encourage all of you to read, deepens in the process done by the Windows print spooler and its print jobs. When a print job is launched two different kind of files are created in the
nnnnn.SPLwith the content to be printed.
nnnnn.SHDwith all the metadata related to the print job.
I’m not describing the
SHD file format because this knowledge is not an essential requirement to understand the exploitation process (see this and this). As short, when the spooler service is started it searchs for shadow files (
.SHD) on his default directory (
C:\Windows\system32\spool\PRINTERS habitually) checking its content and launching the printing of the associated spool file (
.SPL) doing this as
SYSTEM without impersonating the owner user. Now, puzzling all the details it is posible to build a functional PoC.
Using Visual Studio to open the original solution, we will first modify the
printserver\pserver.c file as described next:
We will also modify the
hMonitor variable declaration:
Next we include the
strsafe.h header and the next code fragment from this post at stackoverflow, that will be in charge for storing the malicious DLL content in memory:
Finally, as a standard user in a Windows 10 system without the CVE-2020-1048 security update we will launch the
printserver.exe with our
evil.dll in the same path:
The execution will be stopped waiting for our interaction; without restarting the execution we can check that a new printer has been added, PrintDemon, with a print job paused:
To reboot the
spooler service as a standard user we have to reboot the system. Once the system has started we’ll restart the print job queue:
As a result our malicious file has been planted on
We can use the malicious DLL also released by Yarden Shafir and Alex Ionescu with Faxing Your Way to SYSTEM — Part Two, published April 30, to accomplish a DLL hijacking attack in order to escalate privileges to system. That process will not be discussed in this article but I think it is slightly described by me on twitter.
A few days after the release of PrintDemon, more precisely May 15, BC Security (@BCSecurity1), actual maintainer of the Empire framework, release a PowerShell exploit. Probably my fault but I have to say that the exploit is not functional without a spooler service reboot.
On the other hand, on the past day August 5 Peleg Hadar (@peleghd) and Tomer Bar released all the details about the CVE-2020-1048 on both talks on Black Hat and DEF CON (video, slides y whitepaper). The exploit and additional material could be found on github.
My bypass for CVE-2020-1048
After the PrintDemon device deletion and the corresponding update installation we will initiate anew the virtual printer creation process using the next PowerShell command:
As showed above we will get an access denied error trying to create the local printer port. The security update added some checks for the correct write privileges on the specified path, blocking the process at this point; but wait, are you sure the process is completely blocked? We will need to find a trick to bypass this check…
First we will install a generic printer driver; we will also create a new directory that we will later use as the print job destination:
After that we will add a local printer port pointing to a file on this directory and we will confirm that everything went well:
The process will end correctly because our standard user has write privileges on the specified directory. Finally we will add the virtual printer device using the local printer port and the generic driver:
At this moment we will pause the virtual printer:
Now we will send a test printer job that will be paused on the printer queue:
Without going into specifics (more details) we will generate a junction point for the directory pointed by our local printer port,
C:\Users\me\test, and later we will use a symbolic link to point
If we restart the paused printer queue we will accomplish the creation of the
Once the bypass was confirmed I developed a C# PoC using NtApiDotNet by James Forshaw to programmatically create the required symlinks and IlMerge 3.0.40 plus MSBuild.ILMerge.Task to get the final binary. After making some questions and several tests I got a functional PoC.
The first execution requires to use as arguments the first step plus the path of our malicious file; after that we will need to reboot the system:
Once the system has started we will use as arguments of the binary the final step plus the name of the file that will be created on
As a result we will have planted our malicious DLL on
C:\Windows\system32 allowing us to execute a DLL hijacking to elevate privileges on a vulnerable system.
- CVE-2020-1048 | Windows Print Spooler Elevation of Privilege Vulnerability
- PrintDemon: Print Spooler Privilege Escalation, Persistence & Stealth (CVE-2020-1048 & more)
- PrintDemon (CVE-2020-1048)
- DEF CON Safe Mode - Peleg Hadar and Tomer Bar - After Stuxnet Printing still the Stairway to Heaven
- Print Spooler Research Tools
- Windows Print Spooler Patch Bypass Re-Enables Persistent Backdoor
- CVE-2020-1337 Windows Privilege Escalation
- CVE-2020-1337 – PrintDemon is dead, long live PrintDemon!
- CVE-2020-1337 - Binary Planting (CVE-2020-1048 bypass)