Created
April 27, 2020 17:35
-
-
Save rabbitt/3de38df90ab1c9fbae38bf32dcd4ce1c to your computer and use it in GitHub Desktop.
Rudimentary port scanner using scapy and half-open connections
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is great