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.
- cli/__init__.py +0 -0
- cli/commands/__init__.py +0 -0
- cli/commands/browser.py +220 -0
- cli/commands/describe.py +54 -0
- cli/commands/email.py +3 -0
- cli/commands/network.py +126 -0
- cli/commands/utils.py +3 -0
- cli/main.py +18 -0
- core/__init__.py +0 -0
- core/browser/browser_history.py +156 -0
- core/browser/common_words.py +60 -0
- core/browser/downloads_history.py +129 -0
- core/browser/fav_screen.py +70 -0
- core/browser/logins.py +108 -0
- core/browser/unusual_patterns.py +111 -0
- core/data/__init__.py +0 -0
- core/data/data_recovery.py +157 -0
- core/db/db.py +12 -0
- core/email/email_parser.py +87 -0
- core/email/header_analysis.py +87 -0
- core/models/orm.py +48 -0
- core/models/schemas.py +76 -0
- core/network/__init__.py +0 -0
- core/network/arp_scan.py +16 -0
- core/network/dns_recon.py +90 -0
- core/network/fingerprinting.py +117 -0
- core/network/ip_info.py +29 -0
- core/network/network_map.py +120 -0
- core/network/ping_sweep.py +53 -0
- core/network/port_scanner.py +93 -0
- core/network/smb_scan.py +32 -0
- core/network/snmp_scan.py +43 -0
- core/network/traceroute.py +40 -0
- core/network/utils.py +27 -0
- core/registry.py +25 -0
- core/utils/__init__.py +0 -0
- core/utils/gerar_amostras.py +33 -0
- forensic_cli-1.2.5.dist-info/METADATA +293 -0
- forensic_cli-1.2.5.dist-info/RECORD +44 -0
- forensic_cli-1.2.5.dist-info/WHEEL +5 -0
- forensic_cli-1.2.5.dist-info/entry_points.txt +2 -0
- forensic_cli-1.2.5.dist-info/licenses/LICENSE +21 -0
- forensic_cli-1.2.5.dist-info/licenses/LICENSE-GPL +675 -0
- 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
|
core/network/__init__.py
ADDED
|
File without changes
|
core/network/arp_scan.py
ADDED
|
@@ -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
|
core/network/ip_info.py
ADDED
|
@@ -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
|