Blog: How Tos
PID spoofing attacks with ntlmrelayx and named pipes
During my time researching software that I have installed on my local machine I have come across quite a few solutions that use named pipes as a form of inter-process communication.
Named pipes is a Microsoft technology that supports a data channel similar to a TCP socket. Named pipes can be accessed locally or remotely and typically sit on top of the SMB protocol on port 445. A unique feature of named pipes in comparison to TCP is that a pipe connection supports DACL’s out of the box, limiting what users are permitted to read or write to the pipe.
As the name suggests, addressing a pipe is through a unique name when the pipe is created on the server side, similar to a listening TCP port like 443 or 80. Another unique feature of pipes allows the server to impersonate the client user. Usually this is to drop client privileges, but sometimes this can be abused to increase privileges like the PrivEsc vuln we found in Docker not so long ago.
Quite often the server side of a named pipe is implemented within high privilege services. Usually as an aid to lower privilege processes from the same software vendor. An example could be to perform a high privileged action without the need for the low privileged user to elevate or need administrative credentials.
Because of this, vendors will generally go to great lengths to ensure that the action the client is requesting is authorised and does compromise the host in anyway. Unfortunately, quite often this can be implemented incorrectly. Either due to assumptions of the vendor or bypassed due to coding errors.
One method I have seen is the use of the GetNamedPipeClientProcessId/ GetNamedPipeServerProcessId API’s. These two API’s retrieve the PID of the client or server process at the other end of the pipe. Sometimes, the PID is then used to validate that the client/server is the expected process, by maybe checking the signature of the executable belonging to the process or other various kinds of validation.
This got me thinking, are we able to spoof the PID that is returned when these API calls are made? After some digging around I came across James Forshaw’s excellent blog post on named pipe client PID spoofing. Reading through the blog post it seems using built in Windows API’s, it is no longer possible to spoof PID’s due some previous CVE’s being fixed or other various restrictions. James’s article asks the question on whether impacket can be used for this purpose. Well, as it turns out, you can.
Depending on whether the client is connecting to the server over SMBv1 or SMBv2/3 will determine how we need to patch the SMB packet being submitted. SMBv1 has a specific field allocated for the PID as you can see below:
The story is a little different for SMBv2/3 as it seems Microsoft deprecated its use and marked the field as Reserved. Lucky for us, even though it’s reserved, it is still treated as the PID of the process sending/receiving the SMB packet. Here’s the SMBv2 Header:
Now that we know where we need to patch these values, we need to figure out how to patch them within impacket. Digging around within impackets smb.py I came across the sendSMB function. This is impackets default implementation of sendSMB for SMB1 packet:
Impackets default implementation for SMBv1 actually fills in the PID of the process prior to submitting the packet, so for us to patch this value we need to hook the smb.getData function so that we can update the PID before submitting the packet. The equivalent function for an SMBv2/3 packet is implemented inside smb3.py.
For an SMBv2/3 packet, the hook can be done on sendSMB itself, since the field is marked as Reserved it defaults to 0 and is not even set to anything inside sendSMB. My python foo is not the best, I am far more comfortable in C++/C# land, but I finally came up with this python code:
The code essentially handles the hooks for SMBv1 packets getData function and SMBv2/3’s sendSMB function. Also, we are only interested in overriding the PID on SMB2_CREATE/SMB_COMT_NT_CREATE_ANDX commands as these handle the opening of remote files and pipes over SMB. A quick test using the SMBConnection from impacket with our hooks in place along with a spoofed PID of 0x666 resulted in the following packet captured by Wireshark. Wireshark even goes to the lengths of labelling the Reserved field as PID for us instead of Reserved:
Since named pipes are opened over SMB connections, this also means they are vulnerable to NTLM relay attacks if the correct mitigations are not in place. Interestingly, there doesn’t appear to be a generic attack built into ntlmrelayx for named pipes, so as part of this research I decided to implement a basic named pipe attack into ntlmrelayx that implements the PID spoofing functionality above.
Named Pipe client options: --np-name NAME The name of the pipe to connect to --np-payload FILE Path to a file used as the payload --np-pid PID A specific client connection PID to use (cycle to 50000 is default)
ntlmrelayx.py has 3 new arguments that facilitate the attack mode along with a new np:// client protocol that can be used within the targets file or single target on the command line. Hopefully, most of the argument descriptions speak for themselves. The –np-payload argument is a file that will be used for the payload of data that should be written to the pipe on successful connection. Generally this would be specific to what the server is expected to read on the other side.
This generic attack model only supports a single one-way message sent to the server. If the attack requires several message exchanges prior to the malicious message being transmitted, then you will need to customise the impacket/examples/ntlmrelayx/attacks/npattack.py file to your specific needs. If a PID is not specified using the –np-pid argument, the default action will then be to connect to the pipe over and over by incrementing the PID by 4 each time (Windows PID’s are multiples of 4) and resending the payload.
Check out our impacket repo on GitHub with the named pipe attack mode implemented. You can also use the NPAttackSample project which implements a vulnerable client/server model which you can attack with ntlmrelayx.
Using the NPAttackSample project, run the NPServer and NPClient on a machine you intend on relaying a connection to using ntlmrelayx. You can use the NPClient console to enter a command that will be sent to the NPServer program for execution. NPServer will validate that the connecting client application is NPClient prior to execution by checking the connecting client’s PID against the process name. If everything checks out, the command is executed. This is an NPAttackSample executing calc:
Using our version of impacket from GitHub, install and run ntlmrelayx from your attacking machine. Start ntlmrelayx with the below options. The attacking host is Windows in this example, so the SMB relaying server was disabled. Alternatively, if you are on a Linux box, you can leave the SMB server in place. The target address should be the host you have NPServer running on.
ntlmrelayx.py -smb2support --np-name NPAttackPipe --np-payload payload.txt --no-smb-server --http-port 8080 -t np://192.168.0.2
With the attacking machine running Windows, we can cheat and just navigate to http://localhost:8080 for credentials to be relayed to our vulnerable server. Of course, on a real engagement this can be paired with Responder or mitm6 so that poisoning can occur, and credentials are relayed automatically.
The setup above will use PID cycling to find the correct PID. It shouldn’t take too long a freshly booted VM to find the right PID, but again, if you want to cheat, just use the –np-pid option with the PID of the running NPClient program.
The payload file should contain a simple command to execute followed by a new line, there are no argument processors built into the server, so only the command will work. For example, calc.exe or cmd.exe on its own.
The attack can be performed both locally and remotely. The example presented here is a remote attack since we are also leveraging relaying to demonstrate that no known credentials are needed either to exploit vulnerable named pipe servers that don’t implement SMB signing.
If you are attacking locally you do not need ntlmrelayx. Just a simple impacket script that uses SMBConnection with the customised hooks will work the same way to spoof PID’s. You can use npattack.py as a reference. A local attack has the added benefit of process enumeration or even launching an instance of the process that will be validated by the server prior to the attack.
As a developer, what can you do to prevent such attacks?
First things first, if your named pipe is only intended to be accesses from the local machine, make sure you add NT AUTHORITY\NETWORK as a deny rule to the pipe’s DACL. This will prevent the pipe from being accessed over the network. Alternatively, use ALPC or similar RPC interfaces that are designed for local communication only.
Secondly, do not rely only the GetNamedPipeClientProcessId/GetNamedPipeServerProcessId API’s for security validation since as this research demonstrates, it can be spoofed.