forensic-cli 1.2.5__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.
Files changed (44) hide show
  1. cli/__init__.py +0 -0
  2. cli/commands/__init__.py +0 -0
  3. cli/commands/browser.py +220 -0
  4. cli/commands/describe.py +54 -0
  5. cli/commands/email.py +3 -0
  6. cli/commands/network.py +126 -0
  7. cli/commands/utils.py +3 -0
  8. cli/main.py +18 -0
  9. core/__init__.py +0 -0
  10. core/browser/browser_history.py +156 -0
  11. core/browser/common_words.py +60 -0
  12. core/browser/downloads_history.py +129 -0
  13. core/browser/fav_screen.py +70 -0
  14. core/browser/logins.py +108 -0
  15. core/browser/unusual_patterns.py +111 -0
  16. core/data/__init__.py +0 -0
  17. core/data/data_recovery.py +157 -0
  18. core/db/db.py +12 -0
  19. core/email/email_parser.py +87 -0
  20. core/email/header_analysis.py +87 -0
  21. core/models/orm.py +48 -0
  22. core/models/schemas.py +76 -0
  23. core/network/__init__.py +0 -0
  24. core/network/arp_scan.py +16 -0
  25. core/network/dns_recon.py +90 -0
  26. core/network/fingerprinting.py +117 -0
  27. core/network/ip_info.py +29 -0
  28. core/network/network_map.py +120 -0
  29. core/network/ping_sweep.py +53 -0
  30. core/network/port_scanner.py +93 -0
  31. core/network/smb_scan.py +32 -0
  32. core/network/snmp_scan.py +43 -0
  33. core/network/traceroute.py +40 -0
  34. core/network/utils.py +27 -0
  35. core/registry.py +25 -0
  36. core/utils/__init__.py +0 -0
  37. core/utils/gerar_amostras.py +33 -0
  38. forensic_cli-1.2.5.dist-info/METADATA +293 -0
  39. forensic_cli-1.2.5.dist-info/RECORD +44 -0
  40. forensic_cli-1.2.5.dist-info/WHEEL +5 -0
  41. forensic_cli-1.2.5.dist-info/entry_points.txt +2 -0
  42. forensic_cli-1.2.5.dist-info/licenses/LICENSE +21 -0
  43. forensic_cli-1.2.5.dist-info/licenses/LICENSE-GPL +675 -0
  44. forensic_cli-1.2.5.dist-info/top_level.txt +2 -0
@@ -0,0 +1,87 @@
1
+ import os
2
+ import json
3
+ import csv
4
+ import re
5
+ from datetime import datetime
6
+
7
+ SPF_REGEX = re.compile(r"spf=(pass|fail|softfail|neutral)", re.IGNORECASE)
8
+ DKIM_REGEX = re.compile(r"dkim=(pass|fail|none)", re.IGNORECASE)
9
+ DMARC_REGEX = re.compile(r"dmarc=(pass|fail|none)", re.IGNORECASE)
10
+ IP_REGEX = re.compile(r"\b\d{1,3}(?:\.\d{1,3}){3}\b")
11
+
12
+ def extract_ips_from_received(received_headers):
13
+ ips = []
14
+ if not received_headers:
15
+ return ips
16
+ if isinstance(received_headers, str):
17
+ received_headers = [received_headers]
18
+ for header in received_headers:
19
+ found_ips = IP_REGEX.findall(header)
20
+ ips.extend(found_ips)
21
+ return list(set(ips))
22
+
23
+ def analyze_email_headers(email_data):
24
+ headers_to_check = ["received", "authentication-results", "spf", "dkim", "dmarc"]
25
+
26
+ result = {
27
+ "subject": email_data.get("subject"),
28
+ "from": email_data.get("from"),
29
+ "to": email_data.get("to"),
30
+ "date": email_data.get("date"),
31
+ "message_id": email_data.get("message_id"),
32
+ "spf": "Desconhecido",
33
+ "dkim": "Desconhecido",
34
+ "dmarc": "Desconhecido",
35
+ "origin_ips": [],
36
+ }
37
+
38
+ received_headers = email_data.get("received", [])
39
+ if received_headers:
40
+ result["origin_ips"] = extract_ips_from_received(received_headers)
41
+
42
+ auth_results = email_data.get("authentication_results", "")
43
+ if auth_results:
44
+ spf_match = SPF_REGEX.search(auth_results)
45
+ dkim_match = DKIM_REGEX.search(auth_results)
46
+ dmarc_match = DMARC_REGEX.search(auth_results)
47
+ if spf_match:
48
+ result["spf"] = spf_match.group(1).lower()
49
+ if dkim_match:
50
+ result["dkim"] = dkim_match.group(1).lower()
51
+ if dmarc_match:
52
+ result["dmarc"] = dmarc_match.group(1).lower()
53
+
54
+ return result
55
+
56
+ def analyze_emails_from_json(json_file):
57
+ with open(json_file, "r", encoding="utf-8") as f:
58
+ emails = json.load(f)
59
+
60
+ analyzed_results = []
61
+ for email_data in emails:
62
+ analyzed_results.append(analyze_email_headers(email_data))
63
+ return analyzed_results
64
+
65
+ def export_analysis(results, prefix="email_header_analysis"):
66
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
67
+ json_file = f"{prefix}_{timestamp}.json"
68
+ csv_file = f"{prefix}_{timestamp}.csv"
69
+
70
+ with open(json_file, "w", encoding="utf-8") as f:
71
+ json.dump(results, f, indent=4, ensure_ascii=False)
72
+
73
+ headers = ["subject", "from", "to", "date", "message_id", "spf", "dkim", "dmarc", "origin_ips"]
74
+ with open(csv_file, "w", newline="", encoding="utf-8") as f:
75
+ writer = csv.DictWriter(f, fieldnames=headers)
76
+ writer.writeheader()
77
+ for r in results:
78
+ r_copy = r.copy()
79
+ r_copy["origin_ips"] = ", ".join(r_copy["origin_ips"])
80
+ writer.writerow(r_copy)
81
+
82
+ print(f"\n✅ Análise de cabeçalhos salva em JSON: {json_file} e CSV: {csv_file}")
83
+
84
+ if __name__ == "__main__":
85
+ json_input = input("Digite o caminho do JSON gerado pelo email_parser: ").strip()
86
+ results = analyze_emails_from_json(json_input)
87
+ export_analysis(results)
core/models/orm.py ADDED
@@ -0,0 +1,48 @@
1
+ from sqlalchemy import Column, String, Text, ForeignKey, DateTime, Integer
2
+ from sqlalchemy.orm import relationship
3
+ from datetime import datetime
4
+ import uuid
5
+ from core.db.db import Base
6
+
7
+ class Module(Base):
8
+ __tablename__ = "modules"
9
+
10
+ id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
11
+ name = Column(String, unique=True, index=True)
12
+ description = Column(Text)
13
+ functionalities = relationship("Functionality", back_populates="module")
14
+
15
+ class Functionality(Base):
16
+ __tablename__ = "functionalities"
17
+
18
+ id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
19
+ module_id = Column(String, ForeignKey("modules.id"))
20
+ name = Column(String)
21
+ description = Column(Text)
22
+
23
+ module = relationship("Module", back_populates="functionalities")
24
+ executions = relationship("Execution", back_populates="functionality")
25
+
26
+ class Execution(Base):
27
+ __tablename__ = "executions"
28
+
29
+ id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
30
+ functionality_id = Column(String, ForeignKey("functionalities.id"))
31
+ network = Column(String, nullable=False)
32
+ status = Column(String, default="started")
33
+ started_at = Column(DateTime, default=datetime.utcnow)
34
+ finished_at = Column(DateTime, nullable=True)
35
+ cli_version = Column(String, nullable=True)
36
+
37
+ functionality = relationship("Functionality", back_populates="executions")
38
+ result = relationship("Result", back_populates="execution", uselist=False)
39
+
40
+ class Result(Base):
41
+ __tablename__ = "results"
42
+
43
+ id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
44
+ execution_id = Column(String, ForeignKey("executions.id"))
45
+ data = Column(Text)
46
+ created_at = Column(DateTime, default=datetime.utcnow)
47
+
48
+ execution = relationship("Execution", back_populates="result")
core/models/schemas.py ADDED
@@ -0,0 +1,76 @@
1
+ from pydantic import BaseModel
2
+ from typing import List, Optional, Any
3
+ from datetime import datetime
4
+
5
+ class ReportBase(BaseModel):
6
+ file_path: str
7
+
8
+ class ReportCreate(ReportBase):
9
+ result_id: str
10
+
11
+ class Report(ReportBase):
12
+ id: str
13
+ created_at: str
14
+
15
+ class Config:
16
+ orm_mode = True
17
+
18
+ class ResultBase(BaseModel):
19
+ data: str
20
+
21
+ class ResultCreate(ResultBase):
22
+ execution_id: str
23
+
24
+ class Result(ResultBase):
25
+ id: str
26
+ created_at: datetime
27
+
28
+ class Config:
29
+ orm_mode = True
30
+
31
+ class ExecutionBase(BaseModel):
32
+ network: str
33
+ cli_version: Optional[str] = None
34
+
35
+ class ExecutionCreateSchema(ExecutionBase):
36
+ func_id: str
37
+ data: Optional[Any] = None
38
+
39
+ class ExecutionSchema(ExecutionBase):
40
+ id: str
41
+ functionality_id: str
42
+ status: str
43
+ started_at: datetime
44
+ finished_at: Optional[datetime]
45
+ result: Optional[Result] = None
46
+
47
+ class Config:
48
+ orm_mode = True
49
+
50
+ class FunctionalityBase(BaseModel):
51
+ name: str
52
+ description: Optional[str]
53
+
54
+ class FunctionalityCreate(FunctionalityBase):
55
+ module_id: str
56
+
57
+ class Functionality(FunctionalityBase):
58
+ id: str
59
+ executions: List[ExecutionSchema] = []
60
+
61
+ class Config:
62
+ orm_mode = True
63
+
64
+ class ModuleBase(BaseModel):
65
+ name: str
66
+ description: Optional[str]
67
+
68
+ class ModuleCreate(ModuleBase):
69
+ pass
70
+
71
+ class Module(ModuleBase):
72
+ id: str
73
+ functionalities: List[Functionality] = []
74
+
75
+ class Config:
76
+ orm_mode = True
File without changes
@@ -0,0 +1,16 @@
1
+ from scapy.all import ARP, Ether, srp
2
+ import typer
3
+
4
+ def arp_scan(network_ips):
5
+ active_hosts = []
6
+
7
+ typer.echo(f"[+] Iniciando ARP scan ({len(network_ips)} IPs)...")
8
+ with typer.progressbar(network_ips, label="ARP Scan") as progress:
9
+ for ip in network_ips:
10
+ pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip)
11
+ ans, _ = srp(pkt, timeout=1, verbose=0)
12
+ if ans:
13
+ active_hosts.append(ip)
14
+ progress.update(1)
15
+
16
+ return active_hosts
@@ -0,0 +1,90 @@
1
+ import socket
2
+ import typer
3
+ import json
4
+ import csv
5
+ from pathlib import Path
6
+
7
+ COMMON_SUBDOMAINS = [
8
+ "www", "mail", "ftp", "webmail", "smtp", "imap", "pop", "vpn",
9
+ "portal", "intranet", "extranet", "remote", "login", "sso", "admin",
10
+ "test", "dev", "staging", "qa", "demo", "beta", "preprod",
11
+ "api", "api1", "api2", "graphql",
12
+ "db", "database", "mysql", "postgres", "mongo", "redis", "mssql",
13
+ "files", "ftp", "storage", "cdn", "download", "uploads",
14
+ "secure", "firewall", "monitor", "logs", "siem",
15
+ "ns1", "ns2", "ns3", "dns1", "dns2",
16
+ "cloud", "aws", "azure", "gcp",
17
+ "shop", "store", "payment", "support", "helpdesk", "status"
18
+ ]
19
+
20
+ def reverse_dns(ip: str):
21
+ try:
22
+ hostname, _, _ = socket.gethostbyaddr(ip)
23
+
24
+ return {"ip": ip, "hostname": hostname}
25
+ except socket.herror:
26
+ return {"ip": ip, "hostname": "N/A"}
27
+
28
+ def dns_lookup(domain: str):
29
+ try:
30
+ host, _, ips = socket.gethostbyname_ex(domain)
31
+
32
+ return {"domain": domain, "ips": ips}
33
+ except socket.gaierror:
34
+ return {"domain": domain, "ips": []}
35
+
36
+ def brute_subdomains(domain: str):
37
+ found = []
38
+ for sub in COMMON_SUBDOMAINS:
39
+ subdomain = f"{sub}.{domain}"
40
+ try:
41
+ host, _, ips = socket.gethostbyname_ex(subdomain)
42
+ found.append({"domain": subdomain, "ips": ips})
43
+ except socket.gaierror:
44
+ continue
45
+
46
+ return found
47
+
48
+ def save_results(results: list, output_dir: str, filename: str = "dns_recon"):
49
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
50
+
51
+ json_file = Path(output_dir) / f"{filename}.json"
52
+ csv_file = Path(output_dir) / f"{filename}.csv"
53
+
54
+ with open(json_file, "w", encoding="utf-8") as jf:
55
+ json.dump(results, jf, indent=4, ensure_ascii=False)
56
+
57
+ with open(csv_file, "w", newline="", encoding="utf-8") as cf:
58
+ writer = csv.writer(cf)
59
+ writer.writerow(["domain", "ips", "hostname", "ip"])
60
+ for item in results:
61
+ writer.writerow([
62
+ item.get("domain", ""),
63
+ ", ".join(item.get("ips", [])),
64
+ item.get("hostname", ""),
65
+ item.get("ip", "")
66
+ ])
67
+
68
+ return {"json_file": str(json_file), "csv_file": str(csv_file)}
69
+
70
+ def dns_recon(ips_or_domains: list[str], output_dir: str = None, with_subdomains: bool = False):
71
+ results = []
72
+
73
+ typer.echo(f"[+] Iniciando DNS Recon ({len(ips_or_domains)} alvos)...")
74
+ with typer.progressbar(ips_or_domains, label="DNS Recon") as progress:
75
+ for target in progress:
76
+ if target.count(".") == 3 and all(x.isdigit() for x in target.split(".")):
77
+ results.append(reverse_dns(target))
78
+ else:
79
+ result = dns_lookup(target)
80
+ results.append(result)
81
+
82
+ if with_subdomains:
83
+ subs = brute_subdomains(target)
84
+ results.extend(subs)
85
+
86
+ if output_dir:
87
+ files = save_results(results, output_dir)
88
+ typer.echo(f"[+] Resultados salvos em {files['json_file']} e {files['csv_file']}")
89
+
90
+ return results
@@ -0,0 +1,117 @@
1
+ import subprocess
2
+ import platform
3
+ import re
4
+ import typer
5
+ from concurrent.futures import ThreadPoolExecutor, as_completed
6
+ import threading
7
+
8
+ SERVER_PORTS = [21, 22, 23, 25, 53, 80, 443, 139, 445, 3306, 3389]
9
+ SERVER_TYPE_MAP = {
10
+ 21: "FTP",
11
+ 22: "SSH",
12
+ 23: "Telnet",
13
+ 25: "SMTP",
14
+ 53: "DNS",
15
+ 80: "Web HTTP",
16
+ 443: "Web HTTPS",
17
+ 139: "SMB",
18
+ 445: "SMB",
19
+ 3306: "MySQL",
20
+ 3389: "RDP"
21
+ }
22
+
23
+ VERSION_ALERTS = {
24
+ "ftp": ["vsftpd 2.3.4"],
25
+ "ssh": ["OpenSSH 5.3"],
26
+ "smb": ["Samba 3.0"],
27
+ "mysql": ["MySQL 5.5"]
28
+ }
29
+
30
+ lock = threading.Lock()
31
+
32
+ def extract_service_version(banner: str):
33
+ if not banner:
34
+ return None
35
+ match = re.search(r"([a-zA-Z]+)[ /]?([0-9]+\.[0-9]+(?:\.[0-9]+)?)", banner)
36
+ if match:
37
+ return f"{match.group(1)} {match.group(2)}"
38
+ return banner.strip()
39
+
40
+ def detect_os_single(ip: str):
41
+ os_estimate = "Desconhecido"
42
+ try:
43
+ ping_cmd = ["ping", "-c", "1", "-W", "1", ip] if platform.system() != "Windows" else ["ping", "-n", "1", "-w", "1000", ip]
44
+ ping_proc = subprocess.run(ping_cmd, capture_output=True, text=True)
45
+ output = ping_proc.stdout
46
+ ttl_line = next((line for line in output.splitlines() if "ttl=" in line.lower()), None)
47
+ if ttl_line:
48
+ ttl = int(ttl_line.lower().split("ttl=")[1].split()[0])
49
+ if ttl <= 64:
50
+ os_estimate = "Linux/Unix"
51
+ elif ttl <= 128:
52
+ os_estimate = "Windows"
53
+ except Exception:
54
+ os_estimate = "Host inatingível"
55
+ return os_estimate
56
+
57
+ def detect_os(ip: str, ports=None, mac_vendor=None):
58
+ os_estimate = detect_os_single(ip)
59
+ host_type = "Desconhecido"
60
+ services = []
61
+ alerts = []
62
+
63
+ if ports:
64
+ for p in ports:
65
+ banner = p.get("banner", "").lower()
66
+ service_name = SERVER_TYPE_MAP.get(p["port"], f"Port {p['port']}")
67
+ services.append(service_name)
68
+
69
+ if any(x in banner for x in ["microsoft", "windows"]):
70
+ os_estimate = "Windows"
71
+ elif any(x in banner for x in ["linux", "ubuntu", "debian", "centos", "red hat"]):
72
+ os_estimate = "Linux/Unix"
73
+
74
+ for key, vulnerable_versions in VERSION_ALERTS.items():
75
+ if key in banner:
76
+ for v in vulnerable_versions:
77
+ if v.lower() in banner:
78
+ alerts.append(f"{service_name} vulnerável ({v})")
79
+
80
+ version = extract_service_version(p.get("banner"))
81
+ if version:
82
+ p["version"] = version
83
+
84
+ if mac_vendor:
85
+ vendor = mac_vendor.lower()
86
+ if any(x in vendor for x in ["cisco", "huawei", "juniper", "mikrotik"]):
87
+ os_estimate = "Dispositivo de rede"
88
+ host_type = "Roteador/Switch"
89
+
90
+ if ports and host_type == "Desconhecido":
91
+ port_numbers = [p["port"] for p in ports]
92
+ server_ports = [p for p in SERVER_PORTS if p in port_numbers]
93
+ host_type = "Servidor" if server_ports else "Desktop"
94
+
95
+ return {
96
+ "os": os_estimate,
97
+ "host_type": host_type,
98
+ "services": services,
99
+ "alerts": alerts
100
+ }
101
+
102
+ def detect_os_with_progress(ip_ports_list):
103
+ results = []
104
+ with typer.progressbar(ip_ports_list, label="Fingerprinting") as progress:
105
+ with ThreadPoolExecutor(max_workers=50) as executor:
106
+ futures = {executor.submit(detect_os, ip_ports['ip'], ip_ports.get('ports'), ip_ports.get('mac')): ip_ports for ip_ports in ip_ports_list}
107
+
108
+ for future in as_completed(futures):
109
+ ip_ports = futures[future]
110
+ try:
111
+ res = future.result()
112
+ with lock:
113
+ results.append({"ip": ip_ports['ip'], "fingerprint": res})
114
+ except Exception:
115
+ pass
116
+ progress.update(1)
117
+ return results
@@ -0,0 +1,29 @@
1
+ import typer
2
+ import ipwhois
3
+
4
+ def ip_info_lookup(ip):
5
+ try:
6
+ obj = ipwhois.IPWhois(ip)
7
+ res = obj.lookup_rdap()
8
+
9
+ return {
10
+ "ip": ip,
11
+ "asn": res.get("asn"),
12
+ "asn_country": res.get("asn_country_code"),
13
+ "network_name": res.get("network", {}).get("name"),
14
+ "org": res.get("network", {}).get("org"),
15
+ "cidr": res.get("network", {}).get("cidr")
16
+ }
17
+ except Exception:
18
+ return {"ip": ip, "error": "Não foi possível obter informações"}
19
+
20
+ def ip_info_batch(ips):
21
+ results = []
22
+
23
+ typer.echo(f"[+] Iniciando IP Info Lookup ({len(ips)} IPs)...")
24
+ with typer.progressbar(ips, label="IP Info") as progress:
25
+ for ip in ips:
26
+ results.append(ip_info_lookup(ip))
27
+ progress.update(1)
28
+
29
+ return results
@@ -0,0 +1,120 @@
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
+ }
@@ -0,0 +1,53 @@
1
+ import platform
2
+ import subprocess
3
+ from scapy.all import conf, ARP, Ether, srp
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ import threading
6
+ import typer
7
+
8
+ conf.verb = 0
9
+ lock = threading.Lock()
10
+
11
+ def parse_network(network: str):
12
+ if "-" in network:
13
+ base_ip = network.rsplit(".", 1)[0]
14
+ start, end = network.rsplit(".", 1)[1].split("-")
15
+ return [f"{base_ip}.{i}" for i in range(int(start), int(end)+1)]
16
+ else:
17
+ return [network]
18
+
19
+ def ping_host(ip: str) -> bool:
20
+ try:
21
+ if platform.system() == "Windows":
22
+ output = subprocess.run(
23
+ ["ping", "-n", "1", "-w", "500", ip],
24
+ capture_output=True,
25
+ text=True
26
+ )
27
+ return "TTL=" in output.stdout.upper()
28
+ else:
29
+ pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip)
30
+ ans, _ = srp(pkt, timeout=1, verbose=0)
31
+ return bool(ans)
32
+ except Exception:
33
+ return False
34
+
35
+ def ping_sweep(network: str):
36
+ ips = parse_network(network)
37
+ hosts = []
38
+
39
+ with typer.progressbar(ips, label="Ping Sweep") as progress:
40
+ with ThreadPoolExecutor(max_workers=50) as executor:
41
+ futures = {executor.submit(ping_host, ip): ip for ip in ips}
42
+
43
+ for future in as_completed(futures):
44
+ ip = futures[future]
45
+ try:
46
+ if future.result():
47
+ with lock:
48
+ hosts.append(ip)
49
+ except Exception:
50
+ pass
51
+ progress.update(1)
52
+
53
+ return hosts