Tags: crypto 

Rating: 5.0

At first I found that there's an error in password check logic: after each check the value of the right hash (which is read from file before the first check) is overwritten by it's xor with hash of the entered password. So:
1) we can enter the same password two times in a row and get the value of the right hash: 0x785e64baad24
2) we can learn hash of any password we enter (by xor'ing the hashes returned before and after we send the password). I've made a little base of pairs hash: password this way.
3) we need to find such set of passwords, that sum modulo 2 of their hashes would be equivalent to the right hash. We can write a system of linear equations modulo 2 to get the set. The i-th equation would get us the i-th bit of the sum:
xor(aij and xj) = bi, where
aij - value of bit in i-th position of hash of j-th password, which we'd got on step 2
xj - shows us if j-th hash is in the set (1 - it's in the set, 0 - it isn't)
bi - value of bit in i-th position of right hash

There would be 48 equations (since there are 48 bit of the hash), so we need to know hashes of only 48 different passwords. If the system is inconsistent we should drop one of the hashes and add some new one (but I've got the solution from the first try)

import socket

def get_bit(n, i):
    return ((n >> i) & 1) == 1

def add_lines(l1, l2):
    if len(l1) != len(l2):
        raise Exception
 for i in range(len(l1)):
        l1[i] ^= l2[i]

def solve(A):  # solves the system of linear equations Z/2Z
    for i in range(48):
        for k in range(i, 48):
            if A[k][i]:
                T = A.pop(k)
                A.insert(i, T)
                break
        if not A[i][i]:
            return None
        for j in range(48):
            if i == j:
                continue
            if A[j][i]:
                add_lines(A[j], A[i])

hash_base = {}
right_hash = int('785e64baad24', 16)

base_name = "hashbase" # there are pairs of hash(password):password, got from the server on previous step
for l in open(base_name, 'rt'):
    if len(l) < 14: continue
    known_hash, pwd = l.split('\t')
    hash_base[int(known_hash.strip(), 16)] = pwd.strip()

using_hashes = list(hash_base.keys())[1:49]  # since there are 48 equations we need only 48 vectors

A = []
for i in range(48):
    A.append([])
    for j in range(len(using_hashes)):
        A[-1].append(get_bit(using_hashes[j], i))
    A[-1].append(get_bit(right_hash, i))

print(len(A))

solve(A)
for i in range(48):
    print(bool(A[i][-1]))

# now we send the solution to the server and get the flag
hh = 0
sock = socket.socket()
sock.connect(("school.fluxfingers.net", 1513))
for i in range(48):
    if A[i][-1]:
        pwd = hash_base[using_hashes[i]].encode()
        sock.send(b'login ' + pwd + b'\n')
        ans = sock.recv(4096)
        if ans[-1] != '\n':
            ans += sock.recv(4096)
        print(ans)
sock.send(b'getflag\n')
print(sock.recv(4096))
print(sock.recv(4096))
print(sock.recv(4096))


The answer from the server:
    b'Login successful\n'
    b'Okay, sure! Let me grab that flag for you.'
    b'\nflag{more_MATH_than_crypto_but_thats_n0t_a_CATegory}\n'

P.S. It's interesting, that the complexity of the solution is O(n^2) local CPU operations and O(n) requests to the server, where n - is the length of hash in bits. So even if the hash were 256 or 512 bits long it still would be easy to solve it.