#!/usr/bin/env python3
"""
DNS Fuzzer using JRSFuzz
by Javantea
Oct 11, 2015
Based on SMTP Fuzzer using JRSFuzz
by Javantea
Oct 11, 2015
Based on SMTP Grammar Fuzzer
by Javantea
Sept 14, 2015

A very reasonable dumb fuzzer for DNS. It's a mutation fuzzer.
Provide the following values to fuzz a binary file you have captured:
python3 dns_jrsfuzz1.py host port filename

If you get connection refused, try forcing the family using -4 or -6 for IPv4 
or IPv6.

TODO:
Random transaction id
Handle errors returned by the server.
Monitors with GDB and ASAN integration.
"""
import jrsfuzz
import sys
import socket
import select

def main():
	# This works on any DNS server but it won't work on certain types
	data = b'\xabL\x01 \x00\x01\x00\x00\x00\x00\x00\x01\x03www\x05Peach\x03edu\x00\x00\x01\x00\x01\x00\x00)\x10\x00\x00\x00\x00\x00\x00\x00'
	host, port = 'localhost', 53
	family = socket.AF_UNSPEC
	socktype = socket.SOCK_DGRAM
	# TCP support
	if '-t' in sys.argv:
		socktype = socket.SOCK_STREAM
		sys.argv.remove('-t')
	#end if
	if '-4' in sys.argv:
		family = socket.AF_INET
		sys.argv.remove('-4')
	#end if
	if '-6' in sys.argv:
		family = socket.AF_INET6
		sys.argv.remove('-6')
	#end if
	if len(sys.argv) > 1:
			host = sys.argv[1]
	#end if
	if len(sys.argv) > 2:
			port = int(sys.argv[2])
	#end if
	dest = (host, port)
	gai = socket.getaddrinfo(host, port, family, socktype)
	
	if len(sys.argv) > 3:
		filename = sys.argv[3]
		data = open(filename, 'rb').read()
	#end if
	lines_output = len(data) * 256
	print("%i outputs, %3.3f MB" % (lines_output, lines_output*len(data)/(1<<20)), file=sys.stderr)

	our_data = None
	for family, socktype, proto, canonname, addr in gai:
		try:
			s = socket.socket(family, socktype, proto)
			s.connect(addr)
		except Exception as e:
			print(e,)
			continue
		#end try
		our_data = (family, socktype, proto, canonname, addr)
		break
	#next family, socktype, proto, canonname, addr
	
	if our_data == None:
		print("Can't connect to dest", dest)
	#end if
	family, socktype, proto, canonname, addr = our_data
	
	prevResp = None
	for i in range(lines_output):
		x = jrsfuzz.JRSFuzz(data, i)
		# We might be sending 2 commands, but let it be.
		s = socket.socket(family, socktype, proto)
		s.connect(addr)
		try:
			s.send(x)
		except BrokenPipeError:
			print("Broken Pipe.", i, x)
		except ConnectionResetError:
			print("Conn Reset.", i, x)
		#end try
		f = select.select([s], [], [], 0.01)
		resp = b''
		if len(f) > 0 and len(f[0]) > 0:
			try:
				resp = s.recv(1024)
			except ConnectionResetError:
				print("Conn Reset on recv.", i, x)
				resp = b''
			#end try
		#end try
		print(resp)
		if resp != prevResp: print(i, x)
		prevResp = resp
		s.close()
	#next i
#end def main()

if __name__ == '__main__':
	main()
#end if