forensic-cli 1.2.13__tar.gz → 1.4.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.
- {forensic_cli-1.2.13/src/forensic_cli.egg-info → forensic_cli-1.4.0}/PKG-INFO +1 -1
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/pyproject.toml +1 -1
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/setup.py +1 -1
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/commands/network.py +70 -9
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/browser/common_words.py +0 -1
- forensic_cli-1.4.0/src/core/network/network_map.py +126 -0
- forensic_cli-1.4.0/src/core/network/port_scanner.py +138 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0/src/forensic_cli.egg-info}/PKG-INFO +1 -1
- forensic_cli-1.2.13/src/core/network/network_map.py +0 -120
- forensic_cli-1.2.13/src/core/network/port_scanner.py +0 -93
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/LICENSE +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/LICENSE-GPL +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/README.md +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/setup.cfg +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/__init__.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/commands/__init__.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/commands/browser.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/commands/describe.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/commands/email.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/commands/utils.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/cli/main.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/__init__.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/browser/browser_history.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/browser/downloads_history.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/browser/fav_screen.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/browser/logins.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/browser/unusual_patterns.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/data/__init__.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/data/data_recovery.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/db/db.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/email/email_parser.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/email/header_analysis.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/models/orm.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/models/schemas.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/__init__.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/arp_scan.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/dns_recon.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/fingerprinting.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/ip_info.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/ping_sweep.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/smb_scan.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/snmp_scan.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/traceroute.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/network/utils.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/registry.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/utils/__init__.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/core/utils/gerar_amostras.py +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/forensic_cli.egg-info/SOURCES.txt +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/forensic_cli.egg-info/dependency_links.txt +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/forensic_cli.egg-info/entry_points.txt +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/forensic_cli.egg-info/requires.txt +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/src/forensic_cli.egg-info/top_level.txt +0 -0
- {forensic_cli-1.2.13 → forensic_cli-1.4.0}/tests/test_file_inspector.py +0 -0
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="forensic_cli",
|
|
5
|
-
version="1.
|
|
5
|
+
version="1.4.0",
|
|
6
6
|
author="Erick Gabriel dos Santos Alves",
|
|
7
7
|
author_email="erickgabrielalves0@gmail.com",
|
|
8
8
|
description="Toolkit modular para análise de evidências digitais",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
from rich import print
|
|
3
3
|
from rich.text import Text
|
|
4
|
+
from rich.table import Table
|
|
4
5
|
|
|
5
6
|
from core.network.network_map import run as run_network_map
|
|
6
|
-
from core.network.port_scanner import scan_host
|
|
7
|
+
from core.network.port_scanner import scan_host, parse_ports
|
|
7
8
|
from core.network.ping_sweep import parse_network, ping_host
|
|
8
9
|
from core.network.fingerprinting import detect_os
|
|
9
10
|
from core.network.traceroute import traceroute_host
|
|
@@ -15,20 +16,80 @@ from core.network.snmp_scan import snmp_scan
|
|
|
15
16
|
|
|
16
17
|
network_app = typer.Typer(help="Conjunto de ferramentas para análise e exploração de redes")
|
|
17
18
|
|
|
18
|
-
@network_app.command("map", help="Mapeia dispositivos ativos na rede e salva os resultados
|
|
19
|
+
@network_app.command("map", help="Mapeia dispositivos ativos na rede e salva os resultados")
|
|
19
20
|
def map(
|
|
20
|
-
network: str = typer.Option(..., help="Range de IPs da rede.
|
|
21
|
-
|
|
21
|
+
network: str = typer.Option(..., "--network", "-n", help="Range de IPs da rede. Ex: 192.168.1.1-254"),
|
|
22
|
+
|
|
23
|
+
ports: str = typer.Option(
|
|
24
|
+
"21,22,80,443,445,8080",
|
|
25
|
+
"--ports",
|
|
26
|
+
"-p",
|
|
27
|
+
help="Portas para escanear em cada host."
|
|
28
|
+
),
|
|
29
|
+
|
|
30
|
+
output_dir: str = typer.Option(
|
|
31
|
+
"./output",
|
|
32
|
+
"--output",
|
|
33
|
+
"-o",
|
|
34
|
+
help="Diretório para salvar os resultados"
|
|
35
|
+
),
|
|
22
36
|
):
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
typer.echo(f"[+] Iniciando mapeamento da rede {network} nas portas [{ports}]...")
|
|
38
|
+
|
|
39
|
+
result = run_network_map(network, ports, output_dir)
|
|
40
|
+
|
|
41
|
+
if result and result.get('json_file'):
|
|
42
|
+
typer.echo(f"Resultados salvos em: {result.get('json_file')} e {result.get('csv_file')}")
|
|
25
43
|
|
|
26
44
|
@network_app.command("scan", help="Realiza um scan de portas em um host específico")
|
|
27
45
|
def scan(
|
|
28
|
-
|
|
46
|
+
target: str = typer.Option(..., "--target", "-t", help="Alvo do scan (IP ou hostname)"),
|
|
47
|
+
ports: str = typer.Option("21,22,53,80,443,445,3306,8080", "--ports", "-p", help="Portas para escanear. Ex: '22,80,100-200'")
|
|
29
48
|
):
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
try:
|
|
50
|
+
ports_to_scan = parse_ports(ports)
|
|
51
|
+
num_ports = len(ports_to_scan)
|
|
52
|
+
|
|
53
|
+
if num_ports == 0:
|
|
54
|
+
print("[yellow]Nenhuma porta especificada para o scan.[/yellow]")
|
|
55
|
+
raise typer.Exit()
|
|
56
|
+
|
|
57
|
+
typer.echo(f"[+] Iniciando scan em {num_ports} porta(s) de {target}...")
|
|
58
|
+
|
|
59
|
+
results = []
|
|
60
|
+
with typer.progressbar(length=num_ports, label="Scanning ports") as progress:
|
|
61
|
+
def update_progress():
|
|
62
|
+
progress.update(1)
|
|
63
|
+
|
|
64
|
+
results = scan_host(
|
|
65
|
+
target=target,
|
|
66
|
+
ports=ports,
|
|
67
|
+
callback=update_progress
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
table = Table(title=f"Resultados do Scan para {target}")
|
|
71
|
+
table.add_column("Porta", style="cyan")
|
|
72
|
+
table.add_column("Protocolo", style="magenta")
|
|
73
|
+
table.add_column("Status", style="green")
|
|
74
|
+
table.add_column("Serviço", style="yellow")
|
|
75
|
+
table.add_column("Banner / Detalhes", style="white", overflow="fold")
|
|
76
|
+
|
|
77
|
+
if not results:
|
|
78
|
+
print(f"[yellow]Nenhuma porta aberta encontrada para '{target}' no range '{ports}'.[/yellow]")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
for res in results:
|
|
82
|
+
table.add_row(
|
|
83
|
+
str(res["port"]),
|
|
84
|
+
res["protocol"],
|
|
85
|
+
res["status"],
|
|
86
|
+
res["service"],
|
|
87
|
+
res["banner"]
|
|
88
|
+
)
|
|
89
|
+
print(table)
|
|
90
|
+
|
|
91
|
+
except ValueError as e:
|
|
92
|
+
print(f"[bold red]Erro: {e}[/bold red]")
|
|
32
93
|
|
|
33
94
|
@network_app.command("sweep", help="Verifica hosts ativos em um range de IPs via ping")
|
|
34
95
|
def sweep(
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import csv
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from .ping_sweep import ping_sweep
|
|
9
|
+
from .arp_scan import arp_scan
|
|
10
|
+
from .port_scanner import scan_host
|
|
11
|
+
from .utils import get_mac, get_vendor, get_hostname
|
|
12
|
+
from .fingerprinting import detect_os
|
|
13
|
+
from .traceroute import traceroute_host
|
|
14
|
+
from .snmp_scan import snmp_scan
|
|
15
|
+
from .dns_recon import dns_recon
|
|
16
|
+
from .smb_scan import smb_scan
|
|
17
|
+
from .ip_info import ip_info_lookup
|
|
18
|
+
|
|
19
|
+
def _process_host(host: str, ports_to_scan: str) -> dict:
|
|
20
|
+
open_ports = scan_host(host, ports=ports_to_scan)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
mac = get_mac(host)
|
|
24
|
+
vendor = get_vendor(mac) if mac else None
|
|
25
|
+
except Exception:
|
|
26
|
+
mac = None
|
|
27
|
+
vendor = None
|
|
28
|
+
|
|
29
|
+
hostname = get_hostname(host)
|
|
30
|
+
|
|
31
|
+
os_info = detect_os(host, ports=open_ports, mac_vendor=vendor)
|
|
32
|
+
hops = traceroute_host(host)
|
|
33
|
+
snmp_info = snmp_scan(host)
|
|
34
|
+
dns_info = dns_recon([host])
|
|
35
|
+
smb_info = smb_scan([host])
|
|
36
|
+
ip_info = ip_info_lookup(host)
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"host": host,
|
|
40
|
+
"hostname": hostname,
|
|
41
|
+
"mac": mac,
|
|
42
|
+
"vendor": vendor,
|
|
43
|
+
"open_ports": open_ports,
|
|
44
|
+
"os_info": os_info,
|
|
45
|
+
"traceroute": hops,
|
|
46
|
+
"snmp": snmp_info,
|
|
47
|
+
"dns": dns_info[0] if dns_info else {},
|
|
48
|
+
"smb": smb_info[0] if smb_info else {},
|
|
49
|
+
"ip_info": ip_info
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def build_network_map(network: str, ports: str, workers: int = 50):
|
|
53
|
+
hosts = ping_sweep(network)
|
|
54
|
+
if not hosts:
|
|
55
|
+
typer.echo("[+] Ping sweep não encontrou hosts, tentando ARP scan...")
|
|
56
|
+
hosts = arp_scan(network)
|
|
57
|
+
|
|
58
|
+
if not hosts:
|
|
59
|
+
typer.echo("[!] Nenhum host ativo encontrado na rede especificada.")
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
results = []
|
|
63
|
+
typer.echo(f"[+] {len(hosts)} host(s) ativo(s) encontrado(s). Iniciando análise detalhada...")
|
|
64
|
+
|
|
65
|
+
with ThreadPoolExecutor(max_workers=workers) as executor:
|
|
66
|
+
future_to_host = {executor.submit(_process_host, host, ports): host for host in hosts}
|
|
67
|
+
|
|
68
|
+
with typer.progressbar(as_completed(future_to_host), length=len(hosts), label="Mapeando Rede") as progress:
|
|
69
|
+
for future in progress:
|
|
70
|
+
try:
|
|
71
|
+
host_data = future.result()
|
|
72
|
+
results.append(host_data)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
host = future_to_host[future]
|
|
75
|
+
typer.echo(f"\n[!] Erro ao processar o host {host}: {exc}")
|
|
76
|
+
|
|
77
|
+
return results
|
|
78
|
+
|
|
79
|
+
def save_network_map(results: list, output_dir: str, prefix="network_map"):
|
|
80
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
81
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
82
|
+
|
|
83
|
+
json_file = os.path.join(output_dir, f"{prefix}_{timestamp}.json")
|
|
84
|
+
csv_file = os.path.join(output_dir, f"{prefix}_{timestamp}.csv")
|
|
85
|
+
|
|
86
|
+
with open(json_file, "w", encoding="utf-8") as f:
|
|
87
|
+
json.dump(results, f, indent=4, ensure_ascii=False)
|
|
88
|
+
|
|
89
|
+
flat_results = []
|
|
90
|
+
for row in results:
|
|
91
|
+
flat_row = {
|
|
92
|
+
"host": row.get("host"),
|
|
93
|
+
"hostname": row.get("hostname"),
|
|
94
|
+
"mac": row.get("mac"),
|
|
95
|
+
"vendor": row.get("vendor"),
|
|
96
|
+
"os_type": row.get("os_info", {}).get("os"),
|
|
97
|
+
"host_type": row.get("os_info", {}).get("host_type"),
|
|
98
|
+
"open_ports_count": len(row.get("open_ports", [])),
|
|
99
|
+
"traceroute_hops": len(row.get("traceroute", [])),
|
|
100
|
+
"snmp_sys_name": row.get("snmp", {}).get("sys_name"),
|
|
101
|
+
"dns_hostname": row.get("dns", {}).get("hostname"),
|
|
102
|
+
"smb_shares_count": len(row.get("smb", {}).get("shares", [])),
|
|
103
|
+
"asn": row.get("ip_info", {}).get("asn"),
|
|
104
|
+
"network_name": row.get("ip_info", {}).get("network_name"),
|
|
105
|
+
}
|
|
106
|
+
flat_results.append(flat_row)
|
|
107
|
+
|
|
108
|
+
if not flat_results:
|
|
109
|
+
return json_file, None
|
|
110
|
+
|
|
111
|
+
with open(csv_file, "w", newline="", encoding="utf-8") as f:
|
|
112
|
+
fieldnames = flat_results[0].keys()
|
|
113
|
+
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
114
|
+
writer.writeheader()
|
|
115
|
+
writer.writerows(flat_results)
|
|
116
|
+
|
|
117
|
+
return json_file, csv_file
|
|
118
|
+
|
|
119
|
+
def run(network: str, ports: str, output_dir: str, prefix="network_map"):
|
|
120
|
+
results = build_network_map(network, ports)
|
|
121
|
+
|
|
122
|
+
if results:
|
|
123
|
+
json_file, csv_file = save_network_map(results, output_dir, prefix)
|
|
124
|
+
return {"results": results, "json_file": json_file, "csv_file": csv_file}
|
|
125
|
+
else:
|
|
126
|
+
return {}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import ssl
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
4
|
+
from typing import Callable, Dict, List, Optional, Set
|
|
5
|
+
|
|
6
|
+
TCP_PROBES: Dict[int, bytes] = {
|
|
7
|
+
80: b"HEAD / HTTP/1.1\r\nHost: target\r\n\r\n",
|
|
8
|
+
21: b"FEAT\r\n",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
PORT_ALERTS: Dict[int, str] = {
|
|
12
|
+
21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS",
|
|
13
|
+
80: "HTTP", 110: "POP3", 137: "NetBIOS-NS", 139: "NetBIOS", 143: "IMAP",
|
|
14
|
+
443: "HTTPS", 445: "SMB", 993: "IMAPS", 995: "POP3S",
|
|
15
|
+
1900: "UPnP", 3306: "MySQL", 3389: "RDP", 5432: "PostgreSQL",
|
|
16
|
+
5900: "VNC", 8080: "HTTP-Proxy",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def _clean_banner(banner_bytes: bytes) -> str:
|
|
20
|
+
try:
|
|
21
|
+
banner_str = banner_bytes.decode(errors='ignore')
|
|
22
|
+
printable_banner = "".join(char for char in banner_str if char.isprintable() or char.isspace())
|
|
23
|
+
return printable_banner.strip()
|
|
24
|
+
except Exception:
|
|
25
|
+
return "Falha ao decodificar banner"
|
|
26
|
+
|
|
27
|
+
def parse_ports(port_str: str) -> List[int]:
|
|
28
|
+
ports: Set[int] = set()
|
|
29
|
+
try:
|
|
30
|
+
parts = port_str.split(',')
|
|
31
|
+
for part in parts:
|
|
32
|
+
if '-' in part:
|
|
33
|
+
start, end = map(int, part.split('-'))
|
|
34
|
+
ports.update(range(start, end + 1))
|
|
35
|
+
else:
|
|
36
|
+
ports.add(int(part))
|
|
37
|
+
except ValueError:
|
|
38
|
+
raise ValueError("Formato de porta inválido. Use '80', '22,80,443' ou '1-1024'.")
|
|
39
|
+
|
|
40
|
+
return sorted(list(ports))
|
|
41
|
+
|
|
42
|
+
def _scan_tcp(ip: str, port: int, timeout: float) -> Optional[Dict]:
|
|
43
|
+
try:
|
|
44
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
45
|
+
s.settimeout(timeout)
|
|
46
|
+
if s.connect_ex((ip, port)) == 0:
|
|
47
|
+
banner = ""
|
|
48
|
+
if port == 443:
|
|
49
|
+
try:
|
|
50
|
+
context = ssl.create_default_context()
|
|
51
|
+
with context.wrap_socket(s, server_hostname=ip) as ss:
|
|
52
|
+
cert = ss.getpeercert()
|
|
53
|
+
subject = dict(x[0] for x in cert.get('subject', []))
|
|
54
|
+
banner = f"SSL Cert for: {subject.get('commonName', 'N/A')}"
|
|
55
|
+
except Exception:
|
|
56
|
+
banner = "SSL Handshake Error"
|
|
57
|
+
else:
|
|
58
|
+
try:
|
|
59
|
+
if port in TCP_PROBES:
|
|
60
|
+
s.send(TCP_PROBES[port])
|
|
61
|
+
raw_banner = s.recv(1024)
|
|
62
|
+
cleaned_banner = _clean_banner(raw_banner)
|
|
63
|
+
if port in [80, 8080] and cleaned_banner:
|
|
64
|
+
banner = cleaned_banner.splitlines()[0]
|
|
65
|
+
else:
|
|
66
|
+
banner = cleaned_banner
|
|
67
|
+
except (socket.timeout, ConnectionResetError):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"port": port, "protocol": "TCP", "status": "open",
|
|
72
|
+
"banner": banner or "sem banner",
|
|
73
|
+
"service": PORT_ALERTS.get(port, "unknown")
|
|
74
|
+
}
|
|
75
|
+
except (socket.timeout, OSError):
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def _scan_udp(ip: str, port: int, timeout: float) -> Optional[Dict]:
|
|
81
|
+
try:
|
|
82
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
83
|
+
s.settimeout(timeout)
|
|
84
|
+
s.sendto(b"", (ip, port))
|
|
85
|
+
try:
|
|
86
|
+
s.recvfrom(1024)
|
|
87
|
+
return {
|
|
88
|
+
"port": port, "protocol": "UDP", "status": "open",
|
|
89
|
+
"banner": "response received",
|
|
90
|
+
"service": PORT_ALERTS.get(port, "unknown")
|
|
91
|
+
}
|
|
92
|
+
except socket.timeout:
|
|
93
|
+
return {
|
|
94
|
+
"port": port, "protocol": "UDP", "status": "open|filtered",
|
|
95
|
+
"banner": "no response",
|
|
96
|
+
"service": PORT_ALERTS.get(port, "unknown")
|
|
97
|
+
}
|
|
98
|
+
except (OSError, socket.timeout):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def scan_host(
|
|
104
|
+
target: str,
|
|
105
|
+
ports: str,
|
|
106
|
+
udp: bool = False,
|
|
107
|
+
workers: int = 100,
|
|
108
|
+
timeout: float = 1.0,
|
|
109
|
+
callback: Optional[Callable] = None
|
|
110
|
+
) -> List[Dict]:
|
|
111
|
+
try:
|
|
112
|
+
ip = socket.gethostbyname(target)
|
|
113
|
+
except socket.gaierror:
|
|
114
|
+
raise ValueError(f"Hostname '{target}' não pôde ser resolvido.")
|
|
115
|
+
|
|
116
|
+
tcp_ports_to_scan = parse_ports(ports)
|
|
117
|
+
udp_ports_to_scan = parse_ports("53,67,68,161,500") if udp else []
|
|
118
|
+
|
|
119
|
+
total_ports = len(tcp_ports_to_scan) + len(udp_ports_to_scan)
|
|
120
|
+
if total_ports == 0:
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
results: List[Dict] = []
|
|
124
|
+
with ThreadPoolExecutor(max_workers=workers) as executor:
|
|
125
|
+
futures = [executor.submit(_scan_tcp, ip, port, timeout) for port in tcp_ports_to_scan]
|
|
126
|
+
futures += [executor.submit(_scan_udp, ip, port, timeout) for port in udp_ports_to_scan]
|
|
127
|
+
|
|
128
|
+
for future in as_completed(futures):
|
|
129
|
+
res = future.result()
|
|
130
|
+
if res:
|
|
131
|
+
results.append(res)
|
|
132
|
+
|
|
133
|
+
if callback:
|
|
134
|
+
callback()
|
|
135
|
+
|
|
136
|
+
results.sort(key=lambda x: x["port"])
|
|
137
|
+
|
|
138
|
+
return results
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import json
|
|
3
|
-
import csv
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
import threading
|
|
6
|
-
import typer
|
|
7
|
-
|
|
8
|
-
from core.network.ping_sweep import ping_sweep
|
|
9
|
-
from core.network.arp_scan import arp_scan
|
|
10
|
-
from core.network.port_scanner import scan_host
|
|
11
|
-
from core.network.utils import get_mac, get_vendor, get_hostname
|
|
12
|
-
from core.network.fingerprinting import detect_os
|
|
13
|
-
from core.network.traceroute import traceroute_host
|
|
14
|
-
from core.network.snmp_scan import snmp_scan
|
|
15
|
-
from core.network.dns_recon import dns_recon
|
|
16
|
-
from core.network.smb_scan import smb_scan
|
|
17
|
-
from core.network.ip_info import ip_info_lookup
|
|
18
|
-
|
|
19
|
-
lock = threading.Lock()
|
|
20
|
-
|
|
21
|
-
def build_network_map(network):
|
|
22
|
-
hosts = ping_sweep(network)
|
|
23
|
-
if not hosts:
|
|
24
|
-
hosts = arp_scan(network)
|
|
25
|
-
|
|
26
|
-
results = []
|
|
27
|
-
|
|
28
|
-
typer.echo(f"[+] Iniciando varredura da rede ({len(hosts)} hosts)...")
|
|
29
|
-
|
|
30
|
-
with typer.progressbar(hosts, label="Network Map") as progress:
|
|
31
|
-
for host in hosts:
|
|
32
|
-
open_ports = scan_host(host)
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
mac = get_mac(host)
|
|
36
|
-
vendor = get_vendor(mac) if mac else None
|
|
37
|
-
except Exception:
|
|
38
|
-
mac = None
|
|
39
|
-
vendor = None
|
|
40
|
-
|
|
41
|
-
hostname = get_hostname(host)
|
|
42
|
-
os_info = detect_os(host, ports=open_ports, mac_vendor=vendor)
|
|
43
|
-
|
|
44
|
-
hops = traceroute_host(host)
|
|
45
|
-
|
|
46
|
-
snmp_info = snmp_scan(host)
|
|
47
|
-
|
|
48
|
-
dns_info = dns_recon([host])
|
|
49
|
-
|
|
50
|
-
smb_info = smb_scan([host])
|
|
51
|
-
|
|
52
|
-
ip_info = ip_info_lookup(host)
|
|
53
|
-
|
|
54
|
-
host_data = {
|
|
55
|
-
"host": host,
|
|
56
|
-
"hostname": hostname,
|
|
57
|
-
"mac": mac,
|
|
58
|
-
"vendor": vendor,
|
|
59
|
-
"open_ports": open_ports,
|
|
60
|
-
"os_info": os_info,
|
|
61
|
-
"traceroute": hops,
|
|
62
|
-
"snmp": snmp_info,
|
|
63
|
-
"dns": dns_info[0] if dns_info else {},
|
|
64
|
-
"smb": smb_info[0] if smb_info else {},
|
|
65
|
-
"ip_info": ip_info
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
with lock:
|
|
69
|
-
results.append(host_data)
|
|
70
|
-
|
|
71
|
-
progress.update(1)
|
|
72
|
-
|
|
73
|
-
return results
|
|
74
|
-
|
|
75
|
-
def save_network_map(results, output_dir: str, prefix="network_map"):
|
|
76
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
77
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
78
|
-
|
|
79
|
-
json_file = os.path.join(output_dir, f"{prefix}_{timestamp}.json")
|
|
80
|
-
csv_file = os.path.join(output_dir, f"{prefix}_{timestamp}.csv")
|
|
81
|
-
|
|
82
|
-
with open(json_file, "w", encoding="utf-8") as f:
|
|
83
|
-
json.dump(results, f, indent=4, ensure_ascii=False)
|
|
84
|
-
|
|
85
|
-
with open(csv_file, "w", newline="", encoding="utf-8") as f:
|
|
86
|
-
fieldnames = ["host", "hostname", "mac", "vendor", "os_type", "host_type",
|
|
87
|
-
"open_ports_count", "traceroute_hops", "snmp_sys_name", "dns_hostname",
|
|
88
|
-
"smb_shares_count", "vulnerabilities_count", "asn", "network_name"]
|
|
89
|
-
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
90
|
-
writer.writeheader()
|
|
91
|
-
|
|
92
|
-
for row in results:
|
|
93
|
-
writer.writerow({
|
|
94
|
-
"host": row["host"],
|
|
95
|
-
"hostname": row["hostname"],
|
|
96
|
-
"mac": row["mac"],
|
|
97
|
-
"vendor": row["vendor"],
|
|
98
|
-
"os_type": row["os_info"].get("os"),
|
|
99
|
-
"host_type": row["os_info"].get("host_type"),
|
|
100
|
-
"open_ports_count": len(row["open_ports"]),
|
|
101
|
-
"traceroute_hops": len(row["traceroute"]),
|
|
102
|
-
"snmp_sys_name": row["snmp"].get("sys_name"),
|
|
103
|
-
"dns_hostname": row["dns"].get("hostname"),
|
|
104
|
-
"smb_shares_count": len(row["smb"].get("shares", [])),
|
|
105
|
-
"asn": row["ip_info"].get("asn"),
|
|
106
|
-
"network_name": row["ip_info"].get("network_name"),
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
return json_file, csv_file
|
|
110
|
-
|
|
111
|
-
def run(network: str, output_dir: str, prefix="network_map"):
|
|
112
|
-
results = build_network_map(network)
|
|
113
|
-
json_file, csv_file = save_network_map(results, output_dir, prefix)
|
|
114
|
-
|
|
115
|
-
typer.echo(f"[+] Network map salvo em: {json_file} e {csv_file}")
|
|
116
|
-
return {
|
|
117
|
-
"results": results,
|
|
118
|
-
"json_file": json_file,
|
|
119
|
-
"csv_file": csv_file,
|
|
120
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import socket
|
|
2
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
3
|
-
import threading
|
|
4
|
-
import typer
|
|
5
|
-
|
|
6
|
-
COMMON_TCP_PORTS = [
|
|
7
|
-
21, 22, 23, 25, 53, 80, 110, 139, 143, 443, 445,
|
|
8
|
-
3389, 3306, 8080, 8443, 5900, 135, 995, 993, 1723
|
|
9
|
-
]
|
|
10
|
-
COMMON_UDP_PORTS = [53, 67, 68, 69, 123, 161, 162, 500, 514]
|
|
11
|
-
|
|
12
|
-
PORT_ALERTS = {
|
|
13
|
-
21: "FTP",
|
|
14
|
-
22: "SSH",
|
|
15
|
-
23: "Telnet",
|
|
16
|
-
25: "SMTP",
|
|
17
|
-
53: "DNS",
|
|
18
|
-
80: "HTTP",
|
|
19
|
-
443: "HTTPS",
|
|
20
|
-
445: "SMB",
|
|
21
|
-
3306: "MySQL",
|
|
22
|
-
3389: "RDP"
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
lock = threading.Lock()
|
|
26
|
-
|
|
27
|
-
def scan_tcp(ip, port):
|
|
28
|
-
result = None
|
|
29
|
-
try:
|
|
30
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
31
|
-
s.settimeout(1)
|
|
32
|
-
if s.connect_ex((ip, port)) == 0:
|
|
33
|
-
banner = ""
|
|
34
|
-
try:
|
|
35
|
-
if port in [21, 22, 25, 80, 443, 3306]:
|
|
36
|
-
s.send(b"HEAD / HTTP/1.0\r\n\r\n")
|
|
37
|
-
banner = s.recv(1024).decode(errors="ignore").strip()
|
|
38
|
-
except (socket.timeout, ConnectionResetError):
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
|
-
result = {
|
|
42
|
-
"port": port,
|
|
43
|
-
"protocol": "TCP",
|
|
44
|
-
"status": "open",
|
|
45
|
-
"banner": banner if banner else "sem banner",
|
|
46
|
-
"alert": PORT_ALERTS.get(port)
|
|
47
|
-
}
|
|
48
|
-
except (socket.timeout, OSError):
|
|
49
|
-
pass
|
|
50
|
-
return result
|
|
51
|
-
|
|
52
|
-
def scan_udp(ip, port):
|
|
53
|
-
result = None
|
|
54
|
-
try:
|
|
55
|
-
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
56
|
-
s.settimeout(1)
|
|
57
|
-
s.sendto(b"", (ip, port))
|
|
58
|
-
try:
|
|
59
|
-
data, _ = s.recvfrom(1024)
|
|
60
|
-
banner = data.decode(errors="ignore").strip()
|
|
61
|
-
status = "open"
|
|
62
|
-
except socket.timeout:
|
|
63
|
-
banner = "sem resposta"
|
|
64
|
-
status = "open|filtered"
|
|
65
|
-
result = {
|
|
66
|
-
"port": port,
|
|
67
|
-
"protocol": "UDP",
|
|
68
|
-
"status": status,
|
|
69
|
-
"banner": banner,
|
|
70
|
-
"alert": PORT_ALERTS.get(port)
|
|
71
|
-
}
|
|
72
|
-
except (OSError, socket.timeout):
|
|
73
|
-
pass
|
|
74
|
-
return result
|
|
75
|
-
|
|
76
|
-
def scan_host(ip, tcp_ports=None, udp_ports=None):
|
|
77
|
-
tcp_ports = tcp_ports or COMMON_TCP_PORTS
|
|
78
|
-
udp_ports = udp_ports or COMMON_UDP_PORTS
|
|
79
|
-
all_ports = tcp_ports + udp_ports
|
|
80
|
-
results = []
|
|
81
|
-
|
|
82
|
-
with typer.progressbar(all_ports, label=f"Scanning {ip}") as progress:
|
|
83
|
-
with ThreadPoolExecutor(max_workers=100) as executor:
|
|
84
|
-
futures = {executor.submit(scan_tcp if port in tcp_ports else scan_udp, ip, port): port for port in all_ports}
|
|
85
|
-
|
|
86
|
-
for future in as_completed(futures):
|
|
87
|
-
res = future.result()
|
|
88
|
-
if res:
|
|
89
|
-
with lock:
|
|
90
|
-
results.append(res)
|
|
91
|
-
progress.update(1)
|
|
92
|
-
|
|
93
|
-
return results
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|