Compare commits
10 Commits
581efb8f6d
...
ad6febaf7e
Author | SHA1 | Date |
---|---|---|
Dionysus | ad6febaf7e | |
Dionysus | 2f598dd7fa | |
Dionysus | 7ccc6c0608 | |
Dionysus | 6b77faf586 | |
Dionysus | a2b8d53a83 | |
Dionysus | 1ea867e1cb | |
Dionysus | a0ae406fb9 | |
Dionysus | 47607786b1 | |
Dionysus | cab5fb4eb5 | |
Dionysus | 6e769ac39e |
Binary file not shown.
After Width: | Height: | Size: 3.0 MiB |
Binary file not shown.
Before Width: | Height: | Size: 74 KiB |
|
@ -2,8 +2,6 @@
|
|||
|
||||
PTRStream is an asynchronous reverse DNS lookup tool developed in Python. It generates random IP addresses and performs reverse DNS lookups using various DNS servers.
|
||||
|
||||
![](.screens/preview.png)
|
||||
|
||||
## Requirements
|
||||
- [python](https://www.python.org/)
|
||||
- [aiodns](https://pypi.org/project/aiodns/) *(pip install aiodns)*
|
||||
|
@ -25,4 +23,10 @@ The results are cached and saved to a file named ptr_{date}_{seed}.txt after eve
|
|||
|
||||
Output to elastic search possibly.
|
||||
|
||||
Extracting geo data from the ptr response...
|
||||
|
||||
Still a work in progress I guess...
|
||||
|
||||
|
||||
## Preview
|
||||
![](.screens/preview.gif)
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
#/usr/bin/env python
|
||||
# arpa stream - developed by acidvegas in python (https://git.acid.vegas/ptrstream)
|
||||
|
||||
'''
|
||||
I have no idea where we are going with this, but I'm sure it'll be fun...
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import random
|
||||
|
||||
try:
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
raise ImportError('missing required \'dnspython\' library (pip install dnspython)')
|
||||
|
||||
|
||||
class colors:
|
||||
axfr = '\033[34m'
|
||||
error = '\033[31m'
|
||||
success = '\033[32m'
|
||||
ns_query = '\033[33m'
|
||||
ns_zone = '\033[36m'
|
||||
reset = '\033[0m'
|
||||
|
||||
|
||||
def genip() -> str:
|
||||
'''Generate a random IP address with 1 to 4 octets.'''
|
||||
num_octets = random.randint(1, 4)
|
||||
ip_parts = [str(random.randint(0, 255)) for _ in range(num_octets)]
|
||||
return '.'.join(ip_parts)
|
||||
|
||||
|
||||
def query_ns_records(ip: str) -> list:
|
||||
'''
|
||||
Query NS records for a given IP.
|
||||
|
||||
:param ip: The IP address to query NS records for.
|
||||
'''
|
||||
try:
|
||||
ns_records = [str(record.target)[:-1] for record in dns.resolver.resolve(f'{ip}.in-addr.arpa', 'NS')]
|
||||
if ns_records:
|
||||
print(f'{colors.ns_zone}Queried NS records for {ip}: {ns_records}{colors.reset}')
|
||||
return ns_records
|
||||
except Exception as e:
|
||||
print(f'{colors.error}Error querying NS records for {ip}: {e}{colors.reset}')
|
||||
return []
|
||||
|
||||
|
||||
def resolve_ns_to_ip(ns_hostname: str) -> list:
|
||||
'''
|
||||
Resolve NS hostname to IP.
|
||||
|
||||
:param ns_hostname: The NS hostname to resolve.
|
||||
'''
|
||||
try:
|
||||
ns_ips = [ip.address for ip in dns.resolver.resolve(ns_hostname, 'A')]
|
||||
if ns_ips:
|
||||
print(f'{colors.ns_query}Resolved NS hostname {ns_hostname} to IPs: {ns_ips}{colors.reset}')
|
||||
return ns_ips
|
||||
except Exception as e:
|
||||
print(f'{colors.error}Error resolving NS {ns_hostname}: {e}{colors.reset}')
|
||||
return []
|
||||
|
||||
|
||||
def axfr_check(ip: str, ns_ip: str):
|
||||
'''
|
||||
Perform AXFR check on a specific nameserver IP.
|
||||
|
||||
:param ip: The IP address to perform the AXFR check on.
|
||||
:param ns_ip: The nameserver IP to perform the AXFR check on.
|
||||
'''
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.nameservers = [ns_ip]
|
||||
try:
|
||||
if resolver.resolve(f'{ip}.in-addr.arpa', 'AXFR'):
|
||||
print(f'{colors.success}[SUCCESS]{colors.reset} AXFR on {ns_ip} for {ip}.in-addr.arpa')
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f'{colors.error}[FAIL]{colors.reset} AXFR on {ns_ip} for {ip}.in-addr.arpa - Error: {e}')
|
||||
return False
|
||||
|
||||
|
||||
def process_ip(ip: str):
|
||||
'''
|
||||
Process each IP: Fetch NS records and perform AXFR check.
|
||||
|
||||
:param ip: The IP address to process.
|
||||
'''
|
||||
for ns_hostname in query_ns_records(ip):
|
||||
for ns_ip in resolve_ns_to_ip(ns_hostname):
|
||||
if axfr_check(ip, ns_ip):
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='DNS AXFR Check Script')
|
||||
parser.add_argument('--concurrency', type=int, default=100, help='Number of concurrent workers')
|
||||
args = parser.parse_args()
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
|
||||
futures = {executor.submit(process_ip, genip()): ip for ip in range(args.concurrency)}
|
||||
while True:
|
||||
done, _ = concurrent.futures.wait(futures, return_when=concurrent.futures.FIRST_COMPLETED)
|
||||
for future in done:
|
||||
future.result() # We don't need to store the result as it's already printed
|
||||
futures[executor.submit(process_ip, genip())] = genip()
|
||||
futures = {future: ip for future, ip in futures.items() if future not in done}
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
TIMEOUT=2
|
||||
|
||||
genip() {
|
||||
num_octets=$((RANDOM % 4 + 1))
|
||||
ip=""
|
||||
for i in $(seq 1 $num_octets); do
|
||||
if [ $i -ne 1 ]; then
|
||||
ip+="."
|
||||
fi
|
||||
ip+=$((RANDOM % 256))
|
||||
done
|
||||
echo $ip
|
||||
}
|
||||
|
||||
TEMP=$(mktemp -d)
|
||||
while true; do
|
||||
ip=$(genip)
|
||||
ns_records=$(dig +time=$TIMEOUT +short $ip.in-addr.arpa NS)
|
||||
for ns in $ns_records; do
|
||||
ns_ips=$(dig +time=$TIMEOUT +short $ns A $ns AAAA)
|
||||
for ns_ip in $ns_ips; do
|
||||
#echo -e "AXFR on \033[36m${ns%.}\033[0m \033[90m($ns_ip)\033[0m for \033[33m$ip.in-addr.arpa\033[0m"
|
||||
dig AXFR @$ns_ip $ip.in-addr.arpa > $TEMP/$ip.in-addr.arpa.txt
|
||||
if [ ! -s "$zone_file" ] || grep -qE "Transfer failed|connection reset|connection refused" "$zone_file"; then
|
||||
echo -e "\033[31m[FAIL]\033[0m AXFR on \033[36m${ns%.}\033[0m \033[90m($ns_ip)\033[0m for \033[33m$ip.in-addr.arpa\033[0m"
|
||||
rm -f "$zone_file"
|
||||
else
|
||||
echo -e "\033[32m[SUCCESS]\033[0m AXFR on \033[36m${ns%.}\033[0m \033[90m($ns_ip)\033[0m for \033[33m$ip.in-addr.arpa\033[0m"
|
||||
break
|
||||
fi
|
||||
done
|
||||
done
|
||||
done
|
142
ptrstream.py
142
ptrstream.py
|
@ -4,6 +4,7 @@
|
|||
import argparse
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import urllib.request
|
||||
|
@ -13,15 +14,14 @@ try:
|
|||
except ImportError:
|
||||
raise ImportError('missing required \'aiodns\' library (pip install aiodns)')
|
||||
|
||||
|
||||
# Colors
|
||||
class colors:
|
||||
ip = '\033[35m'
|
||||
ip_match = '\033[96m'
|
||||
ptr = '\033[93m'
|
||||
spooky = '\033[31m'
|
||||
invalid = '\033[90m'
|
||||
reset = '\033[0m'
|
||||
ip = '\033[35m'
|
||||
ip_match = '\033[96m' # IP address mfound within PTR record
|
||||
ptr = '\033[93m'
|
||||
spooky = '\033[31m' # .gov or .mil indicator
|
||||
invalid = '\033[90m'
|
||||
reset = '\033[0m'
|
||||
separator = '\033[90m'
|
||||
|
||||
|
||||
|
@ -32,17 +32,14 @@ def get_dns_servers() -> list:
|
|||
return [server for server in results if ':' not in server]
|
||||
|
||||
|
||||
async def rdns(semaphore: asyncio.Semaphore, ip_address: str, custom_dns_server: str):
|
||||
async def rdns(semaphore: asyncio.Semaphore, ip_address: str, resolver: aiodns.DNSResolver):
|
||||
'''
|
||||
Perform a reverse DNS lookup on an IP address.
|
||||
|
||||
:param ip_address: The IP address to lookup.
|
||||
:param custom_dns_server: The DNS server to use for lookups.
|
||||
:param semaphore: A semaphore to limit the number of concurrent lookups.
|
||||
:param timeout: The timeout for the lookup.
|
||||
:param semaphore: The semaphore to use for concurrency.
|
||||
:param ip_address: The IP address to perform a reverse DNS lookup on.
|
||||
'''
|
||||
async with semaphore:
|
||||
resolver = aiodns.DNSResolver(nameservers=[custom_dns_server], rotate=False, timeout=args.timeout)
|
||||
reverse_name = ipaddress.ip_address(ip_address).reverse_pointer
|
||||
try:
|
||||
answer = await resolver.query(reverse_name, 'PTR')
|
||||
|
@ -64,67 +61,73 @@ def rig(seed: int) -> str:
|
|||
ip = ipaddress.ip_address(shuffled_index)
|
||||
yield str(ip)
|
||||
|
||||
def fancy_print(ip: str, result: str):
|
||||
'''
|
||||
Print the IP address and PTR record in a fancy way.
|
||||
|
||||
async def main():
|
||||
'''Generate random IPs and perform reverse DNS lookups.'''
|
||||
:param ip: The IP address.
|
||||
:param result: The PTR record.
|
||||
'''
|
||||
if result in ('127.0.0.1', 'localhost'):
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}-> {result}{colors.reset}')
|
||||
else:
|
||||
if ip in result:
|
||||
result = result.replace(ip, f'{colors.ip_match}{ip}{colors.ptr}')
|
||||
elif (daship := ip.replace('.', '-')) in result:
|
||||
result = result.replace(daship, f'{colors.ip_match}{daship}{colors.ptr}')
|
||||
elif (revip := '.'.join(ip.split('.')[::-1])) in result:
|
||||
result = result.replace(revip, f'{colors.ip_match}{revip}{colors.ptr}')
|
||||
elif result.endswith('.gov') or result.endswith('.mil'):
|
||||
result = result.replace('.gov', f'{colors.spooky}.gov{colors.reset}')
|
||||
result = result.replace('.mil', f'{colors.spooky}.mil{colors.reset}')
|
||||
elif '.gov.' in result or '.mil.' in result:
|
||||
result = result.replace('.gov.', f'{colors.spooky}.gov.{colors.ptr}')
|
||||
result = result.replace('.mil.', f'{colors.spooky}.mil.{colors.ptr}')
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}->{colors.reset} {colors.ptr}{result}{colors.reset}')
|
||||
|
||||
|
||||
async def main(args: argparse.Namespace):
|
||||
'''
|
||||
Generate random IPs and perform reverse DNS lookups.
|
||||
|
||||
:param args: The command-line arguments.
|
||||
'''
|
||||
if args.resolvers:
|
||||
if os.path.exists(args.resolvers):
|
||||
with open(args.resolvers) as file:
|
||||
dns_resolvers = [item.strip() for item in file.read().splitlines()]
|
||||
else:
|
||||
raise FileNotFoundError(f'could not find file \'{args.resolvers}\'')
|
||||
else:
|
||||
dns_resolvers = get_dns_servers()
|
||||
dns_resolvers = random.shuffle(dns_resolvers)
|
||||
|
||||
resolver = aiodns.DNSResolver(nameservers=dns_resolvers, timeout=args.timeout, tries=args.retries, rotate=True)
|
||||
semaphore = asyncio.Semaphore(args.concurrency)
|
||||
|
||||
tasks = []
|
||||
results_cache = []
|
||||
|
||||
if args.resolvers:
|
||||
with open(args.resolvers) as file:
|
||||
dns_servers = [server.strip() for server in file.readlines()]
|
||||
seed = random.randint(10**9, 10**10 - 1)
|
||||
ip_generator = rig(seed)
|
||||
|
||||
while True:
|
||||
if not args.resolvers:
|
||||
dns_servers = []
|
||||
while not dns_servers:
|
||||
try:
|
||||
dns_servers = get_dns_servers()
|
||||
except:
|
||||
time.sleep(300)
|
||||
|
||||
seed = random.randint(10**9, 10**10 - 1)
|
||||
ip_generator = rig(seed)
|
||||
|
||||
for ip in ip_generator:
|
||||
if len(tasks) < args.concurrency:
|
||||
dns = random.choice(dns_servers)
|
||||
task = asyncio.create_task(rdns(semaphore, ip, dns))
|
||||
tasks.append(task)
|
||||
else:
|
||||
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
tasks = list(pending)
|
||||
for task in done:
|
||||
ip, result = task.result()
|
||||
if result:
|
||||
if result in ('127.0.0.1', 'localhost'):
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}-> {result}{colors.reset}')
|
||||
elif ip in result:
|
||||
result = result.replace(ip, f'{colors.ip_match}{ip}{colors.ptr}')
|
||||
elif (daship := ip.replace('.', '-')) in result:
|
||||
result = result.replace(daship, f'{colors.ip_match}{daship}{colors.ptr}')
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}->{colors.reset} {colors.ptr}{result}{colors.reset}')
|
||||
elif (revip := '.'.join(ip.split('.')[::-1])) in result:
|
||||
result = result.replace(revip, f'{colors.ip_match}{revip}{colors.ptr}')
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}->{colors.reset} {colors.ptr}{result}{colors.reset}')
|
||||
elif result.endswith('.gov') or result.endswith('.mil'):
|
||||
result = result.replace('.gov', f'{colors.spooky}.gov{colors.reset}')
|
||||
result = result.replace('.mil', f'{colors.spooky}.gov{colors.reset}')
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}->{colors.reset} {colors.ptr}{result}{colors.reset}')
|
||||
elif '.gov.' in result or '.mil.' in result:
|
||||
result = result.replace('.gov.', f'{colors.spooky}.gov.{colors.reset}')
|
||||
result = result.replace('.mil.', f'{colors.spooky}.mil.{colors.reset}')
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}->{colors.reset} {colors.ptr}{result}{colors.reset}')
|
||||
else:
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.separator}->{colors.reset} {colors.ptr}{result}{colors.reset}')
|
||||
|
||||
results_cache.append(f'{ip}:{result}')
|
||||
if len(results_cache) >= 1000:
|
||||
stamp = time.strftime('%Y%m%d')
|
||||
with open(f'ptr_{stamp}_{seed}.txt', 'a') as file:
|
||||
file.writelines(f"{record}\n" for record in results_cache)
|
||||
results_cache = []
|
||||
for ip in ip_generator:
|
||||
if len(tasks) < args.concurrency:
|
||||
task = asyncio.create_task(rdns(semaphore, ip, resolver))
|
||||
tasks.append(task)
|
||||
else:
|
||||
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
tasks = list(pending)
|
||||
for task in done:
|
||||
ip, result = task.result()
|
||||
if result:
|
||||
fancy_print(ip, result)
|
||||
results_cache.append(f'{ip}:{result}')
|
||||
if len(results_cache) >= 1000:
|
||||
stamp = time.strftime('%Y%m%d')
|
||||
with open(f'ptr_{stamp}_{seed}.txt', 'a') as file:
|
||||
file.writelines(f"{record}\n" for record in results_cache)
|
||||
results_cache = []
|
||||
|
||||
|
||||
|
||||
|
@ -133,6 +136,7 @@ if __name__ == '__main__':
|
|||
parser.add_argument('-c', '--concurrency', type=int, default=50, help='Control the speed of lookups.')
|
||||
parser.add_argument('-t', '--timeout', type=int, default=5, help='Timeout for DNS lookups.')
|
||||
parser.add_argument('-r', '--resolvers', type=str, help='File containing DNS servers to use for lookups.')
|
||||
parser.add_argument('-rt', '--retries', type=int, default=3, help='Number of times to retry a DNS lookup.')
|
||||
args = parser.parse_args()
|
||||
|
||||
asyncio.run(main())
|
||||
asyncio.run(main(args))
|
||||
|
|
Loading…
Reference in New Issue