# 
# TAOF, the general purpose TCP fuzzer.
# Copyright (C) 2007 Rodrigo Marcos
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# 

#Python libraries
import time
import socket
import timeoutsocket
import sys
from struct import pack

#Taof libraries
import fuzzutils

#Third party libraries
import gtk

#Fuzzing signatures
overflowstrings = ["A" * 33, "A" * 128, "A" * 240, "A" * 255, "A" * 256, "A" * 257, "A" * 420, "A" * 511, "A" * 512, "A" * 1023, "A" * 1024, "A" * 2047, "A" * 2048, "A" * 4096, "A" * 4097, "A" * 5000, "A" * 10000, "A" * 20000, "A" * 32762, "A" * 32763, "A" * 32764, "A" * 32765, "A" * 32766, "A" * 32767, "A" * 32768, "A" * 65534, "A" * 65535, "A" * 65536]
formatstrings   = ["%s" * 4, "%s" * 8, "%s" * 15, "%s" * 30, "%x" * 1024, "%n" * 1025 , "%s" * 2048, "%s%n%x%d" * 5000, "%s" * 30000, "%s" * 40000, "%.1024d", "%.2048d", "%.4096d", "%.8200d", "%99999999999s", "%99999999999d", "%99999999999x", "%99999999999n", "%99999999999s" * 1000, "%99999999999d" * 1000, "%99999999999x" * 1000, "%99999999999n" * 1000, "%08x" * 100, "%%20s" * 1000,"%%20x" * 1000,"%%20n" * 1000,"%%20d" * 1000, "%#0123456x%08x%x%s%p%n%d%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%#0123456x%%x%%s%%p%%n%%d%%o%%u%%c%%h%%l%%q%%j%%z%%Z%%t%%i%%e%%g%%f%%a%%C%%S%%08x"]
integerstrings  = [-1, 0, 1, 127, 128, 255, 256, 32767, 32768, 65535, 65536, 2097151, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741823, 1073741824, 2147483647, 2147483648, 4294967295]

integerstrings_little_endian = []
for i in integerstrings:
	integerstrings_little_endian.append(pack('<I',i))
integerstrings_big_endian = []
for i in integerstrings:
	integerstrings_big_endian.append(pack('>I',i))




#############################################################################
#
# Fuzzer class 
#
#############################################################################

class fuzzer:
	
	def wait(self, seconds):
		t=time.time()
		
		while 1==1:
			if time.time()>t + seconds:
				break
			else:
				while gtk.events_pending():	# Run pending events
					gtk.main_iteration_do(False)			
				


	def send_as_is(self):
	#This function sends "as_is" requests
		for requestid in range(0, fuzzutils.context["xmllog"].get_number_requests(fuzzutils.context["xmldoc"])):
			myrequest = fuzzutils.context["xmllog"].get_request(fuzzutils.context["xmldoc"],requestid)
			if myrequest.attributes["as_is"].value == "1":
				try:
					current_request = fuzzutils.context["logger"].read_request(requestid+1)
					self.remoteserver.send(current_request)
					self.recv(fuzzutils.context["RECV_BUFFER_SIZE"])
				except:
#					print str(sys.exc_info()[0])
					return "0"
		return "1"					
	


	def fuzz_loop(self, signatures, first_part, last_part, isnumber, checklen, lvalue, middle_part, len_position, lformat):
#TO DO non dictionary attacks:
# Learn the result of a bad request and compare the result to it instead of dumping all the results on the debugging file
# Threading

		for sg in signatures:
			#time.sleep(fuzzutils.context["wait"]) # Wait for each request
			fuzzutils.context["currentsignature"] = fuzzutils.context["currentsignature"] + 1
			if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
				break
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),'*')#Adds a '*' on the fuzzing dialog

			while gtk.events_pending():	# Run pending events
				gtk.main_iteration_do(False)

			if not isnumber:
				sg=sg.strip()	#Removing the new line if this is a dictionay attack, otherwise... there shouldn't be a newline
			try:
				if checklen=='1' and not isnumber:
					injectedlen = lvalue + len(sg)
					if lformat == '1': # ASCII
						injectedlen = str(injectedlen)
					elif lformat == '2': # Little endian
						injectedlen = pack('<l',injectedlen)
					else: # Big endian
						injectedlen = pack('>l',injectedlen)

					if len_position == 0: # PREPEND value
						if isnumber:
							fuzzed_request = first_part + injectedlen + middle_part + str(sg) + last_part
						else:						
							fuzzed_request = first_part + injectedlen + middle_part + sg + last_part
					else:			# POSTPEND value
						if isnumber:
							fuzzed_request = first_part + str(sg) + middle_part + injectedlen + last_part
						else:						
							fuzzed_request = first_part + sg + middle_part + injectedlen + last_part

				else:
					if isnumber:
						fuzzed_request = first_part + str(sg) + last_part
					else:
						fuzzed_request = first_part + sg + last_part

				if fuzzutils.context["debug"]:
					if isnumber:
						debugstring = "\n*************************<REQUEST>***********************\n" 
					else:
						debugstring = "\n*************************<REQUEST>*********************** - " + str(len(sg)) + " characters\n" 
					self.file_object.write(debugstring)
					self.file_object.write(fuzzed_request)
					self.file_object.write("\n*************************<REQUEST/>***********************\n")
				if fuzzutils.context["proto"]=="tcp": # TCP
					self.remoteserver.send(fuzzed_request) # Send...
					results = self.remoteserver.recv(fuzzutils.context["RECV_BUFFER_SIZE"]) # and receive...
				else:						# UDP

					self.remoteserver.sendto(fuzzed_request, (fuzzutils.context["dest_host"], int(fuzzutils.context["dest_port"]))) # Send...
					results, addr = self.remoteserver.recvfrom(fuzzutils.context["RECV_BUFFER_SIZE"]) # and receive...
				
				if results=="": # This usually means that the remote server closed the connection
				#Although we run the risk of sending things twice, otherwise we might skip some vulnerabilities
					res = self.reconnect() # Taof first try to reconnect and repeat the request again
					#Let's handle the reconnection.
					if res=='1': # Success reconnecting
						if fuzzutils.context["proto"]=="tcp": # TCP
							self.remoteserver.send(fuzzed_request) # Send...
							results = self.remoteserver.recv(fuzzutils.context["RECV_BUFFER_SIZE"]) # and receive...
						else:						# UDP
							self.remoteserver.sendto(fuzzed_request, (fuzzutils.context["dest_host"], int(fuzzutils.context["dest_port"]))) # Send...
							results, addr = self.remoteserver.recvfrom(fuzzutils.context["RECV_BUFFER_SIZE"]) # and receive...
						#self.remoteserver.send(fuzzed_request)
						#results = self.remoteserver.recv(fuzzutils.context["RECV_BUFFER_SIZE"])
						if fuzzutils.context["debug"]:
							self.file_object.write("\n+++++++++++++++++++++++++<RESPONSE>+++++++++++++++++++++++++\n")
							self.file_object.write(results)															
							self.file_object.write("\n+++++++++++++++++++++++++<RESPONSE/>+++++++++++++++++++++++++\n")
					elif res=='-1': # This might be good (connection issue, the service might have died)
						if fuzzutils.context["debug"]:
							if isnumber:
								debugstring = "\n*** Check this out carefully. This request killed the service.\n" 
							else:
								debugstring = "\n*** Check this out carefully. This request killed the service. Request - " + str(len(sg)) + " characters\n" 
							self.file_object.write(debugstring)
							self.file_object.write("\n*************************<BUFFER>***********************\n")
							self.file_object.write(fuzzed_request)
							self.file_object.write("\n*************************<BUFFER/>***********************\n")
							self.file_object.write("\n[*] I have to give up. I can not connect to the server" )
						fuzzutils.context["stopfuzzing"]=2
				else: # everything went ok
					if fuzzutils.context["debug"]:
						self.file_object.write("\n+++++++++++++++++++++++++<RESPONSE>+++++++++++++++++++++++++\n")
						self.file_object.write(results)															
						self.file_object.write("\n+++++++++++++++++++++++++<RESPONSE/>+++++++++++++++++++++++++\n")								
			except socket.error, msg:
				if fuzzutils.context["debug"]:
					self.file_object.write("\n----------------------------<SOCKET EXCEPTION>-------------------------\n")
					logtemp = "Socket.error: " + str(msg)
					self.file_object.write(logtemp)		
					self.file_object.write("\n----------------------------<SOCKET EXCEPTION/>-------------------------\n")
				res = self.reconnect()
				if res=='-1': # This might be good (connection issue, the service might have died)
					if fuzzutils.context["debug"]:
						if isnumber:
							debugstring = "\n*** Check this out carefully. This request killed the service.\n" 
						else:
							debugstring = "\n*** Check this out carefully. This request killed the service. Request - " + str(len(sg)) + " characters\n" 
						self.file_object.write(debugstring)
						self.file_object.write("\n*************************<BUFFER>***********************\n")
						self.file_object.write(fuzzed_request)
						self.file_object.write("\n*************************<BUFFER/>***********************\n")
						self.file_object.write("\n[*] I have to give up. I can not connect to the server" )
					fuzzutils.context["stopfuzzing"]=2
				
			except:
				if fuzzutils.context["debug"]:
					self.file_object.write("\n----------------------------<EXCEPTION>-------------------------\n")
					self.file_object.write(str(sys.exc_info()[1]) )#+ " - " + sys.exc_info()[1])
					self.file_object.write("\n----------------------------<EXCEPTION/>-------------------------\n")
				res = self.reconnect()
				if res=='-1': # This might be good (connection issue, the service might have died)
					if fuzzutils.context["debug"]:
						if isnumber:
							debugstring = "\n*** Check this out carefully. This request killed the service.\n" 
						else:
							debugstring = "\n*** Check this out carefully. This request killed the service. Request - " + str(len(sg)) + " characters\n" 
						self.file_object.write(debugstring)
						self.file_object.write("\n*************************<BUFFER>***********************\n")
						self.file_object.write(fuzzed_request)
						self.file_object.write("\n*************************<BUFFER/>***********************\n")
						self.file_object.write("\n[*] I have to give up. I can not connect to the server" )
					fuzzutils.context["stopfuzzing"]=2


	def reconnect(self):
#This function reconnects to the remote server. If not possible the first time it tries again.
#Returns '1' on success and '-1' if reconnection was not possible
		try:
			if fuzzutils.context["proto"]=="tcp": # TCP
				self.remoteserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			else:				#UDP
				self.remoteserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
				result = self.send_as_is()
				return '1'
		except:
			pass
		
		connected = 0
		for i in range(0,2):
			try:	
				self.remoteserver.connect((fuzzutils.context["dest_host"], int(fuzzutils.context["dest_port"])))
				connected=1

			except socket.error, desc:
				#print desc
				if desc[0]==106:
					connected=1	# Already connected
				else:
					logtemp = "\n[*] It was not possible to connect to " + fuzzutils.context["dest_host"] + ":" + fuzzutils.context["dest_port"] + ". It might be down. Retrying now..."
					fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
					#time.sleep(fuzzutils.context["wait_reconnection"]) # Let's wait a little bit
			except:
				#print str(sys.exc_info()[1])
				logtemp = "\n[*] It was not possible to connect to " + fuzzutils.context["dest_host"] + ":" + fuzzutils.context["dest_port"] + ". It might be down. Retrying now..."
				fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
				self.wait(fuzzutils.context["wait_reconnection"]) # Let's wait a little bit
			if connected==1:
				break

		if connected==0: # Ooops, we could not connect to the server. The service might have died
			logtemp = "\n[*] I could not connect to the server. I might have killed the service (which is good!)."
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
			return '-1'

		result = self.send_as_is()
	#	if fuzzutils.context["debug"]:
	#		logtemp = "Authentication result:" + result + "\n"
	#		self.file_object.write(logtemp)
		return '1'
				
	def run(self):
	# This is the main loop for fuzzing
			
		if fuzzutils.context["debug"]:
			self.file_object = open("debugging" , "w")			
			logtemp = "Starting fuzzing session against " + fuzzutils.context["dest_host"] + ":" + fuzzutils.context["dest_port"]  + " - " + time.strftime("%H:%M:%S")##########################
			self.file_object.write(logtemp)

		fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
		fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),"Check 'debugging' file for further information" + '\n\n')
		#initialization

		res = self.reconnect() # it establishes the connection to the server

		if res!='1':
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),"\nCould not connect to the server.\n")			
			if fuzzutils.context["debug"]:
				self.file_object.write("Could not connect to the server.\n")
			return "1"
		#loops though all the requests					
		for requestid in range(0, fuzzutils.context["xmllog"].get_number_requests(fuzzutils.context["xmldoc"])):

			if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
				break
			current_request = fuzzutils.context["logger"].read_request(requestid+1) # we read the original request
			logtemp = "Fuzzing request: " + str(requestid)
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
			logtemp = "	Number of fuzzing points: " + str(fuzzutils.context["xmllog"].get_number_fuzz(fuzzutils.context["xmldoc"], requestid))
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')

			if fuzzutils.context["debug"]:
				logtemp = "Fuzzing request: " + str(requestid) + "\n"
				self.file_object.write(logtemp)
				logtemp = "	Number of fuzzing points: " + str(fuzzutils.context["xmllog"].get_number_fuzz(fuzzutils.context["xmldoc"],requestid)) + "\n"
				self.file_object.write(logtemp)
			
			#It goes through each of the fuzzing points on the current request
			for fuzzid in range (0, fuzzutils.context["xmllog"].get_number_fuzz(fuzzutils.context["xmldoc"], requestid)):
				if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
					break
				fuzzutils.context["currentsignature"] = 0
				ffrom, fto = fuzzutils.context["xmllog"].get_from_to(fuzzutils.context["xmldoc"], requestid, fuzzid)
				#it gest the boundaries of the fuzzing point

				checklen, lfrom, lto, lvalue, lformat = fuzzutils.context["xmllog"].get_len(fuzzutils.context["xmldoc"], requestid, fuzzid)
				#It gets all the necessary parameters for variable lenght calculations

				print "Testing fuzzing point ", fuzzid, ": (", ffrom, " - ", fto, ")"
				if fuzzutils.context["debug"]:
					logtemp = "Testing fuzzing point " + str(fuzzid) + ": (" + ffrom + " - " + fto + ")"
					self.file_object.write(logtemp)

				ffrom = int(ffrom)
				fto = int(fto)
				

				if checklen=='1': # This means that we need to include a variable lenght with the request
					lfrom = int(lfrom)
					lto = int(lto)
					lvalue = int(lvalue)
					
					#in order to inject the length in the correct place, we calculate the location:
					if lto<=ffrom:
						# the lenght value is located BEFORE the injection point
						first_part = current_request[:lfrom]
						middle_part = current_request[lto:ffrom]
						last_part = current_request[fto:]
						len_position = 0

					else: # We trust the value entry (we shouldn't!). To do, check that "lfrom>=to"
						# the lenght value is located AFTER the injection point
						first_part = current_request[:ffrom]
						middle_part = current_request[fto:lfrom]
						last_part = current_request[lto:]
						len_position = 1

				else: # No lenght has to be included
					first_part = current_request[:ffrom]
					last_part = current_request[fto:]
					middle_part = ''
					len_position = -1				
	
				#fuzzing buffer overflows
				if fuzzutils.context["xmllog"].fuzz_bo(fuzzutils.context["xmldoc"], requestid, fuzzid)=='1':
					logtemp = "\n + Buffer overflows"
					fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')

					self.fuzz_loop(overflowstrings, first_part, last_part, 0, checklen, lvalue, middle_part, len_position, lformat)
				if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
					break
			
				#fuzzing format strings								 
				if fuzzutils.context["xmllog"].fuzz_fs(fuzzutils.context["xmldoc"], requestid, fuzzid)=='1':
					logtemp = "\n + Format Strings"
					fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
					
					self.fuzz_loop(formatstrings, first_part, last_part, 0, checklen, lvalue, middle_part, len_position, lformat)
				
				if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
					break
	
				#fuzzing integer strings
				if fuzzutils.context["xmllog"].fuzz_int(fuzzutils.context["xmldoc"], requestid, fuzzid)=='1':
					logtemp = "\n + Integer Overflows"
					fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
					 
					#Numbers in Ascii
					self.fuzz_loop(integerstrings, first_part, last_part, 1, checklen, lvalue, middle_part, len_position, lformat)
					if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
						break
					#Numbers in little Endian
					self.fuzz_loop(integerstrings_little_endian, first_part, last_part, 0, checklen, lvalue, middle_part, len_position, lformat)
					if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
						break
					#Numbers in big Endian
					self.fuzz_loop(integerstrings_big_endian, first_part, last_part, 0, checklen, lvalue, middle_part, len_position, lformat)

				if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
					break

				if fuzzutils.context["xmllog"].fuzz_dict(fuzzutils.context["xmldoc"], requestid, fuzzid)=='1' and fuzzutils.context["dictionary"]!='':
					logtemp = "\n + Dictionary Attack"
					dictionary_file = open(fuzzutils.context["dictionary"])
					fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),logtemp + '\n')
					self.fuzz_loop(dictionary_file, first_part, last_part, 0, checklen, lvalue, middle_part, len_position, lformat)
					dictionary_file.close()
				
				if fuzzutils.context["stopfuzzing"]!=0: # The user decided to stop fuzzing
					break
	

		if fuzzutils.context["debug"]:
			self.file_object.close()
		if fuzzutils.context["stopfuzzing"]==1: # The user decided to stop fuzzing
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),"\n[*] Fuzzing session finished by user.\n")
			fuzzutils.context["stopfuzzing"]=0
		elif fuzzutils.context["stopfuzzing"]==2:
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),"\n[*] Fuzzing session because remote server is not responding.\n")
			fuzzutils.context["stopfuzzing"]=0
		else:
			fuzzutils.context["fuzzinglog"].insert(fuzzutils.context["fuzzinglog"].get_end_iter(),"\n[*] Fuzzing session finished.\n")

