ghostnet 1.2.1__py3-none-any.whl
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.
- ghostnet/__init__.py +6 -0
- ghostnet/__main__.py +25 -0
- ghostnet/cli.py +415 -0
- ghostnet/config.py +66 -0
- ghostnet/constants.py +167 -0
- ghostnet/detectors.py +606 -0
- ghostnet/engine.py +597 -0
- ghostnet/menu.py +449 -0
- ghostnet/netmap.py +97 -0
- ghostnet/parsing.py +84 -0
- ghostnet/reporting.py +169 -0
- ghostnet/vulndb.py +191 -0
- ghostnet/webapp.py +543 -0
- ghostnet-1.2.1.dist-info/METADATA +175 -0
- ghostnet-1.2.1.dist-info/RECORD +20 -0
- ghostnet-1.2.1.dist-info/WHEEL +5 -0
- ghostnet-1.2.1.dist-info/entry_points.txt +2 -0
- ghostnet-1.2.1.dist-info/licenses/LICENSE +21 -0
- ghostnet-1.2.1.dist-info/top_level.txt +2 -0
- vuln.py +217 -0
ghostnet/__init__.py
ADDED
ghostnet/__main__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from .cli import main
|
|
5
|
+
from .constants import Colors
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _run():
|
|
9
|
+
exit_code = 0
|
|
10
|
+
try:
|
|
11
|
+
exit_code = main()
|
|
12
|
+
except KeyboardInterrupt:
|
|
13
|
+
print(f"\n{Colors.BRIGHT_RED}Program terminated by user.{Colors.RESET}")
|
|
14
|
+
exit_code = 130
|
|
15
|
+
except Exception as e:
|
|
16
|
+
print(f"\n{Colors.BRIGHT_RED}An unexpected error occurred: {e}{Colors.RESET}")
|
|
17
|
+
exit_code = 1
|
|
18
|
+
finally:
|
|
19
|
+
if len(sys.argv) <= 1:
|
|
20
|
+
time.sleep(1.5)
|
|
21
|
+
sys.exit(exit_code)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
_run()
|
ghostnet/cli.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import socket
|
|
5
|
+
import ssl
|
|
6
|
+
import re
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import random
|
|
10
|
+
import struct
|
|
11
|
+
import uuid
|
|
12
|
+
import copy
|
|
13
|
+
import errno
|
|
14
|
+
import logging
|
|
15
|
+
import threading
|
|
16
|
+
import getpass
|
|
17
|
+
import platform
|
|
18
|
+
import ipaddress
|
|
19
|
+
import asyncio
|
|
20
|
+
import xml.etree.ElementTree as ET
|
|
21
|
+
from datetime import datetime, timedelta
|
|
22
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
23
|
+
from urllib.parse import urljoin, urlparse
|
|
24
|
+
from typing import List, Dict, Any, Tuple, Optional, Union
|
|
25
|
+
import pytz
|
|
26
|
+
import requests
|
|
27
|
+
import urllib3
|
|
28
|
+
from bs4 import BeautifulSoup
|
|
29
|
+
|
|
30
|
+
from .constants import *
|
|
31
|
+
import argparse
|
|
32
|
+
from .config import Config
|
|
33
|
+
from .parsing import parse_target_spec, parse_port_spec
|
|
34
|
+
from .engine import Scanner
|
|
35
|
+
from .reporting import Reporter
|
|
36
|
+
from .netmap import NetworkMapper, format_topology
|
|
37
|
+
from .menu import interactive_main
|
|
38
|
+
|
|
39
|
+
def parse_args():
|
|
40
|
+
parser = argparse.ArgumentParser(
|
|
41
|
+
prog="ghostnet",
|
|
42
|
+
description="GhostNet - Advanced Network Scanner",
|
|
43
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
44
|
+
epilog="""\
|
|
45
|
+
examples:
|
|
46
|
+
ghostnet -t 192.168.1.1 scan common ports
|
|
47
|
+
ghostnet -t 192.168.1.0/24 -p- whole subnet, all 65535 ports
|
|
48
|
+
ghostnet -t host -A aggressive: -sV -O -b
|
|
49
|
+
ghostnet -t host -sV -T4 version detection, fast timing
|
|
50
|
+
ghostnet -t 192.168.1.0/24 -sn host discovery (ping sweep) only
|
|
51
|
+
sudo ghostnet -t host --syn-scan SYN scan (needs scapy/root)
|
|
52
|
+
ghostnet -t host -oA results write results.txt/.json/.xml
|
|
53
|
+
ghostnet --update-db refresh the local NVD database
|
|
54
|
+
|
|
55
|
+
Run with no arguments for an interactive menu.
|
|
56
|
+
""",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Target specification
|
|
60
|
+
target_group = parser.add_argument_group("Target Selection")
|
|
61
|
+
target_group.add_argument("-t", "--target", dest="targets", action="append",
|
|
62
|
+
help="Target IP address, hostname, or network (CIDR)")
|
|
63
|
+
target_group.add_argument("-iL", "--input-file", dest="input_file",
|
|
64
|
+
help="Input from file containing targets (one per line)")
|
|
65
|
+
|
|
66
|
+
# Port specification
|
|
67
|
+
port_group = parser.add_argument_group("Port Selection")
|
|
68
|
+
port_group.add_argument("-p", "--ports", dest="ports",
|
|
69
|
+
help="Port(s) to scan (e.g., 22,80,443 or 1-1000; '-' for all 65535)")
|
|
70
|
+
port_group.add_argument("--top-ports", dest="top_ports", type=int,
|
|
71
|
+
help="Scan the top N most common ports")
|
|
72
|
+
|
|
73
|
+
# Scan types
|
|
74
|
+
scan_group = parser.add_argument_group("Scan Techniques")
|
|
75
|
+
scan_group.add_argument("-sS", "--syn-scan", dest="syn_scan", action="store_true",
|
|
76
|
+
help="TCP SYN scan (requires root/admin privileges)")
|
|
77
|
+
scan_group.add_argument("-sT", "--tcp-scan", dest="tcp_scan", action="store_true",
|
|
78
|
+
help="TCP connect scan")
|
|
79
|
+
scan_group.add_argument("-sU", "--udp-scan", dest="udp_scan", action="store_true",
|
|
80
|
+
help="UDP scan")
|
|
81
|
+
scan_group.add_argument("-sF", "--fast", dest="fast_mode", action="store_true",
|
|
82
|
+
help="Fast scan mode")
|
|
83
|
+
scan_group.add_argument("-sn", "--ping-scan", dest="ping_scan", action="store_true",
|
|
84
|
+
help="Host discovery only (ping sweep); no port scan")
|
|
85
|
+
scan_group.add_argument("-Pn", "--no-ping", dest="no_ping", action="store_true",
|
|
86
|
+
help="Skip host discovery; treat all hosts as online "
|
|
87
|
+
"(accepted for nmap compatibility)")
|
|
88
|
+
|
|
89
|
+
# Service and OS detection
|
|
90
|
+
service_group = parser.add_argument_group("Service/OS Detection")
|
|
91
|
+
service_group.add_argument("-sV", "--service-version", dest="service_detection", action="store_true",
|
|
92
|
+
help="Probe open ports to determine service/version info")
|
|
93
|
+
service_group.add_argument("-O", "--os-detection", dest="os_detection", action="store_true",
|
|
94
|
+
help="Enable OS detection")
|
|
95
|
+
service_group.add_argument("-b", "--banner", dest="banner_grabbing", action="store_true",
|
|
96
|
+
help="Perform banner grabbing on open ports")
|
|
97
|
+
service_group.add_argument("-A", "--aggressive", dest="aggressive", action="store_true",
|
|
98
|
+
help="Aggressive scan: enable -sV, -O and -b")
|
|
99
|
+
|
|
100
|
+
# Timing and performance
|
|
101
|
+
timing_group = parser.add_argument_group("Timing and Performance")
|
|
102
|
+
timing_group.add_argument("-T", dest="timing", type=int, choices=range(6),
|
|
103
|
+
help="Timing template: -T0 (paranoid) .. -T5 (insane)")
|
|
104
|
+
timing_group.add_argument("--min-rate", dest="min_rate", type=int,
|
|
105
|
+
help="Send packets no slower than <number> per second")
|
|
106
|
+
timing_group.add_argument("--max-rate", dest="max_rate", type=int,
|
|
107
|
+
help="Send packets no faster than <number> per second")
|
|
108
|
+
timing_group.add_argument("--max-retries", dest="max_retries", type=int, default=1,
|
|
109
|
+
help="Maximum number of retries")
|
|
110
|
+
timing_group.add_argument("--timeout", dest="timeout", type=float, default=None,
|
|
111
|
+
help="Set timeout in seconds")
|
|
112
|
+
timing_group.add_argument("--threads", dest="threads", type=int, default=None,
|
|
113
|
+
help="Number of concurrent threads")
|
|
114
|
+
|
|
115
|
+
# Evasion techniques
|
|
116
|
+
evasion_group = parser.add_argument_group("Evasion and Spoofing")
|
|
117
|
+
evasion_group.add_argument("--spoof-mac", dest="spoof_mac",
|
|
118
|
+
help="Spoof MAC address")
|
|
119
|
+
evasion_group.add_argument("--spoof-ip", dest="spoof_ip",
|
|
120
|
+
help="Spoof source IP address")
|
|
121
|
+
evasion_group.add_argument("--source-port", dest="source_port", type=int,
|
|
122
|
+
help="Use specified source port")
|
|
123
|
+
evasion_group.add_argument("--data-length", dest="data_length", type=int,
|
|
124
|
+
help="Append random data to sent packets")
|
|
125
|
+
evasion_group.add_argument("--ttl", dest="ttl", type=int,
|
|
126
|
+
help="Set IP time-to-live field")
|
|
127
|
+
evasion_group.add_argument("--randomize", dest="randomize", action="store_true",
|
|
128
|
+
help="Randomize target and port order")
|
|
129
|
+
evasion_group.add_argument("--fragmentation", dest="fragmentation", action="store_true",
|
|
130
|
+
help="Fragment IP packets")
|
|
131
|
+
evasion_group.add_argument("--scan-delay", dest="scan_delay", type=float, default=0,
|
|
132
|
+
help="Add delay between packets")
|
|
133
|
+
|
|
134
|
+
# Output options
|
|
135
|
+
output_group = parser.add_argument_group("Output")
|
|
136
|
+
output_group.add_argument("-v", "--verbose", dest="verbose", action="store_true",
|
|
137
|
+
help="Increase verbosity level")
|
|
138
|
+
output_group.add_argument("-q", "--quiet", dest="quiet", action="store_true",
|
|
139
|
+
help="Quiet mode")
|
|
140
|
+
output_group.add_argument("-oN", "--output-normal", dest="output_normal",
|
|
141
|
+
help="Output in normal format to the given filename")
|
|
142
|
+
output_group.add_argument("-oX", "--output-xml", dest="output_xml",
|
|
143
|
+
help="Output in XML format to the given filename")
|
|
144
|
+
output_group.add_argument("-oJ", "--output-json", dest="output_json",
|
|
145
|
+
help="Output in JSON format to the given filename")
|
|
146
|
+
output_group.add_argument("-oA", "--output-all", dest="output_all",
|
|
147
|
+
help="Write all three formats to BASENAME.txt/.json/.xml")
|
|
148
|
+
output_group.add_argument("--no-color", dest="no_color", action="store_true",
|
|
149
|
+
help="Disable colored output")
|
|
150
|
+
|
|
151
|
+
# Misc options
|
|
152
|
+
misc_group = parser.add_argument_group("Misc")
|
|
153
|
+
misc_group.add_argument("--interface", dest="interface",
|
|
154
|
+
help="Use specified network interface")
|
|
155
|
+
misc_group.add_argument("-V", "--version", action="version", version=f"GhostNet {VERSION}")
|
|
156
|
+
misc_group.add_argument("--vuln-scan", dest="vuln_scan", action="store_true",
|
|
157
|
+
help="Perform basic vulnerability checks")
|
|
158
|
+
misc_group.add_argument("--traceroute", "--map", dest="network_map", action="store_true",
|
|
159
|
+
help="Map network topology via traceroute (requires scapy/root)")
|
|
160
|
+
misc_group.add_argument("--update-db", dest="update_db", action="store_true",
|
|
161
|
+
help="Refresh the local NVD vulnerability database and exit "
|
|
162
|
+
"(needs an NVD API key; see --help)")
|
|
163
|
+
misc_group.add_argument("-n", "--no-dns", dest="no_dns", action="store_true",
|
|
164
|
+
help="Disable DNS resolution")
|
|
165
|
+
|
|
166
|
+
return parser.parse_args()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def configure_from_args(args):
|
|
170
|
+
"""Configure the scanner from command line arguments"""
|
|
171
|
+
config = Config()
|
|
172
|
+
|
|
173
|
+
# Parse targets
|
|
174
|
+
if args.targets:
|
|
175
|
+
for target_spec in args.targets:
|
|
176
|
+
config.targets.extend(parse_target_spec(target_spec))
|
|
177
|
+
|
|
178
|
+
if args.input_file:
|
|
179
|
+
try:
|
|
180
|
+
with open(args.input_file, 'r') as f:
|
|
181
|
+
for line in f:
|
|
182
|
+
line = line.strip()
|
|
183
|
+
if line and not line.startswith('#'):
|
|
184
|
+
config.targets.extend(parse_target_spec(line))
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(f"Error reading target file: {e}")
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
|
|
189
|
+
# Parse ports
|
|
190
|
+
if args.ports:
|
|
191
|
+
config.ports = parse_port_spec(args.ports)
|
|
192
|
+
elif args.top_ports:
|
|
193
|
+
# Take the top N most common ports
|
|
194
|
+
top_n = min(args.top_ports, len(COMMON_PORTS))
|
|
195
|
+
config.ports = list(COMMON_PORTS.keys())[:top_n]
|
|
196
|
+
|
|
197
|
+
# Scan type
|
|
198
|
+
if args.syn_scan:
|
|
199
|
+
config.scan_type = "tcp_syn"
|
|
200
|
+
elif args.udp_scan:
|
|
201
|
+
config.scan_type = "udp"
|
|
202
|
+
else:
|
|
203
|
+
config.scan_type = "tcp_connect" # Default
|
|
204
|
+
|
|
205
|
+
# Timing template (-T0..-T5): a baseline for timeout/threads/scan-delay
|
|
206
|
+
# that explicit flags below still override.
|
|
207
|
+
if args.timing is not None:
|
|
208
|
+
timeout, threads, delay = TIMING_TEMPLATES[args.timing]
|
|
209
|
+
config.timeout = timeout
|
|
210
|
+
config.threads = threads
|
|
211
|
+
config.scan_delay = delay
|
|
212
|
+
|
|
213
|
+
# Performance settings (explicit flags win over the timing template).
|
|
214
|
+
if args.timeout is not None:
|
|
215
|
+
config.timeout = args.timeout
|
|
216
|
+
if args.threads is not None:
|
|
217
|
+
config.threads = args.threads
|
|
218
|
+
if args.scan_delay:
|
|
219
|
+
config.scan_delay = args.scan_delay
|
|
220
|
+
config.min_rate = args.min_rate
|
|
221
|
+
config.max_rate = args.max_rate
|
|
222
|
+
if args.max_retries is not None:
|
|
223
|
+
config.max_retries = args.max_retries
|
|
224
|
+
|
|
225
|
+
# Detection features. -A (aggressive) implies -sV, -O and -b.
|
|
226
|
+
config.service_detection = args.service_detection or args.aggressive
|
|
227
|
+
config.os_detection = args.os_detection or args.aggressive
|
|
228
|
+
config.banner_grabbing = args.banner_grabbing or args.aggressive
|
|
229
|
+
config.vuln_check = args.vuln_scan
|
|
230
|
+
config.network_map = args.network_map
|
|
231
|
+
|
|
232
|
+
# Host discovery.
|
|
233
|
+
config.ping_scan = args.ping_scan # -sn: discovery only
|
|
234
|
+
config.no_ping = args.no_ping # -Pn: skip discovery
|
|
235
|
+
|
|
236
|
+
# Fast mode
|
|
237
|
+
if args.fast_mode:
|
|
238
|
+
config.fast_mode = True
|
|
239
|
+
config.timeout = 0.5
|
|
240
|
+
config.threads = 100
|
|
241
|
+
|
|
242
|
+
# Evasion techniques
|
|
243
|
+
if args.spoof_ip:
|
|
244
|
+
config.source_ip = args.spoof_ip
|
|
245
|
+
if args.spoof_mac:
|
|
246
|
+
config.source_mac = args.spoof_mac
|
|
247
|
+
if args.source_port:
|
|
248
|
+
config.source_port = args.source_port
|
|
249
|
+
if args.ttl:
|
|
250
|
+
config.ttl = args.ttl
|
|
251
|
+
config.randomize = args.randomize
|
|
252
|
+
config.fragmentation = args.fragmentation
|
|
253
|
+
|
|
254
|
+
# DNS resolution
|
|
255
|
+
config.dns_resolution = not args.no_dns
|
|
256
|
+
|
|
257
|
+
# Interface
|
|
258
|
+
if args.interface:
|
|
259
|
+
config.interface = args.interface
|
|
260
|
+
|
|
261
|
+
# Output settings
|
|
262
|
+
config.verbose = args.verbose
|
|
263
|
+
if args.quiet:
|
|
264
|
+
config.log_level = logging.ERROR
|
|
265
|
+
elif args.verbose:
|
|
266
|
+
config.log_level = logging.DEBUG
|
|
267
|
+
|
|
268
|
+
# Output format and file
|
|
269
|
+
if args.output_xml:
|
|
270
|
+
config.output_file = args.output_xml
|
|
271
|
+
config.output_format = "xml"
|
|
272
|
+
elif args.output_json:
|
|
273
|
+
config.output_file = args.output_json
|
|
274
|
+
config.output_format = "json"
|
|
275
|
+
elif args.output_normal:
|
|
276
|
+
config.output_file = args.output_normal
|
|
277
|
+
config.output_format = "text"
|
|
278
|
+
|
|
279
|
+
return config
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def update_vuln_db():
|
|
283
|
+
"""Refresh the local NVD vulnerability database (wraps vuln.py).
|
|
284
|
+
|
|
285
|
+
Returns an exit code. Requires an NVD API key (NVD_API_KEY env var or a
|
|
286
|
+
.env file); see https://nvd.nist.gov/developers/request-an-api-key.
|
|
287
|
+
"""
|
|
288
|
+
try:
|
|
289
|
+
import vuln
|
|
290
|
+
except Exception as e:
|
|
291
|
+
print(f"{Colors.BRIGHT_RED}Could not load the NVD updater: {e}{Colors.RESET}",
|
|
292
|
+
file=sys.stderr)
|
|
293
|
+
return 1
|
|
294
|
+
|
|
295
|
+
print(f"{Colors.BRIGHT_CYAN}Refreshing the NVD vulnerability database...{Colors.RESET}")
|
|
296
|
+
try:
|
|
297
|
+
vuln.main()
|
|
298
|
+
except Exception as e:
|
|
299
|
+
# NVDVulnerabilityFetcher raises when no API key is configured.
|
|
300
|
+
print(f"{Colors.BRIGHT_RED}Database update failed: {e}{Colors.RESET}",
|
|
301
|
+
file=sys.stderr)
|
|
302
|
+
return 1
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def host_is_up(target, timeout=1.0):
|
|
308
|
+
"""Lightweight TCP host discovery: an open port OR a refused connection
|
|
309
|
+
means the host is up; only a timeout/unreachable counts as down. Uses
|
|
310
|
+
connect_ex (which returns an error code rather than raising). No root
|
|
311
|
+
required."""
|
|
312
|
+
reachable = {0, errno.ECONNREFUSED, errno.ECONNRESET}
|
|
313
|
+
for port in (80, 443, 22, 445, 3389):
|
|
314
|
+
try:
|
|
315
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
316
|
+
sock.settimeout(timeout)
|
|
317
|
+
if sock.connect_ex((target, port)) in reachable:
|
|
318
|
+
return True
|
|
319
|
+
except OSError:
|
|
320
|
+
continue
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def discover_hosts(targets, timeout=1.0):
|
|
326
|
+
"""Return the subset of targets that appear to be online."""
|
|
327
|
+
up = []
|
|
328
|
+
for target in targets:
|
|
329
|
+
if host_is_up(target, timeout):
|
|
330
|
+
up.append(target)
|
|
331
|
+
return up
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def scan_summary(results, elapsed):
|
|
336
|
+
"""One-line nmap-style summary of a results dict."""
|
|
337
|
+
hosts = len(results)
|
|
338
|
+
open_ports = sum(len(d.get("open_ports", {})) for d in results.values())
|
|
339
|
+
return (f"GhostNet done: {hosts} host(s) scanned, {open_ports} open port(s) "
|
|
340
|
+
f"in {elapsed:.2f}s")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def run_cli(args):
|
|
345
|
+
"""Run a scan from parsed command-line arguments. Returns an exit code."""
|
|
346
|
+
if args.no_color:
|
|
347
|
+
Colors.disable()
|
|
348
|
+
|
|
349
|
+
# Database refresh is a standalone action (no targets needed).
|
|
350
|
+
if getattr(args, "update_db", False):
|
|
351
|
+
return update_vuln_db()
|
|
352
|
+
|
|
353
|
+
config = configure_from_args(args)
|
|
354
|
+
|
|
355
|
+
if not config.targets:
|
|
356
|
+
print(f"{Colors.BRIGHT_RED}Error: no targets specified. "
|
|
357
|
+
f"Use -t <target> or -iL <file>.{Colors.RESET}", file=sys.stderr)
|
|
358
|
+
return 2
|
|
359
|
+
|
|
360
|
+
# -sn: host discovery only, then exit.
|
|
361
|
+
if config.ping_scan:
|
|
362
|
+
up = discover_hosts(config.targets, config.timeout)
|
|
363
|
+
print(f"{Colors.BRIGHT_CYAN}Host discovery:{Colors.RESET}")
|
|
364
|
+
for target in config.targets:
|
|
365
|
+
status = (f"{Colors.BRIGHT_GREEN}up{Colors.RESET}" if target in up
|
|
366
|
+
else f"{Colors.BRIGHT_BLACK}down{Colors.RESET}")
|
|
367
|
+
print(f" {target:<18} {status}")
|
|
368
|
+
print(f"\n{len(up)}/{len(config.targets)} host(s) up")
|
|
369
|
+
return 0
|
|
370
|
+
|
|
371
|
+
# For multi-host scans, skip hosts that are down unless -Pn was given.
|
|
372
|
+
if not config.no_ping and len(config.targets) > 1:
|
|
373
|
+
up = discover_hosts(config.targets, config.timeout)
|
|
374
|
+
if up:
|
|
375
|
+
config.targets = up
|
|
376
|
+
|
|
377
|
+
start = time.time()
|
|
378
|
+
scanner = Scanner(config)
|
|
379
|
+
scanner.initialize()
|
|
380
|
+
results = scanner.run()
|
|
381
|
+
elapsed = time.time() - start
|
|
382
|
+
|
|
383
|
+
reporter = Reporter(config, results)
|
|
384
|
+
print(reporter.generate_report())
|
|
385
|
+
|
|
386
|
+
# Output files: -oA writes all three formats; otherwise the single
|
|
387
|
+
# configured format.
|
|
388
|
+
if args.output_all:
|
|
389
|
+
for fmt, ext in (("text", "txt"), ("json", "json"), ("xml", "xml")):
|
|
390
|
+
config.output_format = fmt
|
|
391
|
+
config.output_file = f"{args.output_all}.{ext}"
|
|
392
|
+
Reporter(config, results).save_report()
|
|
393
|
+
elif config.output_file:
|
|
394
|
+
reporter.save_report()
|
|
395
|
+
|
|
396
|
+
# Optional network topology mapping (traceroute).
|
|
397
|
+
if config.network_map:
|
|
398
|
+
mapper = NetworkMapper(config)
|
|
399
|
+
topology = mapper.map_network(config.targets)
|
|
400
|
+
print("\n" + format_topology(topology))
|
|
401
|
+
|
|
402
|
+
print(f"\n{Colors.BRIGHT_WHITE}{scan_summary(results, elapsed)}{Colors.RESET}")
|
|
403
|
+
return 0
|
|
404
|
+
|
|
405
|
+
# Network Mapper class for network topology mapping
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def main():
|
|
409
|
+
"""Entry point: dispatch to the CLI when arguments are given, otherwise
|
|
410
|
+
launch the interactive menu."""
|
|
411
|
+
if len(sys.argv) > 1:
|
|
412
|
+
args = parse_args()
|
|
413
|
+
return run_cli(args)
|
|
414
|
+
interactive_main()
|
|
415
|
+
return 0
|
ghostnet/config.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import socket
|
|
5
|
+
import ssl
|
|
6
|
+
import re
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import random
|
|
10
|
+
import struct
|
|
11
|
+
import uuid
|
|
12
|
+
import copy
|
|
13
|
+
import errno
|
|
14
|
+
import logging
|
|
15
|
+
import threading
|
|
16
|
+
import getpass
|
|
17
|
+
import platform
|
|
18
|
+
import ipaddress
|
|
19
|
+
import asyncio
|
|
20
|
+
import xml.etree.ElementTree as ET
|
|
21
|
+
from datetime import datetime, timedelta
|
|
22
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
23
|
+
from urllib.parse import urljoin, urlparse
|
|
24
|
+
from typing import List, Dict, Any, Tuple, Optional, Union
|
|
25
|
+
import pytz
|
|
26
|
+
import requests
|
|
27
|
+
import urllib3
|
|
28
|
+
from bs4 import BeautifulSoup
|
|
29
|
+
|
|
30
|
+
from .constants import *
|
|
31
|
+
|
|
32
|
+
class Config:
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self.scan_type = "tcp_syn" # Default scan type
|
|
35
|
+
self.ports = []
|
|
36
|
+
self.targets = []
|
|
37
|
+
self.timeout = 1.0
|
|
38
|
+
self.threads = 50
|
|
39
|
+
self.verbose = False
|
|
40
|
+
self.output_file = None
|
|
41
|
+
self.output_format = "text"
|
|
42
|
+
self.randomize = False
|
|
43
|
+
self.scan_delay = 0
|
|
44
|
+
self.service_detection = False
|
|
45
|
+
self.os_detection = False
|
|
46
|
+
self.banner_grabbing = False
|
|
47
|
+
self.evasion_level = 0
|
|
48
|
+
self.interface = None
|
|
49
|
+
self.source_port = None
|
|
50
|
+
self.ttl = None
|
|
51
|
+
self.source_ip = None
|
|
52
|
+
self.source_mac = None
|
|
53
|
+
self.fragmentation = False
|
|
54
|
+
self.log_level = logging.INFO
|
|
55
|
+
self.vuln_check = False
|
|
56
|
+
self.dns_resolution = True
|
|
57
|
+
self.scan_id = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
58
|
+
self.fast_mode = False
|
|
59
|
+
self.network_map = False
|
|
60
|
+
self.min_rate = None # packets/sec floor (raises the max cap)
|
|
61
|
+
self.max_rate = None # packets/sec cap (None = unlimited)
|
|
62
|
+
self.max_retries = 1 # retries for lossy scans (UDP / SYN)
|
|
63
|
+
self.ping_scan = False # -sn: host discovery only
|
|
64
|
+
self.no_ping = False # -Pn: skip host discovery
|
|
65
|
+
|
|
66
|
+
# CVE intergration using NVD API
|
ghostnet/constants.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Shared constants, feature flags, and optional-dependency symbols.
|
|
3
|
+
|
|
4
|
+
scapy/impacket symbols are defined as None when the libraries are absent so
|
|
5
|
+
that `from .constants import *` always resolves in every submodule; the
|
|
6
|
+
HAS_SCAPY / HAS_IMPACKET guards ensure the None values are never called.
|
|
7
|
+
"""
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
# UTF-8 on the Windows console.
|
|
12
|
+
if os.name == 'nt':
|
|
13
|
+
import codecs
|
|
14
|
+
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach())
|
|
15
|
+
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach())
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from impacket import smb, smb3, nmb, nt_errors
|
|
19
|
+
from impacket.smbconnection import SMBConnection
|
|
20
|
+
HAS_IMPACKET = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
HAS_IMPACKET = False
|
|
23
|
+
smb = smb3 = nmb = nt_errors = None
|
|
24
|
+
SMBConnection = None
|
|
25
|
+
print("[!] impacket not available - SMB features disabled "
|
|
26
|
+
"(pipx inject ghostnet impacket, or pip install impacket)",
|
|
27
|
+
file=sys.stderr)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from scapy.all import (
|
|
31
|
+
IP, TCP, UDP, ICMP, Ether, sr1, srp1, send, sendp, conf,
|
|
32
|
+
sniff, rdpcap, wrpcap, RandShort,
|
|
33
|
+
)
|
|
34
|
+
HAS_SCAPY = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
HAS_SCAPY = False
|
|
37
|
+
IP = TCP = UDP = ICMP = Ether = sr1 = srp1 = send = sendp = conf = None
|
|
38
|
+
sniff = rdpcap = wrpcap = RandShort = None
|
|
39
|
+
print("[!] scapy not available - SYN/OS/traceroute disabled "
|
|
40
|
+
"(pipx inject ghostnet scapy, or pip install scapy)",
|
|
41
|
+
file=sys.stderr)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Colors:
|
|
45
|
+
RESET = "\033[0m"
|
|
46
|
+
BLACK = "\033[30m"
|
|
47
|
+
RED = "\033[31m"
|
|
48
|
+
GREEN = "\033[32m"
|
|
49
|
+
YELLOW = "\033[33m"
|
|
50
|
+
BLUE = "\033[34m"
|
|
51
|
+
MAGENTA = "\033[35m"
|
|
52
|
+
CYAN = "\033[36m"
|
|
53
|
+
WHITE = "\033[37m"
|
|
54
|
+
BRIGHT_BLACK = "\033[90m"
|
|
55
|
+
BRIGHT_RED = "\033[91m"
|
|
56
|
+
BRIGHT_GREEN = "\033[92m"
|
|
57
|
+
BRIGHT_YELLOW = "\033[93m"
|
|
58
|
+
BRIGHT_BLUE = "\033[94m"
|
|
59
|
+
BRIGHT_MAGENTA = "\033[95m"
|
|
60
|
+
BRIGHT_CYAN = "\033[96m"
|
|
61
|
+
BRIGHT_WHITE = "\033[97m"
|
|
62
|
+
RESET = "\033[0m"
|
|
63
|
+
BLACK = "\033[30m"
|
|
64
|
+
RED = "\033[31m"
|
|
65
|
+
GREEN = "\033[32m"
|
|
66
|
+
YELLOW = "\033[33m"
|
|
67
|
+
BLUE = "\033[34m"
|
|
68
|
+
MAGENTA = "\033[35m"
|
|
69
|
+
CYAN = "\033[36m"
|
|
70
|
+
WHITE = "\033[37m"
|
|
71
|
+
BOLD = "\033[1m"
|
|
72
|
+
UNDERLINE = "\033[4m"
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def disable(cls):
|
|
76
|
+
"""Blank out every ANSI code (used by --no-color)."""
|
|
77
|
+
for name in list(vars(cls)):
|
|
78
|
+
value = getattr(cls, name)
|
|
79
|
+
if isinstance(value, str) and value.startswith("\033"):
|
|
80
|
+
setattr(cls, name, "")
|
|
81
|
+
|
|
82
|
+
# Constants
|
|
83
|
+
|
|
84
|
+
VERSION = "1.2.1"
|
|
85
|
+
BANNER = f"""{Colors.BRIGHT_CYAN}
|
|
86
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
87
|
+
║ ║
|
|
88
|
+
║ ░██████╗░██╗░░██╗░█████╗░░██████╗████████╗ ║
|
|
89
|
+
║ ██╔════╝░██║░░██║██╔══██╗██╔════╝╚══██╔══╝ ║
|
|
90
|
+
║ ██║░░██╗░███████║██║░░██║╚█████╗░░░░██║░░░ ║
|
|
91
|
+
║ ██║░░╚██╗██╔══██║██║░░██║░╚═══██╗░░░██║░░░ ║
|
|
92
|
+
║ ╚██████╔╝██║░░██║╚█████╔╝██████╔╝░░░██║░░░ ║
|
|
93
|
+
║ ░╚═════╝░╚═╝░░╚═╝░╚════╝░╚═════╝░░░░╚═╝░░░ ║
|
|
94
|
+
║ ║
|
|
95
|
+
║ ███╗░░██╗███████╗████████╗ ║
|
|
96
|
+
║ ████╗░██║██╔════╝╚══██╔══╝ ║
|
|
97
|
+
║ ██╔██╗██║█████╗░░░░░██║░░░ ║
|
|
98
|
+
║ ██║╚████║██╔══╝░░░░░██║░░░ ║
|
|
99
|
+
║ ██║░╚███║███████╗░░░██║░░░ ║
|
|
100
|
+
║ ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░ ║
|
|
101
|
+
║ ║
|
|
102
|
+
╚═══════════════════════════════════════════════════════════╝{Colors.RESET}
|
|
103
|
+
|
|
104
|
+
{Colors.BRIGHT_WHITE}Advanced Network Scanner v{VERSION}
|
|
105
|
+
{Colors.BRIGHT_GREEN}» Welcome to Ghost Net >==< «{Colors.RESET}"""
|
|
106
|
+
|
|
107
|
+
# Common ports to scan by default
|
|
108
|
+
COMMON_PORTS = {
|
|
109
|
+
21: "FTP",
|
|
110
|
+
22: "SSH",
|
|
111
|
+
23: "Telnet",
|
|
112
|
+
25: "SMTP",
|
|
113
|
+
53: "DNS",
|
|
114
|
+
80: "HTTP",
|
|
115
|
+
110: "POP3",
|
|
116
|
+
111: "RPC",
|
|
117
|
+
135: "MSRPC",
|
|
118
|
+
139: "NetBIOS",
|
|
119
|
+
143: "IMAP",
|
|
120
|
+
443: "HTTPS",
|
|
121
|
+
445: "SMB",
|
|
122
|
+
993: "IMAPS",
|
|
123
|
+
995: "POP3S",
|
|
124
|
+
1433: "MSSQL",
|
|
125
|
+
1521: "Oracle",
|
|
126
|
+
3306: "MySQL",
|
|
127
|
+
3389: "RDP",
|
|
128
|
+
5432: "PostgreSQL",
|
|
129
|
+
5900: "VNC",
|
|
130
|
+
8080: "HTTP-Proxy",
|
|
131
|
+
8443: "HTTPS-Alt"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Common service fingerprints for basic service detection
|
|
135
|
+
SERVICE_FINGERPRINTS = {
|
|
136
|
+
b"SSH-": "SSH",
|
|
137
|
+
b"HTTP/": "HTTP",
|
|
138
|
+
b"220 ": "FTP",
|
|
139
|
+
b"SMTP": "SMTP",
|
|
140
|
+
b"POP3": "POP3",
|
|
141
|
+
b"IMAP": "IMAP",
|
|
142
|
+
b"RFB ": "VNC",
|
|
143
|
+
b"Microsoft SQL Server": "MSSQL",
|
|
144
|
+
b"MySQL": "MySQL",
|
|
145
|
+
b"5432": "PostgreSQL",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# OS fingerprinting signatures based on TTL values (simplified)
|
|
149
|
+
OS_FINGERPRINTS = {
|
|
150
|
+
64: "Linux/Unix",
|
|
151
|
+
128: "Windows",
|
|
152
|
+
255: "Cisco/Network Device",
|
|
153
|
+
254: "Solaris/AIX"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Configuration
|
|
157
|
+
|
|
158
|
+
TIMING_TEMPLATES = {
|
|
159
|
+
0: (5.0, 1, 5.0), # paranoid
|
|
160
|
+
1: (5.0, 1, 1.0), # sneaky
|
|
161
|
+
2: (3.0, 10, 0.4), # polite
|
|
162
|
+
3: (1.0, 50, 0.0), # normal (defaults)
|
|
163
|
+
4: (0.5, 100, 0.0), # aggressive
|
|
164
|
+
5: (0.3, 200, 0.0), # insane
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|