#
tokens: 23970/50000 17/17 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
 1 | # Python
 2 | __pycache__/
 3 | *.py[cod]
 4 | *$py.class
 5 | *.so
 6 | .Python
 7 | venv/
 8 | env/
 9 | ENV/
10 | .env
11 | .venv
12 | .eggs/
13 | *.egg-info/
14 | 
15 | # Development
16 | .git
17 | .github
18 | .vscode
19 | .idea
20 | 
21 | # Build artifacts
22 | dist/
23 | build/
24 | *.log 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python virtual environment
 2 | venv/
 3 | .venv/
 4 | env/
 5 | ENV/
 6 | 
 7 | # Python bytecode
 8 | __pycache__/
 9 | *.py[cod]
10 | *$py.class
11 | 
12 | # Environment variables
13 | .env
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 | 
19 | 
20 | # OS specific files
21 | .DS_Store
22 | Thumbs.db 
```

--------------------------------------------------------------------------------
/demos/README.md:
--------------------------------------------------------------------------------

```markdown
1 | ### Ocr2Text
2 | ![Ocr2Text](ocr2text.png)
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
 1 | mcp==1.6.0
 2 | numpy==1.26.4
 3 | opencv-python-headless==4.9.0.80
 4 | openai==1.70.0
 5 | openai-agents==0.0.7
 6 | pdf2image==1.17.0
 7 | pytesseract
 8 | python-dotenv==1.1.0
 9 | requests==2.32.4
10 | tqdm==4.67.1
11 | typing-inspection==0.4.0
12 | typing_extensions==4.13.1
13 | uvicorn==0.34.0
14 | sublist3r
```

--------------------------------------------------------------------------------
/.vscode/mcp.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "servers": {
 3 |         "HydraMCP": {
 4 |             "command": "docker",
 5 |             "args": [
 6 |                 "run",
 7 |                 "--rm",
 8 |                 "-i",
 9 |                 "--net=host",
10 |                 "--privileged",
11 |                 "--name",
12 |                 "hydramcp",
13 |                 "hydramcp"
14 |             ]
15 |         }
16 |     }
17 | }
```

--------------------------------------------------------------------------------
/banner.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | cat << "BANNER"
 4 | • Title: HydraΜCP — The Model Context Protocol (MCP) Pentesting Toolkit
 5 | • Version: 0.1.0
 6 | • License: MIT
 7 | • Description: A lightweight, extensible cybersecurity toolkit that connects AI assistants
 8 |                to security tools through the Model Context Protocol (MCP), enabling
 9 |                AI-assisted security research, scanning, and analysis.
10 | • Community: @happyhackingspace | https://happyhacking.space  
11 | • Author: Built with ❤️ by @atiilla  
12 | BANNER
13 | 
```

--------------------------------------------------------------------------------
/toolkit/holehe.py:
--------------------------------------------------------------------------------

```python
 1 | # INFORMATION:
 2 | # - Tool: Holehe
 3 | # - Description: Email account checker
 4 | # - Usage: Checks if an email address is registered on various websites
 5 | # - Parameters: email (required)
 6 | 
 7 | import subprocess
 8 | import logging
 9 | from typing import Dict, Any
10 | # Logging setup
11 | logging.basicConfig(
12 |     level=logging.INFO,
13 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
14 | )
15 | logger = logging.getLogger("holehe-tool")
16 | 
17 | 
18 | class Holehe:
19 |     """
20 |     Holehe wrapper class for checking email registrations.
21 |     """
22 | 
23 |     def __init__(self):
24 |         """Initialize Holehe wrapper."""
25 |         logger.info("Holehe wrapper initialized.")
26 | 
27 |     def scan(self, email: str) -> Dict[str, Any]:
28 |         """
29 |         Run the holehe scan for a given email.
30 |         
31 |         Args:
32 |             email (str): The email address to check.
33 |         
34 |         Returns:
35 |             Dict[str, Any]: Result or error from the holehe command.
36 |         """
37 |         logger.info(f"Running Holehe scan for: {email}")
38 |         try:
39 |             result = subprocess.run(["holehe", email], capture_output=True, text=True, check=True)
40 |             output = result.stdout.strip()
41 |             return {
42 |                 "email": email,
43 |                 "success": True,
44 |                 "output": output
45 |             }
46 |         except subprocess.CalledProcessError as e:
47 |             logger.error(f"Holehe scan failed: {e.stderr.strip()}")
48 |             return {
49 |                 "email": email,
50 |                 "success": False,
51 |                 "error": e.stderr.strip()
52 |             }
53 | 
54 | 
55 | def ExecHolehe(email: str) -> Dict[str, Any]:
56 |     """
57 |     Convenience wrapper to run a holehe scan.
58 |     
59 |     Args:
60 |         email (str): Email address to scan.
61 |     
62 |     Returns:
63 |         Dict[str, Any]: Holehe scan results.
64 |     """
65 |     scanner = Holehe()
66 |     return scanner.scan(email)
67 | 
```

--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import List, Optional, Dict, Any
  2 | 
  3 | from mcp.server.fastmcp import FastMCP
  4 | 
  5 | from toolkit.holehe import ExecHolehe
  6 | from toolkit.nmap import ExecNmap
  7 | from toolkit.wpscan import ExecWpscan
  8 | from toolkit.zmap import ExecZmap
  9 | from toolkit.sqlmap import ExecSqlmap
 10 | from toolkit.ocr2text import ExecOcr2Text
 11 | from toolkit.sublist3r import ExecSublist3r
 12 | from toolkit.dnsrecon import ExecDNSRecon
 13 | from toolkit.sherlock import ExecSherlock
 14 | 
 15 | # Create server
 16 | mcp = FastMCP(name="HydraΜCP",
 17 |     version="0.1.0"
 18 | )
 19 | 
 20 | 
 21 | @mcp.tool()
 22 | def ZmapScanner(
 23 |     target: str,
 24 |     port: int,
 25 |     bandwidth: Optional[str] = "1M",
 26 | ) -> Dict[str, Any]:
 27 |     """Wrapper for running ZMap network scanning."""
 28 |     return ExecZmap(target, port, bandwidth)
 29 | 
 30 | 
 31 | @mcp.tool()
 32 | def WPScanScanner(
 33 |     url: str,
 34 | ) -> Dict[str, Any]:
 35 |     """Wrapper for running WPScan vulnerability scanning."""
 36 |     return ExecWpscan(url)
 37 | 
 38 | 
 39 | @mcp.tool()
 40 | def HoleheScanner(
 41 |     email: str,
 42 | ) -> Dict[str, Any]:
 43 |     """Wrapper for running Holehe email registration checking."""
 44 |     return ExecHolehe(email)
 45 | 
 46 | 
 47 | @mcp.tool()
 48 | def NmapScanner(
 49 |     target: str,
 50 |     ports: Optional[str] = None,
 51 | ) -> Dict[str, Any]:
 52 |     """Wrapper for running Nmap network scanning."""
 53 |     return ExecNmap(target, ports)
 54 | 
 55 | 
 56 | @mcp.tool()
 57 | def SqlmapScanner(
 58 |     url: str,
 59 |     data: Optional[str] = None,
 60 | ) -> Dict[str, Any]:
 61 |     """Wrapper for running Sqlmap vulnerability scanning."""
 62 |     return ExecSqlmap(url, data)
 63 | 
 64 | @mcp.tool()
 65 | def OcrScanner(
 66 |     file_path: str,
 67 | ) -> Dict[str, Any]:
 68 |     """Wrapper for running OCR (Optical Character Recognition) on images and PDFs.
 69 |     
 70 |     The file_path can be:
 71 |     - A local file path
 72 |     - A direct URL (http/https)
 73 |     - A URL prefixed with @ symbol
 74 |     """
 75 |     return ExecOcr2Text(file_path)
 76 | 
 77 | @mcp.tool()
 78 | def Sublist3rScanner(
 79 |     domain: str,
 80 |     output_dir: Optional[str] = "output",
 81 | ) -> List[str]:
 82 |     """Wrapper for running Sublist3r subdomain enumeration."""
 83 |     return ExecSublist3r(domain, output_dir)
 84 | 
 85 | @mcp.tool()
 86 | def DNSReconScanner(
 87 |     domain: str,
 88 |     scan_type: Optional[str] = "std",
 89 |     name_server: Optional[str] = None,
 90 |     range: Optional[str] = None,
 91 |     dictionary: Optional[str] = None,
 92 | ) -> Dict[str, Any]:
 93 |     """Wrapper for running DNSRecon for DNS reconnaissance."""
 94 |     kwargs = {}
 95 |     if name_server:
 96 |         kwargs["name_server"] = name_server
 97 |     if range:
 98 |         kwargs["range"] = range
 99 |     if dictionary:
100 |         kwargs["dictionary"] = dictionary
101 |     
102 |     return ExecDNSRecon(domain, scan_type, **kwargs)
103 | 
104 | @mcp.tool()
105 | def SherlockScanner(
106 |     usernames: List[str],
107 | ) -> Dict[str, Any]:
108 |     """Wrapper for running Sherlock username enumeration."""
109 |     return ExecSherlock(usernames)
110 | 
111 | 
112 | if __name__ == "__main__":
113 |     mcp.run(transport="stdio")
```

--------------------------------------------------------------------------------
/toolkit/nmap.py:
--------------------------------------------------------------------------------

```python
 1 | # INFORMATION:
 2 | # - Tool: Nmap
 3 | # - Description: Network scanning tool
 4 | # - Usage: Scans networks for hosts and services
 5 | # - Parameters: target (required), ports (optional)
 6 | 
 7 | import subprocess
 8 | import logging
 9 | import re
10 | from typing import List, Optional, Tuple
11 | 
12 | # Configure logging
13 | logging.basicConfig(level=logging.INFO)
14 | logger = logging.getLogger(__name__)
15 | 
16 | # Define dynamic mappings of phrases to Nmap options
17 | KEYWORD_MAP = {
18 |     "no ping": "-Pn",
19 |     "skip ping": "-Pn",
20 |     "version": "-sV",
21 |     "service detection": "-sV",
22 |     "os detection": "-O",
23 |     "aggressive": "-A",
24 |     "verbose": "-v",
25 |     "top ports": "--top-ports 100",
26 |     "udp": "-sU",
27 |     "quick": "-T4",
28 | }
29 | 
30 | def parse_nmap_prompt(prompt: str) -> Tuple[str, Optional[str], List[str]]:
31 |     prompt = prompt.lower()
32 |     options = []
33 |     ports = None
34 | 
35 |     # Extract IP address (supports v4 only here)
36 |     ip_match = re.search(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", prompt)
37 |     target = ip_match.group(0) if ip_match else ""
38 | 
39 |     # Extract ports if mentioned like 'ports 22,80' or 'scan port 443'
40 |     port_match = re.search(r"port[s]?\s*(\d{1,5}(?:\s*,\s*\d{1,5})*)", prompt)
41 |     if port_match:
42 |         ports = port_match.group(1).replace(" ", "")
43 | 
44 |     # Match keywords to options
45 |     for phrase, option in KEYWORD_MAP.items():
46 |         if phrase in prompt:
47 |             options.append(option)
48 | 
49 |     return target, ports, options
50 | 
51 | def ExecNmap(
52 |     target: str,
53 |     ports: Optional[str] = None,
54 |     options: Optional[List[str]] = None,
55 | ) -> str:
56 |     """Run an Nmap network scan on the specified target."""
57 |     logger.debug("Running Nmap with target=%s, ports=%s, options=%s", target, ports, options)
58 | 
59 |     if subprocess.run(["which", "nmap"], capture_output=True).returncode != 0:
60 |         return {"error": "Nmap is not installed. Install it with 'sudo apt install nmap'."}
61 | 
62 |     cmd = ["nmap", target]
63 |     if ports:
64 |         cmd += ["-p", ports]
65 |     if options:
66 |         cmd += options
67 | 
68 |     logger.info("Executing command: %s", ' '.join(cmd))
69 | 
70 |     try:
71 |         result = subprocess.run(cmd, capture_output=True, text=True, check=True)
72 |         return {"success": True, "output": result.stdout}
73 |     except subprocess.CalledProcessError as e:
74 |         logger.error("Nmap command failed: %s", e.stderr)
75 |         return {"success": False, "error": e.stderr.strip()}
76 |     except Exception as e:
77 |         logger.exception("Unexpected error during Nmap execution")
78 |         return {"success": False, "error": f"Error executing Nmap scan: {str(e)}"}
79 | 
80 | def scan_from_prompt(prompt: str) -> str:
81 |     target, ports, options = parse_nmap_prompt(prompt)
82 |     if not target:
83 |         return {"success": False, "error": "Could not detect a valid IP address in the prompt."}
84 |     return ExecNmap(target, ports, options)
85 | 
86 | 
```

--------------------------------------------------------------------------------
/toolkit/wpscan.py:
--------------------------------------------------------------------------------

```python
 1 | # INFORMATION:
 2 | # - Tool: WPScan
 3 | # - Description: WordPress vulnerability scanner
 4 | # - Usage: Scans WordPress sites for vulnerabilities and security issues
 5 | # - Parameters: url (required), format (optional), api_token (optional)
 6 | 
 7 | import subprocess
 8 | import logging
 9 | from typing import Any, Dict, List, Optional
10 | import json
11 | 
12 | # Logging setup
13 | logging.basicConfig(
14 |     level=logging.INFO,
15 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
16 | )
17 | logger = logging.getLogger("wpscan-tool")
18 | 
19 | class WPScan:
20 |     """WPScan tool wrapper class."""
21 | 
22 |     def __init__(self):
23 |         """Initialize WPScan wrapper."""
24 |         self._check_installed()
25 | 
26 |     def _check_installed(self) -> None:
27 |         """Verify WPScan is installed."""
28 |         try:
29 |             subprocess.run(["wpscan", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
30 |             logger.info("WPScan installation verified.")
31 |         except (subprocess.SubprocessError, FileNotFoundError):
32 |             logger.warning("wpscan command not found. Make sure WPScan is installed and in your PATH.")
33 |             raise RuntimeError("WPScan is not installed or not in PATH.")
34 | 
35 |     def get_version(self) -> Optional[str]:
36 |         """Get installed WPScan version."""
37 |         try:
38 |             result = subprocess.run(["wpscan", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
39 |             version_line = result.stdout.strip().split('\n')[-1]
40 |             logger.info(f"WPScan version: {version_line}")
41 |             return version_line
42 |         except (subprocess.SubprocessError, FileNotFoundError) as e:
43 |             logger.error(f"WPScan version check failed: {e}")
44 |             return None
45 | 
46 |     def scan(self, url: str, **kwargs: Any) -> Dict[str, Any]:
47 |         """Run the WPScan scan."""
48 |         if not url:
49 |             raise ValueError("Target URL must be provided.")
50 | 
51 |         cmd = ["wpscan", "--url", url,"--random-user-agent","--ignore-main-redirect"]
52 | 
53 |         # Append additional options to the command
54 |         if "format" in kwargs:
55 |             cmd.append("--format")
56 |             cmd.append(kwargs["format"])
57 |         if kwargs.get("verbose", False):
58 |             cmd.append("--verbose")
59 |         if kwargs.get("random_user_agent", False):
60 |             cmd.append("--random-user-agent")
61 |         if "max_threads" in kwargs:
62 |             cmd.append("--max-threads")
63 |             cmd.append(str(kwargs["max_threads"]))
64 |         if "api_token" in kwargs:
65 |             cmd.append("--api-token")
66 |             cmd.append(kwargs["api_token"])
67 |         if "enumerate_opts" in kwargs:
68 |             cmd.append("--enumerate")
69 |             cmd.append(",".join(kwargs["enumerate_opts"]))
70 |         
71 |         logger.info(f"Preparing WPScan command: {' '.join(cmd)}")
72 | 
73 |         # Run the scan
74 |         try:
75 |             result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
76 |             output = result.stdout
77 |             # If format is json, parse it
78 |             if kwargs.get("format", "json") == "json":
79 |                 return json.loads(output)
80 |             return {"status": "success", "output": output}
81 |         except subprocess.SubprocessError as e:
82 |             logger.error(f"Error during WPScan scan: {e}")
83 |             return {"status": "error", "message": str(e)}
84 | 
85 | # Example usage (optional convenience function)
86 | def ExecWpscan(url: str, **kwargs: Any) -> Dict[str, Any]:
87 |     """Convenience wrapper to run a WPScan scan."""
88 |     scanner = WPScan()
89 |     try:
90 |         return scanner.scan(url, **kwargs)
91 |     except Exception as e:
92 |         logger.error(f"WPScan scan failed: {e}")
93 |         return {"error": str(e)}
94 | 
```

--------------------------------------------------------------------------------
/toolkit/zmap.py:
--------------------------------------------------------------------------------

```python
  1 | # INFORMATION:
  2 | # - Tool: ZMap
  3 | # - Description: High-speed network scanner
  4 | # - Usage: Scans large network ranges quickly
  5 | # - Parameters: target (required), port (required), bandwidth (optional)
  6 | 
  7 | from typing import Any, Dict, List
  8 | import ipaddress
  9 | import logging
 10 | import subprocess
 11 | import re
 12 | import random
 13 | 
 14 | # Logging setup
 15 | logging.basicConfig(
 16 |     level=logging.INFO,
 17 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 18 | )
 19 | logger = logging.getLogger("zmap-tool")
 20 | 
 21 | 
 22 | class ZMap:
 23 |     """ZMap scanner class for network scanning operations."""
 24 | 
 25 |     def __init__(self):
 26 |         """Initialize ZMap scanner."""
 27 |         self.interface = "eth0"
 28 |         self.gateway_mac = self._generate_random_mac()
 29 |         self._check_installed()
 30 | 
 31 |     def _check_installed(self) -> None:
 32 |         """Verify ZMap is installed."""
 33 |         try:
 34 |             subprocess.run(["zmap", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 35 |         except (subprocess.SubprocessError, FileNotFoundError):
 36 |             logger.warning("ZMap is not installed or not in PATH.")
 37 | 
 38 |     def _generate_random_mac(self) -> str:
 39 |         """Generate a random locally administered unicast MAC address."""
 40 |         mac = [0x02, 0x00, 0x00,
 41 |                random.randint(0x00, 0x7F),
 42 |                random.randint(0x00, 0xFF),
 43 |                random.randint(0x00, 0xFF)]
 44 |         return ':'.join(f'{b:02x}' for b in mac)
 45 | 
 46 |     def _convert_ip_range_to_cidr(self, ip_range: str) -> str:
 47 |         """Convert IP range or CIDR to canonical CIDR format."""
 48 |         try:
 49 |             network = ipaddress.IPv4Network(ip_range, strict=False)
 50 |             return str(network)
 51 |         except ValueError:
 52 |             pass
 53 | 
 54 |         match = re.match(r'(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)', ip_range)
 55 |         if not match:
 56 |             raise ValueError(f"Invalid IP range format: {ip_range}")
 57 | 
 58 |         start_ip, end_ip = match.group(1), match.group(2)
 59 |         start, end = ipaddress.IPv4Address(start_ip), ipaddress.IPv4Address(end_ip)
 60 | 
 61 |         for prefixlen in range(32, -1, -1):
 62 |             network = ipaddress.IPv4Network(f"{start_ip}/{prefixlen}", strict=False)
 63 |             if network[0] <= start and network[-1] >= end:
 64 |                 return str(network)
 65 | 
 66 |         raise ValueError(f"Could not convert IP range {ip_range} to CIDR notation")
 67 | 
 68 |     def get_version(self) -> str:
 69 |         """Get installed ZMap version."""
 70 |         try:
 71 |             result = subprocess.run(["zmap", "--version"], check=True, stdout=subprocess.PIPE, text=True)
 72 |             return result.stdout.strip()
 73 |         except subprocess.SubprocessError as e:
 74 |             logger.error(f"ZMap version check failed: {e}")
 75 |             raise RuntimeError("ZMap is not installed or misconfigured.")
 76 | 
 77 |     def scan(self, target_port: int, subnets: List[str], bandwidth: str = "1M") -> Dict[str, Any]:
 78 |         """Run the ZMap scan."""
 79 |         cidr_subnets = [self._convert_ip_range_to_cidr(subnet) for subnet in subnets]
 80 | 
 81 |         if not (0 < target_port <= 65535):
 82 |             raise ValueError("Port must be between 1 and 65535")
 83 | 
 84 |         cmd = [
 85 |             "zmap",
 86 |             "-p", str(target_port),
 87 |             "-B", bandwidth,
 88 |             "-i", self.interface,
 89 |             "-G", self.gateway_mac,
 90 |             "--blacklist-file=/dev/null",
 91 |             "-o", "-"
 92 |         ]
 93 |         cmd.extend(cidr_subnets)
 94 | 
 95 |         logger.info(f"Running ZMap scan: {' '.join(cmd)}")
 96 | 
 97 |         try:
 98 |             result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
 99 |             hosts = [line.strip() for line in result.stdout.splitlines() if line.strip()]
100 |             return {
101 |                 "port": target_port,
102 |                 "hosts": hosts,
103 |                 "total_hosts": len(hosts),
104 |                 "subnets_scanned": cidr_subnets
105 |             }
106 |         except subprocess.CalledProcessError as e:
107 |             logger.error(f"ZMap scan failed: {e.stderr.strip()}")
108 |             return {
109 |                 "error": "Scan failed",
110 |                 "details": e.stderr,
111 |                 "exit_code": e.returncode
112 |             }
113 | 
114 | 
115 | def ExecZmap(target: str, port: int, bandwidth: str = "1M") -> Dict[str, Any]:
116 |     """Convenience wrapper to run a ZMap scan."""
117 |     zmap = ZMap()
118 |     try:
119 |         return zmap.scan(target_port=port, subnets=[target], bandwidth=bandwidth)
120 |     except Exception as e:
121 |         logger.error(f"Scan error: {e}")
122 |         return {"error": str(e)}
123 | 
```

--------------------------------------------------------------------------------
/toolkit/sublist3r.py:
--------------------------------------------------------------------------------

```python
  1 | # INFORMATION:
  2 | # - Tool: Sublist3r
  3 | # - Description: Fast subdomain enumeration tool
  4 | # - Usage: Finds subdomains via search engines
  5 | # - Parameters: domain (required), output_dir (optional)
  6 | 
  7 | import subprocess
  8 | import os
  9 | import json
 10 | import logging
 11 | import re
 12 | from typing import List, Dict, Any
 13 | 
 14 | # Logging setup
 15 | logging.basicConfig(
 16 |     level=logging.INFO,
 17 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 18 | )
 19 | logger = logging.getLogger("sublist3r-tool")
 20 | 
 21 | 
 22 | class Sublist3r:
 23 |     """
 24 |     Sublist3r wrapper class for subdomain enumeration.
 25 |     """
 26 | 
 27 |     def __init__(self):
 28 |         """Initialize Sublist3r wrapper."""
 29 |         logger.info("Sublist3r wrapper initialized.")
 30 | 
 31 |     def extract_subdomains_from_output(self, output: str) -> List[str]:
 32 |         """
 33 |         Extract subdomains from the command output.
 34 |         
 35 |         Args:
 36 |             output (str): Command output text
 37 |             
 38 |         Returns:
 39 |             List[str]: List of extracted subdomains
 40 |         """
 41 |         # Remove ANSI color codes
 42 |         ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
 43 |         clean_output = ansi_escape.sub('', output)
 44 |         
 45 |         # Extract subdomains using regex
 46 |         # Look for lines that appear to be domains (contain at least one dot and no special chars)
 47 |         domain_pattern = re.compile(r'^(?:[\w-]+\.)+[\w-]+$', re.MULTILINE)
 48 |         return domain_pattern.findall(clean_output)
 49 | 
 50 |     def scan(self, domain: str, output_dir: str) -> Dict[str, Any]:
 51 |         """
 52 |         Run Sublist3r tool to enumerate subdomains of a given domain.
 53 | 
 54 |         Args:
 55 |             domain (str): The target domain to enumerate subdomains for.
 56 |             output_dir (str): The directory to save the output files.
 57 | 
 58 |         Returns:
 59 |             Dict[str, Any]: Results or error from the Sublist3r command.
 60 |         """
 61 |         # Create output directory if it doesn't exist
 62 |         os.makedirs(output_dir, exist_ok=True)
 63 |         
 64 |         output_file = os.path.join(output_dir, f"{domain}_subdomains.txt")
 65 |         
 66 |         logger.info(f"Running Sublist3r scan for domain: {domain}")
 67 |         try:
 68 |             # Run without output file first to capture stdout
 69 |             result = subprocess.run(
 70 |                 ["sublist3r", "-d", domain],
 71 |                 capture_output=True, 
 72 |                 text=True,
 73 |                 check=True
 74 |             )
 75 |             output = result.stdout.strip()
 76 |             
 77 |             # Try to extract subdomains from the output
 78 |             subdomains_from_output = self.extract_subdomains_from_output(output)
 79 |             
 80 |             # Also try to run with output file as fallback
 81 |             subprocess.run(
 82 |                 ["sublist3r", "-d", domain, "-o", output_file],
 83 |                 capture_output=True,
 84 |                 text=True,
 85 |                 check=True
 86 |             )
 87 |             
 88 |             # Parse the output file to get subdomains
 89 |             subdomains_from_file = []
 90 |             if os.path.exists(output_file):
 91 |                 with open(output_file, 'r') as f:
 92 |                     subdomains_from_file = [line.strip() for line in f if line.strip()]
 93 |             
 94 |             # Combine subdomains from both sources
 95 |             subdomains = list(set(subdomains_from_output + subdomains_from_file))
 96 |             
 97 |             # Save results to JSON file
 98 |             json_output_file = os.path.join(output_dir, f"{domain}_subdomains.json")
 99 |             with open(json_output_file, 'w') as f:
100 |                 json.dump(subdomains, f)
101 |                 
102 |             logger.info(f"Sublist3r found {len(subdomains)} subdomains for {domain}")
103 |             logger.info(f"Sublist3r results saved to {json_output_file}")
104 |             
105 |             return {
106 |                 "domain": domain,
107 |                 "success": True,
108 |                 "subdomains": subdomains,
109 |                 "output": output
110 |             }
111 |             
112 |         except subprocess.CalledProcessError as e:
113 |             logger.error(f"Sublist3r scan failed: {e.stderr.strip()}")
114 |             return {
115 |                 "domain": domain,
116 |                 "success": False,
117 |                 "error": e.stderr.strip()
118 |             }
119 | 
120 | 
121 | def ExecSublist3r(domain: str, output_dir: str = "results") -> Dict[str, Any]:
122 |     """
123 |     Convenience wrapper to run a Sublist3r scan.
124 |     
125 |     Args:
126 |         domain (str): The target domain to enumerate subdomains for.
127 |         output_dir (str): The directory to save the output files, defaults to "results".
128 |     
129 |     Returns:
130 |         Dict[str, Any]: Sublist3r scan results.
131 |     """
132 |     scanner = Sublist3r()
133 |     return scanner.scan(domain, output_dir)
```

--------------------------------------------------------------------------------
/toolkit/sherlock.py:
--------------------------------------------------------------------------------

```python
  1 | # INFORMATION:
  2 | # - Tool: Sherlock
  3 | # - Description: Username hunter for social networks
  4 | # - Usage: Finds usernames across multiple social networks
  5 | # - Parameters: usernames (required), various optional parameters
  6 | 
  7 | import subprocess
  8 | import logging
  9 | from typing import Dict, Any, List, Optional
 10 | 
 11 | # Logging setup
 12 | logging.basicConfig(
 13 |     level=logging.INFO,
 14 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 15 | )
 16 | logger = logging.getLogger("sherlock-tool")
 17 | 
 18 | 
 19 | class Sherlock:
 20 |     """
 21 |     Sherlock wrapper class for hunting usernames across social networks.
 22 |     """
 23 | 
 24 |     def __init__(self):
 25 |         """Initialize Sherlock wrapper."""
 26 |         logger.info("Sherlock wrapper initialized.")
 27 | 
 28 |     def hunt(self, 
 29 |              usernames: List[str], 
 30 |              output: Optional[str] = None,
 31 |              folderoutput: Optional[str] = None,
 32 |              verbose: bool = False,
 33 |              tor: bool = False,
 34 |              unique_tor: bool = False,
 35 |              csv: bool = False,
 36 |              xlsx: bool = False,
 37 |              sites: Optional[List[str]] = None,
 38 |              proxy: Optional[str] = None,
 39 |              json_file: Optional[str] = None,
 40 |              timeout: Optional[int] = None,
 41 |              print_all: bool = False,
 42 |              print_found: bool = False,
 43 |              no_color: bool = False,
 44 |              browse: bool = False,
 45 |              local: bool = False,
 46 |              nsfw: bool = False
 47 |              ) -> Dict[str, Any]:
 48 |         """
 49 |         Run the sherlock hunt for given usernames.
 50 |         
 51 |         Args:
 52 |             usernames (List[str]): List of usernames to check.
 53 |             output (Optional[str]): Output file for single username results.
 54 |             folderoutput (Optional[str]): Output folder for multiple username results.
 55 |             verbose (bool): Display extra debugging information.
 56 |             tor (bool): Make requests over Tor.
 57 |             unique_tor (bool): Make requests over Tor with new circuit per request.
 58 |             csv (bool): Create CSV output file.
 59 |             xlsx (bool): Create XLSX output file.
 60 |             sites (Optional[List[str]]): Specific sites to check.
 61 |             proxy (Optional[str]): Proxy URL to use.
 62 |             json_file (Optional[str]): JSON file for data input.
 63 |             timeout (Optional[int]): Request timeout in seconds.
 64 |             print_all (bool): Output sites where username was not found.
 65 |             print_found (bool): Output sites where username was found.
 66 |             no_color (bool): Disable colored terminal output.
 67 |             browse (bool): Open results in browser.
 68 |             local (bool): Force use of local data.json file.
 69 |             nsfw (bool): Include NSFW sites in checks.
 70 |             
 71 |         Returns:
 72 |             Dict[str, Any]: Result or error from the sherlock command.
 73 |         """
 74 |         logger.info(f"Running Sherlock hunt for: {', '.join(usernames)}")
 75 |         
 76 |         cmd = ["sherlock"]
 77 |         
 78 |         # Add all flags and their values
 79 |         if verbose:
 80 |             cmd.append("--verbose")
 81 |         if folderoutput:
 82 |             cmd.extend(["--folderoutput", folderoutput])
 83 |         if output:
 84 |             cmd.extend(["--output", output])
 85 |         if tor:
 86 |             cmd.append("--tor")
 87 |         if unique_tor:
 88 |             cmd.append("--unique-tor")
 89 |         if csv:
 90 |             cmd.append("--csv")
 91 |         if xlsx:
 92 |             cmd.append("--xlsx")
 93 |         if sites:
 94 |             for site in sites:
 95 |                 cmd.extend(["--site", site])
 96 |         if proxy:
 97 |             cmd.extend(["--proxy", proxy])
 98 |         if json_file:
 99 |             cmd.extend(["--json", json_file])
100 |         if timeout:
101 |             cmd.extend(["--timeout", str(timeout)])
102 |         if print_all:
103 |             cmd.append("--print-all")
104 |         if print_found:
105 |             cmd.append("--print-found")
106 |         if no_color:
107 |             cmd.append("--no-color")
108 |         if browse:
109 |             cmd.append("--browse")
110 |         if local:
111 |             cmd.append("--local")
112 |         if nsfw:
113 |             cmd.append("--nsfw")
114 |         
115 |         # Add usernames
116 |         cmd.extend(usernames)
117 |         
118 |         try:
119 |             result = subprocess.run(cmd, capture_output=True, text=True, check=True)
120 |             output = result.stdout.strip()
121 |             return {
122 |                 "usernames": usernames,
123 |                 "success": True,
124 |                 "output": output
125 |             }
126 |         except subprocess.CalledProcessError as e:
127 |             logger.error(f"Sherlock hunt failed: {e.stderr.strip()}")
128 |             return {
129 |                 "usernames": usernames,
130 |                 "success": False,
131 |                 "error": e.stderr.strip()
132 |             }
133 | 
134 | 
135 | def ExecSherlock(usernames: List[str], **kwargs) -> Dict[str, Any]:
136 |     """
137 |     Convenience wrapper to run a sherlock username hunt.
138 |     
139 |     Args:
140 |         usernames (List[str]): Usernames to hunt.
141 |         **kwargs: Additional parameters to pass to Sherlock.hunt().
142 |     
143 |     Returns:
144 |         Dict[str, Any]: Sherlock hunt results.
145 |     """
146 |     scanner = Sherlock()
147 |     return scanner.hunt(usernames, **kwargs) 
```

--------------------------------------------------------------------------------
/toolkit/ocr2text.py:
--------------------------------------------------------------------------------

```python
  1 | # INFORMATION:
  2 | # - Tool: OCR Scanner
  3 | # - Description: Optical Character Recognition tool
  4 | # - Usage: Extracts text from images and PDF files
  5 | # - Parameters: file_path (required) - can be a local file or URL prefixed with @
  6 | 
  7 | import cv2
  8 | import pytesseract
  9 | from pdf2image import convert_from_path
 10 | import os
 11 | import numpy as np
 12 | from typing import Dict, Any
 13 | import requests
 14 | import tempfile
 15 | from urllib.parse import urlparse
 16 | 
 17 | # Configure Tesseract path - use system path for Kali Linux
 18 | if os.path.exists('/usr/bin/tesseract'):
 19 |     pytesseract.pytesseract.tesseract_cmd = r'/usr/bin/tesseract'
 20 | elif os.path.exists('/usr/local/bin/tesseract'):
 21 |     pytesseract.pytesseract.tesseract_cmd = r'/usr/local/bin/tesseract'
 22 | elif os.path.exists('C:\\Program Files\\Tesseract-OCR\\tesseract.exe'):
 23 |     pytesseract.pytesseract.tesseract_cmd = r'C:\\Program Files\\Tesseract-OCR\\tesseract.exe'
 24 | 
 25 | def download_image(url):
 26 |     """Download image from a URL and save to a temporary file"""
 27 |     try:
 28 |         response = requests.get(url, stream=True)
 29 |         response.raise_for_status()
 30 |         
 31 |         # Get the file extension from the URL
 32 |         parsed_url = urlparse(url)
 33 |         filename = os.path.basename(parsed_url.path)
 34 |         _, ext = os.path.splitext(filename)
 35 |         
 36 |         if not ext:
 37 |             # Default to .png if no extension found
 38 |             ext = '.png'
 39 |             
 40 |         # Create a temporary file with the correct extension
 41 |         temp_file = tempfile.NamedTemporaryFile(suffix=ext, delete=False)
 42 |         temp_filename = temp_file.name
 43 |         
 44 |         # Write the image data to the temporary file
 45 |         with open(temp_filename, 'wb') as f:
 46 |             for chunk in response.iter_content(chunk_size=8192):
 47 |                 f.write(chunk)
 48 |                 
 49 |         return temp_filename
 50 |     except Exception as e:
 51 |         print(f"Error downloading image: {str(e)}")
 52 |         return None
 53 | 
 54 | def process_image(img):
 55 |     """Process image for better OCR results"""
 56 |     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 57 |     gray, img_bin = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
 58 |     gray = cv2.bitwise_not(img_bin)
 59 | 
 60 |     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 1))
 61 |     dilation = cv2.dilate(gray, kernel, iterations=1)
 62 |     erosion = cv2.erode(dilation, kernel, iterations=1)
 63 | 
 64 |     text = pytesseract.image_to_string(erosion)
 65 |     return text
 66 | 
 67 | def extract_text_from_pdf(pdf_path):
 68 |     """Extract text from a PDF file using OCR"""
 69 |     try:
 70 |         # Use poppler_path if on Windows, but we're in Kali Linux so not needed
 71 |         images = convert_from_path(pdf_path)
 72 |         
 73 |         all_text = []
 74 |         for i, image in enumerate(images):
 75 |             opencv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
 76 |             text = process_image(opencv_image)
 77 |             all_text.append(f"=== Page {i+1} ===\n{text}\n")
 78 |         
 79 |         return "\n".join(all_text)
 80 |     except Exception as e:
 81 |         return f"Error processing PDF: {str(e)}"
 82 | 
 83 | def extract_text_from_image(image_path):
 84 |     """Extract text from an image file using OCR"""
 85 |     try:
 86 |         img = cv2.imread(image_path)
 87 |         if img is None:
 88 |             return f"Error: Could not read image file {image_path}"
 89 |         return process_image(img)
 90 |     except Exception as e:
 91 |         return f"Error processing image: {str(e)}"
 92 | 
 93 | def ExecOcr2Text(file_path: str) -> Dict[str, Any]:
 94 |     """Execute OCR on a file (image or PDF) or URL"""
 95 |     temp_file = None
 96 |     
 97 |     try:
 98 |         # Check if the file_path is a URL (starts with @ or http/https)
 99 |         if file_path.startswith('@'):
100 |             url = file_path[1:]  # Remove the @ symbol
101 |             print(f"Downloading image from URL: {url}")
102 |             temp_file = download_image(url)
103 |         elif file_path.startswith(('http://', 'https://')):
104 |             url = file_path
105 |             print(f"Downloading image from URL: {url}")
106 |             temp_file = download_image(url)
107 |             
108 |         if temp_file is None and (file_path.startswith('@') or file_path.startswith(('http://', 'https://'))):
109 |             return {
110 |                 "success": False,
111 |                 "error": f"Failed to download image from URL: {url}",
112 |                 "text": ""
113 |             }
114 |             
115 |         # Use the temp file if a URL was provided
116 |         if temp_file:
117 |             file_path = temp_file
118 |         
119 |         # Check if the file exists
120 |         if not os.path.exists(file_path):
121 |             return {
122 |                 "success": False,
123 |                 "error": f"File not found: {file_path}",
124 |                 "text": ""
125 |             }
126 |         
127 |         file_ext = os.path.splitext(file_path)[1].lower()
128 |         
129 |         if file_ext == '.pdf':
130 |             text = extract_text_from_pdf(file_path)
131 |         elif file_ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif']:
132 |             text = extract_text_from_image(file_path)
133 |         else:
134 |             return {
135 |                 "success": False,
136 |                 "error": f"Unsupported file format: {file_ext}",
137 |                 "text": ""
138 |             }
139 |                 
140 |         return {
141 |             "success": True,
142 |             "error": "",
143 |             "text": text
144 |         }
145 |     except Exception as e:
146 |         return {
147 |             "success": False,
148 |             "error": str(e),
149 |             "text": ""
150 |         }
151 |     finally:
152 |         # Clean up temporary file if it was created
153 |         if temp_file and os.path.exists(temp_file):
154 |             try:
155 |                 os.unlink(temp_file)
156 |             except:
157 |                 pass
158 | 
```

--------------------------------------------------------------------------------
/toolkit/dnsrecon.py:
--------------------------------------------------------------------------------

```python
  1 | # INFORMATION:
  2 | # - Tool: DNSRecon
  3 | # - Description: DNS reconnaissance tool
  4 | # - Usage: Performs various DNS-related scans and enumeration
  5 | # - Parameters: domain (required), scan_type (optional), name_server (optional), range (optional), dictionary (optional)
  6 | 
  7 | import subprocess
  8 | import logging
  9 | from typing import Any, Dict, List, Optional
 10 | import json
 11 | 
 12 | # Logging setup
 13 | logging.basicConfig(
 14 |     level=logging.INFO,
 15 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 16 | )
 17 | logger = logging.getLogger("dnsrecon-tool")
 18 | 
 19 | class DNSRecon:
 20 |     """DNSRecon tool wrapper class."""
 21 | 
 22 |     def __init__(self):
 23 |         """Initialize DNSRecon wrapper."""
 24 |         self._check_installed()
 25 | 
 26 |     def _check_installed(self) -> None:
 27 |         """Verify DNSRecon is installed."""
 28 |         try:
 29 |             subprocess.run(["dnsrecon", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 30 |             logger.info("DNSRecon installation verified.")
 31 |         except (subprocess.SubprocessError, FileNotFoundError):
 32 |             logger.warning("dnsrecon command not found. Make sure DNSRecon is installed and in your PATH.")
 33 |             raise RuntimeError("DNSRecon is not installed or not in PATH.")
 34 | 
 35 |     def get_version(self) -> Optional[str]:
 36 |         """Get installed DNSRecon version."""
 37 |         try:
 38 |             result = subprocess.run(["dnsrecon", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
 39 |             version = result.stdout.strip() if result.stdout else result.stderr.strip()
 40 |             logger.info(f"DNSRecon version: {version}")
 41 |             return version
 42 |         except (subprocess.SubprocessError, FileNotFoundError) as e:
 43 |             logger.error(f"DNSRecon version check failed: {e}")
 44 |             return None
 45 | 
 46 |     def scan(self, domain: Optional[str] = None, scan_type: Optional[str] = "std", **kwargs: Any) -> Dict[str, Any]:
 47 |         """Run the DNSRecon scan."""
 48 |         cmd = ["dnsrecon"]
 49 |         
 50 |         # Handle required parameters based on scan type
 51 |         if scan_type == "rvl":
 52 |             if "range" not in kwargs:
 53 |                 raise ValueError("IP range must be provided for reverse lookup scan type.")
 54 |             cmd.extend(["-t", scan_type, "-r", kwargs["range"]])
 55 |         elif scan_type not in ["tld", "zonewalk"]:
 56 |             if not domain:
 57 |                 raise ValueError("Target domain must be provided for this scan type.")
 58 |             cmd.extend(["-t", scan_type, "-d", domain])
 59 |         else:
 60 |             if not domain:
 61 |                 raise ValueError("Target domain must be provided.")
 62 |             cmd.extend(["-t", scan_type, "-d", domain])
 63 |             
 64 |         # Handle optional parameters
 65 |         if "name_server" in kwargs:
 66 |             cmd.extend(["-n", kwargs["name_server"]])
 67 |         if "dictionary" in kwargs:
 68 |             cmd.extend(["-D", kwargs["dictionary"]])
 69 |         if kwargs.get("filter_wildcard", False):
 70 |             cmd.append("-f")
 71 |         if kwargs.get("axfr", False):
 72 |             cmd.append("-a")
 73 |         if kwargs.get("spf", False):
 74 |             cmd.append("-s")
 75 |         if kwargs.get("bing", False):
 76 |             cmd.append("-b")
 77 |         if kwargs.get("yandex", False):
 78 |             cmd.append("-y")
 79 |         if kwargs.get("crt", False):
 80 |             cmd.append("-k")
 81 |         if kwargs.get("whois", False):
 82 |             cmd.append("-w")
 83 |         if kwargs.get("dnssec", False):
 84 |             cmd.append("-z")
 85 |         if "threads" in kwargs:
 86 |             cmd.extend(["--threads", str(kwargs["threads"])])
 87 |         if "lifetime" in kwargs:
 88 |             cmd.extend(["--lifetime", str(kwargs["lifetime"])])
 89 |         if kwargs.get("tcp", False):
 90 |             cmd.append("--tcp")
 91 |         if "db" in kwargs:
 92 |             cmd.extend(["--db", kwargs["db"]])
 93 |         if "xml" in kwargs:
 94 |             cmd.extend(["-x", kwargs["xml"]])
 95 |         if "csv" in kwargs:
 96 |             cmd.extend(["-c", kwargs["csv"]])
 97 |         if "json" in kwargs:
 98 |             cmd.extend(["-j", kwargs["json"]])
 99 |         if kwargs.get("ignore_wildcard", False):
100 |             cmd.append("--iw")
101 |         if kwargs.get("disable_check_recursion", False):
102 |             cmd.append("--disable_check_recursion")
103 |         if kwargs.get("disable_check_bindversion", False):
104 |             cmd.append("--disable_check_bindversion")
105 |         if kwargs.get("verbose", False):
106 |             cmd.append("-v")
107 |             
108 |         logger.info(f"Preparing DNSRecon command: {' '.join(cmd)}")
109 | 
110 |         # Run the scan
111 |         try:
112 |             result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
113 |             output = result.stdout
114 |             
115 |             # Parse results
116 |             parse_result = self._parse_output(output)
117 |             return {
118 |                 "status": "success",
119 |                 "command": " ".join(cmd),
120 |                 "raw_output": output,
121 |                 "results": parse_result
122 |             }
123 |         except subprocess.SubprocessError as e:
124 |             logger.error(f"Error during DNSRecon scan: {e}")
125 |             return {
126 |                 "status": "error",
127 |                 "message": str(e),
128 |                 "command": " ".join(cmd),
129 |                 "stderr": e.stderr if hasattr(e, 'stderr') else ""
130 |             }
131 |             
132 |     def _parse_output(self, output: str) -> List[Dict[str, Any]]:
133 |         """Parse the output from DNSRecon into structured data."""
134 |         results = []
135 |         lines = output.strip().split('\n')
136 |         
137 |         for line in lines:
138 |             if '[*]' in line or '[-]' in line or '[+]' in line:
139 |                 # Skip the log/status lines
140 |                 continue
141 |                 
142 |             parts = line.strip().split()
143 |             if len(parts) >= 4:
144 |                 try:
145 |                     record = {
146 |                         "record_type": parts[0],
147 |                         "name": parts[1],
148 |                         "data": " ".join(parts[2:])
149 |                     }
150 |                     results.append(record)
151 |                 except Exception as e:
152 |                     logger.error(f"Error parsing output line: {line}, error: {e}")
153 |         
154 |         return results
155 | 
156 | # Example usage (optional convenience function)
157 | def ExecDNSRecon(domain: Optional[str] = None, scan_type: str = "std", **kwargs: Any) -> Dict[str, Any]:
158 |     """Convenience wrapper to run a DNSRecon scan."""
159 |     scanner = DNSRecon()
160 |     try:
161 |         return scanner.scan(domain, scan_type, **kwargs)
162 |     except Exception as e:
163 |         logger.error(f"DNSRecon scan failed: {e}")
164 |         return {"error": str(e)}
165 | 
```

--------------------------------------------------------------------------------
/toolkit/sqlmap.py:
--------------------------------------------------------------------------------

```python
  1 | # INFORMATION:
  2 | # - Tool: SQLMap
  3 | # - Description: SQL injection vulnerability scanner
  4 | # - Usage: Detects and exploits SQL injection vulnerabilities in web applications
  5 | # - Parameters: url (required), data (optional)
  6 | 
  7 | import subprocess
  8 | import json
  9 | import logging
 10 | import re
 11 | from typing import List, Optional, Dict, Any, Union
 12 | 
 13 | # Configure logging
 14 | logging.basicConfig(level=logging.INFO)
 15 | logger = logging.getLogger(__name__)
 16 | 
 17 | # Database Management System (DBMS) info variables
 18 | isDbmsFound = False
 19 | dbms = ""
 20 | dbmsVersion = ""
 21 | dbmsVersionFound = False
 22 | 
 23 | # Tamper scripts for various DBMS
 24 | tamperscripts = {
 25 |     "MySQL": [
 26 |         "union2urls", "randomcase", "space2comment", "between", "charencode"
 27 |     ],
 28 |     "PostgreSQL": [
 29 |         "randomcase", "space2comment", "postgreSQLbool"
 30 |     ],
 31 |     "Microsoft SQL Server": [
 32 |         "charencode", "space2comment", "union2urls", "mssql08"
 33 |     ],
 34 |     "Oracle": [
 35 |         "oracle2", "space2comment", "union2urls"
 36 |     ],
 37 |     "SQLite": [
 38 |         "randomcase", "space2comment", "union2urls", "sqliteunicode"
 39 |     ],
 40 |     "Generic": [
 41 |         "charencode", "space2comment", "union2urls"
 42 |     ]
 43 | }
 44 | 
 45 | 
 46 | def run_sqlmap(cmd: List[str]) -> subprocess.CompletedProcess:
 47 |     """
 48 |     Helper function to run the sqlmap command.
 49 |     
 50 |     Args:
 51 |         cmd: List of command-line arguments for sqlmap.
 52 |     
 53 |     Returns:
 54 |         subprocess.CompletedProcess: The result of running sqlmap.
 55 |     """
 56 |     logger.debug("Running sqlmap command: %s", " ".join(cmd))
 57 |     try:
 58 |         result = subprocess.run(
 59 |             cmd,
 60 |             capture_output=True,
 61 |             text=True,
 62 |             check=True
 63 |         )
 64 |         return result
 65 |     except subprocess.CalledProcessError as e:
 66 |         logger.error("SQLMap command failed: %s", e.stderr)
 67 |         raise e
 68 | 
 69 | 
 70 | def check_sqlmap_installed() -> bool:
 71 |     """
 72 |     Check if sqlmap is installed on the system.
 73 |     
 74 |     Returns:
 75 |         bool: True if sqlmap is installed, False otherwise
 76 |     """
 77 |     try:
 78 |         subprocess.run(["which", "sqlmap"], capture_output=True, check=True)
 79 |         return True
 80 |     except subprocess.CalledProcessError:
 81 |         return False
 82 | 
 83 | 
 84 | def gather_information(url: str) -> bool:
 85 |     """
 86 |     Gather information about the target URL by running sqlmap with the --batch and -v 0 options.
 87 |     
 88 |     Args:
 89 |         url: Target URL to scan
 90 |     
 91 |     Returns:
 92 |         bool: True if the operation was successful, False otherwise
 93 |     """
 94 |     cmd = ["sqlmap", "-u", url, "--batch", "-v", "0"]
 95 |     
 96 |     try:
 97 |         result = run_sqlmap(cmd)
 98 |         return result.returncode == 0
 99 |     except subprocess.CalledProcessError:
100 |         return False
101 | 
102 | 
103 | def try_tamper(url: str, tamper: str) -> bool:
104 |     """
105 |     Try a specific tamper script on the target URL by running sqlmap with the --batch and -v 0 options.
106 |     
107 |     Args:
108 |         url: Target URL to scan
109 |         tamper: Tamper script to use
110 |     
111 |     Returns:
112 |         bool: True if the operation was successful, False otherwise
113 |     """
114 |     cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--tamper", tamper]
115 |     
116 |     try:
117 |         result = run_sqlmap(cmd)
118 |         return result.returncode == 0
119 |     except subprocess.CalledProcessError:
120 |         return False
121 | 
122 | 
123 | def try_with_risk_and_level(url: str, risk: int, level: int) -> bool:
124 |     """
125 |     Try a specific risk and level on the target URL by running sqlmap with the --batch and -v 0 options.
126 |     
127 |     Args:
128 |         url: Target URL to scan
129 |         risk: Risk level (1-3)
130 |         level: Level (1-5)
131 |     
132 |     Returns:
133 |         bool: True if the operation was successful, False otherwise
134 |     """
135 |     cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--level", str(level), "--risk", str(risk)]
136 |     
137 |     try:
138 |         result = run_sqlmap(cmd)
139 |         return result.returncode == 0
140 |     except subprocess.CalledProcessError:
141 |         return False
142 | 
143 | 
144 | def try_with_technique(url: str, technique: str) -> bool:
145 |     """
146 |     Try a specific technique on the target URL by running sqlmap with the --batch and -v 0 options.
147 |     
148 |     Args:
149 |         url: Target URL to scan
150 |         technique: Technique to use (e.g., "B", "E", "T", "U")
151 |     
152 |     Returns:
153 |         bool: True if the operation was successful, False otherwise
154 |     """
155 |     cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--technique", technique]
156 |     
157 |     try:
158 |         result = run_sqlmap(cmd)
159 |         return result.returncode == 0
160 |     except subprocess.CalledProcessError:
161 |         return False
162 | 
163 | 
164 | def ExecSqlmap(url: str, data: Optional[str] = None) -> Dict[str, Any]:
165 |     """
166 |     Run sqlmap with the given URL and data.
167 |     
168 |     Args:
169 |         url: Target URL to scan
170 |         data: POST data to include in the request
171 |     
172 |     Returns:
173 |         Dict[str, Any]: Result or error from the sqlmap command
174 |     """
175 |     options = []
176 |     if data:
177 |         options.extend(["--data", data])
178 |     
179 |     cmd = ["sqlmap", "-u", url, "--batch", "-v", "0", "--output-dir=/tmp/sqlmap"]
180 |     cmd.extend(options)
181 |     
182 |     try:
183 |         result = run_sqlmap(cmd)
184 |         parsed_output = parse_sqlmap_output(result.stdout)
185 |         return {
186 |             "success": True,
187 |             "url": url,
188 |             "results": parsed_output
189 |         }
190 |     except subprocess.CalledProcessError as e:
191 |         return {
192 |             "success": False,
193 |             "error": str(e),
194 |             "stderr": e.stderr
195 |         }
196 |     except Exception as e:
197 |         return {
198 |             "success": False,
199 |             "error": str(e)
200 |         }
201 | 
202 | 
203 | def categorize_tamperscript(db_tech: str) -> str:
204 |     """
205 |     Categorizes tamper scripts by database technology.
206 |     
207 |     Args:
208 |         db_tech: The name of the database technology (e.g., MySQL, PostgreSQL, etc.)
209 |     
210 |     Returns:
211 |         str: JSON string containing the tamper scripts for the provided DB technology.
212 |     """
213 |     try:
214 |         if db_tech in tamperscripts:
215 |             return json.dumps({
216 |                 "success": True,
217 |                 "database": db_tech,
218 |                 "tamper_scripts": tamperscripts[db_tech]
219 |             })
220 |         else:
221 |             return json.dumps({
222 |                 "success": False,
223 |                 "error": f"Tamper scripts for {db_tech} not found."
224 |             })
225 |     except Exception as e:
226 |         return json.dumps({
227 |             "success": False,
228 |             "error": str(e)
229 |         })
230 | 
231 | 
232 | def parse_sqlmap_output(output: str) -> Dict[str, Any]:
233 |     """
234 |     Parse the output from sqlmap to extract useful information.
235 |     
236 |     Args:
237 |         output: The stdout from sqlmap
238 |     
239 |     Returns:
240 |         Dict: A dictionary containing parsed information
241 |     """
242 |     result = {
243 |         "vulnerable": False,
244 |         "dbms": None,
245 |         "payloads": [],
246 |         "tables": [],
247 |         "raw_output": output
248 |     }
249 |     
250 |     # Check if any vulnerability was found
251 |     if "is vulnerable to" in output:
252 |         result["vulnerable"] = True
253 |     
254 |     # Try to extract DBMS information
255 |     dbms_match = re.search(r"back-end DBMS: (.+?)(?:\n|\[)", output)
256 |     if dbms_match:
257 |         result["dbms"] = dbms_match.group(1).strip()
258 |     
259 |     # Try to extract payload information
260 |     payload_matches = re.findall(r"Payload: (.+?)(?:\n|$)", output)
261 |     result["payloads"] = [p.strip() for p in payload_matches]
262 |     
263 |     # Extract tables if available
264 |     tables_section = re.search(r"Database: .*?\nTable: (.*?)(?:\n\n|\Z)", output, re.DOTALL)
265 |     if tables_section:
266 |         tables_text = tables_section.group(1)
267 |         tables = re.findall(r"\|\s+([^\|]+?)\s+\|", tables_text)
268 |         result["tables"] = [t.strip() for t in tables]
269 |     
270 |     return result
271 | 
272 | 
273 | def ExecSqlmap(url: str, data: Optional[str] = None) -> Dict[str, Any]:
274 |     """
275 |     Run sqlmap vulnerability scan on a specified target URL.
276 |     Main entry point for the MCP server to call this tool.
277 |     
278 |     Args:
279 |         url: Target URL to scan
280 |         data: Optional POST data to include in the request
281 |     
282 |     Returns:
283 |         Dict: JSON-serializable dictionary with scan results
284 |     """
285 |     logger.info(f"Starting SQLMap scan on {url}")
286 |     
287 |     # Check if sqlmap is installed
288 |     if not check_sqlmap_installed():
289 |         return {
290 |             "success": False,
291 |             "error": "SQLMap is not installed. Install it with 'pip install sqlmap' or 'apt-get install sqlmap'."
292 |         }
293 |     
294 |     # Define base command
295 |     cmd = ["sqlmap", "-u", url, "--batch", "--forms", "--json-output"]
296 |     
297 |     # Add data parameter if provided
298 |     if data:
299 |         cmd.extend(["--data", data])
300 |     
301 |     # Run initial scan
302 |     try:
303 |         result = subprocess.run(cmd, capture_output=True, text=True, check=True)
304 |         
305 |         # Check if JSON output is available
306 |         try:
307 |             # Try to parse JSON output
308 |             json_output_path = re.search(r"JSON report saved to: (.+)", result.stdout)
309 |             if json_output_path:
310 |                 with open(json_output_path.group(1), 'r') as json_file:
311 |                     json_data = json.load(json_file)
312 |                     return {
313 |                         "success": True,
314 |                         "url": url,
315 |                         "vulnerable": bool(json_data.get("vulnerabilities", [])),
316 |                         "data": json_data
317 |                     }
318 |         except (json.JSONDecodeError, FileNotFoundError):
319 |             pass
320 |         
321 |         # If JSON parsing failed, parse the output manually
322 |         parsed_results = parse_sqlmap_output(result.stdout)
323 |         return {
324 |             "success": True,
325 |             "url": url,
326 |             "vulnerable": parsed_results["vulnerable"],
327 |             "dbms": parsed_results["dbms"],
328 |             "payloads": parsed_results["payloads"],
329 |             "tables": parsed_results["tables"],
330 |             "output": result.stdout
331 |         }
332 |     
333 |     except subprocess.CalledProcessError as e:
334 |         logger.error(f"SQLMap scan failed: {e.stderr}")
335 |         return {
336 |             "success": False,
337 |             "error": "SQLMap scan failed",
338 |             "details": e.stderr
339 |         }
340 |     except Exception as e:
341 |         logger.exception("Unexpected error during SQLMap execution")
342 |         return {
343 |             "success": False,
344 |             "error": f"Error executing SQLMap scan: {str(e)}"
345 |         }
346 | 
347 | 
348 | 
```

--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------

```html
  1 | <!DOCTYPE html>
  2 | <html lang="en">
  3 | 
  4 | <head>
  5 |    <meta charset="UTF-8">
  6 |    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  7 |    <title>MCPHydra</title>
  8 |    <style>
  9 |       body {
 10 |          background-color: #000000;
 11 |          margin: 0;
 12 | 
 13 |          display: flex;
 14 |          justify-content: center;
 15 |          align-items: center;
 16 |          min-height: 100vh;
 17 |       }
 18 | 
 19 |       #flag-container {
 20 |          position: relative;
 21 |          overflow: hidden;
 22 |          max-width: 100%;
 23 |          height: 100vh;
 24 |       }
 25 | 
 26 | 
 27 |       #tiresult {
 28 |          font-size: 16px;
 29 |          background-color: #000000;
 30 |          font-weight: bold;
 31 |          padding: 3px 5px;
 32 |          margin: 0;
 33 |          display: inline-block;
 34 |          white-space: pre;
 35 |          width: 100%;
 36 |          overflow-x: auto;
 37 |       }
 38 | 
 39 |       #mcphydra {
 40 |          background-color: #000000;
 41 |          font-weight: bold;
 42 |          padding: 3px 5px;
 43 |          margin: 0;
 44 |          display: inline-block;
 45 |          font-size: 18px;
 46 |          white-space: pre;
 47 |          width: 100%;
 48 |          overflow-x: auto;
 49 |          color: #07df07;
 50 |       }
 51 | 
 52 |       /* Responsive font sizes */
 53 |       @media screen and (max-width: 1024px) {
 54 | 
 55 |          #tiresult,
 56 |          #mcphydra {
 57 |             font-size: 20px;
 58 |          }
 59 | 
 60 |          #mcphydra {
 61 |             font-size: 10px;
 62 |          }
 63 |       }
 64 | 
 65 |       @media screen and (max-width: 768px) {
 66 | 
 67 |          #tiresult,
 68 |          #mcphydra {
 69 |             font-size: 20px;
 70 |          }
 71 | 
 72 |          #mcphydra {
 73 |             font-size: 10px;
 74 |          }
 75 |       }
 76 | 
 77 |       @media screen and (max-width: 480px) {
 78 | 
 79 |          #tiresult,
 80 |          #mcphydra {
 81 |             font-size: 14px;
 82 |          }
 83 | 
 84 |          #mcphydra {
 85 |             font-size: 10px;
 86 |          }
 87 |       }
 88 | 
 89 |       /* Animation for each character */
 90 |       #tiresult b {
 91 |          display: inline-block;
 92 |          animation: wave 1s infinite ease-in-out;
 93 |          transform-origin: center bottom;
 94 |          position: relative;
 95 |       }
 96 | 
 97 |       /* Base animation keyframes - will be overridden by JavaScript for responsive sizing */
 98 |       @keyframes wave {
 99 |          0% {
100 |             transform: translateY(0) rotate(0deg);
101 |          }
102 | 
103 |          20% {
104 |             transform: translateY(-1px) rotate(1.5deg) translateX(0.8px);
105 |          }
106 | 
107 |          40% {
108 |             transform: translateY(-0.5px) rotate(0.5deg) translateX(0.4px);
109 |          }
110 | 
111 |          60% {
112 |             transform: translateY(0) rotate(0deg);
113 |          }
114 | 
115 |          80% {
116 |             transform: translateY(0.8px) rotate(-1.5deg) translateX(-0.8px);
117 |          }
118 | 
119 |          100% {
120 |             transform: translateY(0) rotate(0deg);
121 |          }
122 |       }
123 |    </style>
124 | </head>
125 | 
126 | <body>
127 |    <div id="flag-container">
128 |       <pre id="mcphydra">
129 |                          __              __          
130 |    ____ ___  _________  / /_  __  ______/ /________ _
131 |   / __ `__ \/ ___/ __ \/ __ \/ / / / __  / ___/ __ `/
132 |  / / / / / / /__/ /_/ / / / / /_/ / /_/ / /  / /_/ / 
133 | /_/ /_/ /_/\___/ .___/_/ /_/\__, /\__,_/_/   \__,_/  
134 |               /_/          /____/       v0.1.0             
135 | </pre>
136 |       <pre id="tiresult">
137 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
138 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠙</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠋</b><b style="color:#07df07">⠙</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠷</b><b style="color:#07df07">⠄</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢸</b><b style="color:#07df07">⣿</b>
139 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠋</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢠</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
140 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣀</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣴</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣇</b><b style="color:#07df07">⡀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
141 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣷</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
142 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡿</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠙</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠟</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡆</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
143 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⠏</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠁</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
144 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠁</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣦</b><b style="color:#07df07">⡄</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
145 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣦</b><b style="color:#07df07">⡀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣆</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
146 | <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>
147 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⢻</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⡿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⢿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
148 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣠</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣄</b><b style="color:#07df07">⣀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠛</b><b style="color:#07df07">⠹</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⠷</b><b style="color:#07df07">⣄</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⠉</b><b style="color:#07df07">⣹</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
149 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣾</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣷</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣴</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
150 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⢀</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣷</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣆</b><b style="color:#07df07">⡀</b><b style="color:#07df07">⠀</b><b style="color:#07df07">⠈</b><b style="color:#07df07">⠻</b><b style="color:#07df07">⠿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
151 | <b style="color:#07df07">⣿</b><b style="color:#07df07">⣤</b><b style="color:#07df07">⣼</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣶</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b><b style="color:#07df07">⣿</b>
152 |       </pre>
153 |       <script>
154 |          document.addEventListener('DOMContentLoaded', function () {
155 |             const chars = document.querySelectorAll('#tiresult b');
156 | 
157 |             // Handle responsive animation scaling
158 |             function adjustAnimation() {
159 |                const viewportWidth = window.innerWidth;
160 |                let scaleFactor = 1;
161 | 
162 |                if (viewportWidth <= 480) {
163 |                   scaleFactor = 0.5;
164 |                } else if (viewportWidth <= 768) {
165 |                   scaleFactor = 0.7;
166 |                } else if (viewportWidth <= 1024) {
167 |                   scaleFactor = 0.85;
168 |                }
169 | 
170 |                function getRandomColor() {
171 |                   return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
172 |                }
173 | 
174 |                let randomColors = Array.from({ length: 10 }, getRandomColor);
175 |                setInterval(() => {
176 |                   randomColors = Array.from({ length: 10 }, getRandomColor);
177 |                    chars.forEach((char, index) => {
178 |                   const colorIndex = Math.floor(Math.random() * randomColors.length);
179 |                   char.style.color = randomColors[colorIndex];
180 |                });
181 |                }, 2000);
182 |               
183 |                console.log(randomColors);
184 |                
185 |                chars.forEach((char, index) => {
186 |                   const row = Math.floor(index / 130);
187 |                   const col = index % 130;
188 |                   const delay = (col * 0.015 + row * 0.06) % 1.5;
189 | 
190 |                   char.style.animationDelay = delay + 's';
191 |                   const durationVariation = 0.9 + Math.random() * 0.2; // 0.9-1.1
192 |                   char.style.animationDuration = (1.5 * durationVariation) + 's';
193 | 
194 |                   // Scale transform values based on viewport
195 |                   const style = document.createElement('style');
196 |                   style.innerHTML = `
197 |                      @keyframes wave {
198 |                         0% { transform: translateY(0) rotate(0deg); }
199 |                         20% { transform: translateY(${-1 * scaleFactor}px) rotate(${1.5 * scaleFactor}deg) translateX(${0.8 * scaleFactor}px); }
200 |                         40% { transform: translateY(${-0.5 * scaleFactor}px) rotate(${0.5 * scaleFactor}deg) translateX(${0.4 * scaleFactor}px); }
201 |                         60% { transform: translateY(0) rotate(0deg); }
202 |                         80% { transform: translateY(${0.8 * scaleFactor}px) rotate(${-1.5 * scaleFactor}deg) translateX(${-0.8 * scaleFactor}px); }
203 |                         100% { transform: translateY(0) rotate(0deg); }
204 |                      }
205 |                      
206 |                   `;
207 |                   document.head.appendChild(style);
208 |                });
209 |             }
210 | 
211 |             // Initial setup
212 |             adjustAnimation();
213 | 
214 |             // Re-adjust on window resize
215 |             window.addEventListener('resize', adjustAnimation);
216 |          });
217 |       </script>
218 | </body>
219 | 
220 | </html>
```