Skip to content

Instantly share code, notes, and snippets.

@rabbitt
Created April 27, 2020 17:35
Show Gist options
  • Save rabbitt/3de38df90ab1c9fbae38bf32dcd4ce1c to your computer and use it in GitHub Desktop.
Save rabbitt/3de38df90ab1c9fbae38bf32dcd4ce1c to your computer and use it in GitHub Desktop.
Rudimentary port scanner using scapy and half-open connections
#!/usr/bin/env python
import socket
from scapy.all import IP, TCP, ICMP, sr1, RandShort
# only emit warning about scapy not being permitted once
scapy_warning_emitted = False
def get_port_name(port):
try:
return socket.getservbyport(port, 'tcp')
except Exception:
return 'unknown'
def tcp_port_open(ipaddress, port):
try:
# Note: Because we're using a half-open connection here instead of a full
# 3-way handshake most (sane) operating systems will see the SynAck that comes
# back from the target and immediately send a RST.
#
# This happens because the OS's networking stack doesn't know about the userland's
# network activity, and so refuses to acknowledge a connection *it* didn't
# specifically create. This actually works here because we don't care about the
# full connection - just the whether or not the port is open.
ip = IP(dst=ipaddress)
syn = TCP(sport=RandShort(), dport=port, flags="S", seq=40)
response = sr1(ip/syn, timeout=0.1, verbose=0)
if response and TCP in response and response[TCP].flags == 0x12: # 0x12 == SynAck
return True
except OSError as e:
if 'Operation not permitted' in str(e):
if not scapy_warning_emitted:
from scapy.all import conf
print("Not permitted to use scapy on interface {} - attempting normal socket connection".format(conf.iface))
scapy_warning_emitted = True
try:
# if scapy failed, resort to the opening a normal socket (with 0.1s timeout),
# and then immediately close it
socket.create_connection((ipaddress, port), 0.1).close()
return True
except Exception:
# ignore exceptions and just count it as a failure (i.e., return False)
pass
return False
if __name__ == '__main__':
import argparse
from itertools import chain
class RangeString(str):
# to_range, borrowed from rangeString as seen here: https://stackoverflow.com/a/6405816/988225
def to_range(self):
def hyphen_string(hyphenated):
x = [int(x) for x in hyphenated.split('-')]
return range(x[0], x[-1]+1)
return chain(*[hyphen_string(r) for r in self.split(',')])
parser = argparse.ArgumentParser()
parser.add_argument('-H', '--host', help='ip address to scan')
parser.add_argument('-P', '--ports', type=RangeString, help='list of ports to scan (e.g., 1-25,80,443,3306,4000-4100)')
args = parser.parse_args()
host = args.host if args.host else input("[*] Enter host (ip) to scan: ")
ports = args.ports if args.ports else RangeString(input("[*] Enter port range (e.g., 1-20,22,23)"))
print("Scanning ports {} for host {}:".format(args.ports, host))
for port in args.ports.to_range():
try:
if tcp_port_open(host, port):
print("\t{} ({}) open".format(port, get_port_name(port)))
except (socket.timeout, socket.error) as e:
print(str(e))
if 'Operation not permitted' in str(e):
import pipes, sys
print("Unable to perform raw socket operation. Perhaps you need elevated privileges?")
print("Try rerunning using sudo, for example:")
print(" sudo {}".format(' '.join(pipes.quote(x) for x in sys.argv)))
break
@arieltraver
Copy link

This is great

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment