# Directory Structure ``` ├── .dockerignore ├── .gitignore ├── .vscode │ └── mcp.json ├── banner.sh ├── demos │ ├── holehe.gif │ ├── nmap.gif │ ├── ocr2text.png │ ├── README.md │ ├── sherlock.gif │ └── sqlmap.gif ├── Dockerfile ├── docs │ └── index.html ├── LICENSE ├── README.md ├── requirements.txt ├── server.py └── toolkit ├── dnsrecon.py ├── holehe.py ├── nmap.py ├── ocr2text.py ├── sherlock.py ├── sqlmap.py ├── sublist3r.py ├── wpscan.py └── zmap.py ``` # Files -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- ``` # Python __pycache__/ *.py[cod] *$py.class *.so .Python venv/ env/ ENV/ .env .venv .eggs/ *.egg-info/ # Development .git .github .vscode .idea # Build artifacts dist/ build/ *.log ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Python virtual environment venv/ .venv/ env/ ENV/ # Python bytecode __pycache__/ *.py[cod] *$py.class # Environment variables .env .env.local .env.development.local .env.test.local .env.production.local # OS specific files .DS_Store Thumbs.db ``` -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- ```markdown ### Ocr2Text  ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` mcp==1.6.0 numpy==1.26.4 opencv-python-headless==4.9.0.80 openai==1.70.0 openai-agents==0.0.7 pdf2image==1.17.0 pytesseract python-dotenv==1.1.0 requests==2.32.4 tqdm==4.67.1 typing-inspection==0.4.0 typing_extensions==4.13.1 uvicorn==0.34.0 sublist3r ``` -------------------------------------------------------------------------------- /.vscode/mcp.json: -------------------------------------------------------------------------------- ```json { "servers": { "HydraMCP": { "command": "docker", "args": [ "run", "--rm", "-i", "--net=host", "--privileged", "--name", "hydramcp", "hydramcp" ] } } } ``` -------------------------------------------------------------------------------- /banner.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash cat << "BANNER" • Title: HydraΜCP — The Model Context Protocol (MCP) Pentesting Toolkit • Version: 0.1.0 • License: MIT • Description: A lightweight, extensible cybersecurity toolkit that connects AI assistants to security tools through the Model Context Protocol (MCP), enabling AI-assisted security research, scanning, and analysis. • Community: @happyhackingspace | https://happyhacking.space • Author: Built with ❤️ by @atiilla BANNER ``` -------------------------------------------------------------------------------- /toolkit/holehe.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: Holehe # - Description: Email account checker # - Usage: Checks if an email address is registered on various websites # - Parameters: email (required) import subprocess import logging from typing import Dict, Any # Logging setup logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("holehe-tool") class Holehe: """ Holehe wrapper class for checking email registrations. """ def __init__(self): """Initialize Holehe wrapper.""" logger.info("Holehe wrapper initialized.") def scan(self, email: str) -> Dict[str, Any]: """ Run the holehe scan for a given email. Args: email (str): The email address to check. Returns: Dict[str, Any]: Result or error from the holehe command. """ logger.info(f"Running Holehe scan for: {email}") try: result = subprocess.run(["holehe", email], capture_output=True, text=True, check=True) output = result.stdout.strip() return { "email": email, "success": True, "output": output } except subprocess.CalledProcessError as e: logger.error(f"Holehe scan failed: {e.stderr.strip()}") return { "email": email, "success": False, "error": e.stderr.strip() } def ExecHolehe(email: str) -> Dict[str, Any]: """ Convenience wrapper to run a holehe scan. Args: email (str): Email address to scan. Returns: Dict[str, Any]: Holehe scan results. """ scanner = Holehe() return scanner.scan(email) ``` -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- ```python from typing import List, Optional, Dict, Any from mcp.server.fastmcp import FastMCP from toolkit.holehe import ExecHolehe from toolkit.nmap import ExecNmap from toolkit.wpscan import ExecWpscan from toolkit.zmap import ExecZmap from toolkit.sqlmap import ExecSqlmap from toolkit.ocr2text import ExecOcr2Text from toolkit.sublist3r import ExecSublist3r from toolkit.dnsrecon import ExecDNSRecon from toolkit.sherlock import ExecSherlock # Create server mcp = FastMCP(name="HydraΜCP", version="0.1.0" ) @mcp.tool() def ZmapScanner( target: str, port: int, bandwidth: Optional[str] = "1M", ) -> Dict[str, Any]: """Wrapper for running ZMap network scanning.""" return ExecZmap(target, port, bandwidth) @mcp.tool() def WPScanScanner( url: str, ) -> Dict[str, Any]: """Wrapper for running WPScan vulnerability scanning.""" return ExecWpscan(url) @mcp.tool() def HoleheScanner( email: str, ) -> Dict[str, Any]: """Wrapper for running Holehe email registration checking.""" return ExecHolehe(email) @mcp.tool() def NmapScanner( target: str, ports: Optional[str] = None, ) -> Dict[str, Any]: """Wrapper for running Nmap network scanning.""" return ExecNmap(target, ports) @mcp.tool() def SqlmapScanner( url: str, data: Optional[str] = None, ) -> Dict[str, Any]: """Wrapper for running Sqlmap vulnerability scanning.""" return ExecSqlmap(url, data) @mcp.tool() def OcrScanner( file_path: str, ) -> Dict[str, Any]: """Wrapper for running OCR (Optical Character Recognition) on images and PDFs. The file_path can be: - A local file path - A direct URL (http/https) - A URL prefixed with @ symbol """ return ExecOcr2Text(file_path) @mcp.tool() def Sublist3rScanner( domain: str, output_dir: Optional[str] = "output", ) -> List[str]: """Wrapper for running Sublist3r subdomain enumeration.""" return ExecSublist3r(domain, output_dir) @mcp.tool() def DNSReconScanner( domain: str, scan_type: Optional[str] = "std", name_server: Optional[str] = None, range: Optional[str] = None, dictionary: Optional[str] = None, ) -> Dict[str, Any]: """Wrapper for running DNSRecon for DNS reconnaissance.""" kwargs = {} if name_server: kwargs["name_server"] = name_server if range: kwargs["range"] = range if dictionary: kwargs["dictionary"] = dictionary return ExecDNSRecon(domain, scan_type, **kwargs) @mcp.tool() def SherlockScanner( usernames: List[str], ) -> Dict[str, Any]: """Wrapper for running Sherlock username enumeration.""" return ExecSherlock(usernames) if __name__ == "__main__": mcp.run(transport="stdio") ``` -------------------------------------------------------------------------------- /toolkit/nmap.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: Nmap # - Description: Network scanning tool # - Usage: Scans networks for hosts and services # - Parameters: target (required), ports (optional) import subprocess import logging import re from typing import List, Optional, Tuple # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Define dynamic mappings of phrases to Nmap options KEYWORD_MAP = { "no ping": "-Pn", "skip ping": "-Pn", "version": "-sV", "service detection": "-sV", "os detection": "-O", "aggressive": "-A", "verbose": "-v", "top ports": "--top-ports 100", "udp": "-sU", "quick": "-T4", } def parse_nmap_prompt(prompt: str) -> Tuple[str, Optional[str], List[str]]: prompt = prompt.lower() options = [] ports = None # Extract IP address (supports v4 only here) ip_match = re.search(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", prompt) target = ip_match.group(0) if ip_match else "" # Extract ports if mentioned like 'ports 22,80' or 'scan port 443' port_match = re.search(r"port[s]?\s*(\d{1,5}(?:\s*,\s*\d{1,5})*)", prompt) if port_match: ports = port_match.group(1).replace(" ", "") # Match keywords to options for phrase, option in KEYWORD_MAP.items(): if phrase in prompt: options.append(option) return target, ports, options def ExecNmap( target: str, ports: Optional[str] = None, options: Optional[List[str]] = None, ) -> str: """Run an Nmap network scan on the specified target.""" logger.debug("Running Nmap with target=%s, ports=%s, options=%s", target, ports, options) if subprocess.run(["which", "nmap"], capture_output=True).returncode != 0: return {"error": "Nmap is not installed. Install it with 'sudo apt install nmap'."} cmd = ["nmap", target] if ports: cmd += ["-p", ports] if options: cmd += options logger.info("Executing command: %s", ' '.join(cmd)) try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) return {"success": True, "output": result.stdout} except subprocess.CalledProcessError as e: logger.error("Nmap command failed: %s", e.stderr) return {"success": False, "error": e.stderr.strip()} except Exception as e: logger.exception("Unexpected error during Nmap execution") return {"success": False, "error": f"Error executing Nmap scan: {str(e)}"} def scan_from_prompt(prompt: str) -> str: target, ports, options = parse_nmap_prompt(prompt) if not target: return {"success": False, "error": "Could not detect a valid IP address in the prompt."} return ExecNmap(target, ports, options) ``` -------------------------------------------------------------------------------- /toolkit/wpscan.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: WPScan # - Description: WordPress vulnerability scanner # - Usage: Scans WordPress sites for vulnerabilities and security issues # - Parameters: url (required), format (optional), api_token (optional) import subprocess import logging from typing import Any, Dict, List, Optional import json # Logging setup logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("wpscan-tool") class WPScan: """WPScan tool wrapper class.""" def __init__(self): """Initialize WPScan wrapper.""" self._check_installed() def _check_installed(self) -> None: """Verify WPScan is installed.""" try: subprocess.run(["wpscan", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logger.info("WPScan installation verified.") except (subprocess.SubprocessError, FileNotFoundError): logger.warning("wpscan command not found. Make sure WPScan is installed and in your PATH.") raise RuntimeError("WPScan is not installed or not in PATH.") def get_version(self) -> Optional[str]: """Get installed WPScan version.""" try: result = subprocess.run(["wpscan", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) version_line = result.stdout.strip().split('\n')[-1] logger.info(f"WPScan version: {version_line}") return version_line except (subprocess.SubprocessError, FileNotFoundError) as e: logger.error(f"WPScan version check failed: {e}") return None def scan(self, url: str, **kwargs: Any) -> Dict[str, Any]: """Run the WPScan scan.""" if not url: raise ValueError("Target URL must be provided.") cmd = ["wpscan", "--url", url,"--random-user-agent","--ignore-main-redirect"] # Append additional options to the command if "format" in kwargs: cmd.append("--format") cmd.append(kwargs["format"]) if kwargs.get("verbose", False): cmd.append("--verbose") if kwargs.get("random_user_agent", False): cmd.append("--random-user-agent") if "max_threads" in kwargs: cmd.append("--max-threads") cmd.append(str(kwargs["max_threads"])) if "api_token" in kwargs: cmd.append("--api-token") cmd.append(kwargs["api_token"]) if "enumerate_opts" in kwargs: cmd.append("--enumerate") cmd.append(",".join(kwargs["enumerate_opts"])) logger.info(f"Preparing WPScan command: {' '.join(cmd)}") # Run the scan try: result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) output = result.stdout # If format is json, parse it if kwargs.get("format", "json") == "json": return json.loads(output) return {"status": "success", "output": output} except subprocess.SubprocessError as e: logger.error(f"Error during WPScan scan: {e}") return {"status": "error", "message": str(e)} # Example usage (optional convenience function) def ExecWpscan(url: str, **kwargs: Any) -> Dict[str, Any]: """Convenience wrapper to run a WPScan scan.""" scanner = WPScan() try: return scanner.scan(url, **kwargs) except Exception as e: logger.error(f"WPScan scan failed: {e}") return {"error": str(e)} ``` -------------------------------------------------------------------------------- /toolkit/zmap.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: ZMap # - Description: High-speed network scanner # - Usage: Scans large network ranges quickly # - Parameters: target (required), port (required), bandwidth (optional) from typing import Any, Dict, List import ipaddress import logging import subprocess import re import random # Logging setup logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("zmap-tool") class ZMap: """ZMap scanner class for network scanning operations.""" def __init__(self): """Initialize ZMap scanner.""" self.interface = "eth0" self.gateway_mac = self._generate_random_mac() self._check_installed() def _check_installed(self) -> None: """Verify ZMap is installed.""" try: subprocess.run(["zmap", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except (subprocess.SubprocessError, FileNotFoundError): logger.warning("ZMap is not installed or not in PATH.") def _generate_random_mac(self) -> str: """Generate a random locally administered unicast MAC address.""" mac = [0x02, 0x00, 0x00, random.randint(0x00, 0x7F), random.randint(0x00, 0xFF), random.randint(0x00, 0xFF)] return ':'.join(f'{b:02x}' for b in mac) def _convert_ip_range_to_cidr(self, ip_range: str) -> str: """Convert IP range or CIDR to canonical CIDR format.""" try: network = ipaddress.IPv4Network(ip_range, strict=False) return str(network) except ValueError: pass match = re.match(r'(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)', ip_range) if not match: raise ValueError(f"Invalid IP range format: {ip_range}") start_ip, end_ip = match.group(1), match.group(2) start, end = ipaddress.IPv4Address(start_ip), ipaddress.IPv4Address(end_ip) for prefixlen in range(32, -1, -1): network = ipaddress.IPv4Network(f"{start_ip}/{prefixlen}", strict=False) if network[0] <= start and network[-1] >= end: return str(network) raise ValueError(f"Could not convert IP range {ip_range} to CIDR notation") def get_version(self) -> str: """Get installed ZMap version.""" try: result = subprocess.run(["zmap", "--version"], check=True, stdout=subprocess.PIPE, text=True) return result.stdout.strip() except subprocess.SubprocessError as e: logger.error(f"ZMap version check failed: {e}") raise RuntimeError("ZMap is not installed or misconfigured.") def scan(self, target_port: int, subnets: List[str], bandwidth: str = "1M") -> Dict[str, Any]: """Run the ZMap scan.""" cidr_subnets = [self._convert_ip_range_to_cidr(subnet) for subnet in subnets] if not (0 < target_port <= 65535): raise ValueError("Port must be between 1 and 65535") cmd = [ "zmap", "-p", str(target_port), "-B", bandwidth, "-i", self.interface, "-G", self.gateway_mac, "--blacklist-file=/dev/null", "-o", "-" ] cmd.extend(cidr_subnets) logger.info(f"Running ZMap scan: {' '.join(cmd)}") try: result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) hosts = [line.strip() for line in result.stdout.splitlines() if line.strip()] return { "port": target_port, "hosts": hosts, "total_hosts": len(hosts), "subnets_scanned": cidr_subnets } except subprocess.CalledProcessError as e: logger.error(f"ZMap scan failed: {e.stderr.strip()}") return { "error": "Scan failed", "details": e.stderr, "exit_code": e.returncode } def ExecZmap(target: str, port: int, bandwidth: str = "1M") -> Dict[str, Any]: """Convenience wrapper to run a ZMap scan.""" zmap = ZMap() try: return zmap.scan(target_port=port, subnets=[target], bandwidth=bandwidth) except Exception as e: logger.error(f"Scan error: {e}") return {"error": str(e)} ``` -------------------------------------------------------------------------------- /toolkit/sublist3r.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: Sublist3r # - Description: Fast subdomain enumeration tool # - Usage: Finds subdomains via search engines # - Parameters: domain (required), output_dir (optional) import subprocess import os import json import logging import re from typing import List, Dict, Any # Logging setup logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("sublist3r-tool") class Sublist3r: """ Sublist3r wrapper class for subdomain enumeration. """ def __init__(self): """Initialize Sublist3r wrapper.""" logger.info("Sublist3r wrapper initialized.") def extract_subdomains_from_output(self, output: str) -> List[str]: """ Extract subdomains from the command output. Args: output (str): Command output text Returns: List[str]: List of extracted subdomains """ # Remove ANSI color codes ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') clean_output = ansi_escape.sub('', output) # Extract subdomains using regex # Look for lines that appear to be domains (contain at least one dot and no special chars) domain_pattern = re.compile(r'^(?:[\w-]+\.)+[\w-]+$', re.MULTILINE) return domain_pattern.findall(clean_output) def scan(self, domain: str, output_dir: str) -> Dict[str, Any]: """ Run Sublist3r tool to enumerate subdomains of a given domain. Args: domain (str): The target domain to enumerate subdomains for. output_dir (str): The directory to save the output files. Returns: Dict[str, Any]: Results or error from the Sublist3r command. """ # Create output directory if it doesn't exist os.makedirs(output_dir, exist_ok=True) output_file = os.path.join(output_dir, f"{domain}_subdomains.txt") logger.info(f"Running Sublist3r scan for domain: {domain}") try: # Run without output file first to capture stdout result = subprocess.run( ["sublist3r", "-d", domain], capture_output=True, text=True, check=True ) output = result.stdout.strip() # Try to extract subdomains from the output subdomains_from_output = self.extract_subdomains_from_output(output) # Also try to run with output file as fallback subprocess.run( ["sublist3r", "-d", domain, "-o", output_file], capture_output=True, text=True, check=True ) # Parse the output file to get subdomains subdomains_from_file = [] if os.path.exists(output_file): with open(output_file, 'r') as f: subdomains_from_file = [line.strip() for line in f if line.strip()] # Combine subdomains from both sources subdomains = list(set(subdomains_from_output + subdomains_from_file)) # Save results to JSON file json_output_file = os.path.join(output_dir, f"{domain}_subdomains.json") with open(json_output_file, 'w') as f: json.dump(subdomains, f) logger.info(f"Sublist3r found {len(subdomains)} subdomains for {domain}") logger.info(f"Sublist3r results saved to {json_output_file}") return { "domain": domain, "success": True, "subdomains": subdomains, "output": output } except subprocess.CalledProcessError as e: logger.error(f"Sublist3r scan failed: {e.stderr.strip()}") return { "domain": domain, "success": False, "error": e.stderr.strip() } def ExecSublist3r(domain: str, output_dir: str = "results") -> Dict[str, Any]: """ Convenience wrapper to run a Sublist3r scan. Args: domain (str): The target domain to enumerate subdomains for. output_dir (str): The directory to save the output files, defaults to "results". Returns: Dict[str, Any]: Sublist3r scan results. """ scanner = Sublist3r() return scanner.scan(domain, output_dir) ``` -------------------------------------------------------------------------------- /toolkit/sherlock.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: Sherlock # - Description: Username hunter for social networks # - Usage: Finds usernames across multiple social networks # - Parameters: usernames (required), various optional parameters import subprocess import logging from typing import Dict, Any, List, Optional # Logging setup logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("sherlock-tool") class Sherlock: """ Sherlock wrapper class for hunting usernames across social networks. """ def __init__(self): """Initialize Sherlock wrapper.""" logger.info("Sherlock wrapper initialized.") def hunt(self, usernames: List[str], output: Optional[str] = None, folderoutput: Optional[str] = None, verbose: bool = False, tor: bool = False, unique_tor: bool = False, csv: bool = False, xlsx: bool = False, sites: Optional[List[str]] = None, proxy: Optional[str] = None, json_file: Optional[str] = None, timeout: Optional[int] = None, print_all: bool = False, print_found: bool = False, no_color: bool = False, browse: bool = False, local: bool = False, nsfw: bool = False ) -> Dict[str, Any]: """ Run the sherlock hunt for given usernames. Args: usernames (List[str]): List of usernames to check. output (Optional[str]): Output file for single username results. folderoutput (Optional[str]): Output folder for multiple username results. verbose (bool): Display extra debugging information. tor (bool): Make requests over Tor. unique_tor (bool): Make requests over Tor with new circuit per request. csv (bool): Create CSV output file. xlsx (bool): Create XLSX output file. sites (Optional[List[str]]): Specific sites to check. proxy (Optional[str]): Proxy URL to use. json_file (Optional[str]): JSON file for data input. timeout (Optional[int]): Request timeout in seconds. print_all (bool): Output sites where username was not found. print_found (bool): Output sites where username was found. no_color (bool): Disable colored terminal output. browse (bool): Open results in browser. local (bool): Force use of local data.json file. nsfw (bool): Include NSFW sites in checks. Returns: Dict[str, Any]: Result or error from the sherlock command. """ logger.info(f"Running Sherlock hunt for: {', '.join(usernames)}") cmd = ["sherlock"] # Add all flags and their values if verbose: cmd.append("--verbose") if folderoutput: cmd.extend(["--folderoutput", folderoutput]) if output: cmd.extend(["--output", output]) if tor: cmd.append("--tor") if unique_tor: cmd.append("--unique-tor") if csv: cmd.append("--csv") if xlsx: cmd.append("--xlsx") if sites: for site in sites: cmd.extend(["--site", site]) if proxy: cmd.extend(["--proxy", proxy]) if json_file: cmd.extend(["--json", json_file]) if timeout: cmd.extend(["--timeout", str(timeout)]) if print_all: cmd.append("--print-all") if print_found: cmd.append("--print-found") if no_color: cmd.append("--no-color") if browse: cmd.append("--browse") if local: cmd.append("--local") if nsfw: cmd.append("--nsfw") # Add usernames cmd.extend(usernames) try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) output = result.stdout.strip() return { "usernames": usernames, "success": True, "output": output } except subprocess.CalledProcessError as e: logger.error(f"Sherlock hunt failed: {e.stderr.strip()}") return { "usernames": usernames, "success": False, "error": e.stderr.strip() } def ExecSherlock(usernames: List[str], **kwargs) -> Dict[str, Any]: """ Convenience wrapper to run a sherlock username hunt. Args: usernames (List[str]): Usernames to hunt. **kwargs: Additional parameters to pass to Sherlock.hunt(). Returns: Dict[str, Any]: Sherlock hunt results. """ scanner = Sherlock() return scanner.hunt(usernames, **kwargs) ``` -------------------------------------------------------------------------------- /toolkit/ocr2text.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: OCR Scanner # - Description: Optical Character Recognition tool # - Usage: Extracts text from images and PDF files # - Parameters: file_path (required) - can be a local file or URL prefixed with @ import cv2 import pytesseract from pdf2image import convert_from_path import os import numpy as np from typing import Dict, Any import requests import tempfile from urllib.parse import urlparse # Configure Tesseract path - use system path for Kali Linux if os.path.exists('/usr/bin/tesseract'): pytesseract.pytesseract.tesseract_cmd = r'/usr/bin/tesseract' elif os.path.exists('/usr/local/bin/tesseract'): pytesseract.pytesseract.tesseract_cmd = r'/usr/local/bin/tesseract' elif os.path.exists('C:\\Program Files\\Tesseract-OCR\\tesseract.exe'): pytesseract.pytesseract.tesseract_cmd = r'C:\\Program Files\\Tesseract-OCR\\tesseract.exe' def download_image(url): """Download image from a URL and save to a temporary file""" try: response = requests.get(url, stream=True) response.raise_for_status() # Get the file extension from the URL parsed_url = urlparse(url) filename = os.path.basename(parsed_url.path) _, ext = os.path.splitext(filename) if not ext: # Default to .png if no extension found ext = '.png' # Create a temporary file with the correct extension temp_file = tempfile.NamedTemporaryFile(suffix=ext, delete=False) temp_filename = temp_file.name # Write the image data to the temporary file with open(temp_filename, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) return temp_filename except Exception as e: print(f"Error downloading image: {str(e)}") return None def process_image(img): """Process image for better OCR results""" gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray, img_bin = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) gray = cv2.bitwise_not(img_bin) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 1)) dilation = cv2.dilate(gray, kernel, iterations=1) erosion = cv2.erode(dilation, kernel, iterations=1) text = pytesseract.image_to_string(erosion) return text def extract_text_from_pdf(pdf_path): """Extract text from a PDF file using OCR""" try: # Use poppler_path if on Windows, but we're in Kali Linux so not needed images = convert_from_path(pdf_path) all_text = [] for i, image in enumerate(images): opencv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) text = process_image(opencv_image) all_text.append(f"=== Page {i+1} ===\n{text}\n") return "\n".join(all_text) except Exception as e: return f"Error processing PDF: {str(e)}" def extract_text_from_image(image_path): """Extract text from an image file using OCR""" try: img = cv2.imread(image_path) if img is None: return f"Error: Could not read image file {image_path}" return process_image(img) except Exception as e: return f"Error processing image: {str(e)}" def ExecOcr2Text(file_path: str) -> Dict[str, Any]: """Execute OCR on a file (image or PDF) or URL""" temp_file = None try: # Check if the file_path is a URL (starts with @ or http/https) if file_path.startswith('@'): url = file_path[1:] # Remove the @ symbol print(f"Downloading image from URL: {url}") temp_file = download_image(url) elif file_path.startswith(('http://', 'https://')): url = file_path print(f"Downloading image from URL: {url}") temp_file = download_image(url) if temp_file is None and (file_path.startswith('@') or file_path.startswith(('http://', 'https://'))): return { "success": False, "error": f"Failed to download image from URL: {url}", "text": "" } # Use the temp file if a URL was provided if temp_file: file_path = temp_file # Check if the file exists if not os.path.exists(file_path): return { "success": False, "error": f"File not found: {file_path}", "text": "" } file_ext = os.path.splitext(file_path)[1].lower() if file_ext == '.pdf': text = extract_text_from_pdf(file_path) elif file_ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif']: text = extract_text_from_image(file_path) else: return { "success": False, "error": f"Unsupported file format: {file_ext}", "text": "" } return { "success": True, "error": "", "text": text } except Exception as e: return { "success": False, "error": str(e), "text": "" } finally: # Clean up temporary file if it was created if temp_file and os.path.exists(temp_file): try: os.unlink(temp_file) except: pass ``` -------------------------------------------------------------------------------- /toolkit/dnsrecon.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: DNSRecon # - Description: DNS reconnaissance tool # - Usage: Performs various DNS-related scans and enumeration # - Parameters: domain (required), scan_type (optional), name_server (optional), range (optional), dictionary (optional) import subprocess import logging from typing import Any, Dict, List, Optional import json # Logging setup logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("dnsrecon-tool") class DNSRecon: """DNSRecon tool wrapper class.""" def __init__(self): """Initialize DNSRecon wrapper.""" self._check_installed() def _check_installed(self) -> None: """Verify DNSRecon is installed.""" try: subprocess.run(["dnsrecon", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logger.info("DNSRecon installation verified.") except (subprocess.SubprocessError, FileNotFoundError): logger.warning("dnsrecon command not found. Make sure DNSRecon is installed and in your PATH.") raise RuntimeError("DNSRecon is not installed or not in PATH.") def get_version(self) -> Optional[str]: """Get installed DNSRecon version.""" try: result = subprocess.run(["dnsrecon", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) version = result.stdout.strip() if result.stdout else result.stderr.strip() logger.info(f"DNSRecon version: {version}") return version except (subprocess.SubprocessError, FileNotFoundError) as e: logger.error(f"DNSRecon version check failed: {e}") return None def scan(self, domain: Optional[str] = None, scan_type: Optional[str] = "std", **kwargs: Any) -> Dict[str, Any]: """Run the DNSRecon scan.""" cmd = ["dnsrecon"] # Handle required parameters based on scan type if scan_type == "rvl": if "range" not in kwargs: raise ValueError("IP range must be provided for reverse lookup scan type.") cmd.extend(["-t", scan_type, "-r", kwargs["range"]]) elif scan_type not in ["tld", "zonewalk"]: if not domain: raise ValueError("Target domain must be provided for this scan type.") cmd.extend(["-t", scan_type, "-d", domain]) else: if not domain: raise ValueError("Target domain must be provided.") cmd.extend(["-t", scan_type, "-d", domain]) # Handle optional parameters if "name_server" in kwargs: cmd.extend(["-n", kwargs["name_server"]]) if "dictionary" in kwargs: cmd.extend(["-D", kwargs["dictionary"]]) if kwargs.get("filter_wildcard", False): cmd.append("-f") if kwargs.get("axfr", False): cmd.append("-a") if kwargs.get("spf", False): cmd.append("-s") if kwargs.get("bing", False): cmd.append("-b") if kwargs.get("yandex", False): cmd.append("-y") if kwargs.get("crt", False): cmd.append("-k") if kwargs.get("whois", False): cmd.append("-w") if kwargs.get("dnssec", False): cmd.append("-z") if "threads" in kwargs: cmd.extend(["--threads", str(kwargs["threads"])]) if "lifetime" in kwargs: cmd.extend(["--lifetime", str(kwargs["lifetime"])]) if kwargs.get("tcp", False): cmd.append("--tcp") if "db" in kwargs: cmd.extend(["--db", kwargs["db"]]) if "xml" in kwargs: cmd.extend(["-x", kwargs["xml"]]) if "csv" in kwargs: cmd.extend(["-c", kwargs["csv"]]) if "json" in kwargs: cmd.extend(["-j", kwargs["json"]]) if kwargs.get("ignore_wildcard", False): cmd.append("--iw") if kwargs.get("disable_check_recursion", False): cmd.append("--disable_check_recursion") if kwargs.get("disable_check_bindversion", False): cmd.append("--disable_check_bindversion") if kwargs.get("verbose", False): cmd.append("-v") logger.info(f"Preparing DNSRecon command: {' '.join(cmd)}") # Run the scan try: result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) output = result.stdout # Parse results parse_result = self._parse_output(output) return { "status": "success", "command": " ".join(cmd), "raw_output": output, "results": parse_result } except subprocess.SubprocessError as e: logger.error(f"Error during DNSRecon scan: {e}") return { "status": "error", "message": str(e), "command": " ".join(cmd), "stderr": e.stderr if hasattr(e, 'stderr') else "" } def _parse_output(self, output: str) -> List[Dict[str, Any]]: """Parse the output from DNSRecon into structured data.""" results = [] lines = output.strip().split('\n') for line in lines: if '[*]' in line or '[-]' in line or '[+]' in line: # Skip the log/status lines continue parts = line.strip().split() if len(parts) >= 4: try: record = { "record_type": parts[0], "name": parts[1], "data": " ".join(parts[2:]) } results.append(record) except Exception as e: logger.error(f"Error parsing output line: {line}, error: {e}") return results # Example usage (optional convenience function) def ExecDNSRecon(domain: Optional[str] = None, scan_type: str = "std", **kwargs: Any) -> Dict[str, Any]: """Convenience wrapper to run a DNSRecon scan.""" scanner = DNSRecon() try: return scanner.scan(domain, scan_type, **kwargs) except Exception as e: logger.error(f"DNSRecon scan failed: {e}") return {"error": str(e)} ``` -------------------------------------------------------------------------------- /toolkit/sqlmap.py: -------------------------------------------------------------------------------- ```python # INFORMATION: # - Tool: SQLMap # - Description: SQL injection vulnerability scanner # - Usage: Detects and exploits SQL injection vulnerabilities in web applications # - Parameters: url (required), data (optional) import subprocess import json import logging import re from typing import List, Optional, Dict, Any, Union # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Database Management System (DBMS) info variables isDbmsFound = False dbms = "" dbmsVersion = "" dbmsVersionFound = False # Tamper scripts for various DBMS tamperscripts = { "MySQL": [ "union2urls", "randomcase", "space2comment", "between", "charencode" ], "PostgreSQL": [ "randomcase", "space2comment", "postgreSQLbool" ], "Microsoft SQL Server": [ "charencode", "space2comment", "union2urls", "mssql08" ], "Oracle": [ "oracle2", "space2comment", "union2urls" ], "SQLite": [ "randomcase", "space2comment", "union2urls", "sqliteunicode" ], "Generic": [ "charencode", "space2comment", "union2urls" ] } def run_sqlmap(cmd: List[str]) -> subprocess.CompletedProcess: """ Helper function to run the sqlmap command. Args: cmd: List of command-line arguments for sqlmap. Returns: subprocess.CompletedProcess: The result of running sqlmap. """ logger.debug("Running sqlmap command: %s", " ".join(cmd)) try: result = subprocess.run( cmd, capture_output=True, text=True, check=True ) return result except subprocess.CalledProcessError as e: logger.error("SQLMap command failed: %s", e.stderr) raise e def check_sqlmap_installed() -> bool: """ Check if sqlmap is installed on the system. Returns: bool: True if sqlmap is installed, False otherwise """ try: subprocess.run(["which", "sqlmap"], capture_output=True, check=True) return True except subprocess.CalledProcessError: return False def gather_information(url: str) -> bool: """ Gather information about the target URL by running sqlmap with the --batch and -v 0 options. Args: url: Target URL to scan Returns: bool: True if the operation was successful, False otherwise """ cmd = ["sqlmap", "-u", url, "--batch", "-v", "0"] try: result = run_sqlmap(cmd) return result.returncode == 0 except subprocess.CalledProcessError: return False def try_tamper(url: str, tamper: str) -> bool: """ Try a specific tamper script on the target URL by running sqlmap with the --batch and -v 0 options. Args: url: Target URL to scan tamper: Tamper script to use Returns: bool: True if the operation was successful, False otherwise """ cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--tamper", tamper] try: result = run_sqlmap(cmd) return result.returncode == 0 except subprocess.CalledProcessError: return False def try_with_risk_and_level(url: str, risk: int, level: int) -> bool: """ Try a specific risk and level on the target URL by running sqlmap with the --batch and -v 0 options. Args: url: Target URL to scan risk: Risk level (1-3) level: Level (1-5) Returns: bool: True if the operation was successful, False otherwise """ cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--level", str(level), "--risk", str(risk)] try: result = run_sqlmap(cmd) return result.returncode == 0 except subprocess.CalledProcessError: return False def try_with_technique(url: str, technique: str) -> bool: """ Try a specific technique on the target URL by running sqlmap with the --batch and -v 0 options. Args: url: Target URL to scan technique: Technique to use (e.g., "B", "E", "T", "U") Returns: bool: True if the operation was successful, False otherwise """ cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--technique", technique] try: result = run_sqlmap(cmd) return result.returncode == 0 except subprocess.CalledProcessError: return False def ExecSqlmap(url: str, data: Optional[str] = None) -> Dict[str, Any]: """ Run sqlmap with the given URL and data. Args: url: Target URL to scan data: POST data to include in the request Returns: Dict[str, Any]: Result or error from the sqlmap command """ options = [] if data: options.extend(["--data", data]) cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--output-dir=/tmp/sqlmap"] cmd.extend(options) try: result = run_sqlmap(cmd) parsed_output = parse_sqlmap_output(result.stdout) return { "success": True, "url": url, "results": parsed_output } except subprocess.CalledProcessError as e: return { "success": False, "error": str(e), "stderr": e.stderr } except Exception as e: return { "success": False, "error": str(e) } def categorize_tamperscript(db_tech: str) -> str: """ Categorizes tamper scripts by database technology. Args: db_tech: The name of the database technology (e.g., MySQL, PostgreSQL, etc.) Returns: str: JSON string containing the tamper scripts for the provided DB technology. """ try: if db_tech in tamperscripts: return json.dumps({ "success": True, "database": db_tech, "tamper_scripts": tamperscripts[db_tech] }) else: return json.dumps({ "success": False, "error": f"Tamper scripts for {db_tech} not found." }) except Exception as e: return json.dumps({ "success": False, "error": str(e) }) def parse_sqlmap_output(output: str) -> Dict[str, Any]: """ Parse the output from sqlmap to extract useful information. Args: output: The stdout from sqlmap Returns: Dict: A dictionary containing parsed information """ result = { "vulnerable": False, "dbms": None, "payloads": [], "tables": [], "raw_output": output } # Check if any vulnerability was found if "is vulnerable to" in output: result["vulnerable"] = True # Try to extract DBMS information dbms_match = re.search(r"back-end DBMS: (.+?)(?:\n|\[)", output) if dbms_match: result["dbms"] = dbms_match.group(1).strip() # Try to extract payload information payload_matches = re.findall(r"Payload: (.+?)(?:\n|$)", output) result["payloads"] = [p.strip() for p in payload_matches] # Extract tables if available tables_section = re.search(r"Database: .*?\nTable: (.*?)(?:\n\n|\Z)", output, re.DOTALL) if tables_section: tables_text = tables_section.group(1) tables = re.findall(r"\|\s+([^\|]+?)\s+\|", tables_text) result["tables"] = [t.strip() for t in tables] return result def ExecSqlmap(url: str, data: Optional[str] = None) -> Dict[str, Any]: """ Run sqlmap vulnerability scan on a specified target URL. Main entry point for the MCP server to call this tool. Args: url: Target URL to scan data: Optional POST data to include in the request Returns: Dict: JSON-serializable dictionary with scan results """ logger.info(f"Starting SQLMap scan on {url}") # Check if sqlmap is installed if not check_sqlmap_installed(): return { "success": False, "error": "SQLMap is not installed. Install it with 'pip install sqlmap' or 'apt-get install sqlmap'." } # Define base command cmd = ["sqlmap", "-u", url, "--batch", "--forms", "--json-output"] # Add data parameter if provided if data: cmd.extend(["--data", data]) # Run initial scan try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) # Check if JSON output is available try: # Try to parse JSON output json_output_path = re.search(r"JSON report saved to: (.+)", result.stdout) if json_output_path: with open(json_output_path.group(1), 'r') as json_file: json_data = json.load(json_file) return { "success": True, "url": url, "vulnerable": bool(json_data.get("vulnerabilities", [])), "data": json_data } except (json.JSONDecodeError, FileNotFoundError): pass # If JSON parsing failed, parse the output manually parsed_results = parse_sqlmap_output(result.stdout) return { "success": True, "url": url, "vulnerable": parsed_results["vulnerable"], "dbms": parsed_results["dbms"], "payloads": parsed_results["payloads"], "tables": parsed_results["tables"], "output": result.stdout } except subprocess.CalledProcessError as e: logger.error(f"SQLMap scan failed: {e.stderr}") return { "success": False, "error": "SQLMap scan failed", "details": e.stderr } except Exception as e: logger.exception("Unexpected error during SQLMap execution") return { "success": False, "error": f"Error executing SQLMap scan: {str(e)}" } ``` -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>MCPHydra</title> <style> body { background-color: #000000; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } #flag-container { position: relative; overflow: hidden; max-width: 100%; height: 100vh; } #tiresult { font-size: 16px; background-color: #000000; font-weight: bold; padding: 3px 5px; margin: 0; display: inline-block; white-space: pre; width: 100%; overflow-x: auto; } #mcphydra { background-color: #000000; font-weight: bold; padding: 3px 5px; margin: 0; display: inline-block; font-size: 18px; white-space: pre; width: 100%; overflow-x: auto; color: #07df07; } /* Responsive font sizes */ @media screen and (max-width: 1024px) { #tiresult, #mcphydra { font-size: 20px; } #mcphydra { font-size: 10px; } } @media screen and (max-width: 768px) { #tiresult, #mcphydra { font-size: 20px; } #mcphydra { font-size: 10px; } } @media screen and (max-width: 480px) { #tiresult, #mcphydra { font-size: 14px; } #mcphydra { font-size: 10px; } } /* Animation for each character */ #tiresult b { display: inline-block; animation: wave 1s infinite ease-in-out; transform-origin: center bottom; position: relative; } /* Base animation keyframes - will be overridden by JavaScript for responsive sizing */ @keyframes wave { 0% { transform: translateY(0) rotate(0deg); } 20% { transform: translateY(-1px) rotate(1.5deg) translateX(0.8px); } 40% { transform: translateY(-0.5px) rotate(0.5deg) translateX(0.4px); } 60% { transform: translateY(0) rotate(0deg); } 80% { transform: translateY(0.8px) rotate(-1.5deg) translateX(-0.8px); } 100% { transform: translateY(0) rotate(0deg); } } </style> </head> <body> <div id="flag-container"> <pre id="mcphydra"> __ __ ____ ___ _________ / /_ __ ______/ /________ _ / __ `__ \/ ___/ __ \/ __ \/ / / / __ / ___/ __ `/ / / / / / / /__/ /_/ / / / / /_/ / /_/ / / / /_/ / /_/ /_/ /_/\___/ .___/_/ /_/\__, /\__,_/_/ \__,_/ /_/ /____/ v0.1.0 </pre> <pre id="tiresult"> <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠙</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠋</b><b style="color:#07df07">⠙</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠷</b><b style="color:#07df07">⠄</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢸</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠋</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢠</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣀</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣴</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣇</b><b style="color:#07df07">⡀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣷</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡿</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠙</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡆</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⠏</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠁</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠁</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣦</b><b style="color:#07df07">⡄</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣦</b><b style="color:#07df07">⡀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣆</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">mcphydra</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠘</b><b style="color:#07df07">⠛</b><b style="color:#07df07">⠛</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣦</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⣻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⢻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣄</b><b style="color:#07df07">⣀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠛</b><b style="color:#07df07">⠹</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠷</b><b style="color:#07df07">⣄</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⣹</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣷</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣴</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣷</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣆</b><b style="color:#07df07">⡀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> <b style="color:#07df07">⣿</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣼</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b> </pre> <script> document.addEventListener('DOMContentLoaded', function () { const chars = document.querySelectorAll('#tiresult b'); // Handle responsive animation scaling function adjustAnimation() { const viewportWidth = window.innerWidth; let scaleFactor = 1; if (viewportWidth <= 480) { scaleFactor = 0.5; } else if (viewportWidth <= 768) { scaleFactor = 0.7; } else if (viewportWidth <= 1024) { scaleFactor = 0.85; } function getRandomColor() { return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0'); } let randomColors = Array.from({ length: 10 }, getRandomColor); setInterval(() => { randomColors = Array.from({ length: 10 }, getRandomColor); chars.forEach((char, index) => { const colorIndex = Math.floor(Math.random() * randomColors.length); char.style.color = randomColors[colorIndex]; }); }, 2000); console.log(randomColors); chars.forEach((char, index) => { const row = Math.floor(index / 130); const col = index % 130; const delay = (col * 0.015 + row * 0.06) % 1.5; char.style.animationDelay = delay + 's'; const durationVariation = 0.9 + Math.random() * 0.2; // 0.9-1.1 char.style.animationDuration = (1.5 * durationVariation) + 's'; // Scale transform values based on viewport const style = document.createElement('style'); style.innerHTML = ` @keyframes wave { 0% { transform: translateY(0) rotate(0deg); } 20% { transform: translateY(${-1 * scaleFactor}px) rotate(${1.5 * scaleFactor}deg) translateX(${0.8 * scaleFactor}px); } 40% { transform: translateY(${-0.5 * scaleFactor}px) rotate(${0.5 * scaleFactor}deg) translateX(${0.4 * scaleFactor}px); } 60% { transform: translateY(0) rotate(0deg); } 80% { transform: translateY(${0.8 * scaleFactor}px) rotate(${-1.5 * scaleFactor}deg) translateX(${-0.8 * scaleFactor}px); } 100% { transform: translateY(0) rotate(0deg); } } `; document.head.appendChild(style); }); } // Initial setup adjustAnimation(); // Re-adjust on window resize window.addEventListener('resize', adjustAnimation); }); </script> </body> </html> ```