pyprobe-scan 0.1.0__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.
- pyprobe/__init.py +0 -0
- pyprobe/__main__.py +4 -0
- pyprobe/cli.py +31 -0
- pyprobe/cli_args.py +20 -0
- pyprobe/output.py +24 -0
- pyprobe/scanner.py +41 -0
- pyprobe/services/__init__.py +0 -0
- pyprobe/services/common_service_signatures.py +7 -0
- pyprobe/services/commonly_used_ports.py +26 -0
- pyprobe/services/find_service.py +64 -0
- pyprobe/services/scan_port.py +27 -0
- pyprobe_scan-0.1.0.dist-info/METADATA +179 -0
- pyprobe_scan-0.1.0.dist-info/RECORD +16 -0
- pyprobe_scan-0.1.0.dist-info/WHEEL +5 -0
- pyprobe_scan-0.1.0.dist-info/entry_points.txt +2 -0
- pyprobe_scan-0.1.0.dist-info/top_level.txt +1 -0
pyprobe/__init.py
ADDED
|
File without changes
|
pyprobe/__main__.py
ADDED
pyprobe/cli.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from rich import print as rich_print
|
|
5
|
+
|
|
6
|
+
from .scanner import scan
|
|
7
|
+
from .cli_args import get_args
|
|
8
|
+
from .output import print_results, save_json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
start_time = time.perf_counter()
|
|
13
|
+
|
|
14
|
+
target, json_export, timeout, verbose_status = get_args()
|
|
15
|
+
|
|
16
|
+
df = asyncio.run(scan(target,timeout,verbose_status))
|
|
17
|
+
|
|
18
|
+
if type(df) == str:
|
|
19
|
+
rich_print(f"[red]{df}[/red]")
|
|
20
|
+
else:
|
|
21
|
+
print_results(df)
|
|
22
|
+
|
|
23
|
+
elapsed_time = time.perf_counter() - start_time
|
|
24
|
+
rich_print(f"Completed in {elapsed_time:.3f}s\n")
|
|
25
|
+
|
|
26
|
+
if json_export:
|
|
27
|
+
save_json(df)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|
pyprobe/cli_args.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
def get_args():
|
|
4
|
+
|
|
5
|
+
parser = argparse.ArgumentParser(
|
|
6
|
+
prog = "pyprobe",
|
|
7
|
+
description="Pyprobe is a command-line tool and Python package for scanning TCP ports, identifying potentially running services, and exporting scan results to JSON. Built from scratch using Python's socket and concurrency capabilities, it aims to make network reconnaissance accessible through both a CLI and a reusable Python API.",
|
|
8
|
+
epilog="Placeholder"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
parser.add_argument("target")
|
|
12
|
+
|
|
13
|
+
parser.add_argument("-j", "--json", action="store_true",help="Export discovered ports to a JSON file in the same directory")
|
|
14
|
+
|
|
15
|
+
parser.add_argument("-t", "--timeout", type=int, help="Set time duration before connection timeouts, default is 3s.")
|
|
16
|
+
|
|
17
|
+
parser.add_argument("-v", "--verbose", action="store_true", help='Enable verbose logging.')
|
|
18
|
+
args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
return args.target, args.json, args.timeout, args.verbose
|
pyprobe/output.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from rich import print as rich_print
|
|
7
|
+
from rich_tools import df_to_table
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def print_results(df):
|
|
11
|
+
"""Display scan results in a formatted table."""
|
|
12
|
+
rich_print(df_to_table(df))
|
|
13
|
+
rich_print(f"\nNumber of ports open: {df.shape[0]}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def save_json(df):
|
|
17
|
+
"""Export scan results to a JSON file."""
|
|
18
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
19
|
+
file_name = f"PYPROBE-{timestamp}.json"
|
|
20
|
+
|
|
21
|
+
df.to_json(file_name, orient="records", indent=4)
|
|
22
|
+
|
|
23
|
+
rich_print(f"Saved to [green]{file_name}[/green]")
|
|
24
|
+
return Path(file_name)
|
pyprobe/scanner.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from rich import print as rich_print
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import asyncio
|
|
5
|
+
from .services.find_service import find_service_from_port
|
|
6
|
+
from .services.scan_port import scan_port
|
|
7
|
+
from .services.commonly_used_ports import ports
|
|
8
|
+
|
|
9
|
+
ascii_art = r"""
|
|
10
|
+
________ ___ ___ ________ ________ ________ ________ _______
|
|
11
|
+
|\ __ \|\ \ / /|\ __ \|\ __ \|\ __ \|\ __ \|\ ___ \
|
|
12
|
+
\ \ \|\ \ \ \/ / | \ \|\ \ \ \|\ \ \ \|\ \ \ \|\ /\ \ __/|
|
|
13
|
+
\ \ ____\ \ / / \ \ ____\ \ _ _\ \ \\\ \ \ __ \ \ \_|/__
|
|
14
|
+
\ \ \___|\/ / / \ \ \___|\ \ \\ \\ \ \\\ \ \ \|\ \ \ \_|\ \
|
|
15
|
+
\ \__\ __/ / / \ \__\ \ \__\\ _\\ \_______\ \_______\ \_______\
|
|
16
|
+
\|__||\____/ / \|__| \|__|\|__|\|_______|\|_______|\|_______|
|
|
17
|
+
\|____|/
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
async def scan(IP,timeout,verbose_status):
|
|
21
|
+
rich_print(ascii_art)
|
|
22
|
+
current_clock_time = datetime.now().strftime("%H:%M:%S")
|
|
23
|
+
|
|
24
|
+
if verbose_status:
|
|
25
|
+
rich_print(f"Starting async port scan against [yellow]{IP}[/yellow] at {current_clock_time} with verbose logging. \n")
|
|
26
|
+
else:
|
|
27
|
+
rich_print(f"Starting async port scan against [yellow]{IP}[/yellow] at {current_clock_time}\n")
|
|
28
|
+
|
|
29
|
+
tasks = [asyncio.to_thread(scan_port,IP,port,timeout,verbose_status) for port in ports] # list of tasks to be run asynchronously
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
result = await asyncio.gather(*tasks) # fire all tasks
|
|
33
|
+
except Exception as e:
|
|
34
|
+
return "An error occured. Provide a valid IPv4 target address."
|
|
35
|
+
|
|
36
|
+
open_ports = [port for port in result if port is not None]
|
|
37
|
+
|
|
38
|
+
service_tasks = [asyncio.to_thread(find_service_from_port, IP, port) for port in open_ports]
|
|
39
|
+
services = await asyncio.gather(*service_tasks)
|
|
40
|
+
|
|
41
|
+
return pd.DataFrame({"Port":open_ports, "Service":services})
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
ports = [1,3,4,6,7,9,13,17,19,20,21,22,23,24,25,26,30,32,33,37,42,43,49,50,53,70,79,80,81,82,83,84,85,88,89,90,99,100,106,109,110,111,113,119,125,135,139,143,144,146,161,163,179,199,211,212,222,254,255,256,259,264,280,301,306,311,340,366,389,406,407,416,417,425,427,443,444,445,458,464,465,481,497,500,512,513,514,515,524,541,543,544,545,548,554,555,563,587,593,616,617,625,631,636,646,648,666,667,668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800,801,808,843,873,880,888,898,900,901,902,903,911,912,981,987,990,992,993,995,999,1000,1001,1002,1007,1009,1010,1011,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1102,1104,1105,1106,1107,1108,1110,1111,1112,1113,1114,1117,1119,1121,1122,1123,1124,1126,1130,1131,1132,1137,1138,1141,1145,1147,1148,1149,1151,1152,1154,1163,1164,1165,1166,1169,1174,1175,1183,1185,1186,1187,1192,1198,1199,1201,1213,1216,1217,1218,1233,1234,1236,1244,1247,1248,1259,1271,1272,1277,1287,1296,1300,1301,1309,1310,1311,1322,1328,1334,1352,1417,1433,1434,1443,1455,1461,1494,1500,1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687,1688,1700,1717,1718,1719,1720,1721,1723,1755,1761,1782,1783,1801,1805,1812,1839,1840,1862,1863,1864,1875,1900,1914,1935,1947,1971,1972,1974,1984,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2013,2020,2021,2022,2030,2033,2034,2035,2038,2040,2041,2042,2043,2045,2046,2047,2048,2049,2065,2068,2099,2100,2103,2105,2106,2107,2111,2119,2121,2126,2135,2144,2160,2161,2170,2179,2190,2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381,2382,2383,2393,2394,2399,2401,2492,2500,2522,2525,2557,2601,2602,2604,2605,2607,2608,2638,2701,2702,2710,2717,2718,2725,2800,2809,2811,2869,2875,2909,2910,2920,2967,2968,2998,3000,3001,3003,3005,3006,3007,3011,3013,3017,3030,3031,3052,3071,3077,3128,3168,3211,3221,3260,3261,3268,3269,3283,3300,3301,3306,3322,3323,3324,3325,3333,3351,3367,3369,3370,3371,3372,3389,3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689,3690,3703,3737,3766,3784,3800,3801,3809,3814,3826,3827,3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000,4001,4002,4003,4004,4005,4006,4045,4111,4125,4126,4129,4224,4242,4279,4321,4343,4443,4444,4445,4446,4449,4550,4567,4662,4848,4899,4900,4998,5000,5001,5002,5003,5004,5009,5030,5033,5050,5051,5054,5060,5061,5080,5087,5100,5101,5102,5120,5190,5200,5214,5221,5222,5225,5226,5269,5280,5298,5357,5405,5414,5431,5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678,5679,5718,5730,5800,5801,5802,5810,5811,5815,5822,5825,5850,5859,5862,5877,5900,5901,5902,5903,5904,5906,5907,5910,5911,5915,5922,5925,5950,5952,5959,5960,5961,5962,5963,5987,5988,5989,5998,5999,6000,6001,6002,6003,6004,6005,6006,6007,6009,6025,6059,6100,6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565,6566,6567,6580,6646,6666,6667,6668,6669,6689,6692,6699,6779,6788,6789,6792,6839,6881,6901,6969,7000,7001,7002,7004,7007,7019,7025,7070,7100,7103,7106,7200,7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777,7778,7800,7911,7920,7921,7937,7938,7999,8000,8001,8002,8007,8008,8009,8010,8011,8021,8022,8031,8042,8045,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8093,8099,8100,8180,8181,8192,8193,8194,8200,8222,8254,8290,8291,8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651,8652,8654,8701,8800,8873,8888,8899,8994,9000,9001,9002,9003,9009,9010,9011,9040,9050,9071,9080,9081,9090,9091,9099,9100,9101,9102,9103,9110,9111,9200,9207,9220,9290,9415,9418,9485,9500,9502,9503,9535,9575,9593,9594,9595,9618,9666,9876,9877,9878,9898,9900,9917,9929,9943,9944,9968,9998,9999,10000,10001,10002,10003,10004,10009,10010,10012,10024,10025,10082,10180,10215,10243,10566,10616,10617,10621,10626,10628,10629,10778,11110,11111,11967,12000,12174,12265,12345,13456,13722,13782,13783,14000,14238,14441,14442,15000,15002,15003,15004,15660,15742,16000,16001,16012,16016,16018,16080,16113,16992,16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221,20222,20828,21571,22939,23502,24444,24800,25734,25735,26214,27000,27352,27353,27355,27356,27715,28201,30000,30718,30951,31038,31337,32768,32769,32770,32771,32772,32773,32774,32775,32776,32777,32778,32779,32780,32781,32782,32783,32784,32785,33354,33899,34571,34572,34573,35500,38292,40193,40911,41511,42510,44176,44442,44443,44501,45100,48080,49152,49153,49154,49155,49156,49157,49158,49159,49160,49161,49163,49165,49167,49175,49176,49400,49999,50000,50001,50002,50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055,55056,55555,55600,56737,56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389]
|
|
2
|
+
popular_http_ports = [
|
|
3
|
+
80, # Default HTTP (unencrypted web traffic)
|
|
4
|
+
443, # Default HTTPS (encrypted web traffic over SSL/TLS)
|
|
5
|
+
3000, # Common dev servers (Node.js, Express, React, Next.js)
|
|
6
|
+
5000, # Default Flask & ASP.NET Core port
|
|
7
|
+
8000, # Default Python http.server, Django runserver, FastAPI/Uvicorn
|
|
8
|
+
8080, # Alternative HTTP / Tomcat / Jenkins / Proxies
|
|
9
|
+
8443, # HTTPS alternative / Java admin consoles
|
|
10
|
+
8888, # Jupyter Notebook default
|
|
11
|
+
]
|
|
12
|
+
# Common Port to Service Mapping
|
|
13
|
+
popular_services_on_port = {
|
|
14
|
+
21: "FTP (File Transfer Protocol)",
|
|
15
|
+
22: "SSH (Secure Shell)",
|
|
16
|
+
23: "Telnet",
|
|
17
|
+
25: "SMTP (Simple Mail Transfer Protocol)",
|
|
18
|
+
53: "DNS (Domain Name System)",
|
|
19
|
+
80: "HTTP (Hypertext Transfer Protocol)",
|
|
20
|
+
443: "HTTPS (HTTP Secure / TLS)",
|
|
21
|
+
6379: "Redis In-Memory Database",
|
|
22
|
+
3389: "Remote Desktop Protocol (RDP)",
|
|
23
|
+
5432: "PostgreSQL Database Server",
|
|
24
|
+
9929: "Nping Echo Protocol",
|
|
25
|
+
8021: "FTP Proxy on MacOS"
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import socket
|
|
3
|
+
|
|
4
|
+
from .commonly_used_ports import (popular_http_ports,popular_services_on_port)
|
|
5
|
+
from .common_service_signatures import service_signatures
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def find_service_from_port(IP, PORT):
|
|
9
|
+
try:
|
|
10
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
11
|
+
sock.settimeout(3)
|
|
12
|
+
|
|
13
|
+
result = sock.connect_ex((IP, PORT))
|
|
14
|
+
|
|
15
|
+
if result != 0:
|
|
16
|
+
return "Port closed or connection failed"
|
|
17
|
+
|
|
18
|
+
# Try to read a banner first
|
|
19
|
+
try:
|
|
20
|
+
banner = sock.recv(1024)
|
|
21
|
+
except socket.timeout:
|
|
22
|
+
banner = b""
|
|
23
|
+
|
|
24
|
+
if banner.startswith(b"220") and b"FTP" in banner.upper():
|
|
25
|
+
return "Likely FTP"
|
|
26
|
+
|
|
27
|
+
if banner:
|
|
28
|
+
for signature, service in service_signatures.items():
|
|
29
|
+
if signature in banner:
|
|
30
|
+
return service
|
|
31
|
+
|
|
32
|
+
return banner.decode("utf-8", errors="ignore").strip()
|
|
33
|
+
|
|
34
|
+
# Probe HTTP ports if no banner was received
|
|
35
|
+
try:
|
|
36
|
+
response = requests.get(f"http://{IP}:{PORT}/",timeout=3,)
|
|
37
|
+
|
|
38
|
+
server = response.headers.get("Server", "Unknown HTTP server")
|
|
39
|
+
content_type = response.headers.get(
|
|
40
|
+
"Content-Type", ""
|
|
41
|
+
).split(";")[0]
|
|
42
|
+
|
|
43
|
+
return f"HTTP | {server}" # | Content-Type: {content_type}
|
|
44
|
+
|
|
45
|
+
except requests.RequestException:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
# Use the port number as a weak clue
|
|
49
|
+
if PORT in popular_services_on_port:
|
|
50
|
+
return f"Likely: {popular_services_on_port[PORT]}"
|
|
51
|
+
|
|
52
|
+
return "Open port, but no banner received"
|
|
53
|
+
|
|
54
|
+
except OSError as e:
|
|
55
|
+
return f"Socket error: {e}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main():
|
|
59
|
+
result = find_service_from_port("127.0.0.1", 50)
|
|
60
|
+
print(result)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
from .commonly_used_ports import ports
|
|
3
|
+
|
|
4
|
+
target = "127.0.0.1" # ESP32 with a webpage hosted on port 80
|
|
5
|
+
|
|
6
|
+
def scan_port(target, port,timeout=3,verbose_status=False):
|
|
7
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
8
|
+
sock.settimeout(timeout)
|
|
9
|
+
result = sock.connect_ex((target, port))
|
|
10
|
+
|
|
11
|
+
if result == 0:
|
|
12
|
+
if verbose_status:
|
|
13
|
+
print(f"{port}: Open")
|
|
14
|
+
return port
|
|
15
|
+
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
open_ports = []
|
|
20
|
+
|
|
21
|
+
for port in ports:
|
|
22
|
+
result = scan_port(target, port)
|
|
23
|
+
|
|
24
|
+
if result is not None:
|
|
25
|
+
open_ports.append(result)
|
|
26
|
+
|
|
27
|
+
print("Open ports:", open_ports)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyprobe-scan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python network probing and asynchronous port scanning tool
|
|
5
|
+
Author: Ronak Pal
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: networking,port-scanner,network-probing,service-detection,cybersecurity
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: System :: Networking
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: pandas
|
|
20
|
+
Requires-Dist: rich
|
|
21
|
+
Requires-Dist: rich-tools
|
|
22
|
+
|
|
23
|
+
# Pyprobe
|
|
24
|
+
|
|
25
|
+
Pyprobe is a command-line tool and Python package for scanning TCP ports, identifying potentially running services, and exporting scan results to JSON. Built from scratch using Python's socket and concurrency capabilities, it aims to make network reconnaissance accessible through both a CLI and a reusable Python API.
|
|
26
|
+
|
|
27
|
+
<img src="docs/demo-screenshots/dummy-help-scan.png" style="max-width: 80%; height: auto;"></img>
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
| Feature | Description |
|
|
32
|
+
|---|---|
|
|
33
|
+
| **Asynchronous Scanning** | Uses `asyncio` and `asyncio.to_thread()` to scan multiple ports concurrently. |
|
|
34
|
+
| **TCP Port Scanning** | Identifies accessible TCP ports on a target host. |
|
|
35
|
+
| **Service Detection** | Attempts to identify services using port mappings and service signatures. |
|
|
36
|
+
| **Terminal Output** | Displays scan results in a formatted table using Rich. Use `-v` for verbose logging. |
|
|
37
|
+
| **JSON Export** | Saves discovered ports and service information to a JSON file. |
|
|
38
|
+
| **Custom Timeouts** | Set a custom timeout for each TCP connection |
|
|
39
|
+
| **CLI and Python API** | Designed for terminal use and integration into Python programs. |
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
Install from PyPI
|
|
44
|
+
|
|
45
|
+
install it with:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python -m pip install pyprobe-scan
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Install from source
|
|
52
|
+
|
|
53
|
+
Clone the repository:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/fourtysevencode/pyprobe.git
|
|
57
|
+
cd pyprobe
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Create and activate a virtual environment:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python3 -m venv .venv
|
|
64
|
+
source .venv/bin/activate
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Install the project in editable mode:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python -m pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Project Structure
|
|
74
|
+
```
|
|
75
|
+
pyprobe
|
|
76
|
+
├── docs
|
|
77
|
+
│ └── demo-screenshots
|
|
78
|
+
│ └── dummy-help-scan.png
|
|
79
|
+
├── src
|
|
80
|
+
│ └── pyprobe
|
|
81
|
+
│ ├── services
|
|
82
|
+
│ │ ├── __init__.py
|
|
83
|
+
│ │ ├── common_service_signatures.py
|
|
84
|
+
│ │ ├── commonly_used_ports.py
|
|
85
|
+
│ │ ├── find_service.py
|
|
86
|
+
│ │ └── scan_port.py
|
|
87
|
+
│ ├── __init.py
|
|
88
|
+
│ ├── __main__.py
|
|
89
|
+
│ ├── cli_args.py
|
|
90
|
+
│ ├── cli.py
|
|
91
|
+
│ ├── output.py
|
|
92
|
+
│ └── scanner.py
|
|
93
|
+
├── .gitignore
|
|
94
|
+
├── pyproject.toml
|
|
95
|
+
└── README.md
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Usage
|
|
99
|
+
|
|
100
|
+
Display help
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pyprobe-scan --help
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Scan a target
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pyprobe-scan 127.0.0.1
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Replace 127.0.0.1 with the IP address of a host you own or have explicit permission to test.
|
|
113
|
+
|
|
114
|
+
Export results to JSON
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pyprobe-scan 127.0.0.1 --json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The short form is also available (applicable to all commands use -h to view):
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pyprobe-scan 127.0.0.1 -j
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The scan results are saved to a timestamped JSON file in the current working directory.
|
|
127
|
+
|
|
128
|
+
Enable verbose logging:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pyprobe-scan 127.0.0.1 -v
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Set a timeout on each TCP connection:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pyprobe-scan 127.0.0.1 -t 3 # Default is 3s
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Run directly from source
|
|
141
|
+
|
|
142
|
+
If you haven't installed the package yet, run it from the repository root:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
PYTHONPATH=src python -m pyprobe 127.0.0.1
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Using Pyprobe e in Python
|
|
149
|
+
|
|
150
|
+
Pyprobe is also designed to be imported into your own Python projects.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import asyncio
|
|
154
|
+
from pyprobe.scanner import scan
|
|
155
|
+
|
|
156
|
+
async def main():
|
|
157
|
+
results = await scan("127.0.0.1")
|
|
158
|
+
print(results)
|
|
159
|
+
|
|
160
|
+
asyncio.run(main())
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The scanner returns a pandas DataFrame containing discovered ports and their detected service information.
|
|
164
|
+
|
|
165
|
+
You can process the results using pandas:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
results = asyncio.run(scan("127.0.0.1"))
|
|
169
|
+
|
|
170
|
+
print(results)
|
|
171
|
+
print(f"Open ports found: {len(results)}")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Credits
|
|
175
|
+
Shipped to [Hack Club Crescent](https://crescent.hackclub.com) 2026
|
|
176
|
+
|
|
177
|
+
<img src='https://hackatime.hackclub.com/api/v1/badge/U0BJ7PF47LJ/fourtysevencode/pyprobe'></img>
|
|
178
|
+
|
|
179
|
+
_made with 💗 from fourtysevencode_
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pyprobe/__init.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pyprobe/__main__.py,sha256=dlUlpbDREBvZ3EZbGtRNlpLJryM0RTGD3zUPraT5m3M,78
|
|
3
|
+
pyprobe/cli.py,sha256=ugTByIzS7gVf36IAEI36eSL4Z5xCd0BnnTN4an7mto0,631
|
|
4
|
+
pyprobe/cli_args.py,sha256=QDnMnAq-ED-g6x5uBFZlnWBJPlpD5j6aCnkzeU6uoUA,953
|
|
5
|
+
pyprobe/output.py,sha256=TwMyX2tINdaP92UOGvhY2O1Jjqj8MDoLwehCDqMPy_M,618
|
|
6
|
+
pyprobe/scanner.py,sha256=C-stbAlO8PLUTuTCUP67ncmNyAIVDkXeTANHBsZbRzA,1917
|
|
7
|
+
pyprobe/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
pyprobe/services/common_service_signatures.py,sha256=VLJx7Wsft1Zn5cmf1sIb0wTUrT9UbTv1daC6YF0j6cg,240
|
|
9
|
+
pyprobe/services/commonly_used_ports.py,sha256=vPBQK4tBF93jQMsc-DXaKah_IQmOJGVQvUuqYZ8JM7c,6008
|
|
10
|
+
pyprobe/services/find_service.py,sha256=GmngD20sapB6K0XhV-bYyqyx1OEToRzxDEcMu1J_OEw,1808
|
|
11
|
+
pyprobe/services/scan_port.py,sha256=jVJdtfSB7kHbvE2KYL5u14YySeWChiEqo7zImE4xaMY,673
|
|
12
|
+
pyprobe_scan-0.1.0.dist-info/METADATA,sha256=GAygAC0AQ1ofPKCql11BfK69f9jr9INnRJxvlxFXDzs,4565
|
|
13
|
+
pyprobe_scan-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
14
|
+
pyprobe_scan-0.1.0.dist-info/entry_points.txt,sha256=B5QBDTtNQTjIMzCpLpr9MCGJ3c0Ey3_l4F-GQCwC3aE,50
|
|
15
|
+
pyprobe_scan-0.1.0.dist-info/top_level.txt,sha256=M_5K7kLSWGQUXKUFLZaXbRelUowqPnKhIPfTWkRbU40,8
|
|
16
|
+
pyprobe_scan-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyprobe
|