Those of you that will be attending the RSA NetWitness User Conference this year will have an opportunity to hear my colleague Mike Zeberlein and I discuss some common attacker lateral movement tactics, techniques, and procedures (TTPs)  that our team has observed during IR engagements in recent months.  As more and more of our customers deploy core monitoring  via NetWitness, we increasingly have the opportunity to "up our game" at detecting evidence of  lateral movement within core traffic.  In most environments, doing so requires that we understand the way in which Microsoft Windows devices talk to each other via protocols such as SMB, RPC, and NetBIOS.

 

Motivation

 

During a recent engagement, we had detected a backdoor that was used by an APT group that encoded plaintext attacker commands as HTTP server content.  The output of the commands was wrapped up as a base-64 representation using a custom alphabet.  After generating some TShark & Python tools to decode the shells, we were able to understand the execution, enumeration, and collection activities associated with this unauthorized operator.  In particular we noted that this operator was using the AT.EXE tool a great deal to remotely schedule execution of tasks on adjacent devices.  All in all, the attacker was able to compromise dozens upon dozens of internal devices over a three-day weekend using non-interactive means such as using the Microsoft NET.EXE command and the AT.EXE command.

 

As part of our strategy to uncover additional attacker activity, we decided to quickly write some NetWitness content to discover the use of AT.EXE.  AT.EXE is a command-line client that interfaces with the first generation Task Scheduler subsystem that began its life during the Windows NT days.  The current (third) generation of the Task Scheduler debuted alongside Windows Vista / Windows Server 2008.  However, it is important to note that all three generations of the Task Scheduler are still supported on the latest Windows operating systems.  Attackers often employ AT.EXE because, by default, scheduled tasks are run as the local SYSTEM account.

 

Baby Steps

 

In a matter of minutes, we were able to generate a basic parser that detected AT.EXE usage by keying off of the simple string “\PIPE\atsvc” that we observed within the RPC negotiation that accompanies invocation of the first generation Task Scheduler service.  Here is that parser:

 

<parser name="SMB_AT" desc="SMB_AT">

 

<declaration>

<port name="smb" value="445"/>

<port name="nb" value="139"/>

 

<token name="at" value="a&#x00;t&#x00;s&#x00;v&#x00;c&#x00;"/>

<token name="at" value="\PIPE\atsvc" />

 

<number name="state"/>

<number name="umarker"/>

 

<meta key="alert" name="alert" format="Text"/> 

</declaration>

 

<match name="smb">

<assign name="state" value="1"/>

</match>

    <match name="nb">

<assign name="state" value="1"/>

</match>

 

<match name="at">

<if name="state" equal="1">

        <register name="alert" value="atsvc_pipe" />

</if>

</match>

 

</parser>

 

Simple right?

 

Walk Before You Run

 

It’s worth taking a short sidebar at this point to recognize that there are two basic approaches to developing custom NetWitness parsers.  Firstly, one can generate content that identifies specific data such as a signature, indicator of compromise, or other well-defined sequence of bytes.  On the other hand, one can build content that follows a generic approach of comprehensive tokenization of fields, values, and data structures that are resident within standards-based network communication.  The NetWitness service parsers such as the HTTP or SMB parser are good examples of this latter class.  Indeed, the true power of NetWitness derives from generic parsing because it allows analysts to “find evil by describing good and seeing what’s left over”.  In other words, you will never be able to anticipate signatures for every threat your organization faces, but by classifying known-good behavior and identifying deviations from such, it becomes easier to discover abuses of our communication systems that attackers utilize to achieve their objectives.

 

That may be all well and good, but please remember that when you are analyzing the behavior of a new protocol or activity that you are not yet intimately familiar with, you should never hesitate to implement a simple approach and adjust your strategy as you learn more about the relevant communication patterns.  If you try to “boil the ocean” straight away, you may miss out on valuable lessons and insights as you iterate through and validate your understanding of the content.

 

What's in a Named Pipe?

 

The next step for us was to formalize our understanding of this \PIPE\astsvc string.  As it turns out, this value is a “friendly name” associated with the Named Pipe used by the ATSVC server.  A Named Pipe is an operating system construct that facilitates inter-process communication by, among other things, providing a well-known “name” to locate the service. Download Mark Russinovich’s Pipelist tool to explore named pipes on your own.

 

During the establishment of an RPC session, the RPC client requests communication with a well-known service identified by a UUID.  For example, for the ATSVC, that UUID is 1ff70682-0a51-30e8-076d-740be8cee98b.   As part of the server RPC acknowledgment, the server responds with a data structure that contains as friendly pipe name, known as a Secondary Address, such as “\PIPE\atsvc”.  We soon realized that we could parse out named pipes for other RPC services aside from just ATSVC.  Here is some proof-of-concept content you can readily deploy on a 9.8 NextGen stack (after you've done your testing due diligence):

 

<parser name="PipeDetails" desc="PipeDetails">

 

<declaration>

 

<port name="smb" value="445"/>

<port name="nb" value="139"/>

 

<!-- match \PIPE\-->

<token name="pipe" value="&#x5c;PIPE&#x5c;" />

<token name="pipe" value="&#x5c;pipe&#x5c;" />

<token name="pipe" value="&#x5c;Pipe&#x5c;" />

 

<number name="pipe_name_len" />

<string name="check_byte" />

<string name="full_pipe_name" />

<number name="state"/>

 

<meta key="alert" name="alert" format="Text"/>

 

</declaration>

 

<match name="smb">

<assign name="state" value="1"/>

</match>

 

<match name="nb">

<assign name="state" value="1"/>

</match>

 

<match name="pipe">

<if name="state" equal="1" >

        <!-- move back past the \PIPE\ string and two length bytes -->

        <move value="-8" />

 

        <!--read LSB of PIPE length ; assume max pipe length of 255 (FF)-->

        <read name="pipe_name_len" length="1" />

 

        <!-- move past second len byte -->

         <move value="1" />

 

        <!-- decrement the pipe length to account for null byte-->

        <decrement name="pipe_name_len" value="1" />

 

        <!-- read the full pipe name without null byte -->

        <read name="full_pipe_name" length="$pipe_name_len" />

 

 

        <!--read next byte-->

        <read name="check_byte" length="1"/>

 

        <!--

                this is the "sanity checking" part.  if the byte read is the null byte,

                we have more assurance we just read a complete string,

                probably for a pipe length

                -->

        <if name="check_byte" equal="&#00;">

          <register name="alert" value="$full_pipe_name" />

        </if>

</if>

</match>

 

</parser>

 

This is slightly more sophisticated, but not really all that complex.  Following along with the comments, you can see that the approach relies on the fact that pipe names are encoded as C-style character arrays.  In other words, they consist of ASCII representations of the pipe name characters followed by the null byte to indicate the end of the array. Additionally, we rely on the fact that the length of the pipe name string is indicated as a two-byte offset immediately preceding the string name.  By identifying the “\PIPE\” string, reading in the length of the string, reading the string up until the null byte, and checking for the presence of the null byte, we establish a strong degree of confidence that we have just parsed out the Secondary Address field from RPC communication.  In practice this works fairly well.  For example, here is some metadata that we were able to generate using this parser:

 

\pipe\lsass, \pipe\browser, \pipe\spoolss, \pipe\winreg, \pipe\atsvc, \pipe\srvsvc, \pipe\wkssvc, \pipe\ntsvcs, \pipe\hello, \pipe\lsarpc

 

One of These Things

 

Take a close look at the list of pipe names above.  Can you spot the outlier?  Are you aware of a Windows service named Hello? In fact, \pipe\hello does not correspond to a well-known Windows service.  Does this mean it’s malicious?  Perhaps one of your developers downloaded a “hello world” program from MSDN and never bothered to change the pipe name bindings.  As it turns out though, this pipe name did indeed correspond to a malicious tool we discovered in one of our engagements that allowed an RPC tunnel to be established between compromised hosts to extend interactive control across devices.

 

As you can see, by formalizing our understanding of RPC binding by just a few shades and using increasingly generic network data parsing, we are now able to detect an unanticipated threat by relying on an anomaly rather than a signature.

 

Of course, you are not just going to profile pipe names in your head.  Next steps for us include publishing a white list of well-known and utilized pipes so that they can be filtered out while looking for anomalies.  We also plan to continue to learn more about other things that we can observe by parsing out pipe names, such as the remote control of services.  We aim to look for anomalies in the RPC binding.  For example, can an arbitrary named pipe return a well-known pipe name as the Secondary Address in the RPC acknowledgement? If so, this approach may necessitate some slight changes to rely on the UUID.  Finally what can we determine from other RPC connection modalities such as dynamic RPC?

 

Your Turn

 

In this post we have seen how we can quickly deploy custom NetWitness parsers to identify network attacker TTPs.  We've also shown how simple implementations of content that allow us to quickly find specific activity can be further formalized to generate increasingly generic views into network data.  By describing network communications thusly, we are able to engage in an effective method of network forensics that relies less on signature detection and more on anomaly detection.

 

We look forward to hearing your feedback on this topic.  We recognize that NetWitness customers run, operate, and otherwise participate in some of the most advanced and capable security operations organizations in the world. We look forward to continuing this conversation at the User Conference, our other encounters, and here on the RSA NetWitness community.

 

Additional Notes

 

Use of the ATSVC can also be uncovered by looking for filename=atsvc.  However, filename is not an indexed metadata field by default.  Instead of turning this field into IndexValues, you may consider instead creating an application rule that generates a new value-indexed piece of metadata at time of capture to identify session that contained ATSVC constructs.

 

JOB files that correspond to the second generation Task Scheduler subsystem (i.e. SCHTASKS.EXE) are parsed out by the SMB parser. These can be bulk extracted.  In order to parse out the various fields contained within Job files, you may consider using the Python script recently released by Jamie Levy in September of 2012 (gleeda.blogspot.com) or a similar implementation in Perl released by Harlan Carvey in 2009 (windowsir.blogspot.com/2009/09/parsing-job-files.html).


Next steps for us also include creating NextGen parsers that tokenize the various fields of the ATSVC and JOB file constructs as suggested by members of our team.