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