moriarty-project 0.1.22__py3-none-any.whl → 0.1.24__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 (27) hide show
  1. moriarty/__init__.py +1 -1
  2. moriarty/cli/app.py +4 -3
  3. moriarty/cli/domain_cmd.py +5 -1
  4. moriarty/modules/directory_fuzzer.py +25 -5
  5. moriarty/modules/web_crawler.py +448 -91
  6. {moriarty_project-0.1.22.dist-info → moriarty_project-0.1.24.dist-info}/METADATA +3 -3
  7. {moriarty_project-0.1.22.dist-info → moriarty_project-0.1.24.dist-info}/RECORD +9 -27
  8. moriarty/modules/wifippler/__init__.py +0 -92
  9. moriarty/modules/wifippler/cli/__init__.py +0 -8
  10. moriarty/modules/wifippler/cli/commands.py +0 -123
  11. moriarty/modules/wifippler/core/__init__.py +0 -94
  12. moriarty/modules/wifippler/core/attacks/__init__.py +0 -146
  13. moriarty/modules/wifippler/core/attacks/deauth.py +0 -262
  14. moriarty/modules/wifippler/core/attacks/handshake.py +0 -402
  15. moriarty/modules/wifippler/core/attacks/pmkid.py +0 -424
  16. moriarty/modules/wifippler/core/attacks/wep.py +0 -467
  17. moriarty/modules/wifippler/core/attacks/wpa.py +0 -446
  18. moriarty/modules/wifippler/core/attacks/wps.py +0 -474
  19. moriarty/modules/wifippler/core/models/__init__.py +0 -10
  20. moriarty/modules/wifippler/core/models/network.py +0 -240
  21. moriarty/modules/wifippler/core/scanner.py +0 -903
  22. moriarty/modules/wifippler/core/utils/__init__.py +0 -624
  23. moriarty/modules/wifippler/core/utils/exec.py +0 -182
  24. moriarty/modules/wifippler/core/utils/network.py +0 -262
  25. moriarty/modules/wifippler/core/utils/system.py +0 -153
  26. {moriarty_project-0.1.22.dist-info → moriarty_project-0.1.24.dist-info}/WHEEL +0 -0
  27. {moriarty_project-0.1.22.dist-info → moriarty_project-0.1.24.dist-info}/entry_points.txt +0 -0
@@ -1,903 +0,0 @@
1
- """
2
- Módulo de escaneamento WiFi para o WifiPPLER.
3
-
4
- Este módulo fornece funcionalidades avançadas para descoberta e análise de redes WiFi,
5
- incluindo detecção de clientes, análise de sinal e suporte a diferentes modos de varredura.
6
- """
7
- import asyncio
8
- import json
9
- import time
10
- import signal
11
- import tempfile
12
- import shutil
13
- from pathlib import Path
14
- from typing import Dict, List, Optional, Set, Tuple, Any, Callable
15
- from dataclasses import dataclass, field, asdict
16
- from datetime import datetime, timedelta
17
- import subprocess
18
- import re
19
- import logging
20
- from enum import Enum, auto
21
- from concurrent.futures import ThreadPoolExecutor
22
-
23
- from rich.console import Console
24
- from rich.table import Table, Column
25
- from rich.progress import (
26
- Progress, SpinnerColumn, TextColumn, BarColumn,
27
- TimeElapsedColumn, TaskID
28
- )
29
-
30
- from moriarty.modules.wifippler.core.utils import (
31
- run_command_async, is_root, get_interface_mac,
32
- set_monitor_mode, restore_network_interface, command_exists,
33
- randomize_mac, get_wireless_interfaces
34
- )
35
-
36
- from moriarty.modules.wifippler.core.models.network import (
37
- WiFiSecurityType, SECURITY_MAP
38
- )
39
-
40
- # Configuração de logging
41
- logging.basicConfig(
42
- level=logging.INFO,
43
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
44
- )
45
- logger = logging.getLogger(__name__)
46
-
47
- class ScanMode(Enum):
48
- """Modos de varredura disponíveis."""
49
- ACTIVE = auto() # Varredura ativa (envia pacotes)
50
- PASSIVE = auto() # Varredura passiva (apenas escuta)
51
- FAST = auto() # Varredura rápida (canais mais comuns)
52
- DEEP = auto() # Varredura profunda (todos os canais)
53
-
54
- # WiFiSecurityType agora é importado de moriarty.modules.wifippler.core.models.network
55
-
56
- @dataclass
57
- class WiFiClient:
58
- """Representa um dispositivo cliente conectado a uma rede WiFi."""
59
- mac: str
60
- bssid: str
61
- signal: int
62
- packets: int = 0
63
- first_seen: datetime = field(default_factory=datetime.utcnow)
64
- last_seen: datetime = field(default_factory=datetime.utcnow)
65
- vendor: Optional[str] = None
66
- is_associated: bool = True
67
-
68
- def to_dict(self) -> Dict:
69
- """Converte para dicionário."""
70
- return {
71
- 'mac': self.mac,
72
- 'bssid': self.bssid,
73
- 'signal': self.signal,
74
- 'packets': self.packets,
75
- 'first_seen': self.first_seen.isoformat(),
76
- 'last_seen': self.last_seen.isoformat(),
77
- 'vendor': self.vendor,
78
- 'is_associated': self.is_associated
79
- }
80
-
81
- def update_seen(self):
82
- """Atualiza o timestamp da última vez que foi visto."""
83
- self.last_seen = datetime.utcnow()
84
-
85
- @dataclass
86
- class WiFiNetwork:
87
- """Represents a discovered WiFi network."""
88
- bssid: str
89
- ssid: str
90
- channel: int
91
- signal: int
92
- encryption: str
93
- cipher: str
94
- authentication: str
95
- wps: bool = False
96
- wps_locked: bool = False
97
- clients: List[Dict[str, str]] = field(default_factory=list)
98
- last_seen: str = field(default_factory=lambda: datetime.utcnow().isoformat())
99
-
100
- def to_dict(self) -> Dict:
101
- """Convert to dictionary."""
102
- return asdict(self)
103
-
104
- def to_json(self) -> str:
105
- """Convert to JSON string."""
106
- return json.dumps(self.to_dict(), indent=2)
107
-
108
- class WiFiScanner:
109
- """Classe principal para escaneamento de redes WiFi.
110
-
111
- Esta classe fornece funcionalidades avançadas para descoberta e análise
112
- de redes WiFi, incluindo detecção de clientes, análise de sinal e suporte
113
- a diferentes modos de varredura.
114
- """
115
-
116
- def __init__(
117
- self,
118
- interface: str = None,
119
- scan_time: int = 10,
120
- scan_mode: ScanMode = ScanMode.ACTIVE,
121
- output_dir: str = None,
122
- random_mac: bool = False,
123
- verbose: bool = False
124
- ):
125
- """Inicializa o scanner WiFi.
126
-
127
- Args:
128
- interface: Interface de rede a ser usada para escaneamento
129
- scan_time: Tempo de escaneamento em segundos
130
- scan_mode: Modo de varredura (ACTIVE, PASSIVE, FAST, DEEP)
131
- output_dir: Diretório para salvar os resultados
132
- random_mac: Se deve usar um endereço MAC aleatório
133
- verbose: Habilita saída detalhada
134
- """
135
- if not is_root():
136
- raise PermissionError("Este aplicativo requer privilégios de root para executar.")
137
-
138
- # Configuração básica
139
- self.scan_time = max(5, min(scan_time, 300)) # Limita entre 5 e 300 segundos
140
- self.scan_mode = scan_mode
141
- self.verbose = verbose
142
- self.running = False
143
- self.original_mac = None
144
- self.original_mode = None
145
- self.temp_dir = None
146
-
147
- # Configuração de saída
148
- self.output_dir = Path(output_dir) if output_dir else Path.cwd() / 'wifippler_scan'
149
- self.output_file = self.output_dir / 'scan_results.json'
150
-
151
- # Configuração da interface
152
- self.interface = self._setup_interface(interface, random_mac)
153
-
154
- # Dados
155
- self.networks: Dict[str, WiFiNetwork] = {}
156
- self.clients: Dict[str, WiFiClient] = {}
157
- self.scan_start_time = None
158
- self.scan_end_time = None
159
-
160
- # Interface de usuário
161
- self.console = Console()
162
- self.progress = Progress(
163
- SpinnerColumn(),
164
- TextColumn("[progress.description]{task.description}"),
165
- BarColumn(bar_width=40),
166
- "[progress.percentage]{task.percentage:>3.0f}%",
167
- TimeElapsedColumn(),
168
- console=self.console,
169
- transient=True,
170
- )
171
-
172
- # Thread pool para operações em segundo plano
173
- self.executor = ThreadPoolExecutor(max_workers=4)
174
-
175
- # Configuração de sinal para encerramento gracioso
176
- self.should_stop = False
177
- signal.signal(signal.SIGINT, self._signal_handler)
178
- signal.signal(signal.SIGTERM, self._signal_handler)
179
-
180
- def _signal_handler(self, signum, frame):
181
- """Manipula sinais de encerramento."""
182
- self.console.print("\n[bold yellow]Recebido sinal de encerramento. Finalizando...[/]")
183
- self.should_stop = True
184
-
185
- def _setup_interface(self, interface: str = None, random_mac: bool = False) -> str:
186
- """Configura a interface de rede para escaneamento.
187
-
188
- Args:
189
- interface: Nome da interface
190
- random_mac: Se deve usar um endereço MAC aleatório
191
-
192
- Returns:
193
- str: Nome da interface configurada
194
- """
195
- # Obtém interfaces disponíveis
196
- interfaces = get_wireless_interfaces()
197
- if not interfaces:
198
- raise RuntimeError("Nenhuma interface WiFi encontrada.")
199
-
200
- # Seleciona a interface
201
- if interface:
202
- if interface not in [iface['name'] for iface in interfaces]:
203
- raise ValueError(f"Interface {interface} não encontrada ou não é sem fio.")
204
- selected_iface = interface
205
- else:
206
- # Usa a primeira interface sem fio disponível
207
- selected_iface = interfaces[0]['name']
208
-
209
- # Salva o estado original
210
- self.original_mac = get_interface_mac(selected_iface)
211
- self.original_mode = 'managed' # Assumindo modo gerenciado por padrão
212
-
213
- # Configura o endereço MAC aleatório, se solicitado
214
- if random_mac:
215
- if not randomize_mac(selected_iface):
216
- logger.warning("Falha ao definir endereço MAC aleatório. Continuando com o endereço original.")
217
-
218
- # Configura o modo monitor
219
- if not set_monitor_mode(selected_iface):
220
- raise RuntimeError(f"Falha ao configurar o modo monitor na interface {selected_iface}.")
221
-
222
- return selected_iface
223
-
224
- def _restore_interface(self) -> bool:
225
- """Restaura a interface para o estado original.
226
-
227
- Returns:
228
- bool: True se bem-sucedido, False caso contrário
229
- """
230
- try:
231
- # Restaura o modo original
232
- if self.original_mode == 'managed':
233
- if not restore_network_interface(self.interface):
234
- logger.error(f"Falha ao restaurar o modo gerenciado na interface {self.interface}.")
235
- return False
236
-
237
- # Restaura o endereço MAC original, se necessário
238
- if self.original_mac and get_interface_mac(self.interface) != self.original_mac:
239
- # Implementar lógica para restaurar o MAC original
240
- pass
241
-
242
- return True
243
-
244
- except Exception as e:
245
- logger.error(f"Erro ao restaurar a interface: {e}")
246
- return False
247
-
248
- def _create_output_dir(self) -> bool:
249
- """Cria o diretório de saída, se necessário.
250
-
251
- Returns:
252
- bool: True se bem-sucedido, False caso contrário
253
- """
254
- try:
255
- self.output_dir.mkdir(parents=True, exist_ok=True)
256
- return True
257
- except Exception as e:
258
- logger.error(f"Falha ao criar diretório de saída {self.output_dir}: {e}")
259
- return False
260
-
261
- def _parse_airodump_line(self, line: str) -> Optional[WiFiNetwork]:
262
- """Analisa uma linha de saída do airodump-ng.
263
-
264
- Args:
265
- line: Linha de saída do airodump-ng
266
-
267
- Returns:
268
- Optional[WiFiNetwork]: Objeto WiFiNetwork ou None se a linha for inválida
269
- """
270
- if not line.strip() or line.startswith('BSSID') or line.startswith('Station'):
271
- return None
272
-
273
- parts = [p.strip() for p in line.split(',') if p.strip()]
274
- if len(parts) < 10: # Não há dados suficientes para uma rede
275
- return None
276
-
277
- try:
278
- # Extrai informações básicas
279
- bssid = parts[0].upper()
280
- first_seen = datetime.utcnow()
281
- channel = int(parts[3]) if parts[3] else 1
282
- signal = int(parts[8].split()[0]) # Remove 'dBm' se presente
283
-
284
- # Determina o tipo de segurança
285
- encryption = parts[5].strip() or 'NONE'
286
- security = self._determine_security_type(encryption)
287
-
288
- # Cria o objeto de rede
289
- network = WiFiNetwork(
290
- bssid=bssid,
291
- ssid=parts[13] if len(parts) > 13 else "<hidden>",
292
- channel=channel,
293
- frequency=self._channel_to_frequency(channel),
294
- signal=signal,
295
- security=security,
296
- encryption=encryption,
297
- cipher=parts[6].strip() if len(parts) > 6 else '',
298
- authentication=parts[7].strip() if len(parts) > 7 else '',
299
- first_seen=first_seen,
300
- last_seen=first_seen,
301
- essid_hidden=len(parts) <= 13 or not parts[13].strip()
302
- )
303
-
304
- # Verifica recursos avançados
305
- self._detect_network_features(network, parts)
306
-
307
- return network
308
-
309
- except (IndexError, ValueError) as e:
310
- logger.debug(f"Não foi possível analisar a linha da rede: {line}. Erro: {e}")
311
- return None
312
-
313
- def _determine_security_type(self, encryption: str) -> WiFiSecurityType:
314
- """Determina o tipo de segurança com base na string de criptografia.
315
-
316
- Args:
317
- encryption: String de criptografia do airodump-ng
318
-
319
- Returns:
320
- WiFiSecurityType: Tipo de segurança detectado
321
- """
322
- if not encryption or encryption.upper() == 'NONE':
323
- return WiFiSecurityType.NONE
324
-
325
- encryption = encryption.upper()
326
-
327
- # Verifica se a criptografia está no mapeamento
328
- for key, security_type in SECURITY_MAP.items():
329
- if key in encryption:
330
- return security_type
331
-
332
- # Tenta inferir o tipo de segurança com base em padrões comuns
333
- if 'WPA3' in encryption and 'WPA2' in encryption:
334
- return WiFiSecurityType.WPA2
335
- elif 'WPA2' in encryption and 'WPA' in encryption:
336
- return WiFiSecurityType.WPA2
337
- elif 'WPA3' in encryption:
338
- return WiFiSecurityType.WPA3
339
- elif 'WPA2' in encryption:
340
- return WiFiSecurityType.WPA2
341
- elif 'WPA' in encryption:
342
- return WiFiSecurityType.WPA
343
- elif 'WEP' in encryption:
344
- return WiFiSecurityType.WEP
345
-
346
- # Se não conseguir determinar, retorna o tipo padrão
347
- return WiFiSecurityType.WPA2
348
-
349
- def _channel_to_frequency(self, channel: int) -> int:
350
- """Converte um número de canal para frequência em MHz.
351
-
352
- Args:
353
- channel: Número do canal (1-14 para 2.4GHz, 36-165 para 5GHz)
354
-
355
- Returns:
356
- int: Frequência em MHz
357
- """
358
- if 1 <= channel <= 13: # 2.4 GHz
359
- return 2412 + (channel - 1) * 5
360
- elif channel == 14: # 2.4 GHz (apenas Japão)
361
- return 2484
362
- elif 36 <= channel <= 165: # 5 GHz
363
- return 5180 + (channel - 36) * 5
364
- else:
365
- return 0 # Canal desconhecido
366
-
367
- def _detect_network_features(self, network: WiFiNetwork, parts: list) -> None:
368
- """Detecta recursos avançados da rede.
369
-
370
- Args:
371
- network: Objeto WiFiNetwork a ser atualizado
372
- parts: Partes da linha do airodump-ng
373
- """
374
- # Verifica WPS
375
- if len(parts) > 9 and 'WPS' in parts[9]:
376
- network.wps = True
377
- # Detalhes adicionais do WPS podem ser extraídos aqui
378
-
379
- # Verifica recursos avançados (HT/VHT/HE)
380
- if len(parts) > 10:
381
- flags = parts[10].upper()
382
- network.ht = 'HT' in flags
383
- network.vht = 'VHT' in flags
384
- network.he = 'HE' in flags
385
-
386
- # Verifica se é uma rede de alto rendimento
387
- network.high_throughput = network.ht or network.vht or network.he
388
- network.very_high_throughput = network.vht or network.he
389
-
390
- async def scan_networks(self) -> List[WiFiNetwork]:
391
- """Realiza a varredura de redes WiFi próximas.
392
-
393
- Returns:
394
- List[WiFiNetwork]: Lista de redes WiFi descobertas
395
- """
396
- if not self._create_output_dir():
397
- raise RuntimeError("Falha ao criar diretório de saída")
398
-
399
- self.scan_start_time = datetime.utcnow()
400
- self.running = True
401
- self.should_stop = False
402
-
403
- # Configura o prefixo do arquivo temporário
404
- temp_prefix = f"wifippler_scan_{int(time.time())}"
405
- csv_file = self.output_dir / f"{temp_prefix}-01.csv"
406
-
407
- # Configura os parâmetros do airodump-ng
408
- cmd = [
409
- "airodump-ng",
410
- "--write-interval", "1",
411
- "--output-format", "csv",
412
- "--write", str(self.output_dir / temp_prefix),
413
- ]
414
-
415
- # Adiciona parâmetros específicos do modo de varredura
416
- if self.scan_mode == ScanMode.FAST:
417
- cmd.extend(["--band", "bg"]) # Apenas 2.4GHz
418
- elif self.scan_mode == ScanMode.DEEP:
419
- cmd.extend(["--band", "abg"]) # 2.4GHz e 5GHz
420
-
421
- # Adiciona a interface no final
422
- cmd.append(self.interface)
423
-
424
- self.console.print(f"[bold blue]\n🔍 Iniciando varredura WiFi no modo {self.scan_mode.name}...\n")
425
-
426
- try:
427
- # Inicia o airodump-ng em segundo plano
428
- process = await asyncio.create_subprocess_exec(
429
- *cmd,
430
- stdout=asyncio.subprocess.PIPE,
431
- stderr=asyncio.subprocess.PIPE
432
- )
433
-
434
- # Inicia a barra de progresso
435
- with self.progress:
436
- task = self.progress.add_task(
437
- "[cyan]Escaneando redes...",
438
- total=self.scan_time
439
- )
440
-
441
- # Aguarda o arquivo de saída ser criado
442
- start_time = time.time()
443
- while not csv_file.exists() and (time.time() - start_time) < 5:
444
- await asyncio.sleep(0.5)
445
-
446
- # Monitora o progresso
447
- while not self.should_stop and (time.time() - start_time) < self.scan_time:
448
- await asyncio.sleep(0.5)
449
- self.progress.update(
450
- task,
451
- advance=0.5,
452
- description=f"[cyan]Escaneando redes (restam {max(0, int(self.scan_time - (time.time() - start_time)))}s)..."
453
- )
454
-
455
- # Atualiza a lista de redes periodicamente
456
- if int(time.time() - start_time) % 2 == 0:
457
- await self._update_networks_from_file(csv_file)
458
-
459
- # Finaliza o processo
460
- process.terminate()
461
- await process.wait()
462
-
463
- # Atualiza a lista de redes uma última vez
464
- if csv_file.exists():
465
- await self._update_networks_from_file(csv_file)
466
-
467
- return list(self.networks.values())
468
-
469
- except Exception as e:
470
- logger.error(f"Erro durante a varredura: {e}", exc_info=self.verbose)
471
- self.console.print(f"[red]Erro durante a varredura: {e}[/]")
472
- return []
473
-
474
- finally:
475
- self.running = False
476
- self.scan_end_time = datetime.utcnow()
477
- await self._cleanup()
478
-
479
- async def _update_networks_from_file(self, csv_file: Path) -> None:
480
- """Atualiza a lista de redes a partir do arquivo CSV do airodump-ng.
481
-
482
- Args:
483
- csv_file: Caminho para o arquivo CSV
484
- """
485
- try:
486
- if not csv_file.exists():
487
- return
488
-
489
- with open(csv_file, 'r', errors='ignore') as f:
490
- content = f.read()
491
-
492
- # Divide o conteúdo em seções de redes e clientes
493
- sections = re.split(r'\n\n', content.strip())
494
-
495
- if not sections:
496
- return
497
-
498
- # Processa a seção de redes
499
- network_lines = sections[0].split('\n')[1:] # Ignora o cabeçalho
500
- for line in network_lines:
501
- if not line.strip() or line.startswith('Station'):
502
- continue
503
-
504
- network = self._parse_airodump_line(line)
505
- if network:
506
- self._update_network(network)
507
-
508
- # Processa a seção de clientes, se existir
509
- if len(sections) > 1:
510
- client_lines = sections[1].split('\n')[1:] # Ignora o cabeçalho
511
- for line in client_lines:
512
- if not line.strip():
513
- continue
514
-
515
- self._process_client_line(line)
516
-
517
- except Exception as e:
518
- logger.error(f"Erro ao processar arquivo de saída: {e}", exc_info=self.verbose)
519
-
520
- def _update_network(self, network: WiFiNetwork) -> None:
521
- """Atualiza ou adiciona uma rede à lista de redes conhecidas.
522
-
523
- Args:
524
- network: Rede a ser atualizada/adicionada
525
- """
526
- if network.bssid in self.networks:
527
- # Atualiza a rede existente
528
- existing = self.networks[network.bssid]
529
- existing.signal = network.signal
530
- existing.last_seen = datetime.utcnow()
531
-
532
- # Atualiza outros campos, se necessário
533
- if network.ssid and network.ssid != "<hidden>":
534
- existing.ssid = network.ssid
535
-
536
- if network.channel:
537
- existing.channel = network.channel
538
- existing.frequency = self._channel_to_frequency(network.channel)
539
-
540
- # Atualiza a segurança, se disponível
541
- if network.security != WiFiSecurityType.NONE:
542
- existing.security = network.security
543
- existing.encryption = network.encryption
544
- existing.cipher = network.cipher
545
- existing.authentication = network.authentication
546
-
547
- else:
548
- # Adiciona uma nova rede
549
- self.networks[network.bssid] = network
550
-
551
- def _process_client_line(self, line: str) -> None:
552
- """Processa uma linha de cliente do airodump-ng.
553
-
554
- Args:
555
- line: Linha de saída do airodump-ng contendo informações do cliente
556
- """
557
- parts = [p.strip() for p in line.split(',') if p.strip()]
558
- if len(parts) < 6: # Não há dados suficientes para um cliente
559
- return
560
-
561
- try:
562
- client_mac = parts[0].upper()
563
- bssid = parts[5].upper()
564
-
565
- # Verifica se a rede do cliente está na nossa lista
566
- if bssid not in self.networks:
567
- return
568
-
569
- # Cria ou atualiza o cliente
570
- client = WiFiClient(
571
- mac=client_mac,
572
- bssid=bssid,
573
- signal=int(parts[3]) if parts[3] else 0,
574
- packets=int(parts[2]) if parts[2] else 0,
575
- is_associated=parts[4].strip() == '0', # 0 = associado, 1 = não associado
576
- )
577
-
578
- # Atualiza a lista de clientes da rede
579
- self.networks[bssid].add_client(client)
580
-
581
- except (IndexError, ValueError) as e:
582
- logger.debug(f"Não foi possível analisar a linha do cliente: {line}. Erro: {e}")
583
-
584
- async def _cleanup(self):
585
- """Limpa arquivos temporários e restaura o estado original."""
586
- try:
587
- # Encerra processos em segundo plano
588
- await run_command_async(["pkill", "-f", "airodump-ng"])
589
-
590
- # Limpa arquivos temporários
591
- temp_files = list(self.output_dir.glob("wifippler_scan_*"))
592
- for file in temp_files:
593
- try:
594
- if file.is_file():
595
- file.unlink()
596
- elif file.is_dir():
597
- shutil.rmtree(file)
598
- except Exception as e:
599
- logger.debug(f"Falha ao remover arquivo temporário {file}: {e}")
600
-
601
- # Restaura a interface de rede
602
- self._restore_interface()
603
-
604
- except Exception as e:
605
- logger.error(f"Erro durante a limpeza: {e}", exc_info=self.verbose)
606
-
607
- def display_networks(self, networks: List[WiFiNetwork] = None, sort_by: str = 'signal') -> None:
608
- """Exibe as redes descobertas em uma tabela formatada.
609
-
610
- Args:
611
- networks: Lista de redes a serem exibidas. Se None, usa as redes escaneadas.
612
- sort_by: Campo para ordenação (signal, channel, ssid, bssid)
613
- """
614
- if networks is None:
615
- networks = list(self.networks.values())
616
-
617
- if not networks:
618
- self.console.print("[yellow]Nenhuma rede encontrada.[/yellow]")
619
- return
620
-
621
- # Ordena as redes
622
- reverse_sort = sort_by == 'signal' # Ordem decrescente para sinal
623
- networks_sorted = sorted(
624
- networks,
625
- key=lambda x: (
626
- getattr(x, sort_by, 0) if hasattr(x, sort_by) else 0,
627
- x.ssid or ""
628
- ),
629
- reverse=reverse_sort
630
- )
631
-
632
- # Cria a tabela
633
- table = Table(
634
- title=f"📶 Redes WiFi Encontradas ({len(networks_sorted)})",
635
- show_header=True,
636
- header_style="bold magenta",
637
- expand=True
638
- )
639
-
640
- # Adiciona colunas
641
- table.add_column("#", style="dim", width=4)
642
- table.add_column("SSID", min_width=20)
643
- table.add_column("BSSID", width=18)
644
- table.add_column("Canal", width=8, justify="center")
645
- table.add_column("Sinal", width=12, justify="right")
646
- table.add_column("Segurança", width=15)
647
- table.add_column("Clientes", width=10, justify="center")
648
- table.add_column("WPS", width=6, justify="center")
649
-
650
- # Adiciona linhas
651
- for i, network in enumerate(networks_sorted, 1):
652
- # Formata o SSID (mostra "<hidden>" se estiver oculto)
653
- ssid = network.ssid if network.ssid and network.ssid != "<hidden>" else "[dim]&lt;oculto&gt;[/]"
654
-
655
- # Formata o sinal com cores
656
- if network.signal >= -50:
657
- signal_str = f"[green]{network.signal} dBm[/]"
658
- elif network.signal >= -70:
659
- signal_str = f"[yellow]{network.signal} dBm[/]"
660
- else:
661
- signal_str = f"[red]{network.signal} dBm[/]"
662
-
663
- # Formata a segurança
664
- security = network.security.value if network.security else network.encryption
665
-
666
- # Ícone WPS
667
- wps_icon = "✅" if network.wps else "❌"
668
- if network.wps_locked:
669
- wps_icon = "🔒"
670
-
671
- # Adiciona a linha à tabela
672
- table.add_row(
673
- str(i),
674
- ssid,
675
- network.bssid,
676
- str(network.channel),
677
- signal_str,
678
- security,
679
- str(len(network.clients)),
680
- wps_icon
681
- )
682
-
683
- # Exibe a tabela
684
- self.console.print()
685
- self.console.print(table)
686
-
687
- # Adiciona um resumo
688
- self.console.print(f"[bold]Total de redes:[/] {len(networks_sorted)}")
689
-
690
- # Contagem por tipo de segurança
691
- security_counts = {}
692
- for net in networks_sorted:
693
- sec = net.security.value if net.security else "Desconhecido"
694
- security_counts[sec] = security_counts.get(sec, 0) + 1
695
-
696
- if security_counts:
697
- self.console.print("\n[bold]Distribuição de segurança:[/]")
698
- for sec, count in sorted(security_counts.items()):
699
- self.console.print(f" • {sec}: {count}")
700
-
701
- # Tempo de escaneamento
702
- if self.scan_start_time and self.scan_end_time:
703
- duration = (self.scan_end_time - self.scan_start_time).total_seconds()
704
- self.console.print(f"\n[dim]Escaneamento concluído em {duration:.1f} segundos.[/]")
705
-
706
- def get_network_by_bssid(self, bssid: str) -> Optional[WiFiNetwork]:
707
- """Obtém uma rede pelo seu BSSID.
708
-
709
- Args:
710
- bssid: Endereço BSSID da rede
711
-
712
- Returns:
713
- Optional[WiFiNetwork]: A rede encontrada ou None
714
- """
715
- return self.networks.get(bssid.upper())
716
-
717
- def get_networks_by_ssid(self, ssid: str) -> List[WiFiNetwork]:
718
- """Obtém todas as redes com um determinado SSID.
719
-
720
- Args:
721
- ssid: Nome da rede (SSID) a ser procurado
722
-
723
- Returns:
724
- List[WiFiNetwork]: Lista de redes com o SSID especificado
725
- """
726
- return [net for net in self.networks.values()
727
- if net.ssid and net.ssid.lower() == ssid.lower()]
728
-
729
- def get_networks_by_security(self, security_type: WiFiSecurityType) -> List[WiFiNetwork]:
730
- """Obtém todas as redes com um determinado tipo de segurança.
731
-
732
- Args:
733
- security_type: Tipo de segurança a ser filtrado
734
-
735
- Returns:
736
- List[WiFiNetwork]: Lista de redes com o tipo de segurança especificado
737
- """
738
- return [net for net in self.networks.values()
739
- if net.security == security_type]
740
-
741
- def get_networks_with_clients(self) -> List[WiFiNetwork]:
742
- """Obtém todas as redes que possuem clientes conectados.
743
-
744
- Returns:
745
- List[WiFiNetwork]: Lista de redes com clientes
746
- """
747
- return [net for net in self.networks.values() if net.clients]
748
-
749
- def get_wps_networks(self) -> List[WiFiNetwork]:
750
- """Obtém todas as redes com WPS ativado.
751
-
752
- Returns:
753
- List[WiFiNetwork]: Lista de redes com WPS ativado
754
- """
755
- return [net for net in self.networks.values() if net.wps]
756
-
757
- def to_json(self, file_path: str = None) -> Optional[str]:
758
- """Converte as redes descobertas para JSON.
759
-
760
- Args:
761
- file_path: Caminho para salvar o arquivo JSON. Se None, retorna a string JSON.
762
-
763
- Returns:
764
- Optional[str]: String JSON se file_path for None, caso contrário None
765
- """
766
- data = {
767
- 'scan_start': self.scan_start_time.isoformat() if self.scan_start_time else None,
768
- 'scan_end': self.scan_end_time.isoformat() if self.scan_end_time else None,
769
- 'interface': self.interface,
770
- 'networks': [net.to_dict() for net in self.networks.values()]
771
- }
772
-
773
- json_str = json.dumps(data, indent=2, default=str)
774
-
775
- if file_path:
776
- try:
777
- with open(file_path, 'w') as f:
778
- f.write(json_str)
779
- return None
780
- except Exception as e:
781
- logger.error(f"Falha ao salvar arquivo JSON: {e}")
782
- return None
783
-
784
- return json_str
785
-
786
- async def run() -> None:
787
- """Função principal para executar o scanner a partir da linha de comando."""
788
- import argparse
789
-
790
- # Configura o parser de argumentos
791
- parser = argparse.ArgumentParser(description='WiFiPPLER - Ferramenta avançada de análise de redes WiFi')
792
- parser.add_argument('-i', '--interface', help='Interface de rede para escaneamento')
793
- parser.add_argument('-t', '--time', type=int, default=10,
794
- help='Tempo de escaneamento em segundos (padrão: 10)')
795
- parser.add_argument('-m', '--mode', choices=['active', 'passive', 'fast', 'deep'],
796
- default='active', help='Modo de varredura (padrão: active)')
797
- parser.add_argument('-o', '--output', help='Arquivo de saída para salvar os resultados em JSON')
798
- parser.add_argument('--random-mac', action='store_true',
799
- help='Usar endereço MAC aleatório')
800
- parser.add_argument('-v', '--verbose', action='store_true',
801
- help='Modo verboso (mais detalhes de depuração)')
802
-
803
- # Parse dos argumentos
804
- args = parser.parse_args()
805
-
806
- # Mapeia o modo de varredura
807
- scan_mode_map = {
808
- 'active': ScanMode.ACTIVE,
809
- 'passive': ScanMode.PASSIVE,
810
- 'fast': ScanMode.FAST,
811
- 'deep': ScanMode.DEEP
812
- }
813
-
814
- try:
815
- # Cria e configura o scanner
816
- scanner = WiFiScanner(
817
- interface=args.interface,
818
- scan_time=args.time,
819
- scan_mode=scan_mode_map[args.mode],
820
- random_mac=args.random_mac,
821
- verbose=args.verbose
822
- )
823
-
824
- # Executa a varredura
825
- console = Console()
826
- console.print("[bold green]🚀 Iniciando WiFiPPLER - Ferramenta de Análise WiFi[/]\n")
827
-
828
- # Mostra informações iniciais
829
- console.print(f"[bold]Interface:[/] {scanner.interface}")
830
- console.print(f"[bold]Modo de varredura:[/] {args.mode.capitalize()}")
831
- console.print(f"[bold]Tempo de escaneamento:[/] {args.time} segundos")
832
- console.print(f"[bold]Endereço MAC aleatório:[/] {'Sim' if args.random_mac else 'Não'}\n")
833
-
834
- # Realiza a varredura
835
- networks = await scanner.scan_networks()
836
-
837
- # Exibe os resultados
838
- scanner.display_networks(networks)
839
-
840
- # Salva os resultados em um arquivo, se solicitado
841
- if args.output:
842
- result = scanner.to_json(args.output)
843
- if result is None:
844
- console.print(f"[green]✅ Resultados salvos em {args.output}[/]")
845
- else:
846
- console.print(f"[yellow]⚠️ Resultados não foram salvos em {args.output}[/]")
847
-
848
- console.print("\n[bold green]✅ Análise concluída com sucesso![/]")
849
-
850
- except KeyboardInterrupt:
851
- console.print("\n[yellow]⚠️ Escaneamento interrompido pelo usuário.[/]")
852
- except Exception as e:
853
- console.print(f"[red]❌ Erro durante a execução: {e}[/]")
854
- if args.verbose:
855
- import traceback
856
- traceback.print_exc()
857
- finally:
858
- # Garante que a interface seja restaurada
859
- if 'scanner' in locals():
860
- await scanner._cleanup()
861
-
862
- async def main():
863
- """Função de entrada para testes rápidos."""
864
- try:
865
- console = Console()
866
- console.print("[bold blue]🔍 Iniciando teste do WiFiPPLER...[/]\n")
867
-
868
- # Cria o scanner com configurações padrão
869
- scanner = WiFiScanner(scan_time=15, verbose=True)
870
-
871
- # Executa a varredura
872
- console.print("[cyan]Escaneando redes próximas... (pressione Ctrl+C para interromper)[/]")
873
- networks = await scanner.scan_networks()
874
-
875
- # Exibe os resultados
876
- scanner.display_networks(networks)
877
-
878
- # Mostra um resumo
879
- console.print("\n[bold]Resumo:[/]")
880
- console.print(f"Total de redes encontradas: {len(networks)}")
881
- wps_count = sum(1 for net in networks if net.wps)
882
- console.print(f"Redes com WPS ativado: {wps_count}")
883
-
884
- # Se houver redes com WPS, destaca-as
885
- wps_nets = [net for net in networks if net.wps]
886
- if wps_nets:
887
- console.print("\n[bold yellow]⚠️ Redes com WPS ativado:[/]")
888
- for net in wps_nets:
889
- locked = "(travado) " if net.wps_locked else ""
890
- console.print(f"- {net.ssid or '<oculto>'} {locked}({net.bssid}) - Canal {net.channel}")
891
-
892
- console.print("\n[green]✅ Teste concluído com sucesso![/]")
893
-
894
- except KeyboardInterrupt:
895
- console.print("\n[yellow]⚠️ Teste interrompido pelo usuário.[/]")
896
- except Exception as e:
897
- console.print(f"[red]❌ Erro durante o teste: {e}[/]")
898
- finally:
899
- if 'scanner' in locals():
900
- await scanner._cleanup()
901
-
902
- if __name__ == "__main__":
903
- asyncio.run(run())