This post will cover a little project I did last week and is about Named pipe Impersonation in combination with Pass-the-Hash (PTH) to execute binaries as another user. Both techniques used are not new and often used, the only
thing I did here is combination and modification of existing tools. The current public tools all use PTH for network authentication only. The difference to this “new” technique is therefore, that you can also spawn a new shell or C2-Stager as the PTH user for local actions and
network authentication.
2.05.2021: Update
Unfortunately I learned, that my technique can only be used for local actions, but not for network authentication, as Impersonation Tokens are restricted to that.
I faced certain Offensive Security project situations in the past, where I already had the NTLM-Hash of a low privileged
user account and needed a shell for that user on the current compromised system - but that was not possible with the current public tools. Imagine two more facts for a situation like that - the NTLM Hash could not be cracked and there is no process of the victim user to execute shellcode in it or to migrate into that process. This may sound like an absurd edge-case for some of you. I still experienced that multiple times. Not only in one engagement I spend a lot of time searching for the right tool/technique in that specific situation. Last week, @n00py1 tweeted exactly the question I had in mind in those projects:
So I thought: Other people in the field obviously have the same limitations in existing tools.
My personal goals for a tool/technique were:
- Fully featured shell or C2-connection as the victim user-account
- It must to able to also Impersonate
low privileged
accounts - depending on engagement goals it might be needed to access a system with a specific user such as the CEO, HR-accounts, SAP-administrators or others
- The tool has to be used on a fully compromised system without another for example linux box under control in the network, so that it can be used as C2-module for example
The Tweet above therefore inspired me, to again search for existing tools/techniques. There are plenty of tools for network authentication via Pass-the-Hash. Most of them have the primary goal of code execution on remote systems - which needs a privileged users Hash. Some of those are:
If we want to have access to an administrative account and a shell for that account, we can easily use the WMI, DCOM and WinRM PTH-tools, as commands are executed in the users context. The python tools could be executed over a SOCKS tunnel via C2 for example, the Powershell scripts work out-of-the-box locally. SMB PTH tools execute commands as nt-authority\system
, so user impersonation is not possible here. One of my personal goals was not fulfilled - the impersonation of low privileged
accounts. So I had to search for more possibilities.
The best results for local PTH actions are in my opinion indeed Mimikatz’s sekurlsa::pth
and Rubeus’s PTT
features. I tested them again to start software via PTH or inject a Kerberos ticket into existing processes and realized, that they only
provide network authentication for the PTH-user. Network authentication Only? Ok, I have to admit, in the most cases network authentication is enough. You can read/write the Active Directory via LDAP, access network shares via SMB, execute code on remote systems with a privileged user (SMB, WMI, DCOM, WinRM) and so on. But still - the edge case
to start an application as the other user via Pass-the-Hash is not possible. I thought to myself, that it might be possible to modify one of those tools to archieve the specific goal of an interactive shell. To do that, I had to first dig into the code to understand it. Modifying Rubeus was no opion for me, because PTT
uses a Kerberos ticket, which is as far as I know only used for network authentication. That won’t help us authenticating on the localhost for a shell. So I took a look at the Mimikatz feature in the next step.
Mimikatz’s sekurlsa::pth feature
This part will only give some background information to the sekurlsa::pth
Mimikatz module. If you already know about it feel free to skip. Searching for sekurlsa::pth
internals resulted in two good blog posts for me, which I recommend reading for a deeper look into the topic, as I will only explain the high-level process:
A really short high-level overview of the process is as follows:
- MSV1_0 and Kerberos are Windows two Authentication providers, which handle authentication using provided credential material
- The LSASS process on a Windows Operating System contains a structure with MSV1_0 and Kerberos credential material
- Mimikatz
sekurlsa::pth
creates a new process with a dummy password for the PTH user. The process is first created in the SUSPENDED state
- Afterwards it creates a new MSV and Kerberos structure with the user provided NTLM hash and overwrites the original structure for the given user
- The newly created process is RESUMED, so that the specified binary like for example
cmd.exe
is executed
This part is copy & paste from the part II blog:
Overwriting these structures does not change the security information or user information for the local user account. The credentials stored in LSASS are associated with the logon session used for network authentication and not for identifying the local user account associated with a process.
Those of you, who read my other blog posts know, that C/C++ is not my favorite language. Therefore I decided to work with @b4rtik’s SharpKatz code, which is a C# port of the in my opinion most important and most used Mimikatz functions. Normally, I don’t like blog posts explaining a topic with code. Don’t ask me why, but this time I did it myself here. The PTH module first creates a structure for the credential material called data
from the class SEKURLSA_PTH_DATA
:
The NtlmHash of this new structure is filled with our given Hash:
if (!string.IsNullOrEmpty(rc4))
ntlmHashbytes = Utility.StringToByteArray(rc4);
if (!string.IsNullOrEmpty(ntlmHash))
ntlmHashbytes = Utility.StringToByteArray(ntlmHash);
if (ntlmHashbytes.Length != Msv1.LM_NTLM_HASH_LENGTH)
throw new System.ArgumentException();
data.NtlmHash = ntlmHashbytes;
A new process in the SUSPENDED
state is opened. Note, that our PTH username is chosen with an empty password:
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
if(CreateProcessWithLogonW(user, "", domain, @"C:\Windows\System32\", binary, arguments, CreationFlags.CREATE_SUSPENDED, ref pi))
In the next step, the process is opened and the LogonID
of the new process is copied into our credential material object, which is related to our PTH username.
Afterwards, the function Pth_luid
is called. This function first searches for and afterwards overwrites the MSV1.0 and Kerberos credential material with our newly created structure:
If that resulted in success, the process is resumed via NtResumeProcess
.
Named Pipe Impersonation
Thinking about alternative ways for PTH user Impersonation I asked @EthicalChaos about my approach/ideas and the use-case. Brainstorming with you is always a pleasure, thanks for that! Some ideas for the use-case were:
- NTLM challenge response locally via InitializeSecurityContext / AcceptSecurityContext
- Impersonation via process token
- Impersonation via named pipe identity
- Impersonation via RPC Identity
I excluded the first one, because I simply had no idea about that and never worked with it before. Impersonation via process token or RPC Identity required an existing process for the target user to steal the token from. A process for the target user doesn’t exist in my szenario, so only Named Pipe Impersonation was left. And I thought cool, I already worked with that to build a script to get a SYSTEM
shell - NamedPipeSystem.ps1. So I’m not completely lost in the topic and know what it is about.
For everyone out there, who doesn’t know about Named Pipe Impersonation I can recommend the following blog post by @decoder_it:
Again, I will give a short high-level overview for it. Named Pipes are ment to be used for asynchronous or synchronous communication between processes. It’s possible to send or receive data via Named Pipes locally or over the network. Named Pipes on a Windows Operating System are accessible over the IPC$
network share. One Windows API call, namely ImpersonateNamedPipeClient()
allows the server to impersonate any client connecting to it. The only
thing you need for that is the SeImpersonatePrivilege
privilege. Local administrators and many service-accounts have this privilege by default. So opening up a Named Pipe with this privileges enables us to Impersonate any user connecting to that Pipe via ImpersonateNamedPipeClient()
and open a new process with the token of that user-account.
My first thought about Named Pipe Impersonation in combination with PTH was, that I could spawn a new cmd.exe
process via Mimikatz
or SharpKatz
Pass-the-Hash and connect to the Named Pipe over IPC$
in the new process. If the network credentials are used for that, we would be able to fulfill all our goals for a new tool. So I opened up a new Powershell process via PTH and SharpKatz with the following command:
.\SharpKatz.exe --Command pth --User testing --Domain iPad --NtlmHash 7C53CFA5EA7D0F9B3B968AA0FB51A3F5 --Binary "\WindowsPowerShell\v1.0\powershell.exe"
What happens in the background? That is explained above. To test, that we are really using the credentials for the user testing
we can connect to a linux boxes SMBServer:
smbserver.py -ip 192.168.126.131 -smb2support testshare /mnt/share
After opening up the server we can connect to it via simply echoing into the share:
And voila, the authentication as testing
came in, so this definitely works:
@decoder_it’s wrote a Powershell script - pipeserverimpersonate.ps1 - which let’s us easily open up a Named Pipe Server for user Impersonation and to open cmd.exe
afterwards with the token of the connecting user. The next step for me was to test, if connections from this new process connect to the Named Pipe Server with the network credentials. It turned out, that this unfortunately is not the case:
I tried to access the Pipe via 127.0.0.1
, Hostname
, External IP
, but the same result in every case:
I also tried using a NamedPipeClient via Powershell - maybe this would result in network authentication with the user testing
- still no success:
At this point I had no clue on how I could trigger network authentication to localhost for the Named Pipe access. So I gave up on Mimikatz and SharpKatz - but still learned something by doing that. And maybe some of you also learned something in this section. This was a dead end for me.
But what happens exactly when network authentication is triggered? To check that, I monitored the network interface for SMB access from one Windows System to another one:
- The TCP/IP Three-way-handshake is done (SYN,SYN/ACK,ACK)
- Two Negotiate Protocol Requests and Responses
- Session Setup Request,
NTLMSSP_NEGOTIATE
+ NTLMSSP_AUTH
- Tree Connect Request to
IPC$
- Create Request File
testpipe
During my tool research I took a look at @kevin_robertson’s Invoke-SMBExec.ps1 code and found, that this script contains exactly the same packets and sends them manually. So by modifying this script, it could be possible to skip the Windows default behaviour and just send exactly those packets manually. This would simulate a remote system authenticating to our Pipe with the user testing
.
I went through the SMB documentation for some hours, but that did not help me much to be honest. But than I had the idea to just monitor the default Invoke-SMBExec.ps1
traffic for the testing user. Here is the result:
Comparing those two packet captures results in only one very small difference. Invoke-SMBExec.ps1
tries to access the Named Pipe svcctl
. We can easily change that in line 1562 and 2248 for the CreateRequest
and CreateAndXRequest
stage, by using different hex values for another Pipe name. So if we only change those bytes to the following, a CreateRequest
request is send to our attacker controlled Named Pipe:
$SMB_named_pipe_bytes = 0x74,0x00,0x65,0x00,0x73,0x00,0x74,0x00,0x70,0x00,0x69,0x00,0x70,0x00,0x65,0x00 # \testpipe
The result is an local authentication to the Named Pipe as the user testing
:
To get rid of the error message and the resulting timeout we have to do some further changes to the Invoke-SMBExec
code. I therefore modified the script, so that after the CreateRequest
a CloseRequest
, TreeDisconnect
and Logoff
packet is send instead of the default code execution stuff for Service creation and so on. I also removed all Inveigh Session stuff, parameters and so on.
But there still was one more thing to fix. I got the following error from cmd.exe
when impersonating the user testing
via network authentication:
This error didn’t pop up, when a cmd.exe
was opened with the password, accessing the Pipe afterwards.
Googling this error results in many many crap answers ranging from corrupted filesystem, try to repair it
to install DirectX 11
or Disable Antivirus
. I decided to ask the community via Twitter and got a fast response from @tiraniddo, that the error code is likely due to not being able to open the Window Station. A solution for that is changing the WinSta/Desktop
DACL to grant everyone access. I would have never figured this out, so thank you for that! :-) @decoder_it also send a link to RoguePotato, especially the code for setting correct WinSta/Desktop permissions is included there.
Modifying RoguePotato & building one script as PoC
Taking a look at the Desktop.cpp
code from RoguePotato I decided pretty fast, that porting this code to Powershell or C# is no good idea for me as I would need way too much time for that. So my idea was, to modify the RoguePotato code to get a PipeServer which sets correct permissions for WinSta/Desktop
. Doing this was straight forward as I mostly had to remove code. So I removed the RogueOxidResolver components, the IStorageTrigger and so on. The result is the PipeServerImpersonate code.
Testing the server in combination with our modified Invoke-SMBExec script resulted in no shell at first. The CreateProcessAsUserW
function did not open up the desired binary even though the token had SE_ASSIGN_PRIMARY_NAME
privileges. I ended up using CreateProcessWithTokenW
with CREATE_NEW_CONSOLE
as dwCreationFlags, which worked perfectly fine. Opening up the Named Pipe via modified RoguePotato and connecting to it via Invoke-NamedPipePTH.ps1 resulted in successfull Pass-the-Hash to a Named Pipe for Impersonation and binary execution with the new token:
Still - this is not a perfect solution. Dropping PipeServerImpersonate to disk and executing the script in another session is one option, but a single script doing everything is much better in my opinion. Therefore I build a single script, which leverages Invoke-ReflectivePEInjection.ps1 to execute PipeServerImpersonate from memory. This is done in the background via Start-Job
, so that Invoke-NamedPipePTH
can connect to the Pipe afterwards. It’s possible to specify a custom Pipe Name and binary for execution:
This enables us to use it from a C2-Server as module. You could also specify a C2-Stager as binary, so that you will get a new agent with the credentials of the PTH user.
Further ideas & improvements
I see my code still as PoC, because it is far away from being OPSEC safe and I didn’t test that much possible use-cases. Using Syscalls for PipeServerImpersonate and PE-Injection instead of Windows API functions would further improve this for example.
For those of you looking for a C# solution: Sharp-SMBExec is a C# port of Invoke-SMBExec
which can be modified the same way I did here to get a C# version for the PTH to the Named Pipe part. However, the PipeServerImpersonate part should also be ported, which in my opinion is more work todo.
2.05.2021: Update
I also wrote a C# version based on this idea. It can be found here:
https://github.com/S3cur3Th1sSh1t/SharpNamedPipePTH
The whole project gave me the idea, that it would be really cool to also add an option to impacket’s ntlmrelayx.py
to relay connections to a Named Pipe. Imagine you compromised a single host in a customer environment and this single host didn’t gave any valuable credentials but has SMB Signing disabled
. Modifying PipeServerImpersonate, so that the Named Pipe is not closed but re-opened again after executing a binary would make it possible to get a C2-Stager for every single incoming NetNTLMV2 connection. This means raining shells. The connections only need to be relayed to \\targetserver\IPC$\pipename
to get a shell or C2-connection.
2.05.2021: Update
This idea was nice with the background thought, that we have local and
network authentication for the new process. But we can only do stuff locally with an Impersonation token. This is also explained in the next part. Therefore the raining shells would not help us to move anywhere.
Having @itm4n’s article about PrintSpoofer - https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/ - in mind, I also had the idea, if it’s possible to relay a Domain Controllers Computer-Account Hash to our Named Pipe via Spoolsample. This can be done with the MS-RPRN part of the https://github.com/leechristensen/SpoolSample/tree/master/MS-RPRN by using /
instead of \
for the target system like this:
MS-RPRN.exe \\DomainControllerFQDN \\OutCompromisedSystemFQDN/pipe/pipename
This works perfectly fine, and we therefore can get a shell as Domain Controller computer account:
Using TokenViewer gives us the possibility to see, which Token Type and Impersonation Level we have:
I learned after writing this post and fiddling around with the new impersonated users shells, that Impersonation Tokens
are restricted to Operating System local actions only. Processes with this Token Type cannot access ressources in the network like LDAP, SMB, HTTP or whatever else. Therefore, our DC shell for example is useless.
Until someone finds a way to get an Delegation Token
from a process with an Impersonation Token
for example. If the impersonated user is logged on interactively, you can also create a sheduled task for that user and trigger it. This would for example result in network access for that newly created task.
Conclusion
This is the first time, that I created somehow a new technique. At least I didn’t see anyone else using a combination of PTH and Named Pipe Impersonation with the same goal. For me, this was a pretty exciting experience and I learned a lot again.
I hope, that you also learned something from that or at least can use the resulting tool in some engagements whenever you are stuck in a situation described above. The script/tool is released with this post, and feedback is as always very welcome!
20.04.2021: Update
I’m pretty sure, that I before publication of the tool tested the content of Start-Job Scriptblocks for AMSI scans/blocks. And it was not scanned neither blocked. After the publication, Microsoft obviously decided to activate this feature, because the standalone script didn’t work anymore with Defender enabled even after patching AMSI.dll
in memory for the process:
Therefore, I decided to switch from the native Start-Job
function to the Start-ThreadJob
function, which again bypasses Defender because its executed in the same process:
If this description is true, Start-Job
should have scanned and blocked scripts before because it’s another process. But here we stay in the same process, therefore a bypass works:
Links & Resources