affine.group home writeups about

Codegate CTF Preliminaries 2024 - Cogechan_Dating_Game (crypto)

Published on 06 Jun 2024
Writeups

Here we have a remote tamagotchi-like game where we need to do actions to increase characteristics of the player and then win the flag. However, we need an exponential number of actions (more than $2^{33}$) to reach the goal, which is not feasible over the internet. We have to exploit the save/load mechanism in the game. First, we can always save our game by sending a packed description of the state (incl. our characteristics), encrypted and signed with AES-GCM. The characteristics have to mach the actual state, so we have not much choice in this option, other than controlling the nickname.

Second, we can load a game simply by logging in with a matching ID. The trick is that we can use the same ID but different password, which will change the encryption key but not the file location. Since we will know both passwords and corresponding keys, we will use this to craft a nickname to be used in a savegame, that can later be converted into a shorter nickname and part of the old nickname used to control characteristics of the player.

First, we want to find ID/PW1/PW2 values such that the padding is correct in both decryptions, and the nickname lengths match our expectations. This can be done by bruteforce essentially (less than 3 bytes): - fix full ciphertext length to 255 + 1-byte padding \x01 - for a random triple ID/PW1/PW2 we check that the AES-GCM keystream ends with the same byte, so that both decryptions (under PW1 and PW2) will end with \x01 - then we also check that the difference in the first two bytes of keystreams is equal to the xor-difference of nickname lengths (e.g. 239 for PW1 and 53 for PW2 - we can have some range to reduce bruteforce).

Second, we will change the nickname in game 1 (ID/PW1) to force the GCM tags to match in both encryptions. This is done by observing that GCM's tag is GF(2)-linear. So we can flip some 128 bits in the nickname by solving a linear system to enforce the difference between the two tags to be zero. Here, we start with valid state encoding (nick-len || nickname || character || \x01), encrypt with PW1, save tag as tag1, decrypt with PW2 without checking tag, encrypt with PW2 to get the tag (tag2). Now (tag1 xor tag2) is an affine function of the bits of the nickname. Thus, by simple linear algebra we can find a nickname (e.g. 128-130 first bits) that makes tag difference zero.

Now, we save our starting game with ID/PW1 and the crafted nickname. When we login as ID/PW2, the server will load our saved game and interpret it differently. This is possible because we forced the tag to match second decryption under PW2. Furthermore, the server does not check the full length of the save data. So, it will interpret the second part of the nickname as characteristics and ignore the rest. We can also flip them arbitrarily to match the goal.

The game's goal requires friendship=34 which can be achieved by setting friendship to 33 and intelligence to 2^32-1, making one PWN to up intelligence to 33 bits, and then DATE to up friendship to 34 and get the flag.

Code

In [29]:
import random
import string

import hashlib

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad

import Character
from load_and_save import decrypt_and_parse_save_data

def get_random_str():
    return ''.join(random.sample(string.ascii_lowercase + string.ascii_uppercase + string.digits, k = 20)) + os.urandom(10).hex()

# from load_and_save import *

In [23]:
# 2 + nick + 4 + 4 + 4 + 2
# + 1 padding
full_len = 1024
nick_len = full_len - 1 - (2 + 4 + 4 + 4 + 2)
nick_len
Out [23]:
1007

` id: nonce | fname pw1: key1 pw2: key2

key1: [nick len long] [nick + character2] [character1] [tag] key2: [nick len short] [nick] [character2] [character1] [tag] `

Bruteforce random different keys to match padding + fitting nick len.

In [38]:
for _ in tqdm(range(2**24)):
    ID = get_random_str()
    PW1 = get_random_str()
    PW2 = get_random_str()

    nickname = "abcd"

    id_hash = hashlib.sha256(ID.encode()).digest()
    nonce = id_hash[:12]
    file_name = id_hash[16:24].hex()

    pw_hash1 = hashlib.sha256(PW1.encode()).digest()
    key1 = pw_hash1[:16]

    pw_hash2 = hashlib.sha256(PW2.encode()).digest()
    key2 = pw_hash2[:16]

    file_data = nick_len.to_bytes(2, 'little').ljust(256, b"\x00")

    c1 = AES.new(key1, AES.MODE_GCM, nonce)
    c2 = AES.new(key2, AES.MODE_GCM, nonce)
    ct1 = c1.encrypt(b"\x00" * 1024)
    ct2 = c2.encrypt(b"\x00" * 1024)
    if ct1[-1] != ct2[-1]:
        continue

    n1 = int.from_bytes(ct1[:2], 'little')
    n2 = int.from_bytes(ct2[:2], 'little')
    nick_len2 = nick_len ^ n1 ^ n2

    # first 20 for tag manipulation
    # last 20 for character2 manipulation
    if 20 < nick_len2 < nick_len - 20:
        break

ID, PW1, PW2
  0%|▎                                                                                                                                                                                             | 27205/16777216 [00:05<57:53, 4822.68it/s]
Out [38]:
('4beumTNM7QtzjYadS8GJf463ac06c8332c88a569',
 'yQrlh0DcHfmL2aTXuwnKc3707aa764b0ab75b0ba',
 'wtCX1KqHYhenvZoPiEAF8abcc033b0f4d8f15322')

In [39]:
nick_len, nick_len2
Out [39]:
(1007, 899)

In [40]:
def encode(nickname):
    file_data = b""
    file_data += len(nickname).to_bytes(2, 'little')
    assert len(nickname) == nick_len

    # little hacky
    # xor global difference to allow plaintext manipulation later
    file_data += (Bin(nickname) ^ DIFF).bytes

    # initial character
    day = intelligence = friendship = 0
    stamina = 100

    file_data += day.to_bytes(4, 'little')
    file_data += stamina.to_bytes(4, 'little')
    file_data += intelligence.to_bytes(4, 'little')
    file_data += friendship.to_bytes(2, 'little')
    file_data = pad(file_data, 16)
    return file_data


def oracle_diff(nickname, ret_pt=False):
    global LAST_PT
    s = nickname.ljust(nick_len, b"\x00")
    assert len(s) == nick_len
    LAST_PT = pt1 = encode(s)
    assert len(pt1) == full_len, len(pt1)

    c1 = AES.new(key1, AES.MODE_GCM, nonce)
    ct1, tag1 = c1.encrypt_and_digest(pt1)

    c2 = AES.new(key2, AES.MODE_GCM, nonce)
    pt2 = c2.decrypt(ct1)
    if ret_pt:
        return pt2

    c2 = AES.new(key2, AES.MODE_GCM, nonce)
    ct2, tag2 = c2.encrypt_and_digest(pt2)

    return Bin(tag1) ^ Bin(tag2)

Patch the difference to force needed character values.

In [41]:
DIFF = Bin(0)

pt2 = oracle_diff(b"", True)

int_fri = pt2[2+nick_len2+4+4:][:6]
intel = int.from_bytes(int_fri[:4], "little")
frie = int.from_bytes(int_fri[4:], "little")
print(intel, frie)

diff = [0] * nick_len
diff[nick_len2+4+4:nick_len2+4+4+4] = list((intel ^ (2**32-1)).to_bytes(4, "little"))
diff[nick_len2+4+4+4:nick_len2+4+4+4+2] = list((frie ^ 33).to_bytes(2, "little"))

DIFF = Bin(bytes(diff))

pt2 = oracle_diff(b"", True)
int_fri = pt2[2+nick_len2+4+4:][:6]
intel = int.from_bytes(int_fri[:4], "little")
frie = int.from_bytes(int_fri[4:], "little")
intel, frie
3265193657 59994
Out [41]:
(4294967295, 33)

Get matrix corresponding to the tag difference.

In [42]:
L = 140  # num free bits
mat = []
zero = oracle_diff(Bin.zero(L).bytes).vector
for i in range(L):
    nickname = Bin.unit(i, L)
    vec = oracle_diff(nickname.bytes).vector - zero
    mat.append(vec)

mat = matrix(GF(2), mat).transpose()

Find solution to make it zero.

In [43]:
sol = mat.solve_right(zero)
assert oracle_diff(Bin(sol).bytes) == 0
len(sol), sol
Out [43]:
(140,
 (1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))

Test it:

In [44]:
nicksol = Bin(sol).bytes.ljust(nick_len, b"\x00")
print(len(nicksol), nicksol)
print()
dat = encode(nicksol)
print(dat)

c1 = AES.new(key1, AES.MODE_GCM, nonce)
ct1, tag1 = c1.encrypt_and_digest(dat)

c2 = AES.new(key2, AES.MODE_GCM, nonce)
pt2 = c2.decrypt(ct1)

c2 = AES.new(key2, AES.MODE_GCM, nonce)
ct2, tag2 = c2.encrypt_and_digest(pt2)

# same ciphertext
assert ct1 == ct2

# same tag
assert tag1 == tag2

c2 = AES.new(key2, AES.MODE_GCM, nonce)
c2.decrypt_and_verify(ct2, tag1)
1007 b'\x08j\xe85\x08\xd7KN\x94k:}\x93\t=S\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

b'\xef\x03\x08j\xe85\x08\xd7KN\x94k:}\x93\t=S\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\x19a={\xea\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
Out [44]:
b'\x83\x03\xe1\x07\xf0\x18l\xf3\xa0\xe2\xf5\xee\xd6\xa9$\\xd2\\xd4u\\x14\\xcd\\x84\\x95L\\xae\\xbf5\\xde\\x05\\x85\\x8f\\xa7\\x0e\\xbdb\\x89\\xc0h\\xf9\\xe2c\\xac\\x19\(\\xa3\\xc6R;\\x93\\xd3^\-\\xc14\\x06\\xf6\\xb8\\xec\\xa5\\x94\\xbbC\\r\\x12\\x9c\\xdb4U\\x8c\\xa7\\xc2q\!\\xa8\\xd5D\\xb6=\\x88\\x12\\x11KR\\xd3vqG8u\\x05y\\xf6\\x12>\\x14\\xfcOwp0\\xdc\\xd7\\xe7\\xc7g\\x15\\x05\\xa3\\x0eT\_\\xcb\\xa8S0\\xe4\\xab:4\\x04i\\x95\\x18<5\\x84\\xeb\[\\xa7\\x13D\\x86FEeqV\\x1f\\x80{\\x9a\\xf4s\\xe9\\xd5\\xe9I\\xab\\xac\\xe3\\xb1<\#/>\\x0e\\xea\\xc9v9/\\xd1\\xeat\\xb8d\\xc9\\x10\\x87?\\x17n9S\\x90HJ\\x90\\xef\\x13\\xe9\\xfb\\xcf\\xfa\\xb1\\xf1HG\\x01\\xf9\\x1b\\xc7\\xe0\\xa0\\x9e,\\xe0r\\x97R\\x9en\\xf9\\xb32~\\xc8\\xa7\\xb8MY\\xb8\\xd8j\\xc0\\xd69 77\#Y\\x8c\\x898Dc\\x1dR\\x92\!\\xa9\\xe8L>41\\x03\\xb2\!\\xfcp\\xd9\\xad\\xa5\\'\\t\\xa7\\x95K\\x00kN6\\xd8\\xf6\\x14bL\\x96a\\x121\\xf5\\x91\\x04\\xa2\\xe75j\\xfd\\xb6\\xb6\\x7f\\xcd\[\\x92\\xb8\\xad\\x16\\xa5Nv$0w\xf4k\xe5T\x9a\xed\xe6\x8e)\xc6\x82:i\x91\x94\xad\xe34[ZW9\xfe\xdb1G\x1a\xc5E>\xd1a8\xff\xbc#/]\x81m\xa3\xfd6\xad\xbf9\x81\xc7v:\xea\x12\x07\xcdl3\xd4I\xf7\x81%\xbbM\xa2\x02\xd0d\xe2\xbf\xdfh\x9aA\xd8N9\xe6\xeb\x93\xd4+Nf]Y\x83\xaf\xad\x0c7-\x10</0\xfe \xb8/\t\x82\x98\x926\x8d\x83\xd2Pz\xdd\xc3G\xff\x0c|]\xb9\x16[P\x7f+\x9222\x0e\x831\x94\x9d6\xf5d\xf1}\x8f\xe2:`\xde:\x93(\x81\xbcj\xe6B\x158\xe0#\x15-n\x08\xa5\xffC5\xac\xbc\x14#\xdc\x8f\xea~|\x8fP\xe5\xbf\x8ck\xfa\x18\xc4i\x15!\xb5*\xae\xc8:\xd2\x96\xd8\x10\x19\x18\x15\x7f^&O\xac\xc9\x0e\x9c:\xf1\xd7t\xc0\xeaQ;\x05N\x17sb\x8b\xd7\xb6\x97\x88\xf5\t\x04\\\xf0\x04\r\xa08\xc9\xa1\xead$=\\xbf@\\xc3\\xfa\\xd1\\x87\\x91\\xdb\\xf9\\x90\\xde\\xcf\\x91\\x87\\xfc\\xc1\\xa4\\xc8\\xf3\\x97\\xb00U\\xbdH\+\\x01/\\xec\\xda\\xae\\x11W8\\xcb\\xd7\\x1f\\xe9\\xde\\xd9\\xe6\\x05\\xcaKfk\\xca\\xa0\\x9aw7O\\x04&\\x8e\\xa5\\x8d8\\x01\\xca\\xb5\\xf0/\\xb2\\xadCqL\\x7f\\xf8\\xd6?\\xf5\\xf7j\\x08\\xb0KI\\xffS\\xfa\\x1d\\xbc\\x9c2\\x92\\x96\!KA\\xbd\\x84\\xabK;\\x9a\\xe7\\x92\\x16\\xa1\\x8d\\x81\*3\\x90\\x13\\x8e\\x05\\xc7i\\\\\\xef\_\\xe99\\xd4X\\xf8P\\xeb&\\\\\\x0b\\x9a\\xe0\\x0cD\\xe8\\xa7\\xe4\\x84w\\xd3\\x12D.\\xa0\\xf1X\\x0fo\\x0b\\xb8\\x92\\xe4\\x00\\xaf;\\xd6\\x02\-u\\x97?\\x08|\\xe0UI\\xeb \\xab\\xa1\\x8e\\x06\\xb1a\\xef\\x93H$\xeb\x93\x0f\xb2T@\xd0\xf3\xd4\xc0\xc59<D\xb4,o\xfa \xf7$K\\x7f\\x10\\x9a\\r\\xdd\\x13\\xc5$3\xaa\xd9\xac8\xc1Ja\x96\xd7\x0bq\xda\x86R\xbf\x13I\x11\xeb\x96\xa76%\x1d\xe88G\xc0$a\xcd\x07}1\xf0T\x1a\xf9\xdb\xb48\xe7Y\x84\t~\xdc\xdf\xc2\xdbv\x7f\xa6\xa2N\x10\xc6Qd\xf9\x9a\xe4=\xd2\xa49\xab\\T\xa47\xec\xd9\xfdD<|\xd4X\xff\xfa\x8b\xd42H.N\x19M\xce\xaf^e\xa4\xa5/\t\xebC\xd4\xf3\xb2NS;\x07\x9a\xa2CO\xc0\xcd\xe2\x92r\x7f\xb2\xe3\xd2\xc9\xef;\x16\xb1Z|\x15\xfe\xde\xf9T\x8c\x95\xee\xde\x1e?\x92Fx\x8e\xc6\xe6a\t\x02#4?LJmC\xeb\xaa\xd5\x8d\x93\xafA\xad\x07\xcet\xef+BPt\x93&`w}\xbd\xdf\x1fd1\x16s<Z\xb9<a\xc1\xff\xff\xff\xff!\x00K\x0f\xdc\xadt\xdc\x1b\xba\x7fYk\xc7j\x92\xc3\xdb)~?\xc8\x81)~\xd9\xd1\xa7\xb4\xc1\xe3\xd7\xbbnAM\xc9\x9b\x14d\rp\x84\xfd\xc2O\x9b\x1c+\xa6V\x00\xf1&\x1f\x81\xc0\x1b\xd1}\xb7\xaa\x81ZB7qU\x1e\xb5aZ\xee\x1e\x1d8\xa3\xdb\'\xc5\x1f\xb0\xf8"\xaf\xb1d\x9e>\x04\xe1\xa8\xc6\x8c\xd6}\xa95M\r\xf5\xa8\x04\x1f\x0e.\xf3q\x9f\xfa\x01'

In [45]:
C = decrypt_and_parse_save_data(key2, nonce, ct2, tag1)
C.__dict__
Out [45]:
{'nickname': "\x07\x18l֩$u\\x14̈́L5\\x05\\x0ebhc\\x19\(R;^\-4\\x06쥔C\\r\\x124Uq\!D=\\x12\\x11KRvqG8u\\x05y\\x12>\\x14Owp0g\\x15\\x05\\x0eT\_˨S0:4\\x04i\\x18<5\[\\x13DFEeqV\\x1f{sI<\#/>\\x0ev9/td\\x10?\\x17n9SHJ\\x13HG\\x01\\x1bࠞ,rRn2~ȧMYj9 77\#Y8Dc\\x1dR\!L>41\\x03\!p٭'\\tK\\x00kN6\\x14bLa\\x121\\x045j\\x7f\[\\x16Nv$0wkT)Ƃ:i4[ZW91G\x1aE>a8#/]m69v:\x12\x07l3I%M\x02dhAN9+Nf]Y\x0c7-\x10</0 /\t6PzG\x0c|]\x16[P\x7f+22\x0e16d}:`:(jB\x158#\x15-n\x08C5\x14#\u070f~|P忌k\x18i\x15!*:Җ\x10\x19\x18\x15\x7f^&O\x0e:tQ;\x05N\x17sb\u05f6\t\x04\\\x04\r8ɡd$=@чϑ0UH\+\\x01/ڮ\\x11W8\\x1f\\x05Kfkʠw7O\\x04&8\\x01ʵ/CqL\\x7f?j\\x08KIS\\x1d2\!KAK;\\x16\*3\\x13\\x05i\\\\\_9XP&\\\\\\x0b\\x0cDw\\x12D.X\\x0fo\\x0b\\x00;\\x02\-u?\\x08|UI \\x06aH$\x0fT@9<D,o $K\\x7f\\x10\\r\\x13$3٬8Ja\x0bqچR\x13I\x11떧6%\x1d8G$a\x07}1T\x1a۴8Y\t~v\x7fN\x10Qd=Ҥ9\\T7D<|X2H.N\x19Mί^e/\tCNS;\x07COr\x7f;\x16Z|\x15T\x1e?Fxa\t\x02#4?LJmCՍA\x07t+BPt&`w}\x1fd1",
 'day': 1513911062,
 'stamina': 3244375225,
 'intelligence': 4294967295,
 'friendship': 33}

Initiate new game with our forged nickname and save the game.

In [46]:
import server

sock = Sock("3.35.166.110 3434")
#sock = Sock("127.0.0.1 3120")

sock.send(len(ID).to_bytes(2, 'little') + ID.encode())
sock.send(len(PW1).to_bytes(2, 'little') + PW1.encode())
status = sock.recv(1)
print("status load", status[0])

if status == b"\x02":  # load failed
    sock.send(len(nicksol).to_bytes(2, 'little') + nicksol)
    sock.send(server.SAVE_COMMAND.to_bytes(1, 'little'))
    sock.send(len(ct1).to_bytes(2, 'little') + ct1 + tag1)
    status = sock.recv(1)
    print("status save", status[0])
status load 2
status save 11

Reconnect and trigger game load.

In [47]:
sock = Sock("3.35.166.110 3434")
#sock = Sock("127.0.0.1 3120")
sock.send(len(ID).to_bytes(2, 'little') + ID.encode())
sock.send(len(PW2).to_bytes(2, 'little') + PW2.encode())
status = sock.recv(1)
print("status load", status[0])
print(sock.recv(4096))
status load 1
b"\xea\x01\x07\x18l\xd6\xa9$u\\x14\\xcd\\x84L5\\x05\\x0ebhc\\x19\(R;^\-4\\x06\\xec\\xa5\\x94C\\r\\x124Uq\!D=\\x12\\x11KRvqG8u\\x05y\\x12>\\x14Owp0g\\x15\\x05\\x0eT\_\\xcb\\xa8S0:4\\x04i\\x18<5\[\\x13DFEeqV\\x1f{sI<\#/>\\x0ev9/td\\x10?\\x17n9SHJ\\x13HG\\x01\\x1b\\xe0\\xa0\\x9e,rRn2~\\xc8\\xa7MYj9 77\#Y8Dc\\x1dR\!L>41\\x03\!p\\xd9\\xad'\\tK\\x00kN6\\x14bLa\\x121\\x045j\\x7f\[\\x16Nv$0wkT)\xc6\x82:i4[ZW91G\x1aE>a8#/]m69v:\x12\x07l3I%M\x02dhAN9+Nf]Y\x0c7-\x10</0 /\t6PzG\x0c|]\x16[P\x7f+22\x0e16d}:`:(jB\x158#\x15-n\x08C5\x14#\xdc\x8f~|P\xe5\xbf\x8ck\x18i\x15!*:\xd2\x96\x10\x19\x18\x15\x7f^&O\x0e:tQ;\x05N\x17sb\xd7\xb6\t\x04\\\x04\r8\xc9\xa1d$=@\\xd1\\x87\\xcf\\x910UH\+\\x01/\\xda\\xae\\x11W8\\x1f\\x05Kfk\\xca\\xa0w7O\\x04&8\\x01\\xca\\xb5/CqL\\x7f?j\\x08KIS\\x1d2\!KAK;\\x16\*3\\x13\\x05i\\\\\_9XP&\\\\\\x0b\\x0cDw\\x12D.X\\x0fo\\x0b\\x00;\\x02\-u?\\x08|UI \\x06aH$\x0fT@9<D,o $K\\x7f\\x10\\r\\x13$3\xd9\xac8Ja\x0bq\xda\x86R\x13I\x11\xeb\x96\xa76%\x1d8G$a\x07}1T\x1a\xdb\xb48Y\t~v\x7fN\x10Qd=\xd2\xa49\\T7D<|X2H.N\x19M\xce\xaf^e/\tCNS;\x07COr\x7f;\x16Z|\x15T\x1e?Fxa\t\x02#4?LJmC\xd5\x8dA\x07t+BPt&`w}\x1fd1\x16s<Z\xb9<a\xc1\xff\xff\xff\xff!\x00\x00\x00"

Do 1 PWN and 1 DATE to trigger flag.

In [48]:
sock.send(server.PWN_COMMAND.to_bytes(1, 'little'))
print("status  pwn", status[0])

sock.send(server.DATE_COMMAND.to_bytes(1, 'little'))
print("status  date", status[0])

print(sock.recv(4096))
print(sock.recv(4096))
print(sock.recv(4096))
status  pwn 1
status  date 1
b'\x04'
b'\x02'
b'}\x00codegate2024{a61ec211ca2c1265b952aa855fc9e1814dde3b3bcc1bde39e2433925e3c728582bdf169785817ee5c980ac54544880f18a60c5d4a6508e}\n'

Site version #151 from 2024-06-06 22:27:41 (CEST)