220 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# File: src/main.py
 | 
						|
"""Main CLI interface for the forensic reconnaissance tool with two-mode operation."""
 | 
						|
 | 
						|
import click
 | 
						|
import json
 | 
						|
import sys
 | 
						|
import logging
 | 
						|
from pathlib import Path
 | 
						|
from .config import Config
 | 
						|
from .reconnaissance import ForensicReconnaissanceEngine
 | 
						|
from .report_generator import ForensicReportGenerator
 | 
						|
from .web_app import create_app
 | 
						|
 | 
						|
# Module logger
 | 
						|
logger = logging.getLogger(__name__)
 | 
						|
 | 
						|
@click.command()
 | 
						|
@click.argument('target', required=False)
 | 
						|
@click.option('--web', is_flag=True, help='Start web interface instead of CLI')
 | 
						|
@click.option('--shodan-key', help='Shodan API key')
 | 
						|
@click.option('--virustotal-key', help='VirusTotal API key')
 | 
						|
@click.option('--max-depth', default=2, help='Maximum recursion depth for full domain mode (default: 2)')
 | 
						|
@click.option('--output', '-o', help='Output file prefix (will create .json and .txt files)')
 | 
						|
@click.option('--json-only', is_flag=True, help='Only output JSON')
 | 
						|
@click.option('--text-only', is_flag=True, help='Only output text report')
 | 
						|
@click.option('--port', default=5000, help='Port for web interface (default: 5000)')
 | 
						|
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging (DEBUG level)')
 | 
						|
@click.option('--quiet', '-q', is_flag=True, help='Quiet mode (WARNING level only)')
 | 
						|
def main(target, web, shodan_key, virustotal_key, max_depth, output, json_only, text_only, port, verbose, quiet):
 | 
						|
    """Forensic DNS Reconnaissance Tool - Two-Mode Operation
 | 
						|
    
 | 
						|
    MODE 1 - Hostname-only (e.g., 'cc24'):
 | 
						|
      Expands hostname to all TLDs (cc24.com, cc24.net, etc.)
 | 
						|
      No recursive enumeration to avoid third-party infrastructure noise
 | 
						|
      Perfect for discovering domains using a specific hostname
 | 
						|
    
 | 
						|
    MODE 2 - Full domain (e.g., 'cc24.com'):
 | 
						|
      Full recursive reconnaissance with subdomain discovery
 | 
						|
      Maps complete infrastructure of the specified domain
 | 
						|
      Uses max-depth for recursive enumeration
 | 
						|
      Creates forensic-grade evidence chain with operation tracking
 | 
						|
    
 | 
						|
    Examples:
 | 
						|
        recon cc24                           # Mode 1: Find all cc24.* domains (no recursion)
 | 
						|
        recon cc24.com                       # Mode 2: Map cc24.com infrastructure (with forensic tracking)
 | 
						|
        recon cc24.com --max-depth 3         # Mode 2: Deeper recursive enumeration
 | 
						|
        recon cc24 -v                        # Mode 1: Verbose TLD expansion
 | 
						|
        recon --web                          # Start web interface with forensic visualization
 | 
						|
    """
 | 
						|
    
 | 
						|
    # Determine log level
 | 
						|
    if verbose:
 | 
						|
        log_level = "DEBUG"
 | 
						|
    elif quiet:
 | 
						|
        log_level = "WARNING"
 | 
						|
    else:
 | 
						|
        log_level = "INFO"
 | 
						|
    
 | 
						|
    # Create configuration and setup logging
 | 
						|
    config = Config.from_args(shodan_key, virustotal_key, max_depth, log_level)
 | 
						|
    config.setup_logging(cli_mode=True)
 | 
						|
    
 | 
						|
    if web:
 | 
						|
        # Start web interface
 | 
						|
        logger.info("🌐 Starting forensic web interface...")
 | 
						|
        app = create_app(config)
 | 
						|
        logger.info(f"🚀 Forensic web interface starting on http://0.0.0.0:{port}")
 | 
						|
        app.run(host='0.0.0.0', port=port, debug=False)
 | 
						|
        return
 | 
						|
    
 | 
						|
    if not target:
 | 
						|
        click.echo("Error: TARGET is required for CLI mode. Use --web for web interface.")
 | 
						|
        sys.exit(1)
 | 
						|
    
 | 
						|
    # Initialize forensic reconnaissance engine
 | 
						|
    logger.info("🔬 Initializing forensic reconnaissance engine...")
 | 
						|
    engine = ForensicReconnaissanceEngine(config)
 | 
						|
    
 | 
						|
    # Set up progress callback for CLI
 | 
						|
    def progress_callback(message, percentage=None):
 | 
						|
        if percentage is not None:
 | 
						|
            click.echo(f"[{percentage:3d}%] {message}")
 | 
						|
        else:
 | 
						|
            click.echo(f"       {message}")
 | 
						|
    
 | 
						|
    engine.set_progress_callback(progress_callback)
 | 
						|
    
 | 
						|
    # Display startup information
 | 
						|
    click.echo("=" * 80)
 | 
						|
    click.echo("FORENSIC DNS RECONNAISSANCE TOOL")
 | 
						|
    click.echo("=" * 80)
 | 
						|
    click.echo(f"Target: {target}")
 | 
						|
    
 | 
						|
    # Show operation mode
 | 
						|
    if '.' in target:
 | 
						|
        click.echo(f"Mode: Full domain reconnaissance (recursive depth: {max_depth})")
 | 
						|
        click.echo("   → Will map complete infrastructure with forensic tracking")
 | 
						|
        click.echo("   → Creates evidence chain for each discovery operation")
 | 
						|
    else:
 | 
						|
        click.echo(f"Mode: Hostname-only reconnaissance (TLD expansion)")
 | 
						|
        click.echo("   → Will find all domains using this hostname (no recursion)")
 | 
						|
        click.echo("   → Limited forensic tracking for efficiency")
 | 
						|
    
 | 
						|
    click.echo(f"DNS servers: {', '.join(config.DNS_SERVERS[:3])}{'...' if len(config.DNS_SERVERS) > 3 else ''}")
 | 
						|
    click.echo(f"DNS rate limit: {config.DNS_RATE_LIMIT}/s")
 | 
						|
    
 | 
						|
    if shodan_key:
 | 
						|
        click.echo("🕵️ Shodan integration enabled")
 | 
						|
    else:
 | 
						|
        click.echo("Shodan integration disabled (no API key)")
 | 
						|
        
 | 
						|
    if virustotal_key:
 | 
						|
        click.echo("🛡️ VirusTotal integration enabled")
 | 
						|
    else:
 | 
						|
        click.echo("VirusTotal integration disabled (no API key)")
 | 
						|
    
 | 
						|
    click.echo("")
 | 
						|
    
 | 
						|
    # Run forensic reconnaissance
 | 
						|
    try:
 | 
						|
        logger.info(f"🎯 Starting forensic reconnaissance for target: {target}")
 | 
						|
        data = engine.run_reconnaissance(target)
 | 
						|
        
 | 
						|
        # Display final statistics
 | 
						|
        stats = data.get_stats()
 | 
						|
        graph_analysis = data._generate_graph_analysis()
 | 
						|
        
 | 
						|
        click.echo("")
 | 
						|
        click.echo("=" * 80)
 | 
						|
        click.echo("FORENSIC RECONNAISSANCE COMPLETE")
 | 
						|
        click.echo("=" * 80)
 | 
						|
        click.echo(f"Hostnames discovered: {stats['hostnames']}")
 | 
						|
        click.echo(f"IP addresses found: {stats['ip_addresses']}")
 | 
						|
        click.echo(f"Discovery relationships: {stats['discovery_edges']}")
 | 
						|
        click.echo(f"Operations performed: {stats['operations_performed']}")
 | 
						|
        click.echo(f"DNS records collected: {stats['dns_records']}")
 | 
						|
        click.echo(f"Certificates found: {stats['certificates_total']} ({stats['certificates_current']} valid, {stats['certificates_expired']} expired)")
 | 
						|
        click.echo(f"Shodan results: {stats['shodan_results']}")
 | 
						|
        click.echo(f"VirusTotal results: {stats['virustotal_results']}")
 | 
						|
        click.echo(f"Maximum discovery depth: {graph_analysis['max_depth']}")
 | 
						|
        
 | 
						|
        # Calculate and display timing
 | 
						|
        if data.end_time and data.start_time:
 | 
						|
            duration = data.end_time - data.start_time
 | 
						|
            click.echo(f"Total time: {duration}")
 | 
						|
        
 | 
						|
        click.echo("")
 | 
						|
        
 | 
						|
        # Generate forensic reports
 | 
						|
        logger.info("📄 Generating forensic reports...")
 | 
						|
        report_gen = ForensicReportGenerator(data)
 | 
						|
        
 | 
						|
        if output:
 | 
						|
            # Save to files
 | 
						|
            saved_files = []
 | 
						|
            
 | 
						|
            if not text_only:
 | 
						|
                json_file = f"{output}.json"
 | 
						|
                try:
 | 
						|
                    json_content = data.to_json()
 | 
						|
                    with open(json_file, 'w', encoding='utf-8') as f:
 | 
						|
                        f.write(json_content)
 | 
						|
                    saved_files.append(json_file)
 | 
						|
                    logger.info(f"📄 Forensic JSON report saved: {json_file}")
 | 
						|
                except Exception as e:
 | 
						|
                    logger.error(f"❌ Failed to save JSON report: {e}")
 | 
						|
            
 | 
						|
            if not json_only:
 | 
						|
                text_file = f"{output}.txt"
 | 
						|
                try:
 | 
						|
                    with open(text_file, 'w', encoding='utf-8') as f:
 | 
						|
                        f.write(report_gen.generate_text_report())
 | 
						|
                    saved_files.append(text_file)
 | 
						|
                    logger.info(f"📄 Forensic text report saved: {text_file}")
 | 
						|
                except Exception as e:
 | 
						|
                    logger.error(f"❌ Failed to save text report: {e}")
 | 
						|
            
 | 
						|
            if saved_files:
 | 
						|
                click.echo(f"📁 Forensic reports saved:")
 | 
						|
                for file in saved_files:
 | 
						|
                    click.echo(f"   {file}")
 | 
						|
        
 | 
						|
        else:
 | 
						|
            # Output to stdout
 | 
						|
            if json_only:
 | 
						|
                try:
 | 
						|
                    click.echo(data.to_json())
 | 
						|
                except Exception as e:
 | 
						|
                    logger.error(f"❌ Failed to generate JSON output: {e}")
 | 
						|
                    click.echo(f"Error generating JSON: {e}")
 | 
						|
            elif text_only:
 | 
						|
                try:
 | 
						|
                    click.echo(report_gen.generate_text_report())
 | 
						|
                except Exception as e:
 | 
						|
                    logger.error(f"❌ Failed to generate text report: {e}")
 | 
						|
                    click.echo(f"Error generating text report: {e}")
 | 
						|
            else:
 | 
						|
                # Default: show text report
 | 
						|
                try:
 | 
						|
                    click.echo(report_gen.generate_text_report())
 | 
						|
                    click.echo(f"\n📊 To get JSON output with full forensic data, use: --json-only")
 | 
						|
                    click.echo(f"💾 To save reports, use: --output filename")
 | 
						|
                    click.echo(f"🌐 For interactive visualization, use: --web")
 | 
						|
                except Exception as e:
 | 
						|
                    logger.error(f"❌ Failed to generate report: {e}")
 | 
						|
                    click.echo(f"Error generating report: {e}")
 | 
						|
    
 | 
						|
    except KeyboardInterrupt:
 | 
						|
        logger.warning("⚠️ Forensic reconnaissance interrupted by user")
 | 
						|
        click.echo("\n🛑 Reconnaissance interrupted by user.")
 | 
						|
        sys.exit(1)
 | 
						|
    except Exception as e:
 | 
						|
        logger.error(f"❌ Error during forensic reconnaissance: {e}", exc_info=True)
 | 
						|
        click.echo(f"❌ Error during reconnaissance: {e}")
 | 
						|
        if verbose:
 | 
						|
            raise  # Re-raise in verbose mode to show full traceback
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    main() |