moriarty-project 0.1.24__py3-none-any.whl → 0.1.26__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.
@@ -5,14 +5,15 @@ import json
5
5
  from typing import Optional, Dict, List
6
6
 
7
7
  import typer
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table, box
8
11
 
9
12
  from moriarty.modules.web_crawler import WebCrawler
10
-
11
13
  from moriarty.modules.port_scanner import PortScanner, PROFILES
12
14
  from moriarty.modules.passive_recon import PassiveRecon
13
- from rich.console import Console
14
15
 
15
- app = typer.Typer(name="domain", help="🌐 Domain/IP reconnaissance and scanning.")
16
+ app = typer.Typer(help="🔍 Ferramentas avançadas de reconhecimento de domínios")
16
17
  console = Console()
17
18
 
18
19
 
@@ -61,7 +62,7 @@ def scan_full(
61
62
 
62
63
  if output:
63
64
  scanner.export(output)
64
- console.print(f"[green]✅[/green] Results saved to: [cyan]{output}[/cyan]\n")
65
+ console.print(f"[green]✅[/green] Results saved to: [red]{output}[/red]\n")
65
66
 
66
67
 
67
68
  @app.command("subdiscover")
@@ -101,7 +102,7 @@ def subdomain_discovery(
101
102
  from moriarty.modules.subdomain_discovery import SubdomainDiscovery
102
103
  import asyncio
103
104
 
104
- console.print(f"[bold cyan]🔍 Descobrindo subdomínios de:[/bold cyan] {domain}\n")
105
+ console.print(f"[bold red]🔍 Descobrindo subdomínios de:[/bold red] {domain}\n")
105
106
 
106
107
  discovery = SubdomainDiscovery(
107
108
  domain=domain,
@@ -141,7 +142,7 @@ def wayback_urls(
141
142
  from moriarty.modules.wayback_discovery import WaybackDiscovery
142
143
  import asyncio
143
144
 
144
- console.print(f"[bold cyan]🕰️ Buscando URLs históricas de:[/bold cyan] {domain}\n")
145
+ console.print(f"[bold red]🕰️ Buscando URLs históricas de:[/bold red] {domain}\n")
145
146
 
146
147
  wayback = WaybackDiscovery(
147
148
  domain=domain,
@@ -181,7 +182,7 @@ def template_scan(
181
182
 
182
183
  from moriarty.modules.template_scanner import TemplateScanner
183
184
 
184
- console.print(f"[bold cyan]📝 Template scan em:[/bold cyan] {target}\n")
185
+ console.print(f"[bold red]📝 Template scan em:[/bold red] {target}\n")
185
186
 
186
187
  scanner = TemplateScanner(
187
188
  target=target,
@@ -227,7 +228,7 @@ def run_pipeline(
227
228
  from moriarty.modules.pipeline_orchestrator import PipelineOrchestrator
228
229
  import asyncio
229
230
 
230
- console.print(f"[bold cyan]🔄 Executando pipeline:[/bold cyan] {pipeline_file}\n")
231
+ console.print(f"[bold red]🔄 Executando pipeline:[/bold red] {pipeline_file}\n")
231
232
 
232
233
  orchestrator = PipelineOrchestrator(
233
234
  pipeline_file=pipeline_file,
@@ -283,41 +284,7 @@ def stealth_command(
283
284
  console.print(f"[red]❌ Ação inválida: {action}[/red]")
284
285
 
285
286
 
286
- @app.command("ports")
287
- def port_scan(
288
- target: str = typer.Argument(..., help="IP ou domínio"),
289
- ports: str = typer.Option("common", "--ports", "-p", help="Portas: common, all, 1-1000, 80,443"),
290
- scan_type: str = typer.Option("syn", "--type", "-t", help="Tipo: syn, tcp, udp"),
291
- stealth: int = typer.Option(0, "--stealth", "-s", help="Stealth level (0-4)"),
292
- output: str = typer.Option(None, "--output", "-o", help="Arquivo de saída"),
293
- verbose: bool = typer.Option(False, "--verbose", help="Ativar saída detalhada"),
294
- ):
295
- """
296
- 🔌 Scan avançado de portas.
297
-
298
- Exemplos:
299
- moriarty domain ports 8.8.8.8
300
- moriarty domain ports target.com --ports 1-10000
301
- moriarty domain ports target.com --type syn --stealth 3
302
- """
303
- from moriarty.modules.port_scanner import PortScanner
304
- import asyncio
305
-
306
- console.print(f"[bold cyan]🔌 Port scan em:[/bold cyan] {target}\n")
307
-
308
- scanner = PortScanner(
309
- target=target,
310
- ports=ports,
311
- scan_type=scan_type,
312
- stealth_level=stealth,
313
- )
314
-
315
- results = asyncio.run(scanner.scan())
316
-
317
- console.print(f"\n[green]✅ {len(results)} portas abertas encontradas[/green]")
318
-
319
- if output:
320
- scanner.export(results, output)
287
+ # A função port_scan foi movida para a implementação mais completa abaixo
321
288
 
322
289
 
323
290
  @app.command("waf-detect")
@@ -342,7 +309,7 @@ def waf_detection(
342
309
  from moriarty.modules.waf_detector import WAFDetector
343
310
  import asyncio
344
311
 
345
- console.print(f"[bold cyan]🛡️ Detectando WAF em:[/bold cyan] {target}\n")
312
+ console.print(f"[bold red]🛡️ Detectando WAF em:[/bold red] {target}\n")
346
313
 
347
314
  detector = WAFDetector(target=target)
348
315
  waf_info = asyncio.run(detector.detect())
@@ -352,7 +319,7 @@ def waf_detection(
352
319
  console.print(f"[dim]Confidence: {waf_info['confidence']}%[/dim]")
353
320
 
354
321
  if bypass:
355
- console.print("\n[cyan]🔓 Tentando bypass...[/cyan]")
322
+ console.print("\n[red]🔓 Tentando bypass...[/red]")
356
323
  bypass_methods = asyncio.run(detector.attempt_bypass())
357
324
 
358
325
  for method in bypass_methods:
@@ -393,12 +360,12 @@ def passive_recon(
393
360
  console = Console()
394
361
 
395
362
  # Cabeçalho
396
- console.print(f"\n[bold cyan]🌐 Moriarty Passive Recon[/bold cyan]")
363
+ console.print(f"\n[bold red]🌐 Moriarty Passive Recon[/bold red]")
397
364
  console.print(f"[dim]Alvo:[/dim] {domain}")
398
365
  console.print(f"[dim]Data:[/dim] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
399
366
 
400
367
  async def _run():
401
- with console.status(f"[bold green]Coletando informações sobre [cyan]{domain}[/cyan]..."):
368
+ with console.status(f"[bold green]Coletando informações sobre [red]{domain}[/red]..."):
402
369
  recon = PassiveRecon(domain)
403
370
  try:
404
371
  return await recon.collect()
@@ -412,15 +379,15 @@ def passive_recon(
412
379
  if status_only:
413
380
  sub_count = sum(len(v) for v in payload["subdomains"].values())
414
381
  console.print(f"[bold]🔍 Resumo do Reconhecimento[/bold]")
415
- console.print(f" • [cyan]Subdomínios:[/cyan] {sub_count} encontrados")
416
- console.print(f" • [cyan]Fontes:[/cyan] {', '.join(payload['subdomains'].keys()) or 'nenhuma'}")
417
- console.print(f" • [cyan]Credenciais vazadas:[/cyan] {len(payload['leaks'])}")
382
+ console.print(f" • [red]Subdomínios:[/red] {sub_count} encontrados")
383
+ console.print(f" • [red]Fontes:[/red] {', '.join(payload['subdomains'].keys()) or 'nenhuma'}")
384
+ console.print(f" • [red]Credenciais vazadas:[/red] {len(payload['leaks'])}")
418
385
  return
419
386
 
420
387
  # Seção de Subdomínios
421
388
  if payload.get("subdomains"):
422
389
  table = Table(title="[bold]🌐 Subdomínios Encontrados[/bold]", box=box.ROUNDED)
423
- table.add_column("Subdomínio", style="cyan")
390
+ table.add_column("Subdomínio", style="red")
424
391
  table.add_column("Fonte", style="green")
425
392
 
426
393
  for source, subdomains in payload["subdomains"].items():
@@ -432,7 +399,7 @@ def passive_recon(
432
399
  # Seção de Tecnologias
433
400
  if payload.get("technologies", {}).get("detections"):
434
401
  tech_table = Table(title="[bold]🛠️ Tecnologias Detectadas[/bold]", box=box.ROUNDED)
435
- tech_table.add_column("Tecnologia", style="cyan")
402
+ tech_table.add_column("Tecnologia", style="red")
436
403
  tech_table.add_column("Confiança", style="green")
437
404
  tech_table.add_column("Categoria", style="magenta")
438
405
 
@@ -447,7 +414,7 @@ def passive_recon(
447
414
 
448
415
  # Seção de Segurança
449
416
  security_table = Table(title="[bold]🔒 Análise de Segurança[/bold]", box=box.ROUNDED)
450
- security_table.add_column("Item", style="cyan")
417
+ security_table.add_column("Item", style="red")
451
418
  security_table.add_column("Status", style="green")
452
419
 
453
420
  # Verifica HSTS
@@ -468,7 +435,7 @@ def passive_recon(
468
435
  # Seção de Reputação
469
436
  if payload.get("reputation"):
470
437
  rep_table = Table(title="[bold]📊 Reputação do Domínio[/bold]", box=box.ROUNDED)
471
- rep_table.add_column("Fonte", style="cyan")
438
+ rep_table.add_column("Fonte", style="red")
472
439
  rep_table.add_column("Status", style="green")
473
440
 
474
441
  for source, data in payload["reputation"].items():
@@ -491,7 +458,7 @@ def passive_recon(
491
458
  if output:
492
459
  with open(output, "w", encoding="utf-8") as handle:
493
460
  json.dump(payload, handle, indent=2, ensure_ascii=False)
494
- console.print(f"\n[green]✓[/green] Resultados salvos em: [cyan]{output}[/cyan]")
461
+ console.print(f"\n[green]✓[/green] Resultados salvos em: [red]{output}[/red]")
495
462
 
496
463
  except Exception as e:
497
464
  console.print(f"[red]✗ Erro durante o reconhecimento:[/red] {str(e)}")
@@ -501,6 +468,384 @@ def passive_recon(
501
468
  console.print(f"[yellow]⚠ Log de erro salvo em: {output}[/yellow]")
502
469
 
503
470
 
471
+ import asyncio
472
+
473
+ async def _port_scan_async(
474
+ target: str,
475
+ profile: str,
476
+ stealth: int,
477
+ resolve_services: bool,
478
+ check_vulns: bool,
479
+ format: str,
480
+ output: Optional[str]
481
+ ):
482
+ """Função assíncrona que realiza a varredura de portas."""
483
+ # Código da função port_scan aqui...
484
+ console = Console()
485
+
486
+ # Valida os parâmetros
487
+ def validate_parameters():
488
+ if stealth < 0 or stealth > 5:
489
+ console.print("[red]Erro:[/red] O nível de stealth deve estar entre 0 e 5")
490
+ raise typer.Exit(1)
491
+
492
+ if profile not in PROFILES:
493
+ console.print(f"[red]Erro:[/red] Perfil inválido. Use um dos seguintes: {', '.join(PROFILES.keys())}")
494
+ raise typer.Exit(1)
495
+
496
+ # Configura o console do Rich
497
+ console = Console()
498
+
499
+ # Valida os parâmetros
500
+ validate_parameters()
501
+
502
+ # Usa o perfil especificado ou o padrão 'quick'
503
+ ports = PROFILES.get(profile.lower(), PROFILES["quick"])
504
+
505
+ # Configura o arquivo de saída
506
+ output_path = Path(output) if output else None
507
+ if output_path:
508
+ if output_path.suffix not in (".json", ".txt", ".md"):
509
+ console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
510
+ output_path = output_path.with_suffix(".json")
511
+
512
+ # Configura a barra de progresso
513
+ progress_columns = [
514
+ SpinnerColumn(),
515
+ TextColumn("[progress.description]{task.description}"),
516
+ BarColumn(bar_width=None),
517
+ TaskProgressColumn(),
518
+ TimeElapsedColumn(),
519
+ ]
520
+
521
+ # Cabeçalho da varredura
522
+ console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
523
+
524
+ # Tabela de informações
525
+ info_table = Table.grid(padding=(0, 1))
526
+ info_table.add_row("🌐 Alvo:", f"[bold]{target}")
527
+ info_table.add_row("🔢 Portas:", f"[red]{ports}")
528
+ info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
529
+ info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]" if resolve_services else "[red]Desativada[/red]")
530
+ info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]" if check_vulns else "[red]Desativada[/red]")
531
+
532
+ console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
533
+ console.print()
534
+
535
+ async def run_scan() -> List[PortScanResult]:
536
+ """Executa a varredura de portas."""
537
+ # Usa sempre TCP Connect que não requer privilégios de root
538
+ console.print("[yellow]Aviso:[/yellow] Usando varredura TCP Connect (não requer privilégios de root).")
539
+ scan_type = "tcp"
540
+
541
+ # Cria o scanner Nmap
542
+ scanner = PortScanner(
543
+ target=target,
544
+ ports=ports,
545
+ scan_type=scan_type,
546
+ stealth_level=stealth,
547
+ resolve_services=resolve_services,
548
+ check_vulns=check_vulns,
549
+ )
550
+
551
+ # Executa o scan e retorna os resultados
552
+ return await scanner.scan()
553
+
554
+ console.print("🚀 Iniciando varredura de portas...\n")
555
+
556
+ # Executa a varredura com barra de progresso
557
+ with Progress(*progress_columns) as progress:
558
+ task = progress.add_task("[red]Escaneando portas...", total=100)
559
+
560
+ try:
561
+ # Executa o scan de forma assíncrona
562
+ results = await run_scan()
563
+ progress.update(task, completed=100)
564
+
565
+ if not results:
566
+ console.print("\n[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
567
+ return
568
+
569
+ output_format = format.lower()
570
+
571
+ if output_format == "json":
572
+ output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
573
+ if not output_path:
574
+ console.print_json(data=json.loads(output_text))
575
+ elif output_format == "markdown":
576
+ # Formata a saída em Markdown
577
+ output_lines = [
578
+ f"# Resultados da varredura de portas em {target}\n",
579
+ "| Porta | Protocolo | Status | Serviço | Versão | Vulnerabilidades |",
580
+ "|-------|-----------|--------|---------|---------|------------------|"
581
+ ]
582
+
583
+ for result in results:
584
+ service = getattr(result, 'service', None)
585
+ status = "🟢 ABERTA" if getattr(result, 'status', '') == "open" else "🔴 FECHADA"
586
+ service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
587
+ version = getattr(service, 'version', '') if service else ""
588
+ vulns = ", ".join(service.vulns) if service and hasattr(service, 'vulns') and service.vulns else "-"
589
+
590
+ output_lines.append(
591
+ f"| {getattr(result, 'port', '')} | "
592
+ f"{getattr(result, 'protocol', 'tcp').upper()} | "
593
+ f"{status} | "
594
+ f"{service_name} | "
595
+ f"{version} | "
596
+ f"{vulns if vulns and vulns != '[]' else '-'} |"
597
+ )
598
+
599
+ # Adiciona resumo
600
+ open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
601
+ output_text = "\n".join(output_lines)
602
+ output_text += f"\n\n✅ **Varredura concluída!** {len(open_ports)} porta(s) aberta(s) encontrada(s)."
603
+
604
+ if not output_path:
605
+ console.print(output_text)
606
+ else: # Formato de texto simples
607
+ # Cria tabela de resultados
608
+ table = Table(
609
+ title=f"🔍 Resultados da varredura em {target}",
610
+ box=box.ROUNDED,
611
+ header_style="bold magenta",
612
+ expand=True
613
+ )
614
+
615
+ # Adiciona colunas
616
+ table.add_column("Porta", style="red", justify="right")
617
+ table.add_column("Protocolo", style="magenta")
618
+ table.add_column("Status", style="green")
619
+ table.add_column("Serviço", style="yellow")
620
+ table.add_column("Versão", style="blue")
621
+ table.add_column("Vulnerabilidades", style="red")
622
+
623
+ # Adiciona linhas com os resultados
624
+ for result in results:
625
+ service = getattr(result, 'service', None)
626
+ status = "[green]ABERTA[/green]" if getattr(result, 'status', '') == "open" else "[red]FECHADA[/red]"
627
+ service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
628
+ version = getattr(service, 'version', '') if service else ""
629
+ vulns = ", ".join(service.vulns) if service and hasattr(service, 'vulns') and service.vulns else "-"
630
+
631
+ table.add_row(
632
+ str(getattr(result, 'port', '')),
633
+ getattr(result, 'protocol', 'tcp').upper(),
634
+ status,
635
+ service_name,
636
+ version,
637
+ vulns if vulns and vulns != "[]" else "-"
638
+ )
639
+
640
+ # Exibe a tabela
641
+ console.print(table)
642
+
643
+ # Resumo
644
+ open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
645
+ console.print(f"\n✅ [bold green]Varredura concluída![/bold green] {len(open_ports)} porta(s) aberta(s) encontrada(s).")
646
+
647
+ # Prepara o texto para salvar em arquivo
648
+ output_text = str(table)
649
+
650
+ # Salva em arquivo se especificado
651
+ if output_path:
652
+ try:
653
+ output_path.parent.mkdir(parents=True, exist_ok=True)
654
+ with open(output_path, "w", encoding="utf-8") as f:
655
+ f.write(output_text)
656
+ console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
657
+ except Exception as e:
658
+ console.print(f"\n❌ [bold red]Erro ao salvar arquivo:[/bold red] {str(e)}")
659
+
660
+ except KeyboardInterrupt:
661
+ console.print("\n❌ [bold red]Varredura cancelada pelo usuário[/bold red]")
662
+ raise typer.Exit(1)
663
+ except Exception as e:
664
+ console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
665
+ if "Errno 8" in str(e) or "Name or service not known" in str(e):
666
+ console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
667
+ if "No module named 'nmap'" in str(e):
668
+ console.print("💡 [yellow]Dica:[/yellow] O módulo 'python-nmap' não está instalado. Instale com: [red]pip install python-nmap[/red]")
669
+ raise typer.Exit(1)
670
+
671
+ async def run_port_scan(
672
+ target: str,
673
+ profile: str,
674
+ stealth: int,
675
+ concurrency: int,
676
+ timeout: float,
677
+ output: Optional[str],
678
+ format: str,
679
+ ):
680
+ """Função assíncrona que executa a varredura de portas."""
681
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
682
+ from rich.panel import Panel
683
+ from rich.table import Table
684
+ from pathlib import Path
685
+ import json
686
+
687
+ # Valida os parâmetros
688
+ if stealth < 0 or stealth > 5:
689
+ console.print("[red]Erro:[/red] O nível de stealth deve estar entre 0 e 5")
690
+ raise typer.Exit(1)
691
+
692
+ if profile not in PROFILES:
693
+ console.print(f"[red]Erro:[/red] Perfil inválido. Use um dos seguintes: {', '.join(PROFILES.keys())}")
694
+ raise typer.Exit(1)
695
+
696
+ # Usa o perfil especificado ou o padrão 'quick'
697
+ ports = PROFILES.get(profile.lower(), PROFILES["quick"])
698
+
699
+ # Configura o arquivo de saída
700
+ output_path = Path(output) if output else None
701
+ if output_path:
702
+ if output_path.suffix not in (".json", ".txt", ".md"):
703
+ console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
704
+ output_path = output_path.with_suffix(".json")
705
+
706
+ # Configura a barra de progresso
707
+ progress_columns = [
708
+ SpinnerColumn(),
709
+ TextColumn("[progress.description]{task.description}"),
710
+ BarColumn(bar_width=None),
711
+ TaskProgressColumn(),
712
+ TimeElapsedColumn(),
713
+ ]
714
+
715
+ # Cabeçalho da varredura
716
+ console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
717
+
718
+ # Tabela de informações
719
+ info_table = Table.grid(padding=(0, 1))
720
+ info_table.add_row("🌐 Alvo:", f"[bold]{target}")
721
+ info_table.add_row("🔢 Portas:", f"[red]{ports}")
722
+ info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
723
+ info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]")
724
+ info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]")
725
+
726
+ console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
727
+ console.print()
728
+
729
+ # Executa a varredura com barra de progresso
730
+ with Progress(*progress_columns) as progress:
731
+ task = progress.add_task("[red]Escaneando portas...", total=100)
732
+
733
+ try:
734
+ # Cria o scanner Nmap
735
+ scanner = PortScanner(
736
+ target=target,
737
+ ports=ports,
738
+ scan_type="syn" if stealth < 2 else "tcp",
739
+ stealth_level=stealth,
740
+ resolve_services=True,
741
+ check_vulns=True,
742
+ )
743
+
744
+ # Executa o scan de forma assíncrona
745
+ results = await scanner.scan()
746
+ progress.update(task, completed=100)
747
+
748
+ if not results:
749
+ console.print("\n[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
750
+ return
751
+
752
+ # Formata a saída
753
+ output_format = format.lower()
754
+
755
+ if output_format == "json":
756
+ output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
757
+ if not output_path:
758
+ console.print_json(data=json.loads(output_text))
759
+ elif output_format == "markdown":
760
+ output_lines = [
761
+ f"# Resultados da varredura de portas em {target}\n",
762
+ "| Porta | Protocolo | Status | Serviço | Versão |",
763
+ "|-------|-----------|--------|---------|---------|"
764
+ ]
765
+
766
+ for result in results:
767
+ service = getattr(result, 'service', None)
768
+ status = "🟢 ABERTA" if getattr(result, 'status', '') == "open" else "🔴 FECHADA"
769
+ service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
770
+ version = getattr(service, 'version', '') if service else ""
771
+
772
+ output_lines.append(
773
+ f"| {getattr(result, 'port', '')} | "
774
+ f"{getattr(result, 'protocol', 'tcp').upper()} | "
775
+ f"{status} | "
776
+ f"{service_name} | "
777
+ f"{version} |"
778
+ )
779
+
780
+ # Adiciona resumo
781
+ open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
782
+ output_text = "\n".join(output_lines)
783
+ output_text += f"\n\n✅ **Varredura concluída!** {len(open_ports)} porta(s) aberta(s) encontrada(s)."
784
+
785
+ if not output_path:
786
+ console.print(output_text)
787
+ else: # Formato de texto simples
788
+ # Cria tabela de resultados
789
+ table = Table(
790
+ title=f"🔍 Resultados da varredura em {target}",
791
+ box=box.ROUNDED,
792
+ header_style="bold magenta",
793
+ expand=True
794
+ )
795
+
796
+ # Adiciona colunas
797
+ table.add_column("Porta", style="red", justify="right")
798
+ table.add_column("Protocolo", style="magenta")
799
+ table.add_column("Status", style="green")
800
+ table.add_column("Serviço", style="yellow")
801
+ table.add_column("Versão", style="blue")
802
+
803
+ # Adiciona linhas com os resultados
804
+ for result in results:
805
+ service = getattr(result, 'service', None)
806
+ status = "[green]ABERTA[/green]" if getattr(result, 'status', '') == "open" else "[red]FECHADA[/red]"
807
+ service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
808
+ version = getattr(service, 'version', '') if service else ""
809
+
810
+ table.add_row(
811
+ str(getattr(result, 'port', '')),
812
+ getattr(result, 'protocol', 'tcp').upper(),
813
+ status,
814
+ service_name,
815
+ version,
816
+ )
817
+
818
+ # Exibe a tabela
819
+ console.print(table)
820
+
821
+ # Resumo
822
+ open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
823
+ console.print(f"\n✅ [bold green]Varredura concluída![/bold green] {len(open_ports)} porta(s) aberta(s) encontrada(s).")
824
+
825
+ # Prepara o texto para salvar em arquivo
826
+ output_text = str(table)
827
+
828
+ # Salva em arquivo se especificado
829
+ if output_path:
830
+ try:
831
+ output_path.parent.mkdir(parents=True, exist_ok=True)
832
+ with open(output_path, "w", encoding="utf-8") as f:
833
+ f.write(output_text)
834
+ console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
835
+ except Exception as e:
836
+ console.print(f"\n❌ [bold red]Erro ao salvar arquivo:[/bold red] {str(e)}")
837
+
838
+ except KeyboardInterrupt:
839
+ console.print("\n❌ [bold red]Varredura cancelada pelo usuário[/bold red]")
840
+ raise typer.Exit(1)
841
+ except Exception as e:
842
+ console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
843
+ if "Errno 8" in str(e) or "Name or service not known" in str(e):
844
+ console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
845
+ if "No module named 'nmap'" in str(e):
846
+ console.print("💡 [yellow]Dica:[/yellow] O módulo 'python-nmap' não está instalado. Instale com: [red]pip install python-nmap[/red]")
847
+ raise typer.Exit(1)
848
+
504
849
  @app.command("ports")
505
850
  def port_scan(
506
851
  target: str = typer.Argument(..., help="Domínio ou IP para escanear"),
@@ -558,7 +903,7 @@ def port_scan(
558
903
  ),
559
904
  ):
560
905
  """
561
- 🔍 Varredura avançada de portas com detecção de serviços e vulnerabilidades.
906
+ 🔍 Varredura avançada de portas com detecção de serviços e vulnerabilidades usando Nmap.
562
907
 
563
908
  Exemplos:
564
909
  moriarty domain ports example.com
@@ -567,84 +912,218 @@ def port_scan(
567
912
  """
568
913
  import asyncio
569
914
  import json
915
+ import os
916
+ import sys
570
917
  from pathlib import Path
571
- from moriarty.modules.port_scanner import format_scan_results
572
-
573
- # Validações
574
- if profile not in PROFILES:
575
- console.print(f"[red]Erro:[/red] Perfil inválido. Use um destes: {', '.join(PROFILES.keys())}")
576
- raise typer.Exit(1)
577
-
578
- if output:
579
- output = Path(output)
580
- if output.suffix not in (".json", ".txt", ".md"):
918
+ from typing import List, Dict, Any, Optional
919
+
920
+ from rich.progress import (
921
+ Progress,
922
+ SpinnerColumn,
923
+ BarColumn,
924
+ TextColumn,
925
+ TimeElapsedColumn,
926
+ TaskProgressColumn,
927
+ )
928
+ from rich.panel import Panel
929
+ from rich.table import Table, box
930
+ from rich.console import Console
931
+
932
+ from moriarty.modules.port_scanner_nmap import PortScanner, PortScanResult, ServiceInfo
933
+
934
+ # Mapeia os perfis para o formato do Nmap
935
+ port_profiles = {
936
+ "quick": "21-23,25,53,80,110,111,135,139,143,389,443,445,465,587,993,995,1433,1521,2049,3306,3389,5432,5900,6379,8080,8443,9000,10000,27017",
937
+ "web": "80,443,8080,8443,8000,8888,10443,4443",
938
+ "mail": "25,110,143,465,587,993,995",
939
+ "db": "1433,1521,27017-27019,28017,3306,5000,5432,5984,6379,8081",
940
+ "full": "1-1024",
941
+ "all": "1-65535",
942
+ }
943
+
944
+ # Verifica se o Nmap está instalado
945
+ def check_nmap_installed() -> bool:
946
+ """Verifica se o Nmap está instalado no sistema."""
947
+ try:
948
+ import nmap
949
+ return True
950
+ except ImportError:
951
+ return False
952
+
953
+ # Valida os parâmetros de entrada
954
+ def validate_parameters() -> None:
955
+ """Valida os parâmetros de entrada."""
956
+ if not check_nmap_installed():
957
+ console.print("❌ [bold red]Erro:[/bold red] O módulo 'python-nmap' não está instalado.")
958
+ console.print("💡 [yellow]Dica:[/yellow] Instale com: pip install python-nmap")
959
+ raise typer.Exit(1)
960
+
961
+ if stealth < 0 or stealth > 5:
962
+ console.print("❌ [bold red]Erro:[/bold red] O nível de stealth deve estar entre 0 e 5.")
963
+ raise typer.Exit(1)
964
+
965
+ # Inicializa o console do Rich
966
+ console = Console()
967
+
968
+ # Valida os parâmetros
969
+ validate_parameters()
970
+
971
+ # Usa o perfil especificado ou o padrão 'quick'
972
+ ports = port_profiles.get(profile.lower(), port_profiles["quick"])
973
+
974
+ # Configura o arquivo de saída
975
+ output_path = Path(output) if output else None
976
+ if output_path:
977
+ if output_path.suffix not in (".json", ".txt", ".md"):
581
978
  console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
582
- output = output.with_suffix(".json")
583
-
584
- async def _run():
979
+ output_path = output_path.with_suffix(".json")
980
+
981
+ # Configura a barra de progresso
982
+ progress_columns = [
983
+ SpinnerColumn(),
984
+ TextColumn("[progress.description]{task.description}"),
985
+ BarColumn(bar_width=None),
986
+ TaskProgressColumn(),
987
+ TimeElapsedColumn(),
988
+ ]
989
+
990
+ # Cabeçalho da varredura
991
+ console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
992
+
993
+ # Tabela de informações
994
+ info_table = Table.grid(padding=(0, 1))
995
+ info_table.add_row("🌐 Alvo:", f"[bold]{target}")
996
+ info_table.add_row("🔢 Portas:", f"[red]{ports}")
997
+ info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
998
+ info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]" if resolve_services else "[red]Desativada[/red]")
999
+ info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]" if check_vulns else "[red]Desativada[/red]")
1000
+
1001
+ console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
1002
+ console.print()
1003
+
1004
+ async def run_scan() -> List[PortScanResult]:
1005
+ """Executa a varredura de portas."""
1006
+ # Usa sempre TCP Connect que não requer privilégios de root
1007
+ console.print("[yellow]Aviso:[/yellow] Usando varredura TCP Connect (não requer privilégios de root).")
1008
+ scan_type = "tcp"
1009
+
1010
+ # Cria o scanner Nmap
585
1011
  scanner = PortScanner(
586
1012
  target=target,
587
- profile=profile,
588
- concurrency=concurrency,
589
- timeout=timeout,
1013
+ ports=ports,
1014
+ scan_type=scan_type,
590
1015
  stealth_level=stealth,
591
1016
  resolve_services=resolve_services,
592
1017
  check_vulns=check_vulns,
593
1018
  )
1019
+
1020
+ # Executa o scan e retorna os resultados
594
1021
  return await scanner.scan()
595
1022
 
596
- # Executa o scanner
597
- console.print(f"[bold]🔍 Iniciando varredura em:[/bold] {target}")
598
- console.print(f"📊 Perfil: {profile} (portas: {len(PROFILES[profile])})")
599
- console.print(f"🕵️ Nível de stealth: {stealth}")
1023
+ console.print("🚀 Iniciando varredura de portas...\n")
600
1024
 
601
- try:
602
- results = asyncio.run(_run())
1025
+ # Função para salvar resultados em diferentes formatos
1026
+ def save_results(results, output_path, format):
1027
+ output_format = format.lower()
603
1028
 
604
- if not results:
605
- console.print("[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
606
- return
607
-
608
- # Formata a saída
609
- if format.lower() == "json":
610
- output_text = json.dumps([r.to_dict() for r in results], indent=2)
611
- if not output:
612
- console.print_json(data=json.loads(output_text))
613
- else:
614
- output_text = format_scan_results(results, output_format=format)
615
- if not output:
616
- console.print(output_text)
617
-
618
- # Salva em arquivo se especificado
619
- if output:
620
- output.parent.mkdir(parents=True, exist_ok=True)
621
- with open(output, "w", encoding="utf-8") as f:
622
- if output.suffix == ".json":
623
- json.dump([r.to_dict() for r in results], f, indent=2, ensure_ascii=False)
624
- else:
1029
+ try:
1030
+ if output_format == "json":
1031
+ output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
1032
+ with open(output_path, 'w', encoding='utf-8') as f:
625
1033
  f.write(output_text)
626
- console.print(f"\n[green]✅ Resultados salvos em:[/green] {output.absolute()}")
1034
+ elif output_format == "markdown":
1035
+ with open(output_path, 'w', encoding='utf-8') as f:
1036
+ f.write("# Resultados da Varredura de Portas\n\n")
1037
+ f.write("| Porta | Estado | Serviço | Versão |\n")
1038
+ f.write("|-------|--------|---------|---------|\n")
1039
+ for result in results:
1040
+ if result.state == 'open':
1041
+ service = result.service or "desconhecido"
1042
+ version = result.version or ""
1043
+ f.write(f"| {result.port} | ABERTA | {service} | {version} |\n")
1044
+ else: # text
1045
+ with open(output_path, 'w', encoding='utf-8') as f:
1046
+ for result in results:
1047
+ if result.state == 'open':
1048
+ service = result.service or "desconhecido"
1049
+ version = f" ({result.version})" if result.version else ""
1050
+ f.write(f"Porta {result.port}/tcp: ABERTA - {service}{version}\n")
1051
+ except Exception as e:
1052
+ console.print(f"❌ [bold red]Erro ao salvar o arquivo:[/bold red] {str(e)}")
1053
+ raise typer.Exit(1)
627
1054
 
628
- except Exception as e:
629
- console.print(f"[red]❌ Erro durante a varredura:[/red] {str(e)}")
630
- if "object NoneType can't be used in 'await' expression" in str(e):
631
- console.print("[yellow]Dica:[/yellow] Tente aumentar o timeout com --timeout 5.0")
632
- raise typer.Exit(1)
633
- console.print(f"[green]✓ Resultado salvo em[/green] {output}")
1055
+ # Executa o scan de forma assíncrona
1056
+ with Progress(*progress_columns) as progress:
1057
+ task = progress.add_task("[red]Escaneando portas...", total=100)
1058
+
1059
+ try:
1060
+ # Executa a função assíncrona
1061
+ results = asyncio.run(run_scan())
1062
+ progress.update(task, completed=100)
1063
+
1064
+ # Exibe os resultados
1065
+ if results:
1066
+ # Cria uma tabela para exibir os resultados
1067
+ table = Table(title="[bold]Resultados da Varredura[/bold]")
1068
+ table.add_column("Porta", style="red", no_wrap=True)
1069
+ table.add_column("Estado", style="green")
1070
+ table.add_column("Serviço", style="yellow")
1071
+ table.add_column("Versão", style="magenta")
1072
+
1073
+ for result in results:
1074
+ if result.state == 'open':
1075
+ service = result.service or "desconhecido"
1076
+ version = result.version or ""
1077
+ table.add_row(
1078
+ str(result.port),
1079
+ "[green]ABERTA[/green]",
1080
+ service,
1081
+ version
1082
+ )
1083
+
1084
+ console.print()
1085
+ console.print(table)
1086
+
1087
+ # Salva os resultados em um arquivo, se solicitado
1088
+ if output_path:
1089
+ save_results(results, output_path, format)
1090
+ console.print(f"\n✅ Resultados salvos em: [bold red]{output_path}[/bold red]")
1091
+
1092
+ # Exibe saída JSON no console se não houver arquivo de saída e o formato for JSON
1093
+ if format.lower() == "json" and not output_path:
1094
+ output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
1095
+ console.print_json(data=json.loads(output_text))
1096
+
1097
+ else:
1098
+ console.print("\n❌ Nenhuma porta aberta encontrada ou ocorreu um erro durante a varredura.")
1099
+
1100
+ except Exception as e:
1101
+ progress.update(task, completed=100)
1102
+ console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
1103
+ if "Errno 8" in str(e) or "Name or service not known" in str(e):
1104
+ console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
1105
+ raise typer.Exit(1)
634
1106
 
635
1107
 
636
1108
  @app.command("crawl")
637
1109
  def crawl(
638
1110
  domain: str = typer.Argument(..., help="Alvo base (ex: https://example.com)"),
639
- max_pages: int = typer.Option(100, "--max-pages", help="Máximo de páginas"),
640
1111
  max_depth: int = typer.Option(2, "--max-depth", help="Profundidade máxima"),
1112
+ max_pages: int = typer.Option(100, "--max-pages", help="Número máximo de páginas a serem rastreadas"),
641
1113
  concurrency: int = typer.Option(10, "--concurrency", help="Workers paralelos"),
642
1114
  follow_subdomains: bool = typer.Option(False, "--follow-subdomains", help="Seguir subdomínios"),
643
1115
  output: Optional[str] = typer.Option(None, "--output", "-o", help="Arquivo JSON de saída"),
644
1116
  ):
645
- """Crawler leve para mapear formulários e rotas."""
1117
+ """Crawler leve para mapear formulários e rotas.
1118
+
1119
+ Exemplos:
1120
+ moriarty domain crawl https://example.com
1121
+ moriarty domain crawl https://example.com --max-depth 3 --max-pages 50
1122
+ """
646
1123
  import asyncio
647
1124
  import json
1125
+ from typing import Dict, Any
1126
+ from pathlib import Path
648
1127
 
649
1128
  async def _run():
650
1129
  crawler = WebCrawler(
@@ -659,15 +1138,46 @@ def crawl(
659
1138
  finally:
660
1139
  await crawler.close()
661
1140
 
662
- pages = asyncio.run(_run())
663
- summary = {
664
- "total_pages": len(pages),
665
- "forms": sum(len(page.forms) for page in pages.values()),
666
- }
667
- console.print_json(data=summary)
1141
+ try:
1142
+ console.print(f"🚀 Iniciando crawler em [red]{domain}[/red]...")
1143
+ pages = asyncio.run(_run())
1144
+
1145
+ # Cria um dicionário seguro para serialização
1146
+ safe_pages = {}
1147
+ for url, page in pages.items():
1148
+ safe_pages[url] = {
1149
+ 'url': url,
1150
+ 'title': getattr(page, 'title', ''),
1151
+ 'forms': [form.__dict__ for form in getattr(page, 'forms', [])],
1152
+ 'links': getattr(page, 'links', []),
1153
+ 'status_code': getattr(page, 'status_code', 0)
1154
+ }
1155
+
1156
+ # Exibe resumo
1157
+ summary = {
1158
+ "total_pages": len(pages),
1159
+ "forms": sum(len(page.get('forms', [])) for page in safe_pages.values()),
1160
+ "links_unicos": len({link for page in safe_pages.values() for link in page.get('links', [])})
1161
+ }
1162
+
1163
+ console.print("\n📊 [bold]Resumo do Crawling:[/bold]")
1164
+ console.print(f"• Páginas processadas: {summary['total_pages']}")
1165
+ console.print(f"• Formulários encontrados: {summary['forms']}")
1166
+ console.print(f"• Links únicos: {summary['links_unicos']}")
668
1167
 
669
- if output:
670
- payload = {url: page.__dict__ for url, page in pages.items()}
671
- with open(output, "w", encoding="utf-8") as handle:
672
- json.dump(payload, handle, indent=2, ensure_ascii=False)
673
- console.print(f"[green]✓ Resultado salvo em[/green] {output}")
1168
+ # Salva em arquivo se especificado
1169
+ if output:
1170
+ output_path = Path(output)
1171
+ output_path.parent.mkdir(parents=True, exist_ok=True)
1172
+ with open(output_path, "w", encoding="utf-8") as f:
1173
+ json.dump({
1174
+ "summary": summary,
1175
+ "pages": safe_pages
1176
+ }, f, indent=2, ensure_ascii=False)
1177
+ console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
1178
+
1179
+ except Exception as e:
1180
+ console.print(f"\n❌ [bold red]Erro durante o crawling:[/bold red] {str(e)}")
1181
+ if "Failed to establish a new connection" in str(e):
1182
+ console.print("💡 [yellow]Dica:[/yellow] Verifique sua conexão com a internet ou a URL fornecida.")
1183
+ raise typer.Exit(1)