moriarty-project 0.1.10__py3-none-any.whl → 0.1.12__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.
@@ -0,0 +1,612 @@
1
+ """
2
+ Utilitários para o módulo WiFiPPLER.
3
+ """
4
+ import os
5
+ import sys
6
+ import re
7
+ import subprocess
8
+ import shlex
9
+ import logging
10
+ import socket
11
+ import fcntl
12
+ import struct
13
+ import array
14
+ import platform
15
+ import asyncio
16
+ from typing import List, Dict, Tuple, Optional, Set, Any, Union
17
+ from dataclasses import asdict
18
+
19
+ import netifaces
20
+ import psutil
21
+ from rich.console import Console
22
+ from rich.progress import Progress, SpinnerColumn, TextColumn
23
+
24
+ from ..models.network import WiFiNetwork, WiFiClient, WiFiSecurityType, WiFiCipherType, WiFiAuthType
25
+
26
+ # Configuração de logging
27
+ logging.basicConfig(level=logging.INFO)
28
+ logger = logging.getLogger(__name__)
29
+ console = Console()
30
+
31
+ # Constantes
32
+ WIRELESS_EXT = "SIOCGIWNAME"
33
+ SIOCGIFHWADDR = 0x8927
34
+ SIOCGIFADDR = 0x8915
35
+ SIOCGIFNETMASK = 0x891B
36
+ SIOCGIFBRDADDR = 0x8919
37
+ SIOCGIFMTU = 0x8921
38
+ SIOCGIFINDEX = 0x8933
39
+ SIOCGIFNAME = 0x8910
40
+ SIOCGIFFLAGS = 0x8913
41
+ SIOCSIFFLAGS = 0x8914
42
+
43
+ # Mapeamento de códigos de segurança
44
+ SECURITY_MAP = {
45
+ 'WPA2': WiFiSecurityType.WPA2,
46
+ 'WPA3': WiFiSecurityType.WPA3,
47
+ 'WPA2-EAP': WiFiSecurityType.WPA2_ENTERPRISE,
48
+ 'WPA3-EAP': WiFiSecurityType.WPA3_ENTERPRISE,
49
+ 'WEP': WiFiSecurityType.WEP,
50
+ 'OPEN': WiFiSecurityType.NONE,
51
+ 'NONE': WiFiSecurityType.NONE,
52
+ 'WPA': WiFiSecurityType.WPA,
53
+ 'WPA2-PSK': WiFiSecurityType.WPA2,
54
+ 'WPA3-PSK': WiFiSecurityType.WPA3,
55
+ 'WPA2-PSK-CCMP': WiFiSecurityType.WPA2,
56
+ 'WPA2-PSK-TKIP': WiFiSecurityType.WPA2,
57
+ 'WPA-PSK': WiFiSecurityType.WPA,
58
+ 'WPA-PSK-CCMP': WiFiSecurityType.WPA,
59
+ 'WPA-PSK-TKIP': WiFiSecurityType.WPA,
60
+ }
61
+
62
+ # Mapeamento de cifras
63
+ CIPHER_MAP = {
64
+ 'CCMP': WiFiCipherType.CCMP,
65
+ 'TKIP': WiFiCipherType.TKIP,
66
+ 'WEP': WiFiCipherType.WEP_40, # Usando WEP_40 como padrão para WEP
67
+ 'WEP-40': WiFiCipherType.WEP_40,
68
+ 'WEP-104': WiFiCipherType.WEP_104,
69
+ 'WEP-128': WiFiCipherType.WEP_104, # WEP-128 não está definido, usando WEP_104 como alternativa
70
+ 'NONE': WiFiCipherType.NONE,
71
+ 'GCMP': WiFiCipherType.GCMP
72
+ }
73
+
74
+ # Mapeamento de autenticação
75
+ AUTH_MAP = {
76
+ 'PSK': WiFiAuthType.WPA_PSK, # Usando WPA_PSK para PSK
77
+ 'WPA-PSK': WiFiAuthType.WPA_PSK,
78
+ 'WPA2-PSK': WiFiAuthType.WPA2_PSK,
79
+ 'WPA3-SAE': WiFiAuthType.WPA3_SAE,
80
+ 'EAP': WiFiAuthType.WPA_EAP, # Usando WPA_EAP para EAP
81
+ 'WPA-EAP': WiFiAuthType.WPA_EAP,
82
+ 'WPA2-EAP': WiFiAuthType.WPA2_EAP,
83
+ 'WPA3-EAP': WiFiAuthType.WPA3_EAP,
84
+ 'OPEN': WiFiAuthType.OPEN,
85
+ 'SHARED': WiFiAuthType.SHARED,
86
+ 'OWE': WiFiAuthType.OWE,
87
+ 'NONE': WiFiAuthType.OPEN,
88
+ }
89
+
90
+ def is_root() -> bool:
91
+ """Verifica se o script está sendo executado como root."""
92
+ return os.geteuid() == 0
93
+
94
+ def check_dependencies() -> List[str]:
95
+ """Verifica as dependências necessárias."""
96
+ required = ['iwconfig', 'iwlist', 'aircrack-ng', 'airodump-ng', 'aireplay-ng', 'airmon-ng']
97
+ missing = []
98
+
99
+ for dep in required:
100
+ if not command_exists(dep):
101
+ missing.append(dep)
102
+
103
+ return missing
104
+
105
+ def command_exists(cmd: str) -> bool:
106
+ """Verifica se um comando existe no sistema."""
107
+ return shutil.which(cmd) is not None
108
+
109
+ def get_network_interfaces() -> List[str]:
110
+ """Retorna a lista de interfaces de rede sem fio disponíveis."""
111
+ interfaces = []
112
+
113
+ try:
114
+ for interface in netifaces.interfaces():
115
+ if interface.startswith(('wlan', 'wlp', 'wlo', 'wlp')):
116
+ interfaces.append(interface)
117
+ except Exception as e:
118
+ logger.error(f"Erro ao obter interfaces de rede: {e}")
119
+
120
+ return interfaces
121
+
122
+ def get_monitor_interfaces() -> List[str]:
123
+ """Retorna a lista de interfaces em modo monitor."""
124
+ monitor_ifaces = []
125
+
126
+ try:
127
+ for interface in netifaces.interfaces():
128
+ if interface.endswith('mon'):
129
+ monitor_ifaces.append(interface)
130
+ else:
131
+ try:
132
+ with open(f"/sys/class/net/{interface}/type", 'r') as f:
133
+ if f.read().strip() == '803': # ARPHRD_IEEE80211_RADIOTAP
134
+ monitor_ifaces.append(interface)
135
+ except (IOError, FileNotFoundError):
136
+ continue
137
+ except Exception as e:
138
+ logger.error(f"Erro ao obter interfaces em modo monitor: {e}")
139
+
140
+ return monitor_ifaces
141
+
142
+ def set_monitor_mode(interface: str, channel: int = None) -> bool:
143
+ """Ativa o modo monitor em uma interface."""
144
+ if not is_root():
145
+ logger.error("Privilégios de root são necessários para ativar o modo monitor")
146
+ return False
147
+
148
+ try:
149
+ # Para processos que podem interferir
150
+ subprocess.run(['airmon-ng', 'check', 'kill'],
151
+ stdout=subprocess.PIPE,
152
+ stderr=subprocess.PIPE)
153
+
154
+ # Ativa o modo monitor
155
+ cmd = ['airmon-ng', 'start', interface]
156
+ if channel:
157
+ cmd.extend(['-c', str(channel)])
158
+
159
+ result = subprocess.run(cmd,
160
+ stdout=subprocess.PIPE,
161
+ stderr=subprocess.PIPE,
162
+ text=True)
163
+
164
+ if result.returncode != 0:
165
+ logger.error(f"Falha ao ativar o modo monitor: {result.stderr}")
166
+ return False
167
+
168
+ return True
169
+
170
+ except subprocess.CalledProcessError as e:
171
+ logger.error(f"Erro ao executar airmon-ng: {e}")
172
+ return False
173
+ except Exception as e:
174
+ logger.error(f"Erro inesperado: {e}")
175
+ return False
176
+
177
+ def restore_network_interface(interface: str) -> bool:
178
+ """Restaura a interface para o modo gerenciado."""
179
+ if not is_root():
180
+ logger.error("Privilégios de root são necessários para restaurar a interface")
181
+ return False
182
+
183
+ try:
184
+ # Para o modo monitor
185
+ subprocess.run(['airmon-ng', 'stop', interface], check=True)
186
+
187
+ # Reinicia o gerenciador de rede
188
+ if command_exists('systemctl'):
189
+ subprocess.run(['systemctl', 'restart', 'NetworkManager'], check=False)
190
+ elif command_exists('service'):
191
+ subprocess.run(['service', 'network-manager', 'restart'], check=False)
192
+
193
+ return True
194
+ except subprocess.CalledProcessError as e:
195
+ logger.error(f"Erro ao parar o modo monitor: {e}")
196
+ return False
197
+ except Exception as e:
198
+ logger.error(f"Erro inesperado: {e}")
199
+ return False
200
+
201
+ def start_monitor_mode(interface: str, channel: int = None) -> Optional[str]:
202
+ """Inicia o modo monitor em uma interface usando airmon-ng."""
203
+ if not is_root():
204
+ logger.error("Privilégios de root são necessários para iniciar o modo monitor")
205
+ return None
206
+
207
+ try:
208
+ # Para processos que podem interferir
209
+ subprocess.run(['airmon-ng', 'check', 'kill'],
210
+ stdout=subprocess.PIPE,
211
+ stderr=subprocess.PIPE)
212
+
213
+ # Inicia o modo monitor
214
+ cmd = ['airmon-ng', 'start', interface]
215
+ if channel:
216
+ cmd.extend(['-c', str(channel)])
217
+
218
+ result = subprocess.run(cmd,
219
+ stdout=subprocess.PIPE,
220
+ stderr=subprocess.PIPE,
221
+ text=True)
222
+
223
+ if result.returncode != 0:
224
+ logger.error(f"Falha ao iniciar o modo monitor: {result.stderr}")
225
+ return None
226
+
227
+ # Obtém o nome da interface em modo monitor (pode mudar, ex: wlan0 -> wlan0mon)
228
+ for line in result.stdout.split('\n'):
229
+ if 'monitor mode' in line and 'enabled' in line:
230
+ parts = line.split()
231
+ if len(parts) > 0:
232
+ return parts[0].strip('()')
233
+
234
+ return f"{interface}mon" # Padrão comum
235
+
236
+ except subprocess.CalledProcessError as e:
237
+ logger.error(f"Erro ao executar airmon-ng: {e}")
238
+ return None
239
+ except Exception as e:
240
+ logger.error(f"Erro inesperado: {e}")
241
+ return None
242
+
243
+ def stop_monitor_mode(interface: str) -> bool:
244
+ """Para o modo monitor em uma interface usando airmon-ng."""
245
+ if not is_root():
246
+ logger.error("Privilégios de root são necessários para parar o modo monitor")
247
+ return False
248
+
249
+ try:
250
+ # Para o modo monitor
251
+ subprocess.run(['airmon-ng', 'stop', interface], check=True)
252
+
253
+ # Reinicia o gerenciador de rede
254
+ if command_exists('systemctl'):
255
+ subprocess.run(['systemctl', 'restart', 'NetworkManager'], check=False)
256
+ elif command_exists('service'):
257
+ subprocess.run(['service', 'network-manager', 'restart'], check=False)
258
+
259
+ return True
260
+ except subprocess.CalledProcessError as e:
261
+ logger.error(f"Erro ao parar modo monitor: {e}")
262
+ return False
263
+ except Exception as e:
264
+ logger.error(f"Erro inesperado: {e}")
265
+ return False
266
+
267
+ def get_interface_mac(interface: str) -> Optional[str]:
268
+ """Obtém o endereço MAC de uma interface de rede."""
269
+ try:
270
+ with open(f"/sys/class/net/{interface}/address") as f:
271
+ return f.read().strip()
272
+ except (IOError, FileNotFoundError):
273
+ return None
274
+
275
+ def get_interface_ip(interface: str) -> Optional[str]:
276
+ """Obtém o endereço IP de uma interface de rede."""
277
+ try:
278
+ return netifaces.ifaddresses(interface)[netifaces.AF_INET][0]['addr']
279
+ except (KeyError, IndexError):
280
+ return None
281
+
282
+ def get_interface_netmask(interface: str) -> Optional[str]:
283
+ """Obtém a máscara de rede de uma interface de rede."""
284
+ try:
285
+ return netifaces.ifaddresses(interface)[netifaces.AF_INET][0]['netmask']
286
+ except (KeyError, IndexError):
287
+ return None
288
+
289
+ def get_interface_gateway(interface: str) -> Optional[str]:
290
+ """Obtém o gateway padrão de uma interface de rede."""
291
+ try:
292
+ gateways = netifaces.gateways()
293
+ return gateways['default'][netifaces.AF_INET][0]
294
+ except (KeyError, IndexError):
295
+ return None
296
+
297
+ def is_wireless_interface(interface: str) -> bool:
298
+ """Verifica se uma interface é sem fio."""
299
+ try:
300
+ with open(f"/sys/class/net/{interface}/wireless/uevent", 'r') as f:
301
+ return True
302
+ except (IOError, FileNotFoundError):
303
+ return False
304
+
305
+ def get_interface_signal(interface: str) -> Optional[int]:
306
+ """Obtém a intensidade do sinal de uma interface sem fio em dBm."""
307
+ try:
308
+ result = subprocess.run(
309
+ ['iwconfig', interface],
310
+ stdout=subprocess.PIPE,
311
+ stderr=subprocess.PIPE,
312
+ text=True
313
+ )
314
+
315
+ if result.returncode != 0:
316
+ return None
317
+
318
+ match = re.search(r'Signal level=(-?\d+) dBm', result.stdout)
319
+ if match:
320
+ return int(match.group(1))
321
+ return None
322
+ except (subprocess.SubprocessError, ValueError):
323
+ return None
324
+
325
+ def get_interface_ssid(interface: str) -> Optional[str]:
326
+ """Obtém o SSID ao qual a interface está conectada."""
327
+ try:
328
+ result = subprocess.run(
329
+ ['iwgetid', '-r', interface],
330
+ stdout=subprocess.PIPE,
331
+ stderr=subprocess.PIPE,
332
+ text=True
333
+ )
334
+
335
+ if result.returncode == 0 and result.stdout.strip():
336
+ return result.stdout.strip()
337
+ return None
338
+ except (subprocess.SubprocessError, FileNotFoundError):
339
+ return None
340
+
341
+ def get_interface_channel(interface: str) -> Optional[int]:
342
+ """Obtém o canal em que a interface está operando."""
343
+ try:
344
+ result = subprocess.run(
345
+ ['iwlist', interface, 'channel'],
346
+ stdout=subprocess.PIPE,
347
+ stderr=subprocess.PIPE,
348
+ text=True
349
+ )
350
+
351
+ if result.returncode != 0:
352
+ return None
353
+
354
+ match = re.search(r'Channel (\d+)', result.stdout)
355
+ if match:
356
+ return int(match.group(1))
357
+ return None
358
+ except (subprocess.SubprocessError, ValueError):
359
+ return None
360
+
361
+ def get_interface_bitrate(interface: str) -> Optional[float]:
362
+ """Obtém a taxa de transmissão da interface em Mbps."""
363
+ try:
364
+ result = subprocess.run(
365
+ ['iwconfig', interface],
366
+ stdout=subprocess.PIPE,
367
+ stderr=subprocess.PIPE,
368
+ text=True
369
+ )
370
+
371
+ if result.returncode != 0:
372
+ return None
373
+
374
+ match = re.search(r'Bit Rate[:=]([\d.]+) (\w+)', result.stdout)
375
+ if match:
376
+ rate, unit = match.groups()
377
+ rate = float(rate)
378
+ if unit == 'Mb/s':
379
+ return rate
380
+ elif unit == 'Kb/s':
381
+ return rate / 1000
382
+ return None
383
+ except (subprocess.SubprocessError, ValueError):
384
+ return None
385
+
386
+ # Funções para manipulação de pacotes
387
+ def create_deauth_packet(bssid: str, client: str, reason: int = 7, count: int = 1) -> bytes:
388
+ """Cria um pacote de desautenticação."""
389
+ # Cabeçalho do pacote 802.11
390
+ frame = [
391
+ 0x00, 0x00, 0x0c, 0x00, # Radiotap header (versão, pad, len, presente)
392
+ 0x04, 0x80, 0x00, 0x00, # Presente flags
393
+ 0x00, 0x00, 0x00, 0x00, # Timestamp
394
+ 0x00, 0x00, 0x00, 0x00, # Flags e taxa de dados
395
+ 0x00, 0x00, # Canal, sinal, ruído, etc.
396
+ 0x00, 0x00, 0x00, 0x00, # MCS conhecido, flags, mcs
397
+ 0x00, 0x00, 0x00, 0x00, # A-MPDU
398
+
399
+ # Cabeçalho 802.11 (tipo Management, subtipo Deauthentication)
400
+ 0x00, 0x00, 0x0c, 0x00, # Controle de versão, tipo, subtipo, etc.
401
+ 0x00, 0x00, # Duração
402
+ ]
403
+
404
+ # Endereço de destino (broadcast ou cliente específico)
405
+ dest = [int(x, 16) for x in client.split(':')]
406
+ frame.extend(dest)
407
+
408
+ # Endereço de origem (BSSID)
409
+ src = [int(x, 16) for x in bssid.split(':')]
410
+ frame.extend(src)
411
+
412
+ # BSSID (mesmo que origem para redes de infraestrutura)
413
+ frame.extend(src)
414
+
415
+ # Número de sequência
416
+ frame.extend([0x00, 0x00])
417
+
418
+ # Código de motivo (2 bytes, little-endian)
419
+ frame.extend([reason, 0x00])
420
+
421
+ return bytes(frame) * count
422
+
423
+ # Funções para execução de comandos
424
+ async def run_command_async(cmd: Union[str, List[str]], **kwargs) -> subprocess.CompletedProcess:
425
+ """
426
+ Executa um comando de forma assíncrona.
427
+
428
+ Args:
429
+ cmd: Comando a ser executado (string ou lista)
430
+ **kwargs: Argumentos adicionais para asyncio.create_subprocess_exec()
431
+
432
+ Returns:
433
+ subprocess.CompletedProcess: Resultado da execução do comando
434
+ """
435
+ if isinstance(cmd, str):
436
+ cmd = cmd.split()
437
+
438
+ process = await asyncio.create_subprocess_exec(
439
+ *cmd,
440
+ stdout=asyncio.subprocess.PIPE,
441
+ stderr=asyncio.subprocess.PIPE,
442
+ **kwargs
443
+ )
444
+
445
+ stdout, stderr = await process.communicate()
446
+
447
+ return subprocess.CompletedProcess(
448
+ args=cmd,
449
+ returncode=process.returncode,
450
+ stdout=stdout,
451
+ stderr=stderr
452
+ )
453
+
454
+ # Funções para processamento de saída
455
+ def parse_airodump_csv(csv_file: str) -> List[WiFiNetwork]:
456
+ """Analisa o arquivo CSV gerado pelo airodump-ng."""
457
+ networks = []
458
+
459
+ try:
460
+ with open(csv_file, 'r') as f:
461
+ lines = f.readlines()
462
+
463
+ # Encontra o início dos dados das redes
464
+ start_idx = 0
465
+ for i, line in enumerate(lines):
466
+ if line.startswith('BSSID, First time seen,'):
467
+ start_idx = i + 1
468
+ break
469
+
470
+ # Processa as redes
471
+ for line in lines[start_idx:]:
472
+ line = line.strip()
473
+ if not line or line.startswith('Station'):
474
+ break
475
+
476
+ # Extrai os campos da linha
477
+ parts = [p.strip() for p in line.split(',')]
478
+ if len(parts) < 14: # Número mínimo de campos esperados
479
+ continue
480
+
481
+ bssid = parts[0].strip()
482
+ first_seen = parts[1].strip()
483
+ last_seen = parts[2].strip()
484
+ channel = int(parts[3].strip())
485
+ speed = parts[4].strip()
486
+ privacy = parts[5].strip()
487
+ cipher = parts[6].strip()
488
+ auth = parts[7].strip()
489
+ power = int(parts[8].strip())
490
+ beacons = int(parts[9].strip())
491
+ iv = int(parts[10].strip())
492
+ ip = parts[11].strip()
493
+ id_len = int(parts[12].strip())
494
+ essid = parts[13].strip()
495
+
496
+ # Cria o objeto da rede
497
+ network = WiFiNetwork(
498
+ bssid=bssid,
499
+ ssid=essid,
500
+ channel=channel,
501
+ signal=power,
502
+ encryption=privacy,
503
+ cipher=cipher,
504
+ authentication=auth,
505
+ first_seen=first_seen,
506
+ last_seen=last_seen,
507
+ speed=speed,
508
+ beacons=beacons,
509
+ iv=iv,
510
+ ip=ip if ip != '0.0.0.0' else None
511
+ )
512
+
513
+ networks.append(network)
514
+
515
+ except Exception as e:
516
+ logger.error(f"Erro ao analisar arquivo CSV: {e}")
517
+
518
+ return networks
519
+
520
+ def parse_airodump_stations(csv_file: str) -> List[WiFiClient]:
521
+ """Analisa a seção de estações do arquivo CSV do airodump-ng."""
522
+ clients = []
523
+
524
+ try:
525
+ with open(csv_file, 'r') as f:
526
+ lines = f.readlines()
527
+
528
+ # Encontra o início da seção de estações
529
+ start_idx = 0
530
+ for i, line in enumerate(lines):
531
+ if line.startswith('Station MAC,'):
532
+ start_idx = i + 1
533
+ break
534
+
535
+ # Processa as estações
536
+ for line in lines[start_idx:]:
537
+ line = line.strip()
538
+ if not line:
539
+ continue
540
+
541
+ # Extrai os campos da linha
542
+ parts = [p.strip() for p in line.split(',')]
543
+ if len(parts) < 6: # Número mínimo de campos esperados
544
+ continue
545
+
546
+ mac = parts[0].strip()
547
+ first_seen = parts[1].strip()
548
+ last_seen = parts[2].strip()
549
+ power = int(parts[3].strip())
550
+ packets = int(parts[4].strip())
551
+ bssid = parts[5].strip()
552
+
553
+ # Cria o objeto do cliente
554
+ client = WiFiClient(
555
+ mac=mac,
556
+ bssid=bssid,
557
+ signal=power,
558
+ packets=packets,
559
+ first_seen=first_seen,
560
+ last_seen=last_seen
561
+ )
562
+
563
+ clients.append(client)
564
+
565
+ except Exception as e:
566
+ logger.error(f"Erro ao analisar estações do arquivo CSV: {e}")
567
+
568
+ return clients
569
+
570
+ def randomize_mac(interface: str) -> bool:
571
+ """
572
+ Aleatoriza o endereço MAC de uma interface de rede.
573
+
574
+ Args:
575
+ interface: Nome da interface de rede
576
+
577
+ Returns:
578
+ bool: True se bem-sucedido, False caso contrário
579
+ """
580
+ if not is_root():
581
+ logger.error("Privilégios de root são necessários para alterar o endereço MAC")
582
+ return False
583
+
584
+ try:
585
+ # Gera um endereço MAC aleatório
586
+ import random
587
+ new_mac = ':'.join(['%02x' % random.randint(0x00, 0xff) for _ in range(6)])
588
+
589
+ # Desativa a interface
590
+ subprocess.run(['ip', 'link', 'set', interface, 'down'], check=True)
591
+
592
+ # Define o novo endereço MAC
593
+ subprocess.run(['ip', 'link', 'set', 'dev', interface, 'address', new_mac], check=True)
594
+
595
+ # Reativa a interface
596
+ subprocess.run(['ip', 'link', 'set', interface, 'up'], check=True)
597
+
598
+ logger.info(f"Endereço MAC da interface {interface} alterado para {new_mac}")
599
+ return True
600
+ except subprocess.CalledProcessError as e:
601
+ logger.error(f"Falha ao aleatorizar o endereço MAC: {e}")
602
+ return False
603
+
604
+ def get_wireless_interfaces() -> List[Dict[str, Any]]:
605
+ """
606
+ Obtém uma lista de interfaces de rede sem fio.
607
+
608
+ Returns:
609
+ List[Dict[str, Any]]: Lista de interfaces sem fio com suas propriedades
610
+ """
611
+ return [iface for iface in get_network_interfaces()
612
+ if iface.get('wireless', False)]