How to build a Host (ARP) scanner in python using Scapy

by hash3liZer . 04 September 2018

How to build a Host (ARP) scanner in python using Scapy

Scapy is a powerful packet manipulation tool developed in Python to perform networking-related tasks with much modular syntax. Tough the syntax of scapy seems a bit obscure at first but if well-understood could be a very powerful tool in hands of a regular programmer. It allows manipulating layers and fields within in a different fashion. You can even create your own protocols and such packets which could never exist, i.e. there's no restriction on about how you are stacking layers.

Many network discovery tools make use of Address Resolution Protocol (ARP) to find other live hosts in a network. An ARP protocol packet would be sent to the broadcast address on the wire and if there are other hosts available, they will respond with an ARP packet as well which is exactly what we are going to code in the script. Let's see what to do:

Range

First, we will ask our client to provide us with an IP range to scan:

import sys

def main():
    _range = raw_input("Enter the Target Range: ")
    _interface = raw_input("Enter the network interface: ")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print "CTRL+C pressed. Exiting. "
        sys.exit(0)

Now, we have the basic structure of our script. All we have done yet is just asked our client to provide us with the necessary details. Then we will have to break the ip address into a valid range. Split the ip address with "/" delimeter and make valid blocks from it:

import sys

def main():
    _range = raw_input("Enter the Target Range: ")
    _interface = raw_input("Enter the network interface: ")
    ip, ntBits = _range.split('/')
    ip_addresses = []
    st_bit = ip.split('.')[3:4][0]   #Since it's an IPv4
    for n in range(1, int(ntBits)+1):
        eval_ip = ".".join( ip.split('.')[:-1] ) + '.' + str(n)
        ip_addresses.append( eval_ip )

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print "CTRL+C pressed. Exiting. "
        sys.exit(0)

Now, we have a list of ip addresses that we are going to scan for. We will loop through this list and send an ARP packet for each IP address and look for responses. Let's say we provide our script this input: 19.168.1.1/32. A list will be created something like this:

>>> ['192.168.1.1', '192.168.1.2', '192.168.1.3', ...]

Send

As, we have the list of ip addresses to send requests to, we can begin the sending part. At this step, we will loop through the sending list and send an ARP request to each. srp function will help us with this. It will return two kind of packets, the answered and the unanswered. The anwered packets are further divided into sending packets and receiving packets. The idea is the collect the receiving packet and check which hosts have sent these packets. Let's see:

from scapy.all import Ether, ARP
import sys

def main():
    ...
    ...
    
    for ip in ip_addresses:
        _pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip)
        ans, unans = srp( _pkt, iface=_interface, timeout=0.1, verbose=False)
        
if __name__ == "__main__":
    ...

We have two categories of packets here, if a packet has been received, it'll be in the answered group and if not, it will be in the other group. It's easy from here now. We just have to verify that a packet has received from the host.

from scapy.all import Ether, ARP, srp
import sys

def main():
    ...
    ...
    
    for ip in ip_addresses:
        _pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip)
        ans, unans = srp( _pkt, iface=_interface, timeout=0.1, verbose=False)
        for snt, recv in ans:
            if recv:
                print "Host Alive: %s - %s" % (recv[ARP].psrc, recv[Ether].src)
        
if __name__ == "__main__":
    ...

Putting it all together:

import sys
from scapy.all import Ether, ARP, srp

def main():
    _range = raw_input("Enter the Target Range: ")
    _interface = raw_input("Enter the network interface: ")
    ip, ntBits = _range.split('/')
    ip_addresses = []
    st_bit = ip.split('.')[3:4][0]   #Since it's an IPv4
    for n in range(1, int(ntBits)+1):
        eval_ip = ".".join( ip.split('.')[:-1] ) + '.' + str(n)
        ip_addresses.append( eval_ip )

    for ip in ip_addresses:
        _pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip)
        ans, unans = srp( _pkt, iface=_interface, timeout=0.1, verbose=False)
        for snt, recv in ans:
            if recv:
                print "Host Alive: %s - %s" % (recv[ARP].psrc, recv[Ether].src)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print "CTRL+C pressed. Exiting. "
        sys.exit(0)

Execute the script from your terminal:

results

So, we got the results. We have some hosts alive in the network. The next thing you can do is sniff the network traffic and find which host is idle and which is sending more packets.