213 lines
8.6 KiB
Python
213 lines
8.6 KiB
Python
# File: src/main.py
|
|
"""Main CLI interface for the 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 ReconnaissanceEngine
|
|
from .report_generator import ReportGenerator
|
|
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):
|
|
"""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
|
|
|
|
Examples:
|
|
recon cc24 # Mode 1: Find all cc24.* domains (no recursion)
|
|
recon cc24.com # Mode 2: Map cc24.com infrastructure (with recursion)
|
|
recon cc24.com --max-depth 3 # Mode 2: Deeper recursive enumeration
|
|
recon cc24 -v # Mode 1: Verbose TLD expansion
|
|
recon --web # Start web interface
|
|
"""
|
|
|
|
# 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 web interface...")
|
|
app = create_app(config)
|
|
logger.info(f"Web interface starting on http://0.0.0.0:{port}")
|
|
app.run(host='0.0.0.0', port=port, debug=False) # Changed debug to False to reduce noise
|
|
return
|
|
|
|
if not target:
|
|
click.echo("Error: TARGET is required for CLI mode. Use --web for web interface.")
|
|
sys.exit(1)
|
|
|
|
# Initialize reconnaissance engine
|
|
logger.info("Initializing reconnaissance engine...")
|
|
engine = ReconnaissanceEngine(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("=" * 60)
|
|
click.echo("DNS RECONNAISSANCE TOOL")
|
|
click.echo("=" * 60)
|
|
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 of the specified domain")
|
|
else:
|
|
click.echo(f"Mode: Hostname-only reconnaissance (TLD expansion)")
|
|
click.echo(" → Will find all domains using this hostname (no recursion)")
|
|
|
|
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")
|
|
logger.info(f"Shodan API key provided (ends with: ...{shodan_key[-4:] if len(shodan_key) > 4 else shodan_key})")
|
|
else:
|
|
click.echo("Shodan integration disabled (no API key)")
|
|
|
|
if virustotal_key:
|
|
click.echo("VirusTotal integration enabled")
|
|
logger.info(f"VirusTotal API key provided (ends with: ...{virustotal_key[-4:] if len(virustotal_key) > 4 else virustotal_key})")
|
|
else:
|
|
click.echo("VirusTotal integration disabled (no API key)")
|
|
|
|
click.echo("")
|
|
|
|
# Run reconnaissance
|
|
try:
|
|
logger.info(f"Starting reconnaissance for target: {target}")
|
|
data = engine.run_reconnaissance(target)
|
|
|
|
# Display final statistics
|
|
stats = data.get_stats()
|
|
click.echo("")
|
|
click.echo("=" * 60)
|
|
click.echo("RECONNAISSANCE COMPLETE")
|
|
click.echo("=" * 60)
|
|
click.echo(f"Hostnames discovered: {stats['hostnames']}")
|
|
click.echo(f"IP addresses found: {stats['ip_addresses']}")
|
|
click.echo(f"DNS records collected: {stats['dns_records']}")
|
|
click.echo(f"Certificates found: {stats['certificates']}")
|
|
click.echo(f"Shodan results: {stats['shodan_results']}")
|
|
click.echo(f"VirusTotal results: {stats['virustotal_results']}")
|
|
|
|
# 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 reports
|
|
logger.info("Generating reports...")
|
|
report_gen = ReportGenerator(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"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"Text report saved: {text_file}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to save text report: {e}")
|
|
|
|
if saved_files:
|
|
click.echo(f"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"\nTo get JSON output, use: --json-only")
|
|
click.echo(f"To save reports, use: --output filename")
|
|
except Exception as e:
|
|
logger.error(f"Failed to generate report: {e}")
|
|
click.echo(f"Error generating report: {e}")
|
|
|
|
except KeyboardInterrupt:
|
|
logger.warning("Reconnaissance interrupted by user")
|
|
click.echo("\nReconnaissance interrupted by user.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
logger.error(f"Error during 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() |