SECURITY

Listen To Those Pipes: Part 2

This blog post is part thirty of the "Hunting with Splunk: The Basics" series. Shannon Davis provided the first half of this blog on pipe hunting in part twenty-nine, and will now run through the second half! – Ryan Kovar


 

Background

In part 1, we saw how our detections picked up Cobalt Strike named pipes when they were using their default, out of the box, values. I also mentioned how Cobalt Strike provides the ability to change pipe names via malleable profiles to try and avoid being detected. Part 1 you say? Yes, part 1. If you haven’t already read it, you need to go back and do so, as that’s where I cover things like pre-requisites and other interesting tidbits. Reading this one first would be like watching the great Liam Neeson in Taken 4 before you watched him in Taken 3.

A Bit of the Ol’ Switcheroo

Back to malleable profiles. How do you do this, well it’s a rather trivial process, and this post by ZeroSec gives a good run-through. On top of changing profiles, you also want to modify the default Cobalt Strike Beacon binary to avoid using the MSSE pipe names. This is done through Cobalt Strike’s Artifact Kit. Read up on these if you want further detail of how this works.

I’ve now become quite the Fancy Lad having changed all of my pipe names to hopefully avoid the detection I ran earlier. With new pipe names (we’ll find these later), I ran through these same steps again:

  1. Compromise Windows 10 client with Cobalt Strike beacon.
  2. Run “net computers” to see what else is on the network.
  3. Move laterally via PSExec in Cobalt Strike to both Windows 2016 servers.
  4. Show running processes on both Windows 2016 servers.
  5. Run Mimikatz on the 2016 Domain Controller.
     

And here’s the result from the same search I ran earlier.


Exactly 0 events.

What can we do to find this behavior when someone was not super lazy with their use of Cobalt Strike? Well, read on my friend, read on.

A Hunting We Will Go

If you run other detections to pick up things like Mimikatz, you should have one or two threads to pull if something bad happens. I ran the following search in my environment to detect Cobalt Strike Mimikatz activity (it actually picks up other malicious LSASS memory dumping activities as well, and comes from the Splunk Security Content repo):

`sysmon` EventCode=10 TargetImage=*lsass.exe CallTrace=*dbgcore.dll* OR CallTrace=*dbghelp.dll* OR CallTrace=*ntdll.dll*
| stats count min(_time) as firstTime max(_time) as lastTime by Computer, TargetImage, TargetProcessId, SourceImage, SourceProcessId
| rename Computer as dest
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| `access_lsass_memory_for_dump_creation_filter`

This search also uses Sysmon events, but now we are looking for EventCode 10 (Process Access) events. Blackhills Information Security provides a good article on the various Sysmon event codes here, just scroll down to Event Code 10 for more information. After filtering for those events, we then look for only lsass.exe in the TargetImage field and then look for a few DLL’s in the CallTrace field.


The search returned a single result on win-dc-483.surge.local showing dllhost.exe accessing lsass.exe. The dllhost.exe SourceProcessId is 1076 and was run at 11:47:10 on October 28, 2021. This gives us some good information to begin our hunt.

In my previous blog, I was only concerned about parent/child processes and used the great PSTree app to help me sift through those quickly and accurately. Now I want to see if any named pipes were used, which can help me locate other hosts in my environment where there is similar behavior. Here is my first search:

index=main host="WIN-DC-483" source="xmlwineventlog:microsoft-windows-sysmon/operational" ProcessId=1076 EventCode!=7
| reverse
| table _time EventCode EventDescription Description Image PipeName process_name parent_process_name parent_process_id

I’m limiting my search to the domain controller to look for ProcessId=1076 to see what else it may have done around that same time (don’t do a crazy all-time search here!). I’m leaving out EventCode 7 (Image Loading), which can get quite noisy and isn’t part of my initial hypothesis of named pipe hunting. I want to see my events from start to finish, hence using reverse, and then I table out the fields I’m interested in, which gives me this:


First, we have a process creation event (Event Code 1) showing rundll32.exe (process ID of 3552) creating a dllhost.exe process. We then see the dllhost.exe process performing a pipe creation event (Event Code 17). The pipe created was named \Surgesock2\mrpipespostex-28b-0. No wonder our earlier search didn’t find this, as I was quite sneaky, huh?

Ok, so we see a pipe creation event at nearly the same time as our Mimikatz detection, interesting. Let’s perform a wider search on this host to look for all EventCode 17 and 18 events to see the other pipes that have been created and connected to. This can be a bit tricky. You don’t want to search over too long a period as you could get lots of pipes used by applications like Chrome, but you don’t want to miss any low and slow techniques either. Creating an “allow list” of known ”good” pipes and their associated processes would probably benefit you here (I’m feeling lazy, so I’m keeping mine open for now). Here’s my search:

index=main host="WIN-DC-483" source="xmlwineventlog:microsoft-windows-sysmon/operational" EventCode IN (17,18)
| reverse
| table _time EventCode EventDescription process_path PipeName process_name process_id


Ok, so several other pipes were created and connected during my defined timeframe on my domain controller. I see \Surgesock2\mrpipespostex-28b-0 was created and then connected to. I see the System process connecting to a pipe named mrpipessmb.123476. I also see a MrPipes-9336-server pipe was created and connected to, both from a funny process name called 68e5510.exe using a UNC path of \\WIN-DC-483\ADMIN$\68e5510.exe. What uses UNC paths, and things like ADMIN$ typically? Windows file shares? And what protocol is used when connecting to Windows file shares? Maybe SMB? Lots of questions I know, but I’m in a Jeopardy frame of mind right now, so make sure you phrase your answer in the form of a question.

Ok, let’s go crazy and change our data source, as I think I smell some network activity going on. I called out wire data earlier on and I have Zeek running in this environment. Let’s look for that 68e5510.exe process name within the Zeek SMB Files source type with this search:

index=main sourcetype=bro:smb_files:json name=68e5510.exe
| table src_ip dest_ip dest_port name path action


Great, we have some matching SMB network traffic between 10.0.1.17 and 10.0.1.14 for that file. I know that 10.0.1.17 is my Windows 10 client, and 10.0.1.14 is my domain controller. I see multiple file open and file write actions taking place as well for that file. This now gives me another host to investigate, so I’m not going to move over to my Windows 10 client to start hunting there.

We know that the Windows 10 client has copied that executable across to the domain controller, and that appears to be the start of various pipe activities on the domain controller as well. How could the Windows 10 client possibly start up that process after copying the executable across? One way we typically when using PSExec for lateral movement is via RPC. This blog by F-Secure provides a really good explanation of how this is done.

I’m going to now move back to Sysmon data and search for any network connections (Event Code 3) via RPC’s standard port of 135 in that same time frame with this search.

index=main host="WIN-CLIENT-425" source="xmlwineventlog:microsoft-windows-sysmon/operational" EventCode=3 dest_ip=10.0.1.14 dest_port=135
| reverse
| table _time src_ip dest_ip dest_port Image ProcessId


My search returned a few results for that timeframe. Two events were lsass.exe connecting via RPC to my domain controller (quite normal behavior in a Windows domain), and the third was some process called mr_pipes_surge.exe making an RPC call. That doesn’t sound normal to me. So I’ll pull the thread a bit further here and do a wide search on that process name.

index=main host="WIN-CLIENT-425" source="xmlwineventlog:microsoft-windows-sysmon/operational" process_name=mr_pipes_surge.exe
| reverse
| table _time User ProcessId EventCode EventDescription dest_ip dest_port PipeName query answer


Although I ran a rather open search, I want to limit what I’m looking at by limiting what I put in my table. I want to see what users were associated with this process along with the process ID. I want to see the various event codes and their descriptions. As I found pipes in my environment, I want to see if any pipes were associated with that process. DNS is always good, so looking for DNS activity for that process can be helpful. And then finally, I want to know if that process was communicating with any other hosts.

Here are my results:

  • User- SURGE\shannon
  • ProcessId- 2036
  • Pipes- MrPipes-3587-server and \Surgesock2\mrpipespostex-e3e-0
  • DNS- Looked to only query for itself
  • Network- Made connections to 46.101.182.152 on port 443
     

Wrapping It All Up

Remember way up at the top of this blog when we tried to run the Cobalt Strike named pipe detection search that returned 0 results? Could we use some of the pipes discovered after the Mimikatz detection to modify that search to see if we can find any other impacted hosts? Here are the various pipes we’ve seen in our hunt. They seem to follow Cobalt Strike’s standard of appending 4 characters or digits to a string (lazy me for not modifying that behavior).

  • \Surgesock2\mrpipespostex-28b-0
  • \Surgesock2\mrpipespostex-e3e-0
  • MrPipes-3587-server
  • MrPipes-9336-server
  • Mrpipessmb.123476

It looks like we could just add a \\*mrpipes* to the PipeName clause in our detection for a final search of:

`sysmon` EventID=17 OR EventID=18 PipeName IN (\\msagent_*, \\wkssvc*, \\DserNamePipe*, \\srvsvc_*, \\mojo.*, \\postex_*, \\status_*, \\MSSE-*, \\spoolss_*, \\win_svc*, \\ntsvcs*, \\winsock*, \\UIA_PIPE*, \\*mrpipes*)
| stats count min(_time) as firstTime max(_time) as lastTime by Computer, process_name, process_id process_path, PipeName
| rename Computer as dest
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| `cobalt_strike_named_pipes_filter`


And just like that, we pick up a bunch more pipes in our environment, which match up to the various Cobalt Strike actions that I took earlier across all 3 hosts!

So if this were an old children’s fable from long ago, what is the lesson? Pick an easier topic, check the use-by date before eating that cheese? Both are great ideas, but I think the lesson here is to not always rely on hard-coded values to keep you safe. They do have a place, however. Based on various reports, many people who utilize Cobalt Strike are being lazy and using the default values. So I guess do whatever you want in the end, but I’ll give you a final parting gift. If you’re using Zeek, or Corelight, you can run this search which picks up the PSExec lateral movement behavior within Cobalt Strike (I honestly built this before I found the F-Secure blog referenced above, really!).

index=main sourcetype="bro:dce_rpc:json"
| transaction uid startswith="OpenSCManagerA" endswith="CloseServiceHandle" maxspan<5s
| table uid named_pipe endpoint operation


Hopefully, this blog helped you understand a bit more about named pipes, I know I learned a bit more about them doing the research.

In the meantime, happy hunting!

Shannon Davis
Posted by

Shannon Davis

Security practitioner, Melbourne, Australia via Seattle, USA.

TAGS

Listen To Those Pipes: Part 2

Show All Tags
Show Less Tags

Join the Discussion