Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
my-CVE-2021-1675 — Detailed analysis and exploit implementation for Windows PrintNightmare (CVE-2021-1675/34527) with RPC-based privilege escalation and remote code execution via malicious printer driver installation. | Kitploit
Tools/GitHubGitHub/hahaleyile/my-cve-2021-1675
Privilege EscalationVulnerability AnalysisExploitationPapers & ResearchLearning & EducationRemote Access ToolBinary Exploitation
GitHubhahaleyile/my-cve-2021-1675

my-CVE-2021-1675

Detailed analysis and exploit implementation for Windows PrintNightmare (CVE-2021-1675/34527) with RPC-based privilege escalation and remote code execution via malicious printer driver installation.

View Repository
3315 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

= Print Nightmare Analysis Report :imagesdir: Figures :toc: :icons: font :figure-caption: Figure :xrefstyle: short :pdf-theme: basic-theme.yml

On June 29, 2021, a very severe Windows Print Spooler vulnerability was exposed as a 0-day, with a base score of 8.8, and was published on GitHub (now deleted). This vulnerability is the infamous PrintNightmare: CVE-2021-34527, which is even more dangerous than EternalBlue.

== Vulnerability Basic Information

CVE-2021-34527 affects almost all versions of Windows 7 and Windows Server 2008 and later; see <> for details.

In terms of impact, an attacker can use a standard user credential to remotely execute arbitrary code with administrator privileges. As for exploit difficulty, this vulnerability is very easy to exploit, therefore extremely harmful.

In terms of vulnerability characteristics, CVE-2021-34527 is based on CVE-2021-1675. CVE-2021-1675 is both a local privilege escalation and remote code execution vulnerability, and it has many similarities with CVE-2021-34527.

Before understanding how the vulnerability works, we should have a general understanding of the Windows Print Spooler architecture, which will help us clarify the relationships between the modules involved in the vulnerability.

== CVE-2021-1675 Call Flow

=== Windows Print Spooler Architecture

The spooler architecture can be represented as shown in <<spooler_arch>>:

[[spooler_arch]] .Print Spooler Architecture image::Print Spooler Architecture.png[]

Specifically, the Print Spooler is used to manage print jobs and consists of the following components:

winspool.drv:: The dynamic-link library file provided to users. This file defines the spooler-related Win32 API for user calls. All APIs use remote procedure calls to obtain services.

spoolsv.exe:: spoolsv.exe acts as the server role in the architecture, being the first program to process API calls. This design allows the Print Spooler to handle both local and remote print jobs without differentiation.

spoolsv.dll:: The router program. It forwards print requests received by spoolsv.exe to various print providers and decides which provider will ultimately process the request. Its function is to distinguish whether a print job is remote or local. On a remote machine, it will always assign the job to the local print provider.

localspl.dll:: The local print provider. The main task of the print provider is to handle print job management; most of the APIs are implemented in this module.

Continuing research based on the above theory, for example, when I call the AddPrinterDriverEx function (CVE-2021-1675), the following flow occurs:

=== Function Version Selection

First, this function is actually a macro that selects the Unicode version (W) or ANSI version (A) based on the local compilation environment, as shown in <>:

[[AddPrinterDriverEx]] .AddPrinterDriverEx image::AddPrinterDriverEx.png[]

However, whether it is the wide character or narrow character version, the result is essentially the same because Windows kernel strings use Unicode encoding, so the ANSI version call will eventually be converted to a Unicode version call, as shown in <>:

[[AnsiToUnicode]] .AnsiToUnicode image::AnsiToUnicode.png[]

After the parameters of the ANSI function are all converted to the Unicode version, a function is called (<>):

[[AnsiCallUnicode]] .AnsiCallUnicode image::AnsiCallUnicode.png[]

And that function is actually the Unicode version of AddPrinterDriverEx (<>):

[[GetUnicodeProcAddress]] .GetUnicodeProcAddress image::GetUnicodeProcAddress.png[]

=== API Function Sends RPC Request to Spooler Server

After entering the Unicode version of the function, it first selects the function parameter type based on the value of Level:

image::pDriverInfo.png[]

In this vulnerability, we set Level to 2, meaning the type of parameter pDriverInfo is the DRIVER_INFO_2 structure. Then Windows processes the function parameters, and after processing, continues to handle the API through a remote procedure call:

image::set arguments.png[]

image::NdrClientCall3.png[]

=== MSRPC Mechanism

Microsoft's remote procedure call mechanism is built on the DCE standard. In simple terms, a remote procedure call runs a process on a remote system; these processes are predefined by the programmer or the system.

The specific approach of RPC is to serialize the function to be called remotely, transmit it over the network to the remote system, where it is deserialized and executed. In Microsoft's architecture, TCP/IP and SMB are commonly chosen protocols to transport RPC calls.

To use MSRPC, you must first define the IDL interface description of the function to be called, then use the MIDL tool to generate the corresponding serialization stubs for the client and server. For some Win32 APIs, the server stub is already defined, so we only need to generate and use the client stub.

MSRPC uses UUIDs to identify a protocol type. For example, MS-RPRN is used to describe the remote print protocol; all functions related to remote printing are part of this protocol. MSRPC uses UUID 12345678-1234-ABCD-EF00-0123456789AB to identify this protocol (<<rprn_uuid>>):

[[rprn_uuid]] .MS-RPRN UUID image::spoolss uuid.png[]

Then, on this connection, you can use an operation number to identify a specific function within the protocol for remote invocation. For example, AddPrinterDriverEx identifies itself with 89 (<<addPrinterDriverEx_opnum>>):

[[addPrinterDriverEx_opnum]] .AddPrinterDriverEx Opnum image::AddPrinterDriverEx Opnum.png[]

When using MSRPC, two points are worth noting:

[IMPORTANT]

  • TCP/IP connections use dynamic ports, which must be obtained through the endpoint mapper listening on port 135.

"As you can see in your output, the scripts are trying to connect to port 135 (endpoint mapper) in order to get the TCP/IP port where the DCOM endpoint is listening (that is a dynamic port)." -- SecureAuthCorp/impacket issue #412

  • Additionally, some RPC functions require authentication to call, so this vulnerability requires a standard user account. ====

=== spoolsv.exe Processes API Requests

[[call_flow]] .RpcAddPrinterDriverEx Call Flow image::Function Calls.png[]

From <<call_flow>>, we can see that spoolsv.exe calls these functions, and from internal analysis, this module does not perform much besides initialization. Finally, this module calls the function pointed to by pLocalProvidor, which is the LocalAddPrinterDriverEx function in the localspl.dll module. As a local print provider, localspl is indeed the module that implements API functionality.

=== Local Print Provider Function Implementation Logic

[[LocalAddPrinterDriverEx]] .LocalAddPrinterDriverEx image::LocalAddPrinterDriverEx.png[]

First, <> shows that this module verifies whether the spooler is running properly, then jumps to the SplAddPrinterDriverEx function.

[[SplAddPrinterDriverEx]] .SplAddPrinterDriverEx image::SplAddPrinterDriverEx.png[]

The <> function is an important location for determining whether the AddPrinterDriverEx function can be executed successfully. The first half can be ignored because WPP is a logging-related technology; we skip it for now.

In the second half, Microsoft defines a variable v12, a flag that determines whether the function continues execution or exits directly.

[[bittest]] .bittest dwFileCopyFlags image::bittest in spl.png[]

From <>, we can see there are two conditions for continuing execution: either v12 is 0 (meaning bittest succeeds) or Validate succeeds. Validate is a permission check that cannot be easily bypassed. One API inside is OpenProcessToken, which means it needs to elevate privileges in the following process; this cannot be done without being an administrator.

Therefore, to continue execution, we must bypass the bittest check. The value being tested is a4 — the fourth parameter of the function, which is described in Microsoft's documentation as parameter <>:

|=== |Name/value |Description

|APD_STRICT_UPGRADE

0x00000001

|Add the replacement printer driver only if none of the files of the replacement driver are older than any corresponding files of the currently installed driver.

|APD_STRICT_DOWNGRADE

0x00000002

|Add the replacement printer driver only if none of the files of the currently installed driver are older than any corresponding files of the replacement driver.

|APD_COPY_ALL_FILES

0x00000004

|Add the printer driver and copy all the files in the driver directory. File time stamps MUST be ignored.

|APD_COPY_NEW_FILES

0x00000008

|Add the printer driver and copy the files in the driver directory that are newer than any of the corresponding files that are currently in use.

|APD_COPY_FROM_DIRECTORY

0x00000010

|Add the printer driver by using the fully qualified file names that are specified in the _DRIVER_INFO_6 structure. If this flag is specified, one of the other copy flags in this bit field MUST be specified.

|APD_DONT_COPY_FILES_TO_CLUSTER

0x00001000

|When adding a printer driver to a print server cluster, do not copy the driver files to the shared cluster disk.

|APD_COPY_TO_ALL_SPOOLERS

0x00002000

|Add the printer driver to cluster spooler servers.

|APD_INSTALL_WARNED_DRIVER

0x00008000

|Add the printer driver, even if it is in the server's List of Warned Printer Drivers.

|APD_RETURN_BLOCKING_STATUS_CODE

0x00010000

|Specifies the implementation-specific error code to return if the printer driver is blocked from installation by server policy.

|===

bittest 16 checks whether the 16th bit of the variable is 1, corresponding to a parameter value of 0x8000 (APD_INSTALL_WARNED_DRIVER). According to the description, this parameter means adding the printer driver to the server without verification.

It is said that before CVE-2021-1675 was fixed, this parameter had not appeared in the official documentation, which makes it easy to see where the vulnerability lies.

=== Vulnerability Exploitation Method

When the method to add a printer driver is actually executed, if the DRIVER_INFO_2 structure is chosen, the following events occur:

. Open DriverFile, ConfigFile, and DataFile separately to confirm whether these three files exist. Only DataFile is allowed to be a UNC path.

. If all three files exist, copy them to the directory , as shown in <<cp_conf_file>> and <<cp_data_file>>: + [[cp_conf_file]] .Copy Config File image::copy config file.png[] + [[cp_data_file]] .Copy Data File image::copy data file.png[]

ifdef::backend-pdf[] For the execution results of the malicious program, see https://github.com/hahaleyile/my-CVE-2021-1675[my repository]; the demo animation is the gif file in the Figures directory. endif::[]

== Microsoft's Fix for CVE-2021-1675

On June 8, 2021, Microsoft patched CVE-2021-1675, with the following specific changes:

[[path_1675]] .CVE-2021-1675 Patch image::IsElevated.png[]

[[YIsElevationRequired]] .YIsElevationRequired image::YIsElevationRequired.png[]

[[YIsElevated]] .YIsElevated image::YIsElevated.png[]

[[unset_1675]] .Unset APD_INSTALL_WARNED_DRIVER image::JudgeIsElevated.png[]

Microsoft added a user privilege elevation check in the RpcAddPrinterDriverEx function, and users can remove this restriction via the registry (<<path_1675>>). Users only need to create a key named NoWaringNoElevationOnInstall at a specified registry location (<>), or if the RPC account can obtain the TOKEN_QUERY process token (<>), the patch can be bypassed. If the patch takes effect, the 16th bit of the dwFileCopyFlags parameter is set to 0 by an AND operation, meaning the APD_INSTALL_WARNED_DRIVER parameter value is invalidated (<<unset_1675>>).

== Bypassing the CVE-2021-1675 Patch

Although Microsoft patched the RpcAddPrinterDriverEx function, we can still bypass it by remotely calling RpcAsyncAddPrinterDriver. As shown in <<async_send>>, the client-side function directly makes a remote call to the server:

Download Tool
C:\Windows\System32\spool\drivers\x64\3\New

. The reason for copying to this directory is to execute the corresponding files; the 3 indicates that this printer driver is a v3 type driver. First, copy the new files to the New directory to prevent overwriting files in the 3 directory. If there is a file with the same name in 3, the duplicate file is moved to the Old directory as a backup, and then the files from New are copied to 3 to overwrite. This can be observed when executing the RpcAddPrinterDriverEx method a second time: + .Second Time RPC Call image::second time call.png[] + .Backup Files To Old Directory image::backup file.png[] + .Copy New Files To Destination image::copy file.png[] + Based on this mechanism, we can save a file from a remote path to a local path, because the driver file and configuration file parameters can only be local paths, while the data file parameter can be a remote path.

. According to <>, pConfigFile is the configuration dynamic-link library for the device driver, so it needs to be loaded once for initialization. This is confirmed by actual runtime behavior. + .Load pConfigFile image::Load Image.png[] + Thus, by writing a malicious DLL and placing malicious code in the DLL entry point, arbitrary code can be executed with administrator privileges. + .spoolsv.exe is under administrator privilege image::spoolsv user.png[]

. The CreateInternalDriverFileArray() function decides whether to check the spool driver directory based on file operation flags. If the a5 flag is set to False, the driver loading function only checks whether the user directory contains the driver files to be copied. Otherwise, the function attempts to find the target driver in the spool driver directory. This requires that dwFileCopyFlags also sets the parameter APD_COPY_FROM_DIRECTORY. + image::APD.png[] + image::APD_1.png[]

[TIP]

In summary, the exploitation approach for malicious code is: use a malicious DLL as the configfile for initialization, which will execute arbitrary code with administrator privileges. It is only necessary to set up an SMB share on the attacker's host, allowing the target to download the malicious program as a datafile to the local machine and execute it.

== Usage of the Exploit Program

This program is built on Docker and can effectively resolve environment dependency issues.

First, the user downloads the compose file to their own directory and creates a share folder in that directory as a mount point. Users can place malicious programs in the share folder, which will be shared as an SMB path.

Then, the user enters the following command in the terminal:

[source,docker]

docker-compose up -d

to start the cluster (one container). Users can enter the container to operate; the environment inside the container is already configured.

Alternatively, commands can be executed directly in the terminal:

[source,docker]

docker-compose run my_cve python main.py -h

to run the command.

== Exploit Program Execution Results

Thanks to <> for the open-source code for reference!

ifndef::backend-pdf[] .Program running result image::exploit.gif[] endif::[]

[[async_send]] .RpcAsyncAddPrinterDriver Send image::RpcAsyncAddPrinterDriver Send.png[]

The server first allocates space for a thread, then continues the call, as shown in <<async_receive>>:

[[async_receive]] .RpcAsyncAddPrinterDriver Receive image::RpcAsyncAddPrinterDriver Receive.png[]

The continued call pushes all parameters onto the stack and runs YAddPrinterDriverEx as a thread (<<async_yadd>>):

[[async_yadd]] .thread start YAddPrinterDriverEx image::thread start YAddPrinterDriverEx.png[]

This successfully bypasses Microsoft's patch on RpcAddPrinterDriverEx.

In other words, by remotely calling the RpcAsyncAddPrinterDriver function, arbitrary code can still be executed with administrator privileges.

Additionally, according to <>, the patch also has issues with token verification, and on machines where UAC is completely disabled, the patch is ineffective. However, I am not very familiar with this mechanism, so I will not comment further.

== Microsoft's Fix for CVE-2021-34527

On July 6, 2021, Microsoft temporarily resolved the Print Spooler vulnerability through a new round of patches.

According to the official statement, this patch will only allow administrators to install printer drivers on a print server. Furthermore, Microsoft added a Group Policy and two registry keys for users to customize this policy.

From the IDA decompilation of <<restrict_async>>, we can see that Microsoft added token and registry verification to the Async function.

[[restrict_async]] .restrict in async function image::restrict in async.png[]

From <<restrict_rpcadd>>, we can see that Microsoft added verification of user group and its registry key to the RpcAddPrinterDriverEx function.

[[restrict_rpcadd]] .restrict in RpcAddPrinterDriverEx Function image::restrict in rpcadd.png[]

Therefore, it is likely that the fix for CVE-2021-1675 indeed still has issues with token verification.

== Project Plan

. Add support for remote invocation of RpcAsyncAddPrinterDriver to bypass Microsoft's June 8 patch and achieve a CVE-2021-34527 exploit.

. Understand the UAC mechanism and improve this vulnerability analysis.

[bibliography] == References

  • [[[a,Official Website]]] Windows Print Spooler Remote Code Execution Vulnerability https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2021-34527

  • [[[b,dwFileCopyFlags]]] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/b96cc497-59e5-4510-ab04-5484993b259b

  • [[[c,Windows PrintNightmare Vulnerability (CVE-2021-34527) and Patch Analysis]]] https://www.freebuf.com/vuls/279876.html

  • [[[d,gentilkiwi/mimikatz]]] https://github.com/gentilkiwi/mimikatz

  • [[[e,Official Documentation]]] DRIVER_INFO_2 structure https://docs.microsoft.com/en-us/windows/win32/printdocs/driver-info-2

  • [[[f,cube0x0]]] https://github.com/cube0x0

  • [[[g,James Forshaw]]] https://twitter.com/tiraniddo/status/1410726790994169857

  • [[[h,Official Statement]]] KB5005010: Restricting installation of new printer drivers after applying the July 6, 2021 updates https://support.microsoft.com/en-us/topic/kb5005010-restricting-installation-of-new-printer-drivers-after-applying-the-july-6-2021-updates-31b91c02-05bc-4ada-a7ea-183b129578a7