Snort Fortification Against Evasions

I'm trying to post this before the arrival of hurricane Irene and the almost certain power outage since our neighborhood receives electricity from above-ground tree-lined power lines. I'd like to send nature's router a source quench from sending any more disasters our way – between the once-in-a-century East coast 5.9 earthquake and a predicted very destructive hurricane, I think we've had enough!

Most of the evasion techniques I've discussed in past blogs have involved a single technique. I thought I'd demonstrate one with multiple different evasions and how Snort was not fooled by any. I'll discuss the evasions in order of protocol – IP then TCP – while the Scapy program below needs to generate the TCP layer first and then fragment the entire IP packet. Specifically, I fragmented all PUSH segments into 8-byte payloads, fragmenting not only the payload but the 20-byte default-sized TCP header. Next, I wanted to test the TCP reassembly capabilities by causing the TCP sequence numbers to wrap back to 0 from the largest value of 4294967295 or 2**32 – 1 in the middle of the PUSH payload. Finally, I also chopped all the PUSH segments into one-byte payloads that were sent in reverse order from highest to lowest TCP sequence numbers. If Snort or any other IDS/IPS or session reconstructing tool is able to reassemble all these combined evasion attempts, it has robust TCP reassembly.

My Python is not the most efficient, but it gets the job done. The program imports all the necessary modules, creates a list to store the packets before sending them, assigns an Initial Sequence Number value that will cause the TCP sequence numbers to wrap somewhere in the payload of "EVILSTUFF", and then sends the SYN, listens for and increments the servers TCP sequence number for the acknowledgement, and sends the client acknowledgement. This is not very different, except for the care taken to assign the ISN value, than many of the other Scapy programs I've blogged about.

The second part of the program simply crafts a separate packet with a PUSH segment for each letter found in the URL request. The TCP sequence number is incremented by 1 except if the maximum TCP sequence value is found, where it wraps back to 0. Each of these new packets is added to a list and the list is reversed so that the last segment is first, etc., effectively reversing the order of the packets. Finally, each of these PUSH packets is fragmented into 8 bytes and sent on the network. If you decide to run this program, I'd suggest sending it to a netcat listener on port 80 so you can verify that the payload arrives as expected.

The traffic was captured using tcpdump into a file called foolids.pcap. Next, let's run it against Snort. Here is the Snort configuration file.

What is important for this particular evasion attempt is that the frag3 and stream5 preprocessors are defined. I failed to define the frag3 preprocessor and Snort didn't alert. But, my buddies on the Snort team – Russ Hook-and-Ladder Combs (volunteer fireman), Steve Spike Sturges (volleyball superstar), and Todd Awesome Wease (very smart and all around nice guy) – figured out what I was doing wrong. Even though these evasions do not use any specific policy evasions, both the global and specific frag3 and stream5 preprocessors must be defined. The frag3 preprocessors ensure that Snort handles fragmentation properly and should be defined if you have IP fragmentation in your network. The specific "windows" policy doesn't really matter for this evasion – you could have any policy and Snort would reassemble the fragments properly. The policy becomes more important for operating system specific behaviors like overlapping IP fragments or TCP segments that were not used in these attempted evasions. As well, you should always have a stream5 global and policy preprocessor, and you may or may not elect to have specific policies for different operating systems.

The http_inspect preprocessors are needed because the single Snort rule uses the "http_uri" keyword to find the content of "EVILSTUFF" in the URL. I ran Snort version using the following command:

snort –A console –q –K none –c russ.conf –r foolids.pcap

This command displays the alert to the console, quiets start-up messages, does not log, reads the configuration from "russ.conf" and reads the packets from the file "foolids.pcap". Snort found the content despite all evasion attempts:

[**] [1:12345:0] EVILSTUFF [**] [Priority: 0] {TCP} ->

As I was reminded, the Snort code is all there, but it is important to include all the necessary and default Snort configuration preprocessors to prevent evasions.

I'm hoping hurricane Irene evades us all!


What I Learned At Camp

I just returned from teaching my Scapy course at a Cyber Camp in Richmond, VA. The Cyber Camps are part of the US Cyber Challenge to educate and cultivate future cyber security talent, taught by SANS instructors. It was an amazing experience and the students were so bright, I think I learned more than I taught.

For years, I’ve praised the ease of assembling protocol layers to form packets in Scapy. At the same time, I’ve cursed the difficulties Scapy presented changing one or more values in an existing packet. Scapy recomputes any checksums (IP, UDP, TCP, ICMP) when you assemble a packet layer by layer. However, it does not recompute the checksum when you simply alter a value in an already assembled packet, resulting in a bad checksum for the particular layer(s). I’d scoured the Internet for documentation for an elegant remedy and found none, so I basically reassembled a new packet, layer by layer, keeping the values I wanted to preserve and altering the values I wanted to change. It’s a clumsy ham-handed way of doing things, but it worked.

As I lamented about this particular idiosyncrasy of Scapy while teaching, an exceptional student named Chris Cockerham asked what would happen if I just deleted the checksum attribute in the layer of the changed value. Magically, it worked and provided an elegant solution. Let me take you through the process of evolved code. I’ll start with the inelegant solution I used and progress to Chris’ solution.

All the code is entered in the Scapy interactive interface. First, the pcap file “/tmp/onepacket.pcap” is read into a list named “r”. The only packet in the list is stored in variable named “rec”. We display “rec” and see it has a current TTL of 50. Ultimately, we want to change the TTL value to 255. We attempt to do that and redisplay “rec” to find a new value of 255.

We read the packet into Wireshark and discover that the problem is that the IP checksum is now broken since Scapy did not recompute it. This means we cannot send this packet nor do anything useful with it.

Let’s look at the old solution I used. We reread the packet to get a clean version of it. Next, we extract the IP layer. Unfortunately, it’s not just the IP layer; it’s the IP layer and everything that follows.

Therefore in order to disassemble all layers, we have to extract each layer as best we can and clone it – preserving the values we want and updating those we want to change. Similarly, we extract the TCP layer, but find it has the following payload layer. We extract the final layer – the payload.

Now we begin to clone the new layers. The new IP layer is called “newIP” and we preserve the source and destination IP’s and alter the TTL value to be 255. If we wanted to preserve any other field in the IP header instead of settling for Scapy’s default value, we’d have to add its field and value to the new IP layer. Next, we rebuild the TCP layer. We save all the current fields and values in the TCP header. You can see how cumbersome this is. Finally, we build the new packet to be the new IP header followed by the new TCP header followed by the payload. We see that the packet has the desired TTL value of 255.

But what does Wireshark think about the IP checksum?

Wireshark examines the IP checksum and discovers it is correct. We accomplished what we wanted to do – change the IP TTL to 255 and cause Scapy to recompute the IP checksum. But, it was a tedious process.

The elegant solution that Chris suggested involves deleting the IP checksum attribute from the IP header with the line “del rec[IP].chksum”. We set the TTL in the IP layer of “rec” to be 255.

Let’s read the new packet into Wireshark.

Wirehshark displays our new TTL of 255 and a correct IP checksum, reflecting that Scapy recomputed the checksum after the attribute was deleted. This is so much easier than essentially cloning each of the layers, altering the desired value(s), and reassembling the packet.

To expand on this concept, you need to delete the checksum for the layer that you change. For instance, if we change the TCP window size in an assembled packet, we have to delete the TCP checksum. Be careful, though if you are changing IP addresses. Obviously, you’ll need to change the IP checksum. If the protocol that follows is UDP or TCP, both the UDP and TCP checksums have something known as a pseudo header that includes the IP addresses in the checksum. This means that you’ll need to delete the protocol checksum (UDP/TCP) attribute in addition to the IP checksum attribute.

Thanks a lot Chris. I never thought I’d learn so much teaching!