gyntoolkit 2.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gyntoolkit/__init__.py ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """GynToolkit — canivete suíço de pentest (recon, scan, brute, utils).
3
+
4
+ Este ``__init__`` re-exporta a API pública para manter compatibilidade com
5
+ ``import gyntoolkit as g`` (usado pelo lab E2E) e pelo entry point do pyproject.
6
+ """
7
+
8
+ __version__ = "2.1.1"
9
+
10
+ from . import i18n, ui
11
+ from .brute import (
12
+ cupp_generate,
13
+ http_bruteforce,
14
+ load_wordlist,
15
+ print_ethical_warning,
16
+ ssh_bruteforce,
17
+ )
18
+ from .cli import main_entry, main_flow
19
+ from .config import CONFIG, DEFAULTS, load_config
20
+ from .core import (
21
+ LOGO,
22
+ MAX_THREADS,
23
+ PROJECT_ROOT,
24
+ PROMPT,
25
+ TOP_PORTS,
26
+ clear_screen,
27
+ is_admin_windows,
28
+ log,
29
+ sanitize_input,
30
+ show_menu,
31
+ )
32
+ from .export import export_html, export_json, save_report
33
+ from .recon import (
34
+ DNS_TYPES,
35
+ TECH_SIGNATURES,
36
+ dns_lookup,
37
+ escolher_tipo_dns,
38
+ geo_ip,
39
+ hibp_breaches,
40
+ http_fingerprint,
41
+ internetdb_lookup,
42
+ mac_vendor,
43
+ reverse_dns,
44
+ ssl_inspect,
45
+ subdomain_enum,
46
+ traceroute,
47
+ whois_lookup,
48
+ )
49
+ from .scan import (
50
+ check_vulnerabilities,
51
+ connect_scan,
52
+ get_banner,
53
+ network_discovery,
54
+ perform_scan,
55
+ syn_scan,
56
+ )
57
+ from .utils import b64_decode, b64_encode, hash_file, hash_text, jwt_decode
58
+
59
+ __all__ = [
60
+ "__version__",
61
+ # core
62
+ "LOGO", "PROMPT", "TOP_PORTS", "MAX_THREADS", "PROJECT_ROOT", "log",
63
+ "clear_screen", "is_admin_windows", "sanitize_input", "show_menu",
64
+ # scan
65
+ "syn_scan", "connect_scan", "get_banner", "check_vulnerabilities",
66
+ "network_discovery", "perform_scan",
67
+ # recon
68
+ "whois_lookup", "dns_lookup", "geo_ip", "reverse_dns", "subdomain_enum",
69
+ "ssl_inspect", "http_fingerprint", "internetdb_lookup", "hibp_breaches",
70
+ "mac_vendor", "traceroute", "escolher_tipo_dns", "DNS_TYPES", "TECH_SIGNATURES",
71
+ # brute
72
+ "print_ethical_warning", "load_wordlist", "cupp_generate",
73
+ "ssh_bruteforce", "http_bruteforce",
74
+ # utils
75
+ "hash_text", "hash_file", "b64_encode", "b64_decode", "jwt_decode",
76
+ # config / export / ui
77
+ "CONFIG", "DEFAULTS", "load_config", "save_report", "export_json", "export_html", "ui", "i18n",
78
+ # cli
79
+ "main_flow", "main_entry",
80
+ ]
gyntoolkit/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python3
2
+ """Permite executar o pacote com ``python -m gyntoolkit``."""
3
+
4
+ from .cli import main_entry
5
+
6
+ if __name__ == "__main__":
7
+ main_entry()
gyntoolkit/brute.py ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ """Brute force: aviso ético, wordlists, CUPP, SSH e HTTP (basic/form)."""
3
+
4
+ import asyncio
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from colorama import Fore, Style
11
+
12
+ from . import i18n
13
+ from .core import PROJECT_ROOT, log
14
+
15
+
16
+ def print_ethical_warning(action: str) -> bool:
17
+ """Alerta ético antes de operação intrusiva. Retorna True se autorizado."""
18
+ print(f"\n{Fore.RED}{'=' * 60}{Style.RESET_ALL}")
19
+ print(f"{Fore.RED}{i18n.t('brute.warn_active', action=action)}{Style.RESET_ALL}")
20
+ print(f"{Fore.RED}{i18n.t('brute.warn_authorized')}{Style.RESET_ALL}")
21
+ print(f"{Fore.RED}{i18n.t('brute.warn_crime')}{Style.RESET_ALL}")
22
+ print(f"{Fore.RED}{'=' * 60}{Style.RESET_ALL}")
23
+ confirm = input(f"{Fore.YELLOW}{i18n.t('brute.confirm')}{Style.RESET_ALL}").strip()
24
+ return confirm == "AUTORIZO"
25
+
26
+
27
+ def load_wordlist(path: str) -> list[str]:
28
+ """Carrega wordlist do disco, deduplicando e ignorando linhas vazias."""
29
+ p = Path(path).expanduser()
30
+ if not p.is_file():
31
+ print(f"{Fore.RED}{i18n.t('brute.wordlist_not_found', path=p)}{Style.RESET_ALL}")
32
+ return []
33
+ try:
34
+ with p.open("r", encoding="utf-8", errors="ignore") as f:
35
+ words = [w.strip() for w in f if w.strip()]
36
+ return list(dict.fromkeys(words))
37
+ except OSError as e:
38
+ log.error("Falha ao ler wordlist %s: %s", p, e)
39
+ return []
40
+
41
+
42
+ def cupp_generate() -> None:
43
+ """Chama CUPP interativo p/ gerar wordlist customizada."""
44
+ cupp_paths = [
45
+ PROJECT_ROOT / "cupp" / "cupp.py",
46
+ Path.cwd() / "cupp" / "cupp.py",
47
+ ]
48
+ cupp = next((p for p in cupp_paths if p.is_file()), None)
49
+ if not cupp:
50
+ print(f"{Fore.RED}{i18n.t('brute.cupp_not_found')}{Style.RESET_ALL}")
51
+ return
52
+
53
+ python_exe = shutil.which("python") or shutil.which("python3") or sys.executable
54
+ try:
55
+ subprocess.run([python_exe, str(cupp), "-i"], check=False)
56
+ except OSError as e:
57
+ log.error("CUPP execução falhou: %s", e)
58
+ print(f"{Fore.RED}{i18n.t('brute.cupp_err', err=e)}{Style.RESET_ALL}")
59
+
60
+
61
+ async def _ssh_try(host: str, port: int, user: str, password: str, timeout: int) -> tuple[str, str] | None:
62
+ """Tenta uma credencial SSH. Retorna (user, pass) se sucesso."""
63
+ import paramiko
64
+
65
+ def _attempt() -> bool:
66
+ client = paramiko.SSHClient()
67
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
68
+ try:
69
+ client.connect(
70
+ host, port=port, username=user, password=password,
71
+ timeout=timeout, allow_agent=False, look_for_keys=False,
72
+ banner_timeout=timeout, auth_timeout=timeout,
73
+ )
74
+ return True
75
+ except paramiko.AuthenticationException:
76
+ return False
77
+ except (paramiko.SSHException, OSError, EOFError) as e:
78
+ log.debug("SSH %s@%s:%s erro: %s", user, host, port, e)
79
+ return False
80
+ finally:
81
+ client.close()
82
+
83
+ ok = await asyncio.to_thread(_attempt)
84
+ return (user, password) if ok else None
85
+
86
+
87
+ async def ssh_bruteforce(
88
+ host: str,
89
+ users: list[str],
90
+ passwords: list[str],
91
+ port: int = 22,
92
+ workers: int = 8,
93
+ delay: float = 0.1,
94
+ timeout: int = 5,
95
+ ) -> list[tuple[str, str]]:
96
+ """Brute-force SSH assíncrono com controle de concorrência."""
97
+ try:
98
+ import paramiko # noqa: F401
99
+ except ImportError:
100
+ print(f"{Fore.RED}{i18n.t('brute.paramiko_missing')}{Style.RESET_ALL}")
101
+ return []
102
+
103
+ sem = asyncio.Semaphore(workers)
104
+ found: list[tuple[str, str]] = []
105
+ total = len(users) * len(passwords)
106
+ tried = 0
107
+
108
+ async def _guarded(u: str, p: str):
109
+ nonlocal tried
110
+ async with sem:
111
+ await asyncio.sleep(delay)
112
+ result = await _ssh_try(host, port, u, p, timeout)
113
+ tried += 1
114
+ if tried % 25 == 0:
115
+ print(f"{Fore.CYAN}{i18n.t('brute.progress', tried=tried, total=total)}{Style.RESET_ALL}")
116
+ if result:
117
+ found.append(result)
118
+ print(f"{Fore.GREEN}{i18n.t('brute.ssh_valid', user=u, pwd=p)}{Style.RESET_ALL}")
119
+
120
+ tasks = [_guarded(u, p) for u in users for p in passwords]
121
+ await asyncio.gather(*tasks)
122
+ return found
123
+
124
+
125
+ async def http_bruteforce(
126
+ url: str,
127
+ users: list[str],
128
+ passwords: list[str],
129
+ mode: str = "basic",
130
+ user_field: str = "username",
131
+ pass_field: str = "password",
132
+ fail_signature: str = "",
133
+ workers: int = 10,
134
+ delay: float = 0.1,
135
+ timeout: int = 10,
136
+ ) -> list[tuple[str, str]]:
137
+ """Brute-force HTTP (basic-auth ou form POST)."""
138
+ try:
139
+ import aiohttp
140
+ except ImportError:
141
+ print(f"{Fore.RED}{i18n.t('brute.aiohttp_missing')}{Style.RESET_ALL}")
142
+ return []
143
+
144
+ sem = asyncio.Semaphore(workers)
145
+ found: list[tuple[str, str]] = []
146
+ total = len(users) * len(passwords)
147
+ tried = 0
148
+
149
+ timeout_cfg = aiohttp.ClientTimeout(total=timeout)
150
+ connector = aiohttp.TCPConnector(limit=workers, ssl=False)
151
+
152
+ async with aiohttp.ClientSession(timeout=timeout_cfg, connector=connector) as session:
153
+ async def _attempt(u: str, p: str):
154
+ nonlocal tried
155
+ async with sem:
156
+ await asyncio.sleep(delay)
157
+ try:
158
+ if mode == "basic":
159
+ auth = aiohttp.BasicAuth(u, p)
160
+ async with session.get(url, auth=auth) as r:
161
+ ok = r.status not in (401, 403)
162
+ else:
163
+ payload = {user_field: u, pass_field: p}
164
+ async with session.post(url, data=payload, allow_redirects=False) as r:
165
+ body = await r.text()
166
+ ok = r.status in (200, 302) and (not fail_signature or fail_signature not in body)
167
+ except (aiohttp.ClientError, asyncio.TimeoutError) as e:
168
+ log.debug("HTTP %s %s:%s erro: %s", url, u, p, e)
169
+ ok = False
170
+
171
+ tried += 1
172
+ if tried % 25 == 0:
173
+ print(f"{Fore.CYAN}{i18n.t('brute.progress', tried=tried, total=total)}{Style.RESET_ALL}")
174
+ if ok:
175
+ found.append((u, p))
176
+ print(f"{Fore.GREEN}{i18n.t('brute.http_valid', user=u, pwd=p)}{Style.RESET_ALL}")
177
+
178
+ await asyncio.gather(*[_attempt(u, p) for u in users for p in passwords])
179
+ return found