netscan-ks 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netscan_ks-0.1.0/PKG-INFO +6 -0
- netscan_ks-0.1.0/netscan_ks.egg-info/PKG-INFO +6 -0
- netscan_ks-0.1.0/netscan_ks.egg-info/SOURCES.txt +11 -0
- netscan_ks-0.1.0/netscan_ks.egg-info/dependency_links.txt +1 -0
- netscan_ks-0.1.0/netscan_ks.egg-info/entry_points.txt +2 -0
- netscan_ks-0.1.0/netscan_ks.egg-info/requires.txt +2 -0
- netscan_ks-0.1.0/netscan_ks.egg-info/top_level.txt +1 -0
- netscan_ks-0.1.0/pyproject.toml +12 -0
- netscan_ks-0.1.0/scanner/__init__.py +1 -0
- netscan_ks-0.1.0/scanner/banner_grab.py +12 -0
- netscan_ks-0.1.0/scanner/cli.py +103 -0
- netscan_ks-0.1.0/scanner/syn_scan.py +34 -0
- netscan_ks-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
netscan_ks.egg-info/PKG-INFO
|
|
3
|
+
netscan_ks.egg-info/SOURCES.txt
|
|
4
|
+
netscan_ks.egg-info/dependency_links.txt
|
|
5
|
+
netscan_ks.egg-info/entry_points.txt
|
|
6
|
+
netscan_ks.egg-info/requires.txt
|
|
7
|
+
netscan_ks.egg-info/top_level.txt
|
|
8
|
+
scanner/__init__.py
|
|
9
|
+
scanner/banner_grab.py
|
|
10
|
+
scanner/cli.py
|
|
11
|
+
scanner/syn_scan.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
scanner
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "netscan-ks"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.8"
|
|
9
|
+
dependencies = ["scapy", "tqdm"]
|
|
10
|
+
|
|
11
|
+
[project.scripts]
|
|
12
|
+
netscan = "scanner.cli:run"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
def grab_banner(target, port, timeout=2):
|
|
4
|
+
try:
|
|
5
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
6
|
+
sock.settimeout(timeout)
|
|
7
|
+
sock.connect((target, port))
|
|
8
|
+
banner = sock.recv(1024).decode().strip()
|
|
9
|
+
sock.close()
|
|
10
|
+
return banner
|
|
11
|
+
except(socket.timeout, socket.error, UnicodeDecodeError):
|
|
12
|
+
return None
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from math import e
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import ipaddress
|
|
6
|
+
from tqdm import tqdm
|
|
7
|
+
from scanner.syn_scan import syn_scan
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
9
|
+
from scanner.banner_grab import grab_banner
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run():
|
|
13
|
+
threading.excepthook = scapy_thread_error_filter
|
|
14
|
+
start_time = time.time()
|
|
15
|
+
|
|
16
|
+
parser = argparse.ArgumentParser()
|
|
17
|
+
parser.add_argument("-t", "--target", help="Target IP address to scan", required=True)
|
|
18
|
+
parser.add_argument("-p", "--ports", help="Specific ports to scan", nargs="+")
|
|
19
|
+
parser.add_argument("-s", "--start", help="Start of port range to scan" ,type=int)
|
|
20
|
+
parser.add_argument("-e", "--end", help="End of port range to scan", type=int)
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
|
|
23
|
+
target = args.target
|
|
24
|
+
targeted_port = args.ports
|
|
25
|
+
start_port = args.start
|
|
26
|
+
end_port = args.end
|
|
27
|
+
open_ports = []
|
|
28
|
+
filtered_ports = []
|
|
29
|
+
|
|
30
|
+
print(f"Starting scan on {target}...")
|
|
31
|
+
|
|
32
|
+
#Error checks
|
|
33
|
+
if bool(start_port) != bool(end_port):
|
|
34
|
+
print("Error: --start and --end must be used together.")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
ipaddress.ip_address(target)
|
|
39
|
+
except ValueError:
|
|
40
|
+
print(f"Error: '{target}' is not a valid IP address.")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
if start_port and end_port:
|
|
44
|
+
if not (1 <= start_port <= 65535) or not (1 <= end_port <= 65535):
|
|
45
|
+
print("Error: Ports must be between 1 and 65535.")
|
|
46
|
+
return
|
|
47
|
+
if start_port > end_port:
|
|
48
|
+
print("Error: Start port must be less than or equal to end port.")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
#Scanning starts here
|
|
52
|
+
if targeted_port:
|
|
53
|
+
ports = [int(p.strip(",")) for p in targeted_port]
|
|
54
|
+
if any(not (1 <= p <= 65535) for p in ports):
|
|
55
|
+
print("Error: Ports must be between 1 and 65535.")
|
|
56
|
+
return
|
|
57
|
+
with ThreadPoolExecutor(max_workers=50) as executor:
|
|
58
|
+
results = list(tqdm(executor.map(lambda port: scan_and_store(target, port), ports),
|
|
59
|
+
total=len(ports), desc="Scanning"))
|
|
60
|
+
for port, result in results:
|
|
61
|
+
if result == "OPEN":
|
|
62
|
+
open_ports.append(port)
|
|
63
|
+
elif "FILTERED" in result:
|
|
64
|
+
filtered_ports.append(port)
|
|
65
|
+
|
|
66
|
+
if start_port and end_port:
|
|
67
|
+
with ThreadPoolExecutor(max_workers=50) as executor:
|
|
68
|
+
results = list(tqdm(executor.map(lambda port: scan_and_store(target, port), range(start_port, end_port + 1)),
|
|
69
|
+
total=end_port - start_port + 1, desc="Scanning"))
|
|
70
|
+
for port, result in results:
|
|
71
|
+
if result == "OPEN":
|
|
72
|
+
open_ports.append(port)
|
|
73
|
+
elif "FILTERED" in result:
|
|
74
|
+
filtered_ports.append(port)
|
|
75
|
+
|
|
76
|
+
#Outputs
|
|
77
|
+
print(f"Open ports:{len(open_ports)}")
|
|
78
|
+
for port in open_ports:
|
|
79
|
+
banner = grab_banner(target, port)
|
|
80
|
+
if banner:
|
|
81
|
+
print(f"Port {port} is OPEN - Banner: {banner}")
|
|
82
|
+
else:
|
|
83
|
+
print(f"Port {port} is OPEN")
|
|
84
|
+
|
|
85
|
+
print(f"Filtered ports:{len(filtered_ports)}")
|
|
86
|
+
for port in filtered_ports:
|
|
87
|
+
print(f"Port {port} is FILTERED")
|
|
88
|
+
|
|
89
|
+
elapsed = time.time() - start_time
|
|
90
|
+
print(f"Scan completed in {elapsed:.2f} seconds.")
|
|
91
|
+
|
|
92
|
+
def scan_and_store(target, port):
|
|
93
|
+
result = syn_scan(target, port)
|
|
94
|
+
return (port, result)
|
|
95
|
+
|
|
96
|
+
# Scapy's sr1() spawns internal threads for packet sending/receiving.
|
|
97
|
+
# When running my own scans with ThreadPoolExecutor, these internal threads occasionally fail with OSError errno 9 (Bad file descriptor)
|
|
98
|
+
# and errno 22 (Invalid argument) during cleanup. These are known Scapy pipe errors on Windows and do not affect scan results.
|
|
99
|
+
# threading.excepthook is used to suppress these specific errors only. To clarify, the errors do occur on every scan, they are just not visible to the user.
|
|
100
|
+
def scapy_thread_error_filter(args):
|
|
101
|
+
if isinstance(args.exc_value, OSError) and args.exc_value.errno in (9, 22):
|
|
102
|
+
return
|
|
103
|
+
threading.__excepthook__(args)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from scapy.layers.inet import IP, TCP, ICMP
|
|
2
|
+
from scapy.sendrecv import sr1
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
def syn_scan(target, port, retries=2, timeout=2):
|
|
6
|
+
for attempt in range(retries + 1):
|
|
7
|
+
sport = random.randint(1024, 65535)
|
|
8
|
+
packet = IP(dst=target) / TCP(sport=sport,
|
|
9
|
+
dport=port,
|
|
10
|
+
flags="S")
|
|
11
|
+
|
|
12
|
+
response = sr1(packet, timeout=timeout, verbose=0)
|
|
13
|
+
|
|
14
|
+
if response is None:
|
|
15
|
+
if attempt == retries:
|
|
16
|
+
return "FILTERED"
|
|
17
|
+
continue
|
|
18
|
+
|
|
19
|
+
if response.haslayer(TCP):
|
|
20
|
+
flags = response.getlayer(TCP).flags
|
|
21
|
+
if flags & 0x12 == 0x12: # SYN-ACK
|
|
22
|
+
rst = IP(dst=target) / TCP(sport=sport,
|
|
23
|
+
dport=port,
|
|
24
|
+
flags="R",
|
|
25
|
+
seq=response.ack)
|
|
26
|
+
sr1(rst, timeout=timeout, verbose=0)
|
|
27
|
+
return "OPEN"
|
|
28
|
+
elif flags & 0x14 == 0x14: # RST-ACK
|
|
29
|
+
return "CLOSED"
|
|
30
|
+
elif response.haslayer(ICMP):
|
|
31
|
+
icmp = response.getlayer(ICMP)
|
|
32
|
+
if icmp.type == 3:
|
|
33
|
+
return f"FILTERED (ICMP type 3 code {icmp.code})"
|
|
34
|
+
return "UNKNOWN"
|