From 08fc22a93234405903d882c894661b252eb028ad Mon Sep 17 00:00:00 2001 From: Oleksii Shevchuk Date: Sun, 5 Mar 2017 12:18:58 +0200 Subject: [PATCH] select based trivial port scanner --- pupy/modules/port_scan.py | 77 ++++++++++++++++++++++++----------- pupy/network/lib/scan.py | 73 +++++++++++++++++++++++++++++++++ pupy/packages/all/portscan.py | 58 -------------------------- 3 files changed, 127 insertions(+), 81 deletions(-) create mode 100644 pupy/network/lib/scan.py delete mode 100644 pupy/packages/all/portscan.py diff --git a/pupy/modules/port_scan.py b/pupy/modules/port_scan.py index be41cffc..67c6101f 100644 --- a/pupy/modules/port_scan.py +++ b/pupy/modules/port_scan.py @@ -1,23 +1,27 @@ -# -*- coding: UTF8 -*- +# -*- coding: utf-8 -*- + from pupylib.PupyModule import * -from modules.lib.windows.winpcap import init_winpcap +from netaddr import IPNetwork, IPAddress import logging -from datetime import datetime -from netaddr import * +import random +import threading __class_name__="PortScan" @config(cat="network") class PortScan(PupyModule): """ run a TCP port scan """ - dependencies=['portscan', 'scapy'] - max_clients=1 + + abort = None + terminated = threading.Event() + max_clients = 1 + connectable = [] def init_argparse(self): self.arg_parser = PupyArgumentParser(prog="port_scan", description=self.__doc__) self.arg_parser.add_argument('--ports','-p', default="21,22,23,80,139,443,445,1433,1521,3389,7001,8000,8080", help='ports to scan ex: 22,80,443') - self.arg_parser.add_argument('--timeout', default=4, help='timeout (default: %(default)s)') - self.arg_parser.add_argument('--threads', default=10, help='number of threads (default: %(default)s)') + self.arg_parser.add_argument('--timeout', default=10, help='timeout (default: %(default)s)') + self.arg_parser.add_argument('--portion', default=32, help='number of ports scanned per timeout (default: %(default)s)') self.arg_parser.add_argument('target', metavar="ip/range", help='IP/range') def run(self, args): @@ -27,20 +31,47 @@ class PortScan(PupyModule): hosts = list() hosts.append(args.target) - ports = [int(p.strip()) for p in args.ports.split(',')] + ports = [ + p for prange in args.ports.split(',') for p in ( + xrange( + int(prange.split('-')[0]), int(prange.split('-')[1])+1 + ) if '-' in prange else xrange( + int(prange), int(prange)+1 + ) + ) + ] + + ports = list(set(ports)) + random.shuffle(ports) + for host in hosts: - self.success("Scanning remote host: %s" % host) - - t1 = datetime.now() - open_ports = self.client.conn.modules['portscan'].scan(host, ports, args.threads, args.timeout) - if open_ports: - self.log('PORT STATE') - for p in open_ports: - self.log("%s open" % p) + scanner = self.client.conn.modules['network.lib.scan'] + + def set_connectable(ports): + self.connectable = ports + self.terminated.set() + + self.abort = scanner.scanthread( + str(host), ports, set_connectable, timeout=args.timeout, portion=args.portion + ) + + self.terminated.wait() + + ports = sorted(self.connectable) + + if ports: + self.log('{}: {}'.format(host, ', '.join([str(x) for x in ports]))) else: - self.error('No open port found') - - # Checking the time again - t2 = datetime.now() - total = t2 - t1 - self.success('Scanning Completed in: %s' % total) \ No newline at end of file + self.log('{}: closed'.format(host)) + + if self.abort.is_set(): + break + + self.abort = None + + def interrupt(self): + if self.abort: + self.abort.set() + + if self.terminated: + self.terminated.set() diff --git a/pupy/network/lib/scan.py b/pupy/network/lib/scan.py new file mode 100644 index 00000000..5a3a6381 --- /dev/null +++ b/pupy/network/lib/scan.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +import socket +import select +import errno +import time +import threading +import rpyc + +def chunks(l, n): + for i in xrange(0, len(l), n): + yield l[i:i + n] + +def create_socket(host, port): + sock = socket.socket() + sock.setblocking(0) + r = sock.connect_ex((host, port)) + return sock, r + +def scan(host, ports, abort=None, timeout=10, portion=32, on_complete=None): + connectable=[] + for portion in chunks(list(ports), portion): + if not portion: + continue + + if abort and abort.is_set(): + break + + sockets = {} + for port in portion: + sock, r = create_socket(host, port) + if r: + if r in (errno.EAGAIN, errno.EINPROGRESS): + sockets[sock] = port + else: + sock.close() + continue + else: + connectable.append(port) + sock.close() + + start = time.time() + while sockets and time.time() - start < timeout: + socks = list(sockets.iterkeys()) + _, w, _ = select.select([], socks, [], timeout - (time.time() - start)) + + for sock in w: + errcode = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + if errcode == 0: + connectable.append(sockets[sock]) + sock.close() + del sockets[sock] + + if on_complete: + if abort and not abort.is_set(): + on_complete(connectable) + else: + return connectable + +def scanthread(host, ports, on_complete, **kwargs): + host = str(host) + ports = [ x for x in ports ] + abort = threading.Event() + connectable = [] + kwargs.update({ + 'abort': abort, + 'on_complete': rpyc.async(on_complete) + }) + scanner = threading.Thread(target=scan, args=(host, ports), kwargs=kwargs) + scanner.daemon = True + scanner.start() + + return abort diff --git a/pupy/packages/all/portscan.py b/pupy/packages/all/portscan.py deleted file mode 100644 index cbd9926b..00000000 --- a/pupy/packages/all/portscan.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -import socket -import sys -import threading -import Queue - -open_port = [] - -class WorkerThread(threading.Thread) : - - def __init__(self, queue, tid, remote_ip, ports, settimeout) : - threading.Thread.__init__(self) - self.queue = queue - self.tid = tid - self.ports = ports - self.remote_ip = remote_ip - self.timeout = settimeout - - def check_open_port(self, port): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(self.timeout) - result = sock.connect_ex((self.remote_ip, port)) - if result == 0: - sock.close() - open_port.append(port) - - def run(self): - for port in self.ports: - try : - port = self.queue.get(timeout=1) - except Queue.Empty : - return - self.check_open_port(port) - self.queue.task_done() - -def scan(remote_ip, ports, nb_threads, settimeout): - global open_port - open_port = [] - - queue = Queue.Queue() - threads = [] - - for i in range(1, nb_threads): - worker = WorkerThread(queue, i, remote_ip, ports, settimeout) - worker.setDaemon(True) - worker.start() - threads.append(worker) - - for j in ports: - queue.put(j) - - queue.join() - - # wait for all threads to exit - for item in threads : - item.join() - - return open_port \ No newline at end of file