pdknockr- a passive dns drive-by tool 🏎️💨 |
git clone git://git.acid.vegas/pdknockr.git |
Log | Files | Refs | Archive | README |
pdknockr.py (5943B)
1 #!/usr/bin/env python 2 # Passive DNS Knocker (PDK) - developed by acidvegas in python (https://git.acid.vegas/pdknockr) 3 4 import argparse 5 import asyncio 6 import logging 7 import logging.handlers 8 import os 9 import random 10 import time 11 12 try: 13 import aiodns 14 except ImportError: 15 raise SystemExit('missing required \'aiodns\' module (pip install aiodns)') 16 17 18 async def dns_lookup(semaphore: asyncio.Semaphore, domain: str, dns_server: str, record_type: str, timeout: int): 19 ''' 20 Perform a DNS lookup on a target domain. 21 22 :param domain: The target domain to perform the lookup on. 23 :param subdomain: The subdomain to look up. 24 :param dns_server: The DNS server to perform the lookup on. 25 :param dns_type: The DNS record type to look up. 26 :param timeout: The timeout for the DNS lookup. 27 :param semaphore: The semaphore to use for concurrency. 28 ''' 29 async with semaphore: 30 resolver = aiodns.DNSResolver(nameservers=[dns_server], timeout=timeout) 31 32 logging.info(f'Knocking {dns_server} with {domain} ({record_type})') 33 34 try: 35 await resolver.query(domain, record_type) 36 except: 37 pass # We're just knocking so errors are expected and ignored 38 39 40 def read_domain_file(file_path: str): 41 ''' 42 Generator function to read domains line by line. 43 44 :param file_path: The path to the file containing the DNS servers. 45 ''' 46 with open(file_path, 'r') as file: 47 while True: 48 for line in file: 49 line = line.strip() 50 if line: 51 yield line 52 53 54 def read_dns_file(file_path: str): 55 ''' 56 Generator function to read DNS servers line by line. 57 58 :param file_path: The path to the file containing the DNS servers. 59 ''' 60 with open(file_path, 'r') as file: 61 while True: 62 for line in file: 63 line = line.strip() 64 if line: 65 yield line 66 67 68 def generate_subdomain(sub_domains: list, domain: str, max_size: int): 69 ''' 70 Generator function to read subdomains line by line. 71 72 :param sub_domains: The list of subdomains to use for generating noise. 73 ''' 74 while True: 75 subs = random.sample(sub_domains, random.randint(2, max_size)) 76 77 if random.choice([True, False]): 78 subs_index = random.randint(0, max_size - 1) 79 subs[subs_index] = subs[subs_index] + str(random.randint(1, 99)) 80 81 yield random.choice(['.', '-']).join(subs) + '.' + domain 82 83 84 def setup_logging(): 85 '''Setup the logging for the program.''' 86 87 os.makedirs('logs', exist_ok=True) 88 89 sh = logging.StreamHandler() 90 sh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(message)s', '%I:%M %p')) 91 92 log_filename = time.strftime('pdk_%Y-%m-%d_%H-%M-%S.log') 93 94 fh = logging.handlers.RotatingFileHandler(f'logs/{log_filename}', maxBytes=268435456, encoding='utf-8') 95 fh.setFormatter(logging.Formatter('%(asctime)s | %(levelname)9s | %(message)s', '%Y-%m-%d %I:%M %p')) 96 97 logging.basicConfig(level=logging.NOTSET, handlers=(sh,fh)) 98 99 100 async def main(): 101 '''Main function for the program.''' 102 103 parser = argparse.ArgumentParser(description='Passive DNS Knocking Tool') 104 parser.add_argument('-d', '--domains', help='Comma seperate list of domains or file containing list of domains') 105 parser.add_argument('-s', '--subdomains', help='File containing list of subdomains') 106 parser.add_argument('-r', '--resolvers', help='File containing list of DNS resolvers') 107 parser.add_argument('-rt', '--rectype', default='A,AAAA', help='Comma-seperated list of DNS record type (default: A,AAAA)') 108 parser.add_argument('-c', '--concurrency', type=int, default=25, help='Concurrency limit (default: 50)') 109 parser.add_argument('-t', '--timeout', type=int, default=3, help='Timeout for DNS lookup (default: 3)') 110 parser.add_argument('-n', '--noise', action='store_true', help='Enable random subdomain noise') 111 args = parser.parse_args() 112 113 setup_logging() 114 115 args.rectype = [record_type.upper() for record_type in args.rectype.split(',')] 116 117 if not args.domains: 118 raise SystemExit('no domains specified') 119 elif not os.path.exists(args.domains): 120 raise FileNotFoundError('domains file not found') 121 122 if not args.subdomains: 123 raise SystemExit('no subdomains file specified') 124 elif not os.path.exists(args.subdomains): 125 raise FileNotFoundError('subdomains file not found') 126 127 if not args.resolvers: 128 raise SystemExit('no resolvers file specified') 129 elif not os.path.exists(args.resolvers): 130 raise FileNotFoundError('resolvers file not found') 131 132 valid_record_types = ('A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT') 133 134 for record_type in args.rectype: 135 if record_type not in valid_record_types: 136 raise SystemExit(f'invalid record type: {record_type}') 137 138 semaphore = asyncio.BoundedSemaphore(args.concurrency) 139 140 while True: 141 tasks = [] 142 143 for domain in read_domain_file(args.domains): 144 145 for dns_server in read_dns_file(args.resolvers): 146 sub_domain = generate_subdomain(args.subdomains, domain, 3) 147 148 if len(tasks) < args.concurrency: 149 query_record = random.choice(args.record_types) 150 task = asyncio.create_task(dns_lookup(semaphore, domain, sub_domain, dns_server, query_record, args.timeout)) 151 tasks.append(task) 152 153 else: 154 done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) 155 tasks = list(pending) 156 157 await asyncio.wait(tasks) # Wait for any remaining tasks to complete 158 159 if not args.noise: 160 break 161 162 163 164 if __name__ == '__main__': 165 asyncio.run(main())