Tags: crypto 

Rating:

The server checks the password attaches the flag and several word to it, gzip it, encrypts it with aes in cbc mode and sends it us back. The encryption seems to be implemented well, and full decryption of the CT seems to be impossible. But since the answer with our password and the flag is compressed before encryption, we have a little information leakage channel though the size of CT.
If there's same substring in our password and in the flag, then the answer would be compressed better; the longer the substring is the better the compression would be. This way we can recover the flag char by char.
We try all printable charaters by sending
'hxp{' + c + n_random_characters, where
c is the character we try
n_random_characters - string of random characters of length n.
If we choose n properly, then we'll get shorter answer for the right character c, then for any other. (lenght of CT of aes_cbc is multiple of 16 bytes, we need to choose n such that the length of compressed answer would be at the edge between blocks)
So, the listing:

import socket
import os
import string

flag = b'hxp{'
def rand_chars(n):
    s = ''
    for i in range(n):
        c = chr(os.urandom(1)[0])
        while c == ' ' or c == '\n' or c not in string.printable:
            c = chr(os.urandom(1)[0])
        s += c
    return s.encode()

def get_ans_len(pwd):
    sock = socket.socket()
    sock.connect(('131.159.74.81', 1033))
    sock.send(pwd + b' a\n')
    ans = sock.recv(4096)
    return len(ans)

len_to_send = 28

def check_flag(flag_beg, chars_to_verify):  # checks which letter from chars_to_verify is the next letter of flag_beg
    global len_to_send
    while len(chars_to_verify) > 1:
        print("Verifying charset: %s" % chars_to_verify)
        lens = {}
        suf = rand_chars(len_to_send - len(flag_beg) - 1)
        for c in chars_to_verify:
            lens[c] = get_ans_len(flag_beg + c.encode() + suf)
        print(lens)
        if min(lens.values()) == max(lens.values()):  # we need to tune len_to_send to get back to the edge between cipher blocks
            if min(lens.values()) <= 289:
                len_to_send += 1
                print("increased len: %u" % len_to_send)
            else:
                len_to_send -= 1
                print("decreased len: %u" % len_to_send)
        for c in lens.keys():
            if lens[c] > min(lens.values()):
                chars_to_verify = chars_to_verify.replace(c, '')
    print(flag_beg + chars_to_verify[0].encode())
    return chars_to_verify[0].encode()

while flag[-1] != b'}':
    c = check_flag(flag, string.ascii_letters + string.digits + '_}')  # .replace('\n', '').replace(' ', ''))
    flag += c


And the flag:
hxp{1_r34LLy_L1k3_0r4cL3s__n0T_7h3_c0mp4nY}