#!/usr/bin/env python3
"""DNS exfiltration listener — receives data encoded in DNS subdomains.
Listens on port 53/UDP. Logs all queries. Responds with empty answer."""
import socket, struct, sys, datetime, os

LOG_FILE = "/root/dns_exfil.log"

def parse_dns_query(data, addr):
    """Extract queried domain name from DNS packet."""
    if len(data) < 12: return None
    transaction_id = data[:2]
    # Parse QNAME
    pos = 12
    labels = []
    while pos < len(data):
        length = data[pos]
        if length == 0: break
        pos += 1
        labels.append(data[pos:pos+length].decode('ascii', errors='replace'))
        pos += length
    # Skip null terminator
    pos += 1
    # QTYPE (2 bytes) + QCLASS (2 bytes)
    qtype = struct.unpack('>H', data[pos:pos+2])[0] if pos+2 <= len(data) else 0
    return '.'.join(labels), transaction_id, qtype

def build_response(transaction_id, data):
    """Build minimal DNS response."""
    flags = b'\x81\x83'  # standard response, NXDOMAIN (so resolver doesn't cache)
    qdcount = data[4:6]
    ancount = b'\x00\x00'
    nscount = b'\x00\x00'
    arcount = b'\x00\x00'
    question = data[12:]
    return transaction_id + flags + qdcount + ancount + nscount + arcount + question

def main():
    # Stop systemd-resolved on port 53
    os.system("systemctl stop systemd-resolved 2>/dev/null")
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('0.0.0.0', 53))
    
    print(f"[*] DNS exfiltration listener on :53")
    print(f"[*] Logging to {LOG_FILE}")
    sys.stdout.flush()
    
    while True:
        try:
            data, addr = sock.recvfrom(4096)
            result = parse_dns_query(data, addr)
            if result:
                domain, txn_id, qtype = result
                ts = datetime.datetime.now().isoformat()
                line = f"{ts} | {addr[0]}:{addr[1]} | {domain} | qtype={qtype}"
                print(line)
                sys.stdout.flush()
                with open(LOG_FILE, "a") as f:
                    f.write(line + "\n")
            # Send response
            response = build_response(data[:2], data)
            sock.sendto(response, addr)
        except Exception as e:
            print(f"[!] Error: {e}")
            sys.stdout.flush()

if __name__ == "__main__":
    main()
