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