Skip to content

Commit ecebfec

Browse files
Add multi-threaded port scanner with report saving
This script implements a multi-threaded port scanner that identifies open ports on a target IP address, grabs banners, and saves the scan results in both JSON and TXT formats.
1 parent 2281698 commit ecebfec

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

src/port_scanner.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import socket
2+
import threading
3+
import argparse
4+
import time
5+
import json
6+
import random
7+
from datetime import datetime
8+
from queue import Queue
9+
import os
10+
11+
# Thread settings
12+
THREADS = 100
13+
q = Queue()
14+
open_ports = []
15+
16+
# ----- Banner Grabbing -----
17+
def grab_banner(ip, port):
18+
try:
19+
s = socket.socket()
20+
s.settimeout(1)
21+
s.connect((ip, port))
22+
banner = s.recv(1024).decode().strip()
23+
s.close()
24+
return banner
25+
except:
26+
return None
27+
28+
# ----- Worker Function -----
29+
def scan_port(ip):
30+
while not q.empty():
31+
port = q.get()
32+
try:
33+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
34+
s.settimeout(0.5)
35+
36+
result = s.connect_ex((ip, port))
37+
if result == 0:
38+
banner = grab_banner(ip, port)
39+
open_ports.append({
40+
"port": port,
41+
"banner": banner
42+
})
43+
if banner:
44+
print(f"\033[92m[OPEN] {port}/tcp → {banner}\033[0m")
45+
else:
46+
print(f"\033[92m[OPEN] {port}/tcp\033[0m")
47+
s.close()
48+
49+
except:
50+
pass
51+
52+
q.task_done()
53+
54+
# ----- Main Scanner -----
55+
def scan(ip, ports):
56+
print("\n========== Port Scan Started ==========")
57+
print(f"Target: {ip}")
58+
print("Start Time:", datetime.now())
59+
print("Ports:", f"{min(ports)}-{max(ports)}")
60+
print("Threads:", THREADS)
61+
print("----------------------------------------\n")
62+
63+
start = time.time()
64+
65+
# Randomize port order for basic IDS evasion
66+
random.shuffle(ports)
67+
68+
for port in ports:
69+
q.put(port)
70+
71+
for _ in range(THREADS):
72+
t = threading.Thread(target=scan_port, args=(ip,))
73+
t.daemon = True
74+
t.start()
75+
76+
q.join()
77+
78+
duration = round(time.time() - start, 2)
79+
80+
print("\n========== Scan Complete ==========")
81+
print(f"Open Ports Found: {len(open_ports)}")
82+
print(f"Scan Duration: {duration} seconds")
83+
print("====================================\n")
84+
85+
return open_ports, duration
86+
87+
# ----- Save Reports -----
88+
def save_reports(ip, open_ports, duration):
89+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
90+
os.makedirs("reports", exist_ok=True)
91+
92+
# JSON
93+
with open(f"reports/portscan-{timestamp}.json", "w") as jf:
94+
json.dump({
95+
"target": ip,
96+
"open_ports": open_ports,
97+
"scan_duration": duration
98+
}, jf, indent=4)
99+
100+
# TXT
101+
with open(f"reports/portscan-{timestamp}.txt", "w") as tf:
102+
for entry in open_ports:
103+
tf.write(f"{entry['port']} - {entry['banner']}\n")
104+
105+
print(f"Reports saved inside /reports folder.\n")
106+
107+
# ----- Entry Point -----
108+
if __name__ == "__main__":
109+
parser = argparse.ArgumentParser(description="Advanced Multi-threaded Port Scanner")
110+
parser.add_argument("target", help="Target IP or domain")
111+
parser.add_argument(
112+
"-p", "--ports",
113+
help="Port range (e.g. 1-1024 or 80,443,3306)",
114+
default="1-1024"
115+
)
116+
117+
args = parser.parse_args()
118+
119+
# Resolve host
120+
try:
121+
ip = socket.gethostbyname(args.target)
122+
except:
123+
print("\033[91m[ERROR] Unable to resolve host.\033[0m")
124+
exit()
125+
126+
# Parse ports
127+
ports = []
128+
if "-" in args.ports:
129+
start, end = args.ports.split("-")
130+
ports = list(range(int(start), int(end) + 1))
131+
elif "," in args.ports:
132+
ports = [int(p) for p in args.ports.split(",")]
133+
else:
134+
ports = [int(args.ports)]
135+
136+
open_ports, duration = scan(ip, ports)
137+
save_reports(ip, open_ports, duration)

0 commit comments

Comments
 (0)