Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers (18 page)

BOOK: Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers
2.53Mb size Format: txt, pdf, ePub
How H D Moore Solved the Pentagon’s Dilemma

In late 1999, the US Pentagon faced a serious crisis against its computer networks. The headquarters of the US Department of Defense, the Pentagon, announced it was under a coordinated series of sophisticated attacks (
CIO Institute bulletin on computer security, 1999
). A newly released tool, Nmap, made it rather easy for anyone to scan networks for services and vulnerabilities. The Pentagon feared that some attackers were using Nmap to identify and map vulnerabilities in the Pentagon’s massive computer network.

An Nmap scan proves rather easy to detect, correlate to the attacker’s address and then geo-locate that IP address. However, the attackers used an advanced option in Nmap. Instead of just sending scans from their specific attacker addresses, they included decoy scans that appeared to originate from many places around the world (
CIO, 1999
). The Pentagon experts had difficulty distinguishing between actual scans and the decoy scans.

While experts pored over massive reams of data logs with theoretical methods for analysis, a seventeen-year-old from Austin, TX finally presented a working solution. H.D. Moore, legendary creator of the attack framework Metasploit, met with Stephen Northcutt from the NAVY Shadow project. The teenager
suggested using the TTL fields for all incoming packets from Nmap scans (
Verton, 2002
). The time-to-live (TTL) field of an IP packet determines how many hops a packet can take before reaching its destination. Every time a packet crosses a routing device, the router decrements the TTL field. Moore realized this would be an excellent method for determining the origin of the scans. For each source address used in the logged Nmap scans, he sent a single ICMP packet to determine the number of hops between the source address and the scanned machine. He then used this information to distinguish the attacker from the decoys. Clearly, only the attacker would have an accurate TTL while the decoys (unless closely located) would have incorrect values for the TTL. The teenager’s solution worked! Northcutt asked Moore to present his toolkit and research at a SANS conference in 1999 (
Verton, 2002
). Moore dubbed his tool Nlog because it logged various bits of information from Nmap scans.

In the following section, we will use Python to recreate Moore’s analysis and construction of the Nlog toolkit. What you’ll hopefully understand is what a seventeen-year-old teenager figured out over a decade ago: simple, elegant solutions work to detect attackers.

Understanding the TTL Field

Before writing our script, let’s explain the TTL field of an IP packet. The TTL field contains 8 bits, making valid values 0 through 255. When a computer sends an IP packet, it sets the TTL field as the upper bound of hops a packet can take before reaching a destination. Every routing device that touches the packet decrements the TTL. If the field reaches zero, then the router discards the packet to prevent infinite routing loops. For example, if I ping the address 8.8.8.8 with an initial TTL of 64 and it returns with a TTL of 53, I see the packet crossed 11 routing devices on its return.

 target# ping –m 64 8.8.8.8

 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

 64 bytes from 8.8.8.8: icmp_seq=1 ttl=53 time=48.0 ms

 64 bytes from 8.8.8.8: icmp_seq=2 ttl=53 time=49.7 ms

 64 bytes from 8.8.8.8: icmp_seq=3 ttl=53 time=59.4 ms

When Nmap initially introduced decoy scans in version 1.60, the TTL was neither randomized nor calculated correctly for the decoy packets. Failing to correctly calculate the TTL allowed Moore to identify these packets. Obviously the code base for Nmap has grown significantly since 1999 and continues to evolve. In the current code base, Nmap randomizes the TTL using the following algorithm. This algorithm produces a random TTL, averaging about 48 per
packet. A user can also hard-code the TTL using an optional flag, setting the fixed TTL.

 /∗ Time to live ∗/

 if (ttl == -1) {

  myttl = (get_random_uint()% 23) + 37;

 } else {

  myttl = ttl;

 }

To run an Nmap decoy scan, we will utilize the –D flag followed by an IP address. In this case, we will use the address 8.8.8.8 as a decoy address. Furthermore, we will hard code a TTL of 13 using the –ttl. Thus our following commands scan the address 192.168.1.7 with a decoy of 8.8.8.8, using a hard-coded TTL of 13.

 attacker$ nmap 192.168.1.7 -D 8.8.8.8 -ttl 13

 Starting Nmap 5.51 (http://nmap.org) at 2012-03-04 14:54 MST

 Nmap scan report for 192.168.1.7

 Host is up (0.015s latency).

 <..SNIPPED..>

On the target, 192.168.1.7, we fire up tcpdump in verbose mode (-v), disable name resolution (-nn) and filter on the specific address 8.8.8.8 (‘host 8.8.8.8’). We see that Nmap successfully sent decoy packets from 8.8.8.8 using a TTL of 13.

 target# tcpdump –i eth0 –v –nn ‘host 8.8.8.8’

 8.8.8.8.42936 > 192.168.1.7.6: Flags [S], cksum 0xcae7 (correct), seq 690560664, win 3072, options [mss 1460], length 0

 14:56:41.289989 IP (tos 0x0, ttl 13, id 1625, offset 0, flags [none], proto TCP (6), length 44)

  8.8.8.8.42936 > 192.168.1.7.1009: Flags [S], cksum 0xc6fc (correct), seq 690560664, win 3072, options [mss 1460], length 0

 14:56:41.289996 IP (tos 0x0, ttl 13, id 16857, offset 0, flags

 [none], proto TCP (6), length 44)

  8.8.8.8.42936 > 192.168.1.7.1110: Flags [S], cksum 0xc697 (correct), seq 690560664, win 3072, options [mss 1460], length 0

 14:56:41.290003 IP (tos 0x0, ttl 13, id 41154, offset 0, flags [none], proto TCP (6), length 44)

  8.8.8.8.42936 > 192.168.1.7.2601: Flags [S], cksum 0xc0c4 (correct), seq 690560664, win 3072, options [mss 1460], length 0

 14:56:41.307069 IP (tos 0x0, ttl 13, id 63795, offset 0, flags [none], proto TCP (6), length 44)

Parsing TTL Fields with Scapy

Let’s begin writing our script by printing out the source IP address and TTL of incoming packets. At this point we will return to using Scapy for the rest of the chapter. It would be just as easy to write this code using dpkt. We will set up a function to sniff and pass each individual packet to the function testTTL(), which examines the packet for the IP layer, extracting the IP source address and TTL fields and prints these fields to the screen.

 from scapy.all import ∗

 def testTTL(pkt):

  try:

   if pkt.haslayer(IP):

    ipsrc = pkt.getlayer(IP).src

    ttl = str(pkt.ttl)

    print ‘[+] Pkt Received From: ‘+ipsrc+’ with TTL: ‘ \

     + ttl

  except:

   pass

 def main():

  sniff(prn=testTTL, store=0)

 if __name__ == ‘__main__’:

  main()

Running our code, we see that we have received quite a few packets from different source addresses, with varying TTLs. These results also include the decoy scans from 8.8.8.8 with a TTL of 13. As we know that the TTL should be 64 minus 11 = 53 hops away, we can argue that somebody spoofed these. It’s important to note at this point that while Linux/Unix systems usually start with an initial TTL of 64, Windows-based systems start with a TTL of 128. For the purposes of our script here, we’ll assume we are only dissecting IP packets from Linux workstations scanning our target, so let’s add a function to check the received TTL against the actual TTL.

 analyst# python printTTL.py

 [+] Pkt Received From: 192.168.1.7 with TTL: 64

 [+] Pkt Received From: 173.255.226.98 with TTL: 52

 [+] Pkt Received From: 8.8.8.8 with TTL: 13

 [+] Pkt Received From: 8.8.8.8 with TTL: 13

 [+] Pkt Received From: 192.168.1.7 with TTL: 64

 [+] Pkt Received From: 173.255.226.98 with TTL: 52

 [+] Pkt Received From: 8.8.8.8 with TTL: 13

Our function checkTTL() takes an IP source address with its respective received TTL as input and prints out a message for invalid TTLs. First, let’s use a quick conditional statement to eliminate packets from private IP addresses (10.0.0.0–10.255.255.255, 172.16.0.0–172.31.255.255, and 192.168.0.0–192.168.255.255). To do this, we import the IPy library. To avoid the class IP conflicting with the Scapy class IP, we reclassify it as IPTEST. If the IPTEST(ipsrc).iptype() returns ‘PRIVATE’, we return from our checkTTL function, ignoring the packet for examination.

We receive quite a few unique packets from the same source address. We only want to check the source address once. If we have not seen the source address previously, let’s build an IP packet with a destination address equal to the source. Additionally, we make the packet an ICMP echo request so that the destination will respond. Once the destination address responds, we place the TTL value in a dictionary, indexed by the IP source address. We then check to see if that difference between the actual received TTL and the TTL on the original packet exceeds a threshold value. Packets may take different routes on the way to a destination and therefore have different TTLs; however, if thehops distance differs byfive hops, we can assume it may be a spoofed TTL and print a warning message to the screen.

 from scapy.all import ∗

 from IPy import IP as IPTEST

 ttlValues = {}

 THRESH = 5

 def checkTTL(ipsrc, ttl):

  if IPTEST(ipsrc).iptype() == ‘PRIVATE’:

   return

  if not ttlValues.has_key(ipsrc):

   pkt = sr1(IP(dst=ipsrc) / ICMP(), \

    retry=0, timeout=1, verbose=0)

   ttlValues[ipsrc] = pkt.ttl

  if abs(int(ttl) - int(ttlValues[ipsrc])) > THRESH:

   print ‘\n[!] Detected Possible Spoofed Packet From: ‘\

    + ipsrc

   print ‘[!] TTL: ‘ + ttl + ‘, Actual TTL: ‘ \

     + str(ttlValues[ipsrc])

We add some option parsing for the specific address to listen in on, followed by an option to set the threshold to produce the final code. Less than fifty lines of code and we have H.D. Moore’s solution to the Pentagon dilemma from over a decade ago.

 import time

 import optparse

 from scapy.all import ∗

 from IPy import IP as IPTEST

 ttlValues = {}

 THRESH = 5

 def checkTTL(ipsrc, ttl):

  if IPTEST(ipsrc).iptype() == ‘PRIVATE’:

   return

  if not ttlValues.has_key(ipsrc):

   pkt = sr1(IP(dst=ipsrc) / ICMP(), \

    retry=0, timeout=1, verbose=0)

   ttlValues[ipsrc] = pkt.ttl

  if abs(int(ttl) - int(ttlValues[ipsrc])) > THRESH:

   print ‘\n[!] Detected Possible Spoofed Packet From: ‘\

    + ipsrc

   print ‘[!] TTL: ‘ + ttl + ‘, Actual TTL: ‘ \

     + str(ttlValues[ipsrc])

 def testTTL(pkt):

  try:

   if pkt.haslayer(IP):

    ipsrc = pkt.getlayer(IP).src

    ttl = str(pkt.ttl)

    checkTTL(ipsrc, ttl)

  except:

   pass

 def main():

  parser = optparse.OptionParser(“usage%prog “+\

   “-i -t ”)

  parser.add_option(‘-i’, dest=’iface’, type=’string’,\

   help=’specify network interface’)

  parser.add_option(‘-t’, dest=’thresh’, type=’int’,

   help=’specify threshold count ‘)

  (options, args) = parser.parse_args()

  if options.iface == None:

    conf.iface = ‘eth0’

  else:

    conf.iface = options.iface

  if options.thresh != None:

    THRESH = options.thresh

  else:

    THRESH = 5

  sniff(prn=testTTL, store=0)

 if __name__ == ‘__main__’:

  main()

Running our code, we can see that it correctly identifies the decoy Nmap scan from 8.8.8.8 because of the TTL of 13 compared to the actual TTL of 53 (for our packet). It is important to note that our value is generated off an initial default TTL for Linux of 64. Although RFC 1700 recommends the default TTL as 64, Microsoft Windows has used an initial TTL of 128 since MS Windows NT 4.0. Additionally, some other Unix variants have different TTLs such as Solaris 2.x with a default TTL of 255. For now, we will leave the script as and assume spoofed packets are originating from a Linux based machine.

 analyst# python spoofDetect.py –i eth0 –t 5

 [!] Detected Possible Spoofed Packet From: 8.8.8.8

 [!] TTL: 13, Actual TTL: 53

 [!] Detected Possible Spoofed Packet From: 8.8.8.8

 [!] TTL: 13, Actual TTL: 53

 [!] Detected Possible Spoofed Packet From: 8.8.8.8

 [!] TTL: 13, Actual TTL: 53

 [!] Detected Possible Spoofed Packet From: 8.8.8.8

 [!] TTL: 13, Actual TTL: 53

 <..SNIPPED..>

Other books

Naked Lies by Ray Gordon
Long Past Stopping by Oran Canfield
Midsummer Magic by Julia Williams
Brown, Dale - Patrick McLanahan 06 by Fatal Terrain (v1.1)
The Confession by R.L. Stine
If I Should Die by Amy Plum
Lord Soth by Edo Van Belkom
Iron Eyes Must Die by Rory Black