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))