Friday, April 20, 2012

Solving Network Forensic Challenges with Bro :: Part 1

Solving Network Forensic Challenges with Bro :: Part 1

1 Getting to know Bro

Bro is a lot of things but one of its primary strengths is providing a programming language for network analysis. Out of the box, it's more than likely that Bro does more than you know and you'll get to spend a significant amount of time working with the logs to better understand your network and gain the skills to identify actions that require further investigation. If you're interested in some basic Bro scripting tutorials, the Bro team posted the tutorials from the Bro 2011 Workshop here.

If you've never used Bro before, a great way to get it up and running is to install Doug Burk's SecurityOnion Linux Distribution in a VM and work from there. Everything I've done in this post was done in a SecurityOnion VM.

2 Why use Bro?

Bro isn't a signature based IDS. In fact, calling Bro an IDS does it something of a disservice. It's more aptly described as a Network Security Monitoring application or framework. Bro's detections are based primarily on heuristics and when combined with a robust built-in programming language, it becomes a tool you can't ignore.

As for using Bro to solve an old SANS Network Forensics Challenge? While Bro's programming language is not very difficult it does require understanding a lot of underlying capability. I had the pleasure of hanging out with Seth Hall from the Bro project and often I'd hear him talk about how it only took him an hour or so to add some incredible functionality to Bro but it tends to overshadow the fact that Seth has spent years on Bro. Watching Seth whip something up in Bro is indistinguishable from wizardry and my hope is that, by pulling back a little bit of the curtain, I can better understand how to utilize Bro. Getting up to speed with Bro is a daunting task, especially if you avoid things that could be described as navel gazing! Using Bro to solve a forensics challenge's networking based questions, is navel gazing, but it forced me to dig into a solid amount of source code and documentation.

3 The SANS Forensic Challenge

The challenge I worked with had a basic set up. The victim (10.10.10.70/32) is exploited using a client-side spear phishing attack. You can read the full challenge as well as get the trace file on the SANS.org site.

The questions that stuck out as opportunities to use Bro were:

  1. When was the TCP session on port 4444 opened? (Provide the number of seconds since the beginning of the packet capture, rounded to tenths of a second. ie, 49.5 seconds)
  2. When was the TCP session on port 4444 closed? (Provide the number of seconds since the beginning of the packet capture, rounded to tenths of a second. ie, 49.5 seconds)
  3. Vick's computer repeatedly tried to connect back to the malicious server on port 4445, even after the original connection on port 4444 was closed. With respect to these repeated failed connection attempts:

    a. How often does the TCP initial sequence number (ISN) change? (Choose one.)

    1. Every packet
    2. Every third packet
    3. Every 10-15 seconds
    4. Every 30-35 seconds
    5. Every 60 seconds

    b. How often does the IP ID change? (Choose one.)

    1. Every packet
    2. Every third packet
    3. Every 10-15 seconds
    4. Every 30-35 seconds
    5. Every 60 seconds

    c. How often does the source port change? (Choose one.)

    1. Every third packet
    2. Every packet
    3. Every 30-35 seconds
    4. Every 10-15 seconds
    5. Every 60 seconds
  4. Eventually, the malicious server responded and opened a new connection. When was the TCP connection on port 4445 first successfully completed? (Provide the number of seconds since the beginning of the packet capture, rounded to tenths of a second. ie, 49.5 seconds)
  5. Subsequently, the malicious server sent an executable file to the client on port 4445. What was the MD5 sum of this executable file?
  6. When was the TCP connection on port 4445 closed? (Provide the number of seconds since the beginning of the packet capture, rounded to tenths of a second. ie, 49.5 seconds)

The challenge included finding the MD5 and filename of files downloaded, but as you'll see later a specific aspect of the tracefile prevents us from doing that.

4 Learning by failing

The primary reason for challenging myself to solve a network forensics challenge with Bro was to develop both a better understanding of how Bro works and to push myself to write more bro scripts. To wit, I spent a good deal of time failing and much of this post is going to include the process I used to find the information I needed to re-orient myself and make headway.

4.1 Finding Connections

The command line utility tshark is wireshark for CLI lovers and one of my most loved tools. Whenever, I'm looking at pcaps, I try to get a bird's eye view of the what is happening using tshark. For example, a common tshark command I use is to print the source ip and port and the destination ip and port by using the '-T fields' command line option. To save space, I've applied the common 'sort | uniq -c | sort -n' command to tell my terminal to show and count the unique entires. For reference, when run without the sort commands, the output is 2554 lines long.

tshark -r evidence06.pcap -T fields -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport | sort | uniq -c | sort -n
  1 10.10.10.70        10.10.10.255    
  5 10.10.10.70    1035    10.10.10.10 8080
  8 10.10.10.10    8080    10.10.10.70 1035
 15 10.10.10.10    4445    10.10.10.70 1037
 15 10.10.10.10    4445    10.10.10.70 1038
 15 10.10.10.10    4445    10.10.10.70 1039
 15 10.10.10.10    4445    10.10.10.70 1040
 15 10.10.10.10    4445    10.10.10.70 1041
 15 10.10.10.10    4445    10.10.10.70 1042
 15 10.10.10.10    4445    10.10.10.70 1043
 15 10.10.10.70    1037    10.10.10.10 4445
 15 10.10.10.70    1038    10.10.10.10 4445
 15 10.10.10.70    1039    10.10.10.10 4445
 15 10.10.10.70    1040    10.10.10.10 4445
 15 10.10.10.70    1041    10.10.10.10 4445
 15 10.10.10.70    1042    10.10.10.10 4445
 15 10.10.10.70    1043    10.10.10.10 4445
263 10.10.10.70    1044    10.10.10.10 4445
424 10.10.10.70    1036    10.10.10.10 4444
664 10.10.10.10    4445    10.10.10.70 1044
979 10.10.10.10    4444    10.10.10.70 1036

When I had started to look at using Bro for this, I tried to cast a wide net and started with the event "connection_established" which is exported from Bro's event.bif.bro file.

187 ## Generated for an established TCP connection. The event is raised when the
188 ## initial 3-way TCP handshake has successfully finished for a connection.
189 ##
190 ## c: The connection.
...snip...
198 global connection_established: event(c: connection);

Anytime the three way TCP handshake (SYN -> SYN/ACK -> ACK) has completed, this event should fire and since Bro is stream based, we should be able to produce a list of connections from the libpcap file provided in the challenge.

Bro uses the connection as a datatype, if you search through your base/init-bare.bro file you'll find the documentation for this type.

188 # A connection. This is Bro's basic connection type describing IP- and
189 # transport-layer information about the conversation. Note that Bro uses a
190 # liberal interpreation of "connection" and associates instances of this type
191 # also with UDP and ICMP flows.
192 type connection: record {
193   id: conn_id;  ##< The connection's identifying 4-tuple.
194   orig: endpoint; ##< Statistics about originator side.
195   resp: endpoint; ##< Statistics about responder side.
196   start_time: time; ##< The timestamp of the connection's first packet.
197   ## The duration of the conversation. Roughly speaking, this is the interval between
198   ## first and last data packet (low-level TCP details may adjust it somewhat in
199   ## ambigious cases).
200   duration: interval;
201   ## The set of services the connection is using as determined by Bro's dynamic
202   ## protocol detection. Each entry is the label of an analyzer that confirmed that
203   ## it could parse the connection payload.  While typically, there will be at
204   ## most one entry for each connection, in principle it is possible that more than
205   ## one protocol analyzer is able to parse the same data. If so, all will
206   ## be recorded. Also note that the recorced services are independent of any
207   ## transport-level protocols.
208         service: set[string];
209   addl: string; ##< Deprecated.
210   hot: count; ##< Deprecated.
211   history: string;  ##< State history of TCP connections. See *history* in :bro:see:`Conn::Info`.
212   ## A globally unique connection identifier. For each connection, Bro creates an ID
213   ## that is very likely unique across independent Bro runs. These IDs can thus be
214   ## used to tag and locate information  associated with that connection.
215   uid: string;
216 };

You can check out the documentaiton from the Bro site if you want to explore the connection data type further. As you can see each connection is itself a collection of other datatypes to include endpoints, time strings, count. Bro gives us access to the whole fire hose of network information even in script land! Let's take a look at what we can see with the new_connection() event.

mac@securityonion-Analyst:~/challenges/SANS Forensic$ bro -r evidence06.pcap challenge2.bro 
[id=[orig_h=10.10.10.70, orig_p=1036/tcp, resp_h=10.10.10.10, resp_p=4444/tcp], orig=[size=0, state=4, num_pkts=1, num_bytes_ip=48], resp=[size=0, state=4, num_pkts=0, num_bytes_ip=0], start_time=1272498000.577135, duration=0.000071, service={

}, addl=, hot=0, history=Sh, uid=XRD3DR2rr51, dpd=<uninitialized>, conn=<uninitialized>, extract_orig=F, extract_resp=F, dns=<uninitialized>, dns_state=<uninitialized>, ftp=<uninitialized>, http=<uninitialized>, http_state=<uninitialized>, irc=<uninitialized>, smtp=<uninitialized>, smtp_state=<uninitialized>, ssh=<uninitialized>, ssl=<uninitialized>, syslog=<uninitialized>]
[id=[orig_h=10.10.10.70, orig_p=1044/tcp, resp_h=10.10.10.10, resp_p=4445/tcp], orig=[size=0, state=4, num_pkts=1, num_bytes_ip=48], resp=[size=0, state=4, num_pkts=0, num_bytes_ip=0], start_time=1272498122.985483, duration=0.000097, service={

}, addl=, hot=0, history=Sh, uid=LGk3mPtc00b, dpd=<uninitialized>, conn=<uninitialized>, extract_orig=F, extract_resp=F, dns=<uninitialized>, dns_state=<uninitialized>, ftp=<uninitialized>, http=<uninitialized>, http_state=<uninitialized>, irc=<uninitialized>, smtp=<uninitialized>, smtp_state=<uninitialized>, ssh=<uninitialized>, ssl=<uninitialized>, syslog=<uninitialized>]

Even a cursory glance shows that while we're seeing a lot of data from Bro we're not seeing as many connections as we should! Not only did the challenge's description tell us that there was HTTP traffic but compared to the tshark output, we're looking at significantly less entries than we should be seeing even given the disparity between connection and stream based analyzers. Given the documentation from the connection_established event above, it should be somewhat obvious as to why some of the streams are missing. If connection_established only fires when three way handshake is present then we're missing the three way handshake for the HTTP connection. Let's check the first three packets of the trace file and see if they match up with a TCP 3-way handshake.

tshark -r evidence06.pcap -c 3 -T fields -e tcp.flags
0x18
0x10
0x10

That is certainly not a TCP handshake, so it looks like the trace file starts in the middle of a stream. These values show us a PSH,ACK and two ACK flags, instead of the standard 3-way handshake.

For reference, a TCP handshake should look like this:

tshark -r browse.pcap -c 3 -T fields -e tcp.flags   
0x02
0x12
0x10

If you're curious as to how the hex values above map to TCP flags, it's a binary to hex conversion using the following table.

CWRECEURGACKPSHRSTSYNFIN
1286432168421

We can double check our findings with a little bit more abuse of tshark and look for any SYN/ACK flags set which would indicate a response in the TCP 3-way handshake.

tshark -r evidence06.pcap -T fields -e ip.src -e ip.dst -e tcp.flags | awk '{if ($3 == "0x12") print $0}'
10.10.10.10     10.10.10.70     0x12
10.10.10.10     10.10.10.70     0x12

So, not only will we miss the HTTP session that is already started, we're also not going to see any traffic that doesn't have a 3-way handshake. Since the challenge references rejected connections, we're definitely going to need to back to the base.bif.bro file and find a better solution.

Some quick perusing and searching for significant terms lead me to the new_connection() event.

133 ## Generated for every new connection. The event is raised with the first packet
134 ## of a previously unknown connection. Bro uses a flow-based definition of
135 ## "connection" here that includes not only TCP sessions but also UDP and ICMP
136 ## flows.
137 ##
138 ## c: The connection.   
...snip...
149 ##    Handling this event is potentially expensive. For example, during a SYN
150 ##    flooding attack, every spoofed SYN packet will lead to a new
151 ##    event.
152 global new_connection: event(c: connection);
153 

This event is right up our alley! It doesn't care about the 3-way handshake, if it sees a packet it hasn't seen before it fires. Let's change our initial bro script to replace connection_established() with new_connection().

event new_connection(c: connection)
      {                 
      print c;
      }
[id=[orig_h=10.10.10.70, orig_p=1035/tcp, resp_h=10.10.10.10, resp_p=8080/tcp], orig=[size=0, state=0, num_pkts=0, num_bytes_ip=0], resp=[size=0, state=0, num_pkts=0, num_bytes_ip=0], start_time=1272497999.311284, duration=0.0, service={

}, addl=, hot=0, history=, uid=P0lEbfBMXFh, dpd=<uninitialized>, conn=<uninitialized>, extract_orig=F, extract_resp=F, dns=<uninitialized>, dns_state=<uninitialized>, ftp=<uninitialized>, http=<uninitialized>, http_state=<uninitialized>, irc=<uninitialized>, smtp=<uninitialized>, smtp_state=<uninitialized>, ssh=<uninitialized>, ssl=<uninitialized>, syslog=<uninitialized>]
[id=[orig_h=10.10.10.70, orig_p=1036/tcp, resp_h=10.10.10.10, resp_p=4444/tcp], orig=[size=0, state=0, num_pkts=0, num_bytes_ip=0], resp=[size=0, state=0, num_pkts=0, num_bytes_ip=0], start_time=1272498000.577135, duration=0.0, service={

}, addl=, hot=0, history=, uid=y4plS32M81e, dpd=<uninitialized>, conn=<uninitialized>, extract_orig=F, extract_resp=F, dns=<uninitialized>, dns_state=<uninitialized>, ftp=<uninitialized>, http=<uninitialized>, http_state=<uninitialized>, irc=<uninitialized>, smtp=<uninitialized>, smtp_state=<uninitialized>, ssh=<uninitialized>, ssl=<uninitialized>, syslog=<uninitialized>]

Now our output is more reasonable and there are 369 lines of it! That's more like it!

Let's make some formatting changes to our script so it's little more easy to quickly parse the output. We'll make it suggestive of the tshark output above.

In Bro, we can use the '$' to dereference, so if we wanted the origh, we could walk the output above and build our expression: c$id$origh. Bro also provides an fmt() conversion that operates much like printf so we can build a nicely formatted output with four string placeholders (%s) and the data we'd like to see(c$id$orig_h, c$id$orig_p, c$id$resp_h, and finally c$id$resp_p).

event new_connection(c: connection)
   {
   print fmt("New Connection => orig: %s %s resp: %s %s", c$id$orig_h, c$id$orig_p, c$id$resp_h, c$id$resp_p); 
   }
New Connection => orig: 10.10.10.70 1035/tcp resp: 10.10.10.10 8080/tcp
New Connection => orig: 10.10.10.70 1036/tcp resp: 10.10.10.10 4444/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp
...snip...
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp

Now, we have the event fired for each new connection and we can see the originator port and the responder port. Just having this information we can start building an answer to question 9c from the challenge. The question is specific about the originating host and the responder's port so let's be specific as well. A simple logical if statement will give us the ability to only print our nicely formatted output if the originating host is the victim machine (10.10.10.70) and the responder's port is 4445/tcp. The question is also specific about how long it takes for the originator to switch ports so we'll add the start_time to our output.

if (c$id$orig_h == 10.10.10.70 && c$id$resp_p == 4445/tcp)
   print fmt("New Connection => orig: %s %s resp: %s %s time: %s", c$id$orig_h, c$id$orig_p, c$id$resp_h, c$id$resp_p, c$start_time); 
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498035.258314
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498035.594943
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498036.141827
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498036.142471
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498036.6887
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498037.235554
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498037.23652
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498037.782456
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498038.329315
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498038.329973
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498038.876194
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498039.313691
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498039.314346
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498039.860571
New Connection => orig: 10.10.10.70 1037/tcp resp: 10.10.10.10 4445/tcp time: 1272498040.298079
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498047.043801
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498047.40741
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498047.954312
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498047.954969
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498048.391806
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498048.938686
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498048.939329
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498049.485544
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498050.032408
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498050.033078
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498050.579291
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498051.016808
New Connection => orig: 10.10.10.70 1038/tcp resp: 10.10.10.10 4445/tcp time: 1272498051.017456
...snip...
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498107.236156
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498107.782395
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498108.329244
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498108.329911
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498108.876468
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498109.313638
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498109.314295
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498109.860522
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498110.298011
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498110.298669
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498110.844862
New Connection => orig: 10.10.10.70 1043/tcp resp: 10.10.10.10 4445/tcp time: 1272498111.282386
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498118.057545
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498118.391735
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498118.938626
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498118.939275
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498119.485504
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498120.032355
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498120.033013
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498120.579247
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498121.016727
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498121.01738
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498121.563621
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498122.001099
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498122.001752
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498122.548001
New Connection => orig: 10.10.10.70 1044/tcp resp: 10.10.10.10 4445/tcp time: 1272498122.985483

Now we've got just the traffic we're interested in for question 9c. However, counting is still boring, let's have Bro count for us! To do this we'll use a table to map a originator port to the time when we first see that port attempting to connect to the attackers 4445/tcp port. As each new_connection() event is handled, if we haven't seen the originator's port before, we create a new entry in the table for the originator port and map it to the c$start_time. Once we've processed each new_connection() event, we still need to create some valuable output from the data set we've created. The best way to do this is to use the event bro_done().

global source_ports: table[port] of time;

event new_connection(c: connection)
  {
  if (c$id$orig_h == 10.10.10.70 && c$id$resp_p == 4445/tcp)
    {   
    if (c$id$orig_p !in source_ports)
      source_ports[c$id$orig_p] = c$start_time;
    }   
  }

event bro_done()
  {
  local ptime: set[time];
  local sports: vector of port;
  local stime: vector of time;
  local inc: int = 0;

  for (p in source_ports)
    {   
    sports[inc] = p;
    stime[inc] = source_ports[p];
    inc+=1;
    }   
  sort(stime);
  sort(sports);
  for (j in stime)
    {   
    print fmt("Delta Time: %s", stime[j+1] - stime[j]);
    }           
  }
mac@securityonion-Analyst:~/challenges/SANS Forensic$ bro -r evidence06.pcap challenge2.bro
Delta Time: 11.0 secs 785.0 msecs 487.0 usecs
Delta Time: 11.0 secs 730.0 msecs 439.0 usecs
Delta Time: 11.0 secs 795.0 msecs 35.0 usecs
Delta Time: 11.0 secs 735.0 msecs 993.0 usecs
Delta Time: 11.0 secs 884.0 msecs 180.0 usecs
Delta Time: 11.0 secs 960.0 msecs 521.0 usecs
Delta Time: 11.0 secs 907.0 msecs 572.0 usecs   

Once we've run the script we get an output of the differences in time between port changes. The originator attempted to contact port 4445/tcp every 11.7 to 11.9 seconds. Which falls in the range of the 10-15 second option for question 9c!

Given what we've worked through in this blog post alone, it's actually rather simple to answer question ten as well!

global first_contact: time;   
event connection_established(c: connection)
      {
      if (c$id$resp_p == 4445/tcp)
         first_contact = c$start_time;
      }

event bro_done()
      {
      print strftime("Successful connection to 4445/tcp at %Y/%m/%d %H:%M:%S", first_contact);
      }
Successful connection to 4445/tcp at 2010/04/28 19:42:02

5 Wrapping up

Hopefully, this post has gotten you interested in looking at the Bro programming language. There are a lot of posts online about how great Bro is, but scarce few covering how to go about learning the scripting language. Bro's scripting language holds a lot of surprises in store for the freshly minted Bro acolyte and the most efficient way to go from acolyte to journeyman is to spend your time looking at the scripts already being used by Bro. As I worked through the challenge I would use grep to search through the scripts directory ( /usr/local/share/bro on Security Onion) for any relevant terms and read the documentation is the files returned. Think of the default scripts distributed with Bro as a pool of collective knowledge to dip your feet into from time to time.

In part two of this series we will pick up where we've left off and solve more of the questions from the SANs Network Forensics challenge. We'll also take another look at some of the code we used in this post as it /may/ not be the most "bro-ish" way to solve the problem. While we got the answers we needed, I suspect there are ways to do so in a way that fits more in line with how we will eventually write code to run in production.

Date: 2012/04/16

Org version 7.8.06 with Emacs version 24

Validate XHTML 1.0

5 comments:

  1. Hi Scott,

    I love this article and the practical approach to learning a new tool chain with familiar data! Thanks for posting this!

    ReplyDelete
  2. Excellent article.. I'm a beginner to Bro language, and this was amazing.
    I have a question. I wish to analyze every packet's payload in a pcap file. (I'm implementing an anomaly detection system in Bro). Which event(s) should I use for that?

    ReplyDelete
  3. Marcos, Thanks!

    Asma, I'm planning to cover packet analysis in Part 2, but you can probably start with new_pkt(c: connection, p: pkt_hdr). Check out the documentation for it. It's suitable for pcap analysis, but for live stream analysis it will probably bring your sensor to its knees!

    ReplyDelete
  4. Isn't your final example going to give you last contact? I.e. don't you want to check if fist_contact is set and only set if not already set?

    ReplyDelete
  5. Thank you so much for writing this!

    ReplyDelete

Followers