ptrstream- endless stream of rdns |
git clone git://git.acid.vegas/ptrstream.git |
Log | Files | Refs | Archive | README |
ptrstream.py (5478B)
1 #/usr/bin/env python 2 # ptrstream - developed by acidvegas in python (https://git.acid.vegas/ptrstream) 3 4 import argparse 5 import asyncio 6 import ipaddress 7 import os 8 import random 9 import time 10 import urllib.request 11 12 try: 13 import aiodns 14 except ImportError: 15 raise ImportError('missing required \'aiodns\' library (pip install aiodns)') 16 17 # Do not store these in the results file 18 bad_hosts = ['localhost','undefined.hostname.localhost','unknown'] 19 20 # Colors 21 class colors: 22 ip = '\033[35m' 23 ip_match = '\033[96m' # IP address mfound within PTR record 24 ptr = '\033[93m' 25 red = '\033[31m' # .gov or .mil indicator 26 invalid = '\033[90m' 27 reset = '\033[0m' 28 grey = '\033[90m' 29 30 31 def get_dns_servers() -> list: 32 '''Get a list of DNS servers to use for lookups.''' 33 source = urllib.request.urlopen('https://public-dns.info/nameservers.txt') 34 results = source.read().decode().split('\n') 35 return [server for server in results if ':' not in server] 36 37 38 async def rdns(semaphore: asyncio.Semaphore, ip_address: str, resolver: aiodns.DNSResolver): 39 ''' 40 Perform a reverse DNS lookup on an IP address. 41 42 :param semaphore: The semaphore to use for concurrency. 43 :param ip_address: The IP address to perform a reverse DNS lookup on. 44 ''' 45 async with semaphore: 46 reverse_name = ipaddress.ip_address(ip_address).reverse_pointer 47 try: 48 answer = await resolver.query(reverse_name, 'PTR') 49 if answer.name not in bad_hosts and answer.name != ip_address and answer.name != reverse_name: 50 return ip_address, answer.name, True 51 else: 52 return ip_address, answer.name, False 53 except aiodns.error.DNSError as e: 54 if e.args[0] == aiodns.error.ARES_ENOTFOUND: 55 return ip_address, f'{colors.red}No rDNS found{colors.reset}', False 56 elif e.args[0] == aiodns.error.ARES_ETIMEOUT: 57 return ip_address, f'{colors.red}DNS query timed out{colors.reset}', False 58 else: 59 return ip_address, f'{colors.red}DNS error{colors.grey} ({e.args[1]}){colors.reset}', False 60 except Exception as e: 61 return ip_address, f'{colors.red}Unknown error{colors.grey} ({str(e)}){colors.reset}', False 62 63 64 def rig(seed: int) -> str: 65 ''' 66 Random IP generator. 67 68 :param seed: The seed to use for the random number generator. 69 ''' 70 max_value = 256**4 71 random.seed(seed) 72 for _ in range(max_value): 73 shuffled_index = random.randint(0, max_value - 1) 74 ip = ipaddress.ip_address(shuffled_index) 75 yield str(ip) 76 77 78 def fancy_print(ip: str, result: str): 79 ''' 80 Print the IP address and PTR record in a fancy way. 81 82 :param ip: The IP address. 83 :param result: The PTR record. 84 ''' 85 if result in ('127.0.0.1', 'localhost','undefined.hostname.localhost','unknown'): 86 print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.grey}-> {result}{colors.reset}') 87 else: 88 if ip in result: 89 result = result.replace(ip, f'{colors.ip_match}{ip}{colors.ptr}') 90 elif (daship := ip.replace('.', '-')) in result: 91 result = result.replace(daship, f'{colors.ip_match}{daship}{colors.ptr}') 92 elif (revip := '.'.join(ip.split('.')[::-1])) in result: 93 result = result.replace(revip, f'{colors.ip_match}{revip}{colors.ptr}') 94 elif (revip := '.'.join(ip.split('.')[::-1]).replace('.','-')) in result: 95 result = result.replace(revip, f'{colors.ip_match}{revip}{colors.ptr}') 96 print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.grey}->{colors.reset} {colors.ptr}{result}{colors.reset}') 97 98 99 async def main(args: argparse.Namespace): 100 ''' 101 Generate random IPs and perform reverse DNS lookups. 102 103 :param args: The command-line arguments. 104 ''' 105 if args.resolvers: 106 if os.path.exists(args.resolvers): 107 with open(args.resolvers) as file: 108 dns_resolvers = [item.strip() for item in file.read().splitlines()] 109 else: 110 raise FileNotFoundError(f'could not find file \'{args.resolvers}\'') 111 else: 112 dns_resolvers = get_dns_servers() 113 dns_resolvers = random.shuffle(dns_resolvers) 114 115 resolver = aiodns.DNSResolver(nameservers=dns_resolvers, timeout=args.timeout, tries=args.retries, rotate=True) 116 semaphore = asyncio.Semaphore(args.concurrency) 117 118 tasks = [] 119 results_cache = [] 120 121 seed = random.randint(10**9, 10**10 - 1) if not args.seed else args.seed 122 ip_generator = rig(seed) 123 124 for ip in ip_generator: 125 if len(tasks) < args.concurrency: 126 task = asyncio.create_task(rdns(semaphore, ip, resolver)) 127 tasks.append(task) 128 else: 129 done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) 130 tasks = list(pending) 131 for task in done: 132 ip, result, success = task.result() 133 if result: 134 fancy_print(ip, result) 135 if success: 136 results_cache.append(f'{ip}:{result}') 137 if len(results_cache) >= 1000: 138 stamp = time.strftime('%Y%m%d') 139 with open(f'ptr_{stamp}_{seed}.txt', 'a') as file: 140 file.writelines(f"{record}\n" for record in results_cache) 141 results_cache = [] 142 143 144 145 if __name__ == '__main__': 146 parser = argparse.ArgumentParser(description='Perform asynchronous reverse DNS lookups.') 147 parser.add_argument('-c', '--concurrency', type=int, default=100, help='Control the speed of lookups.') 148 parser.add_argument('-t', '--timeout', type=int, default=5, help='Timeout for DNS lookups.') 149 parser.add_argument('-r', '--resolvers', type=str, help='File containing DNS servers to use for lookups.') 150 parser.add_argument('-rt', '--retries', type=int, default=3, help='Number of times to retry a DNS lookup.') 151 parser.add_argument('-s', '--seed', type=int, help='Seed to use for random number generator.') 152 args = parser.parse_args() 153 154 asyncio.run(main(args))