Home TCP1Pctf writeup
Post
Cancel

TCP1Pctf writeup

TCP1PCTF

Thank you for the interesting CTF.

In the end, I got three first blood and two second blood. So did I get 5/7 bonus points?

p.s. I heard the bonus points have gone. so sad.

talking phase

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import random
import time
import base64
from messages import *
from secret import flag
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

class Entity:
	def __init__(self, name):
		self.name = name
		self.privself = None
		self.pubself = None
		self.pubpeer = None
		self.crypt_init()

	def checkflag(self, message):
		if message == b"giv me the flag you damn donut":
			return True
		else:
			return False

	def crypt_init(self):
		self.privself = rsa.generate_private_key(public_exponent=65537,key_size=2048,)
		self.pubself = self.privself.public_key()

	def send_key(self, receiver):
		pubpem = base64.b64encode(self.pubself.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)).decode()
		receiver.receive_key(self, pubpem)

	def send_key_too(self, receiver):
		pubpem = base64.b64encode(self.pubself.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)).decode()
		receiver.receive_key_too(self, pubpem)

	def receive_key(self, sender, key):
		try:
			self.pubpeer = serialization.load_pem_public_key(base64.b64decode(key))
			self.send_key_too(sender)
		except:
			print("[!] Error, Shutting down...")
			exit()

	def receive_key_too(self, sender, key):
		try:
			self.pubpeer = serialization.load_pem_public_key(base64.b64decode(key))
			return
		except:
			print("[!] Error, Shutting down...")
			exit()

	def encrypt_message(self, plaintext):
		msg = self.pubpeer.encrypt(plaintext.encode(), padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None))
		return base64.b64encode(msg)


	def decrypt_message(self, ciphertext):
		ciphertext = base64.b64decode(ciphertext)
		msg = self.privself.decrypt(ciphertext,padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None))
		return msg

	def send_message(self, receiver, message):
		time.sleep(random.uniform(0.5, 1.5))
		encrypted_message = self.encrypt_message(message).decode()
		receiver.receive_message(self, encrypted_message)

	def receive_message(self, sender, message):
		try:
			decrypted_message = self.decrypt_message(message)
			if self.checkflag(decrypted_message):
				msg_sent = flag
			else:
				msg_sent = random.choice(message_choice)

			self.send_message(sender, msg_sent)
		except:
			print("[!] Error, Shutting down...")
			exit()


class Attacker:
	def __init__(self, entityone, entitytwo):
		self.entityone = entityone
		self.entitytwo = entitytwo

	def relay_message(self, receiver, message):
		receiver.receive_message(self, message)

	def receive_message(self, sender, message):
		print(f"{sender.name}: {message}")
		tamp = input(f"{sender.name} (tamper): ")
		if tamp == "fwd":
			msg_sent = message
		else:
			msg_sent = tamp.encode()

		if sender == self.entityone:
			self.relay_message(self.entitytwo, msg_sent)
		elif sender == self.entitytwo:
			self.relay_message(self.entityone, msg_sent)

	
	def relay_key(self, receiver, key):
		receiver.receive_key(self, key)

	def relay_key_too(self, receiver, key):
		receiver.receive_key_too(self, key)

	def receive_key(self, sender, key):
		print(f"{sender.name}: {key}")
		tamp = input(f"{sender.name} (tamper): ")
		if tamp == "fwd":
			key_sent = key
		else:
			key_sent = tamp.encode()

		if sender == self.entityone:
			self.relay_key(self.entitytwo, key_sent)
		elif sender == self.entitytwo:
			self.relay_key(self.entityone, key_sent)

	def receive_key_too(self, sender, key):
		print(f"{sender.name}: {key}")
		tamp = input(f"{sender.name} (tamper): ")
		if tamp == "fwd":
			key_sent = key
		else:
			key_sent = tamp

		if sender == self.entityone:
			self.relay_key_too(self.entitytwo, key_sent)
		elif sender == self.entitytwo:
			self.relay_key_too(self.entityone, key_sent)


entity_a = Entity("Entity A")
entity_b = Entity("Entity B")
entity_c = Attacker(entity_a, entity_b)


def begin_communication():
	while True:
		entity_a.send_key(entity_c)
		entity_a.send_message(entity_c, random.choice(message_choice))

begin_communication()

solve

This chall was based on RSA-OAEP + MITM.

first connection of A and B, they exchange their pubkeys.

we can control to pubpeer of entry_a and entry_b like under the figure, and we can recover their ciphertexts.

sequenceDiagram
    entry_a->>+eve: A's pubpeer
    eve->>+entry_b: ===eve's pubpeer[A]===
    entry_b->>-eve: B's pubpeer
    eve->>+entry_a: ===eve's pubpeer[B]===

we can rewrite the message as “giv me the flag you damn donut”, and decrypt the flag encryption.

sequenceDiagram
    entry_a->>+eve: A's pubpeer
    eve->>+entry_b: ===eve's pubpeer[A]===
    entry_b->>-eve: B's pubpeer
    eve->>+entry_a: ===eve's pubpeer[B]===
    
    entry_a->>+eve: enc_A(msg0)
    eve->>+entry_b: enc("giv me the flag you damn donut")
    entry_b->>-eve: enc_B(flag_message)

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from ptrlib import *
from base64 import b64decode, b64encode
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from Crypto.Util.number import *
from cryptography.hazmat.primitives.asymmetric import padding

# io = process(["python3","chall.py"])
io = remote("nc ctf.tcp1p.team 1965")
io.debug = True


def send_key(pubself):
    return b64encode(pubself.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)).decode()


def entry_a(m):
    if m != b"fwd":
        io.sendlineafter(b": ", m)
    else:
        io.sendlineafter(b": ", m)
    return io.recvlineafter(b": ").decode()


# Entity A
key = b64decode(io.recvlineafter(b": ").decode())
pubpeer_A = serialization.load_pem_public_key(key)
print(public_numbers := pubpeer_A.public_numbers())

new_priv_A = rsa.generate_private_key(public_exponent=3, key_size=2048,)
new_public_key_A = new_priv_A.public_key()
print("new", new_public_key_A.public_numbers())
io.sendlineafter(b": ", send_key(new_public_key_A))

# Entity B
key = b64decode(io.recvlineafter(b": ").decode())
pubpeer_B = serialization.load_pem_public_key(key)
print(public_numbers := pubpeer_B.public_numbers())

new_priv_B = rsa.generate_private_key(public_exponent=3, key_size=2048,)
new_public_key_B = new_priv_B.public_key()
print("new", new_public_key_B.public_numbers())
ret = entry_a(send_key(new_public_key_B))

# recover flag
msg = pubpeer_B.encrypt("giv me the flag you damn donut".encode(), padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
io.sendlineafter(b": ", b64encode(msg))
x = io.recvlineafter(b": ")
ret = new_priv_A.decrypt(b64decode(x), padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
print(ret)
io.sh()
# TCP1P{smooth_talker_or_talk_smoother}

That one RSA challenge

i got first blood!

image-20241013035800070

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Util.number import *
from secret import flag
z = 567
p = getPrime(1024)
q = getPrime(1024)
n = p*q
c = pow(bytes_to_long(flag), 65537, n)
tot = (p-1) * (q-1)
d = int(pow(65537, -1, tot))
dinv = int(pow(d, -1, n))

h = int(dinv >> z)
hpq = (int((p+q)>> z))

with open('out.txt', 'w+') as f:
    f.write(f'{n=}\n')
    f.write(f'{h=}\n')
    f.write(f'{hpq=}\n')
    f.write(f'{c=}\n')

solve

we can get ordinary RSA and some hints(upper bits of $d^{-1},p+q$)

so lower bites of $d^{-1}$ is $l$ and lower bites of $p+q$ is $lpq$

$hl =(h « z) + l$, $pq =(hpq « z)+ lpq $

\[\begin{align} e*d - k*tot &= 1\\ e*d - k*(n - p -q +1) &= 1\\ e*d &= k*(- p - q +1) +1 \mod n\\ e &= (k*(- p - q +1) +1)*d^{-1} \mod n\\ e &= (k*(-pq +1) +1)*hl \mod n\\ \end{align}\]

and $0<=k<=e$, so we using coppersmith, we can recover $l, lpq$

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
n=---ommit---
h=---ommit---
hpq=---ommit---
c=---ommit---
z = 567

from multiprocessing import Pool, cpu_count
from defund_coppersmith import small_roots
from tqdm import tqdm
from Crypto.Util.number import *

def solver(k):
    Z.<di, lpq> = PolynomialRing(Zmod(n))
    bounds = [2^z, 2^z]
    f = (k*(-((hpq << z)+ lpq) + 1) + 1 )*((h << z) + di) - 65537

    ret = small_roots(f, bounds, 2,3)
    if ret !=[]:
        di = ret[0][0]
        d = (h << z) + di
        d = pow(d,-1,n)
        print(long_to_bytes(pow(c,d,n)))
    
threads = [i for i in range(65537)]
with Pool(cpu_count()) as pool:
    for results in tqdm(pool.imap_unordered(solver, threads), total=len(threads)):
        pass
    
b'TCP1P{AmEeeeeEE33333eeee333_T_T_8883938ef7571cc2}'

Guessing Master

got first blood

image-20241013035833708

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import math
import os
from Crypto.Random import random

def bin2hex(binary):
    return hex(int(binary,2))[2:]

def hex2bin(msg):
    return bin(int(msg, 16))[2:]

def count_bit(data_length):
    i = 0
    while (1 << i) - 1 < i + data_length:
        i += 1
    return i

def flip_bit(binary, index):
    return binary[:index] + ('1' if binary[index] == '0' else '0') + binary[index+1:]

def add_bit(index, data):
    output = 0
    length = len(data) + count_bit(len(data)) + 1
    for i in range(1, length):
        if (i & (i - 1)) != 0 and (i >> (index - 1)) & 1:
            output ^= int(data[i - math.ceil(math.log2(i)) - 1])
    return str(output)

def random_flip(binary):
    binary = binary[::-1]
    length = len(binary) + count_bit(len(binary)) + 1
    encoded = ""
    index = 0
    for i in range(1, length):
        if math.log2(i) % 1 == 0: 
            bit = add_bit(int(math.log2(i)) + 1, binary)
            encoded += bit
        else:
            encoded += binary[index]
            index += 1
    rand = random.randrange(0, len(encoded))
    flipped = flip_bit(encoded, rand)
    return flipped[::-1], rand

def main():
    flag = open("flag.txt", "rb").read()
    binarys = hex2bin(flag.hex())
    output = []
    for binary in binarys:
        temp = hex2bin(os.urandom(16).hex())
        encoded, rand = random_flip(temp)
        if int(binary):
            output.append([bin2hex(encoded), rand])
        else:
            output.append([bin2hex(encoded), random.randrange(0, len(encoded))])
    print(output)

if __name__ == "__main__":
    main()

solve

we need to check relation of random_flip and rand.

so I forcus to random_flip function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def random_flip(binary):
    binary = binary[::-1]
    length = len(binary) + count_bit(len(binary)) + 1
    encoded = ""
    index = 0
    for i in range(1, length):
        if math.log2(i) % 1 == 0: 
            bit = add_bit(int(math.log2(i)) + 1, binary)
            encoded += bit
        else:
            encoded += binary[index]
            index += 1
    rand = random.randrange(0, len(encoded))
    flipped = flip_bit(encoded, rand)
    return flipped[::-1], rand

if we get true of result, we can know value of encoded. and additional bits are added when condition math.log2(i) % 1 == 0: is satisfied.

so, we can remove these bits, then get input binnary . and we do this function one more time and check it same or not.

script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import math
from chall import bin2hex, hex2bin, count_bit, add_bit, flip_bit, random_flip


def random_flip(binary, rand):
    binary = binary[::-1]
    length = len(binary) + count_bit(len(binary)) + 1
    encoded = ""
    index = 0
    for i in range(1, length):
        if math.log2(i) % 1 == 0:
            bit = add_bit(int(math.log2(i)) + 1, binary)
            encoded += bit
        else:
            encoded += binary[index]
            index += 1
    flipped = flip_bit(encoded, rand)
    return flipped


pt = "0"
for out in eval(open("output.txt", "r").read()):
    try:
        out, rand = out
        _encoded = hex2bin(out)[::-1]
        encoded = flip_bit(_encoded, rand)
        for i in [2**i - 1 for i in range(int(math.log2(len(encoded))) + 1)][::-1]:
            encoded = encoded[:i] + encoded[i + 1:]
        base = encoded[::-1]
        if random_flip(base, rand) == _encoded:
            pt += "1"
        else:
            pt += "0"
    except:
        pt += "1"
        continue
print("".join([chr(int(pt[i:i + 8],2)) for i in range(0, len(pt), 8)]))
# TCP1P{now_you_are_the_guessing_master}

molecular cryptography

second blood!

image-20241013035903060

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import numpy as np
from secret import flag
from chaotic import generate_sequences

dna_rules = {
    1: {'00': 'A', '11': 'T', '01': 'G', '10': 'C'},
    2: {'00': 'A', '11': 'T', '10': 'G', '01': 'C'},
    3: {'01': 'A', '10': 'T', '00': 'G', '11': 'C'},
    4: {'01': 'A', '10': 'T', '11': 'G', '00': 'C'},
    5: {'10': 'A', '01': 'T', '00': 'G', '11': 'C'},
    6: {'10': 'A', '01': 'T', '11': 'G', '00': 'C'},
    7: {'11': 'A', '00': 'T', '01': 'G', '10': 'C'},
    8: {'11': 'A', '00': 'T', '10': 'G', '01': 'C'}
}

def dna_encode_matrix(P, rule_number):
    rule = dna_rules[rule_number]
    return np.array([
        [rule[f'{num:08b}'[i:i+2]] for num in row for i in range(0, 8, 2)]
        for row in P
    ])

def xor_matrices(matrix1, matrix2, rule_number):
    rule = dna_rules[rule_number]
    rev_rule = {v: k for k, v in rule.items()}
    return np.array([
        [rule[format(int(rev_rule[b1], 2) ^ int(rev_rule[b2], 2), '02b')]
         for b1, b2 in zip(row1, row2)]
        for row1, row2 in zip(matrix1, matrix2)
    ])

def generate_random_keystream(total_bases):
    return np.random.choice(['A', 'C', 'G', 'T'], size=total_bases)

def scramble_matrix(P, lx, ly):
    return P[lx, :][:, ly]

def string_to_matrix(s, num_columns=4):
    ascii_vals = [ord(c) for c in s]
    padded_len = -(-len(ascii_vals) // num_columns) * num_columns
    padded_vals = ascii_vals + [0] * (padded_len - len(ascii_vals))
    return np.array(padded_vals).reshape(-1, num_columns)

def dna_sequence_to_dna_matrix(s, num_columns=16):
    padding_length = (num_columns - len(s) % num_columns) % num_columns
    s_padded = s + 'A' * padding_length
    return np.array(list(s_padded)).reshape(-1, num_columns)

def adjust_sequences(seq, new_length):
    filtered_seq = seq[seq < new_length]
    repeats = -(-new_length // len(filtered_seq))
    return np.tile(filtered_seq, repeats)[:new_length]

if __name__ == "__main__":
    P0 = string_to_matrix(flag)
    rule_number = 3
    dna_encoded_P = dna_encode_matrix(P0, rule_number)
    total_bases = dna_encoded_P.size
    keystream = generate_random_keystream(total_bases)
    key_matrix = keystream.reshape(dna_encoded_P.shape)

    row, col = P0.shape
    lx, ly = generate_sequences(row, 4 * col)
    lx, ly = np.array(lx), np.array(ly)

    scrambled_P = scramble_matrix(dna_encoded_P, lx, ly)
    Pc = xor_matrices(scrambled_P, key_matrix, rule_number)
    print("Encrypted Flag:")
    print(''.join(Pc.flatten()))

    for i in range(1, 3):
        user_input = input(f"Give DNA encryption a try! What do you want to encrypt? ({i}/2)")
        if not all(c in 'ACGT' for c in user_input):
            print("Invalid input")
            continue

        dna_matrix_user = dna_sequence_to_dna_matrix(user_input, num_columns=16)
        data_rows, data_cols = dna_matrix_user.shape
        total_bases_user = data_rows * data_cols

        repeats_key = -(-total_bases_user // total_bases)
        extended_keystream = np.tile(keystream, repeats_key)[:total_bases_user]
        extended_key_matrix = extended_keystream.reshape((data_rows, data_cols))

        lx_user = adjust_sequences(lx, data_rows)
        ly_user = adjust_sequences(ly, data_cols)

        scrambled_user = scramble_matrix(dna_matrix_user, lx_user, ly_user)
        result = xor_matrices(scrambled_user, extended_key_matrix, rule_number)
        print("Encryption Result:")
        print(''.join(result.flatten()))

solve

we can have a cryptographic oracle, but only 2 time. The goal is to recover the key and the flag in two cryptographic oracles.

The trouble is that the point is that the values of the inputs are agitated by the matrix elements in the scramble_matrix function.

recover key

Its so simple. we construct all the elements of the matrix are “A”, we can ignore the scramble_matrix function.

1
2
3
4
5
6
7
SEND2 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGT"
io.sendlineafter(b")", SEND2)
io.recvline()
ct1 = io.recvline().decode()
pt = dna_sequence_to_dna_matrix("A"*256)
ct1 = dna_sequence_to_dna_matrix(ct1[:256])
keystream = xor_matrices(ct1, pt)

substitution part

What would happen if some of the substitutions were known? We can use that information to recover parts of the flag.

Suppose send CGAAAAAA... then the position of the two characters can be determined by looking at the position of the G and C in the response.

This can be repeated several times to obtain the flag.

1
2
3
4
5
6
7
8
9
10
11
key_matrix = keystream.flatten().reshape((16, 16))
scrambled_P = xor_matrices(DNA, key_matrix, rule_number)
io.sendlineafter(b")", SEND3)
io.recvline()
ct2 = dna_sequence_to_dna_matrix(io.recvline().decode())
for rowi, row in enumerate(xor_matrices(keystream, ct2, rule_number)):
    row = row.tolist()
    if not "G" in row:
        continue
    FLAG[i][2 * k] = scrambled_P[rowi][row.index("G")]
    FLAG[i][2 * k + 1] = scrambled_P[rowi][row.index("T")]

finally i got solve

### script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from Crypto.Util.number import *
import numpy as np
from ptrlib import remote, process
from chall import dna_rules, dna_encode_matrix, xor_matrices, dna_sequence_to_dna_matrix, adjust_sequences

rule_number = 3
SEND2 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGT"

FLAG = [["" for i in range(16)] for k in range(16)]

for i in range(16):
    for k in range(8):
        index = 8 * i + k
        SEND3 = "AC" * (index) + "GT" + "AC" * (127 - index)
        # io = process(["python3", "chall.py"])
        io = remote("nc ctf.tcp1p.team 1975")
        io.recvline()

        DNA = io.recvline().decode()
        DNA = dna_sequence_to_dna_matrix(DNA)
        io.sendlineafter(b")", SEND2)
        io.recvline()
        ct1 = io.recvline().decode()

        pt = dna_sequence_to_dna_matrix("A" * 256)
        ct1 = dna_sequence_to_dna_matrix(ct1[:256])
        keystream = xor_matrices(ct1, pt, rule_number)

        key_matrix = keystream.flatten().reshape((16, 16))
        scrambled_P = xor_matrices(DNA, key_matrix, rule_number)
        io.sendlineafter(b")", SEND3)
        io.recvline()
        ct2 = dna_sequence_to_dna_matrix(io.recvline().decode())
        for rowi, row in enumerate(xor_matrices(keystream, ct2, rule_number)):
            row = row.tolist()
            if not "G" in row:
                continue
            FLAG[i][2 * k] = scrambled_P[rowi][row.index("G")]
            FLAG[i][2 * k + 1] = scrambled_P[rowi][row.index("T")]
        print(FLAG)

pt = b""
for row in FLAG:
    row = "".join(row)
    row = row.replace("A", "01")
    row = row.replace("T", "10")
    row = row.replace("G", "00")
    row = row.replace("C", "11")
    pt += long_to_bytes(int(row, 2))

print(pt)
# TCP1P{That_time_I_got_Chemistry_Olympiad_instead_of_CTF_on_weekends_:(}

molecular cryptography revenge

second blood!

image-20241013035926261

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import numpy as np
from PIL import Image
from chaotic import generate_sequences

dna_rules = {
    1: {'00': 'A', '11': 'T', '01': 'G', '10': 'C'},
    2: {'00': 'A', '11': 'T', '10': 'G', '01': 'C'},
    3: {'01': 'A', '10': 'T', '00': 'G', '11': 'C'},
    4: {'01': 'A', '10': 'T', '11': 'G', '00': 'C'},
    5: {'10': 'A', '01': 'T', '00': 'G', '11': 'C'},
    6: {'10': 'A', '01': 'T', '11': 'G', '00': 'C'},
    7: {'11': 'A', '00': 'T', '01': 'G', '10': 'C'},
    8: {'11': 'A', '00': 'T', '10': 'G', '01': 'C'}
}

def dna_encode_matrix(P, rule_number):
    rule = dna_rules[rule_number]
    dna_encoded_rows = []
    for row in P:
        dna_row = []
        for num in row:
            binary_str = f'{num:08b}'
            for i in range(0, 8, 2):
                bit_pair = binary_str[i:i+2]
                dna_row.append(rule[bit_pair])
        dna_encoded_rows.append(dna_row)
    return np.array(dna_encoded_rows)

def dna_decode_matrix(dna_matrix, rule_number):
    rule = dna_rules[rule_number]
    rev_rule = {v: k for k, v in rule.items()}
    decoded_rows = []
    for row in dna_matrix:
        bit_pairs = [rev_rule.get(base, '00') for base in row]
        num_bits = ''.join(bit_pairs)
        num_bytes = len(num_bits) // 8
        decoded_row = [int(num_bits[i*8:(i+1)*8], 2) for i in range(num_bytes)]
        decoded_rows.append(decoded_row)
    return np.array(decoded_rows, dtype=np.uint8)

def xor_matrices(matrix1, matrix2, rule_number):
    rule = dna_rules[rule_number]
    rev_rule = {v: k for k, v in rule.items()}
    xor_rows = []
    for row1, row2 in zip(matrix1, matrix2):
        xor_row = []
        for b1, b2 in zip(row1, row2):
            bits1 = rev_rule[b1]
            bits2 = rev_rule[b2]
            xor_bits = format(int(bits1, 2) ^ int(bits2, 2), '02b')
            xor_row.append(rule[xor_bits])
        xor_rows.append(xor_row)
    return np.array(xor_rows)

def scramble_matrix(P, lx, ly):
    return P[lx, :][:, ly]

def adjust_sequences(seq, new_length):
    filtered_seq = seq[seq < new_length]
    repeats = -(-new_length // len(filtered_seq))
    return np.tile(filtered_seq, repeats)[:new_length]

def parse_user_input(user_input):
    try:
        rows = user_input.strip().split(';')
        matrix = []
        num_columns = None
        for row in rows:
            elements = list(map(int, row.strip().split(',')))
            if not all(0 <= elem <= 255 for elem in elements):
                raise ValueError
            if num_columns is None:
                num_columns = len(elements)
            elif len(elements) != num_columns:
                raise ValueError
            matrix.append(elements)
        return np.array(matrix, dtype=np.uint8)
    except:
        raise ValueError("Invalid input.")

if __name__ == "__main__":
    img = Image.open('flag.png').convert('L')
    img_data = np.array(img)
    P0 = np.random.randint(0, 256, size=img_data.shape, dtype=np.uint8)

    rule_number = 3
    dna_encoded_P = dna_encode_matrix(P0, rule_number)
    total_bases = dna_encoded_P.size
    keystream = np.random.choice(['A', 'C', 'G', 'T'], size=total_bases)
    key_matrix = keystream.reshape(dna_encoded_P.shape)

    lx, ly = generate_sequences(P0.shape[0], dna_encoded_P.shape[1])
    lx, ly = np.array(lx), np.array(ly)

    scrambled_P = scramble_matrix(dna_encoded_P, lx, ly)
    Pc = xor_matrices(scrambled_P, key_matrix, rule_number)
    decoded_Pc = dna_decode_matrix(Pc, 4)

    encrypted_challenge = ';'.join(','.join(map(str, row)) for row in decoded_Pc)
    print("Encrypted Challenge:")
    print(encrypted_challenge)

    for i in range(1, 8):
        user_input = input(f"Give DNA encryption a try! What do you want to encrypt? ({i}/7): ")
        try:
            user_matrix = parse_user_input(user_input)
            dna_encoded_user = dna_encode_matrix(user_matrix, rule_number)
            total_bases_user = dna_encoded_user.size
            repeats_key = -(-total_bases_user // total_bases)
            extended_keystream = np.tile(keystream, repeats_key)[:total_bases_user]
            extended_key_matrix = extended_keystream.reshape(dna_encoded_user.shape)
            data_rows, data_cols = dna_encoded_user.shape
            lx_user = adjust_sequences(lx, data_rows)
            ly_user = adjust_sequences(ly, data_cols)
            scrambled_user = scramble_matrix(dna_encoded_user, lx_user, ly_user)
            result_dna = xor_matrices(scrambled_user, extended_key_matrix, rule_number)
            result_decoded = dna_decode_matrix(result_dna, 4)
            encrypted_input = ';'.join(','.join(map(str, row)) for row in result_decoded)
            print("Encrypted Input:")
            print(encrypted_input)
        except ValueError:
            print("Invalid input.")
            continue

    plaintext_input = input("Prove it that now you know, what is the Plaintext Challenge? ")
    try:
        plaintext_matrix = parse_user_input(plaintext_input)
        if np.array_equal(plaintext_matrix, P0):
            print("Congrats, here is the pixel data of flag.png encrypted with the same secret as the Plaintext Challenge")
            dna_encoded_flag = dna_encode_matrix(img_data, rule_number)
            scrambled_flag = scramble_matrix(dna_encoded_flag, lx, ly)
            Pc_flag = xor_matrices(scrambled_flag, key_matrix, rule_number)
            decoded_Pc_flag = dna_decode_matrix(Pc_flag, 4)
            encrypted_flag = ';'.join(','.join(map(str, row)) for row in decoded_Pc_flag)
            print(encrypted_flag)
        else:
            print("Incorrect plaintext challenge.")
    except ValueError:
        print("Invalid input.")

solve

The difference is that get FLAG by predicting a random matrix, the random matrix is 80*115 in size, and use the oracle up to seven times (three times is enough ig).

recover key

Key recovery is unchanged from the original chall.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rule_number = 3
bases = [["G" for _ in range(460)] for __ in range(80)]

io = remote("nc ctf.tcp1p.team 1985")
io.recvline()
ct = io.recvline().decode()
ct = parse_user_input(ct)
dna_encoded_user = dna_encode_matrix(ct, 4)

io.sendline(serial(dna_decode_matrix(bases, rule_number)))
io.recvline()
DNA = io.recvline().decode()
DNA = parse_user_input(DNA)
result_dnas = [dna_encode_matrix(DNA[i:i + DNA_shape[0]], 4) for i in range(0, len(DNA), DNA_shape[0])]
keystream = xor_matrices(bases, result_dnas[0], rule_number)

recover substitution(vertical)

Consider restoring the vertical substitution. If we transmit a matrix and consider a set() for each row before and after transmission, the same set appears, although in a different position, and can be restored on this basis.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bases = []
while len(bases) != DNA_shape[0]:
    ret = list(choices(["A", "T", "C", "G"], k=DNA_shape[1]))
    ret = sorted(ret)
    if ret in bases:
        continue
    bases.append(ret)

io.sendline(serial(dna_decode_matrix(np.array(bases), rule_number)))
io.recvline()
ret = dna_encode_matrix(parse_user_input(io.recvline().decode()), 4)
ret = xor_matrices(keystream, ret, rule_number).tolist()

lx = []
for row in ret:
    row = sorted(row)
    if row in bases:
        lx.append(bases.index(row))

recover substitution(horizontal)

horizontal is same to vertical

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# horisontal
bases = []
while len(bases) != DNA_shape[1]:
    ret = list(choices(["A", "T", "C", "G"], k=DNA_shape[0]))
    ret = sorted(ret)
    if ret in bases:
        continue
    bases.append(ret)

io.sendline(serial(dna_decode_matrix(np.array(bases).transpose(), rule_number)))
io.recvline()
ret = dna_encode_matrix(parse_user_input(io.recvline().decode()), 4)
ret = xor_matrices(keystream, ret, rule_number).transpose().tolist()

ly = []
bases = bases
for row in ret:
    row = sorted(row)
    if row in bases:
        ly.append(bases.index(row))

recover flag png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lx_inv = [lx.index(i) for i in range(DNA_shape[0])]
ly_inv = [ly.index(i) for i in range(DNA_shape[1])]
# recover r

ret = xor_matrices(dna_encoded_user, keystream, rule_number)
dna_encoded_P = scramble_matrix_inv(ret, lx_inv, ly_inv)
P0 = dna_decode_matrix(dna_encoded_P, rule_number)
for i in range(4):
    io.sendline(b"")
io.sendlineafter(b" Plaintext Challenge? ", serial(P0))
io.recvline()
ret = xor_matrices(dna_encode_matrix(parse_user_input(io.recvline().decode()),4),keystream, rule_number)
dna_encoded_P = scramble_matrix_inv(ret, lx_inv, ly_inv)
img_data = dna_decode_matrix(dna_encoded_P, rule_number)

from PIL import Image
img = Image.fromarray(img_data)
img.save('flag.png')

scrpit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from PIL import Image
from random import choices
from chall import dna_rules, dna_encode_matrix, dna_decode_matrix, xor_matrices, scramble_matrix, adjust_sequences, parse_user_input
import numpy as np
from ptrlib import remote, process

dna_rules = {
    1: {'00': 'A', '11': 'T', '01': 'G', '10': 'C'},
    2: {'00': 'A', '11': 'T', '10': 'G', '01': 'C'},
    3: {'01': 'A', '10': 'T', '00': 'G', '11': 'C'},
    4: {'01': 'A', '10': 'T', '11': 'G', '00': 'C'},
    5: {'10': 'A', '01': 'T', '00': 'G', '11': 'C'},
    6: {'10': 'A', '01': 'T', '11': 'G', '00': 'C'},
    7: {'11': 'A', '00': 'T', '01': 'G', '10': 'C'},
    8: {'11': 'A', '00': 'T', '10': 'G', '01': 'C'}
}


def scramble_matrix_inv(P, lx, ly):
    return P[:, ly][lx, :]


def serial(decoded_Pc):
    return ';'.join(','.join(map(str, row)) for row in decoded_Pc)


DNA_shape = (80, 460)

SEND2 = ""

# make bases
rule_number = 3
bases = [["G" for _ in range(460)] for __ in range(80)]

io = remote("nc ctf.tcp1p.team 1985")
# io = process(["python3", "chall.py"])
io.recvline()
ct = io.recvline().decode()
ct = parse_user_input(ct)
dna_encoded_user = dna_encode_matrix(ct, 4)


io.sendline(serial(dna_decode_matrix(bases, rule_number)))
io.recvline()
DNA = io.recvline().decode()
DNA = parse_user_input(DNA)
result_dnas = [dna_encode_matrix(DNA[i:i + DNA_shape[0]], 4) for i in range(0, len(DNA), DNA_shape[0])]
keystream = xor_matrices(bases, result_dnas[0], rule_number)
open("solve_keystream", "w").write(str(keystream.tolist()))

# vertical
bases = []
while len(bases) != DNA_shape[0]:
    ret = list(choices(["A", "T", "C", "G"], k=DNA_shape[1]))
    ret = sorted(ret)
    if ret in bases:
        continue
    bases.append(ret)

io.sendline(serial(dna_decode_matrix(np.array(bases), rule_number)))
io.recvline()
ret = dna_encode_matrix(parse_user_input(io.recvline().decode()), 4)
ret = xor_matrices(keystream, ret, rule_number).tolist()

lx = []
for row in ret:
    row = sorted(row)
    if row in bases:
        lx.append(bases.index(row))

# horisontal
bases = []
while len(bases) != DNA_shape[1]:
    ret = list(choices(["A", "T", "C", "G"], k=DNA_shape[0]))
    ret = sorted(ret)
    if ret in bases:
        continue
    bases.append(ret)

io.sendline(serial(dna_decode_matrix(np.array(bases).transpose(), rule_number)))
io.recvline()
ret = dna_encode_matrix(parse_user_input(io.recvline().decode()), 4)
ret = xor_matrices(keystream, ret, rule_number).transpose().tolist()

ly = []
bases = bases
for row in ret:
    row = sorted(row)
    if row in bases:
        ly.append(bases.index(row))

print(lx)
print(ly)

lx_inv = [lx.index(i) for i in range(DNA_shape[0])]
ly_inv = [ly.index(i) for i in range(DNA_shape[1])]
# recover r

ret = xor_matrices(dna_encoded_user, keystream, rule_number)
dna_encoded_P = scramble_matrix_inv(ret, lx_inv, ly_inv)
P0 = dna_decode_matrix(dna_encoded_P, rule_number)
for i in range(4):
    io.sendline(b"")
io.sendlineafter(b" Plaintext Challenge? ", serial(P0))
io.recvline()
ret = xor_matrices(dna_encode_matrix(parse_user_input(io.recvline().decode()), 4), keystream, rule_number)
dna_encoded_P = scramble_matrix_inv(ret, lx_inv, ly_inv)
img_data = dna_decode_matrix(dna_encoded_P, rule_number)

img = Image.fromarray(img_data)
img.save('flag.png')

# io.sh()

# TCP1P{Fancy_DNA_and_hyperchaotic_encryption_yet_still_not_IND_CPA}

What’s the Worst That Could Happen

first blood!

image-20241013035954369

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from Crypto.Util.Padding import pad
from aes import AES
from secret import flag
import os

c = AES(os.urandom(16))

admin_id = os.urandom(8).hex().encode()
enc_id = c.encrypt(admin_id)

print(f"Encrypted ID: {enc_id.hex()}")

while True:
    print("1. Encrypt")
    print("2. Admin Login")
    print("3. Exit")
    choice = int(input(">> "))
    if choice == 1:
        msg = input("message: ").encode()
        msg = pad(msg, 16)
        msg_block = [msg[i:i+16] for i in range(0, len(msg), 16)]
        ct_block = []
        for m in msg_block:
            ct_block.append(c.encrypt(m))
        print("Encrypted:", b''.join(ct_block).hex())
    elif choice == 2:
        id = input("Enter ID: ")
        if id.encode() == admin_id:
            print("Welcome Admin!")
            print(flag)
        else:
            print("Invalid ID")
    elif choice == 3:
        break
    else:
        print("Invalid Choice")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env python
from Crypto.Util.number import *

"""
    Copyright (C) 2012 Bo Zhu http://about.bozhu.me

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
"""

Sbox = (
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)

InvSbox = (
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)


# learnt from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)


Rcon = (
    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
    0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
    0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
    0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)


def text2matrix(text):
    text = bytes_to_long(text)
    matrix = []
    for i in range(16):
        byte = (text >> (8 * (15 - i))) & 0xFF
        if i % 4 == 0:
            matrix.append([byte])
        else:
            matrix[i // 4].append(byte)
    return matrix


def matrix2text(matrix):
    text = 0
    for i in range(4):
        for j in range(4):
            text |= (matrix[i][j] << (120 - 8 * (4 * i + j)))
    return long_to_bytes(text)


class AES:
    def __init__(self, master_key):
        self.change_key(master_key)

    def change_key(self, master_key):
        self.round_keys = text2matrix(master_key)
        # print self.round_keys

        for i in range(4, 4 * 11):
            self.round_keys.append([])
            if i % 4 == 0:
                byte = self.round_keys[i - 4][0]        \
                     ^ Sbox[self.round_keys[i - 1][1]]  \
                     ^ Rcon[i // 4]
                self.round_keys[i].append(byte)

                for j in range(1, 4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ Sbox[self.round_keys[i - 1][(j + 1) % 4]]
                    self.round_keys[i].append(byte)
            else:
                for j in range(4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ self.round_keys[i - 1][j]
                    self.round_keys[i].append(byte)

        # print self.round_keys

    def encrypt(self, plaintext):
        self.plain_state = text2matrix(plaintext)

        self.__add_round_key(self.plain_state, self.round_keys[:4])

        for i in range(1, 10):
            self.__round_encrypt(self.plain_state, self.round_keys[4 * i : 4 * (i + 1)])

        self.__sub_bytes(self.plain_state)
        self.__shift_rows(self.plain_state)
        self.__add_round_key(self.plain_state, self.round_keys[40:])

        return matrix2text(self.plain_state)

    def decrypt(self, ciphertext):
        self.cipher_state = text2matrix(ciphertext)

        self.__add_round_key(self.cipher_state, self.round_keys[40:])
        self.__inv_shift_rows(self.cipher_state)
        self.__inv_sub_bytes(self.cipher_state)

        for i in range(9, 0, -1):
            self.__round_decrypt(self.cipher_state, self.round_keys[4 * i : 4 * (i + 1)])

        self.__add_round_key(self.cipher_state, self.round_keys[:4])

        return matrix2text(self.cipher_state)

    def __add_round_key(self, s, k):
        for i in range(4):
            for j in range(4):
                s[i][j] ^= k[i][j]


    def __round_encrypt(self, state_matrix, key_matrix):
        self.__sub_bytes(state_matrix)
        self.__shift_rows(state_matrix)
        self.__mix_columns(state_matrix)
        self.__add_round_key(state_matrix, key_matrix)


    def __round_decrypt(self, state_matrix, key_matrix):
        self.__add_round_key(state_matrix, key_matrix)
        self.__inv_mix_columns(state_matrix)
        self.__inv_shift_rows(state_matrix)
        self.__inv_sub_bytes(state_matrix)

    def __sub_bytes(self, s):
        for i in range(4):
            for j in range(4):
                s[i][j] = Sbox[s[i][j]]


    def __inv_sub_bytes(self, s):
        for i in range(4):
            for j in range(4):
                s[i][j] = InvSbox[s[i][j]]


    def __shift_rows(self, s):
        s[0][1], s[0][2], s[0][3], = s[0][2], s[0][3], s[0][1]
        s[1][1], s[1][2], s[1][3], = s[1][2], s[1][3], s[1][1]
        s[2][1], s[2][2], s[2][3], = s[2][2], s[2][3], s[2][1]
        s[3][1], s[3][2], s[3][3], = s[3][2], s[3][3], s[3][1]


    def __inv_shift_rows(self, s):
        s[0][1], s[0][2], s[0][3], = s[0][3], s[0][1], s[0][2]
        s[1][1], s[1][2], s[1][3], = s[1][3], s[1][1], s[1][2]
        s[2][1], s[2][2], s[2][3], = s[2][3], s[2][1], s[2][2]
        s[3][1], s[3][2], s[3][3], = s[3][3], s[3][1], s[3][2]

    def __mix_single_column(self, a):
        # please see Sec 4.1.2 in The Design of Rijndael
        t = a[0] ^ a[1] ^ a[2] ^ a[3]
        u = a[0]
        a[0] ^= t ^ xtime(a[0] ^ a[1])
        a[1] ^= t ^ xtime(a[1] ^ a[2])
        a[2] ^= t ^ xtime(a[2] ^ a[3])
        a[3] ^= t ^ xtime(a[3] ^ u)


    def __mix_columns(self, s):
        for i in range(4):
            self.__mix_single_column(s[i])


    def __inv_mix_columns(self, s):
        # see Sec 4.1.3 in The Design of Rijndael
        for i in range(4):
            u = xtime(xtime(s[i][0] ^ s[i][2]))
            v = xtime(xtime(s[i][1] ^ s[i][3]))
            s[i][0] ^= u
            s[i][1] ^= v
            s[i][2] ^= u
            s[i][3] ^= v

        self.__mix_columns(s)

solve

The only difference from normal is the direction of the shift rows in AES. So this AES is encrypted every 4 bytes.

The admin_id consists of 0-9a-f, so the whole search can be done

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from tqdm import tqdm
from itertools import product
from ptrlib import *

# io = remote()
# io = process(["python3", "chall.py"])
io = remote("nc ctf.tcp1p.team 19328")
ct = bytes.fromhex(io.recvlineafter(b": ").decode())
print(ct)
cts = [ct[i:i + 4] for i in range(0, 16, 4)]
adid = b""


def sends_all(ms):
    for m in ms:
        io.sendline(1)
        io.sendline(m)
    return [bytes.fromhex(io.recvlineafter(b"Encrypted: ").decode())[:16] for i in range(len(ms))]


D = 1024
for index in range(4):
    pts = [b"0000" * index + bytes(list(i)) + b"0000" * (3 - index) for i in tqdm(product(b"0123456789abcdef", repeat=4), total=16**4)]
    rets = []
    for basei in tqdm(range(0, 16**4, D)):
        rets = sends_all(pts[basei:basei + D])
        for reti, ret in enumerate(rets):
            rets = [ret[_:_ + 4] for _ in range(0, 16, 4)]
            if cts[index] == rets[index]:
                _pts = [pts[reti + basei][_:_ + 4] for _ in range(0, 16, 4)]
                adid += _pts[index]
                print(adid)
                break

        if len(adid) // 4 == index + 1:
            break

io.sendline(2)
io.sendline(adid)
io.sh()

# TCP1P{well_well_well_9a80dfee0912}
This post is licensed under CC BY 4.0 by the author.