defcamp CTF 2025 writeup

  • ~22.60K 字
  1. 1. defcamp 2025
    1. 1.1. ai-exhaustion [Crypto] 212 solve
      1. 1.1.1. chall
      2. 1.1.2. solve
    2. 1.2. Close-message [crypto] 48 solve
      1. 1.2.1. solve
    3. 1.3. scroll [crypto] 19 solve
      1. 1.3.1. chall
      2. 1.3.2. solve

defcamp 2025

BunkyoWesternsで参加した。

一位でした。

ありがとうございました。

image-20250918000502281

ai-exhaustion [Crypto] 212 solve

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
import os
import time


RSA_A = 7
RSA_B = 13

def get_aes_key() -> bytes:
"""Derive AES_KEY from flag.txt modification timestamp"""
ts = int(os.path.getmtime("flag.txt"))
return str(ts).encode()

def xor_layer(data: bytes, key: bytes) -> bytes:
return bytes([c ^ key[i % len(key)] for i, c in enumerate(data)])


def modinv(a: int, m: int = 256) -> int:
t, newt = 0, 1
r, newr = m, a
while newr != 0:
q = r // newr
t, newt = newt, t - q * newt
r, newr = newr, r - q * newr
if r > 1:
raise ValueError("a is not invertible")
if t < 0:
t += m
return t

def affine_encrypt(data: bytes, a: int = RSA_A, b: int = RSA_B) -> bytes:
return bytes([(a * c + b) % 256 for c in data])

B91_ALPHABET = [chr(i) for i in range(33, 124)]

def base91_encode(data: bytes) -> str:
out = []
for b in data:
hi = b // len(B91_ALPHABET)
lo = b % len(B91_ALPHABET)
out.append(B91_ALPHABET[hi] + B91_ALPHABET[lo])
return ''.join(out)


def encrypt(plaintext: bytes, key: bytes) -> str:
xored = xor_layer(plaintext, key)
aff = affine_encrypt(xored)
return base91_encode(aff)


if __name__ == "__main__":
if not os.path.exists("flag.txt"):
print("[-] flag.txt missing")
exit(1)

AES_KEY = get_aes_key()

with open("flag.txt", "rb") as f:
flag = f.read().strip()

enc = encrypt(flag, AES_KEY)

with open("cipher.txt", "w") as f:
f.write(enc)

print("[+] Ciphertext saved to cipher.txt")

solve

xorして、affine変換して、base91しているだけです。xor以外は逆変換が容易なので逆変換を行います。

xorは鍵はflag.txtが作成された時間なので、ciphertxtが作られた時間以前をBFして求めると一部がいい感じに復号されているような鍵を見つけることができますので、後はindexを変更して手で見つけていきます。

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
ct = open("cipher.txt", "r").read()
import os
def get_aes_key(tt) -> bytes:
"""Derive AES_KEY from flag.txt modification timestamp"""
return str(tt).encode()

RSA_A = 7
RSA_B = 13

def affine_encrypt(data: bytes, a: int = RSA_A, b: int = RSA_B) -> bytes:
return bytes([(a * c + b) % 256 for c in data])

def affine_decrypt(data: bytes, a: int = RSA_A, b: int = RSA_B) -> bytes:
return bytes([((c - b)%256 * pow(a, -1, 256))%256 for c in data])


B91_ALPHABET = [chr(i) for i in range(33, 124)]

def base91_encode(data: bytes) -> str:
out = []
for b in data:
hi = b // len(B91_ALPHABET)
lo = b % len(B91_ALPHABET)
out.append(B91_ALPHABET[hi] + B91_ALPHABET[lo])
return ''.join(out)

def base91_decode(data: bytes) -> str:
out = []
for i in range(0, len(data), 2):
b_hi = data[i]
b_lo = data[i+1]
b = B91_ALPHABET.index(b_hi) * len(B91_ALPHABET) + B91_ALPHABET.index(b_lo)
out.append(b)
return bytes(out)

def xor_layer(data: bytes, key: bytes) -> bytes:
return bytes([c ^ key[i % len(key)] for i, c in enumerate(data)])


_ct = affine_decrypt(base91_decode(ct))
assert base91_encode(affine_encrypt(_ct)) == ct

ct = _ct

index = 4
for i in range(0, 10):

key = b'1755442233'

key = key[:index] + str(i).encode() + key[index+1:]

print(xor_layer(ct, key))
print(key)


for i in range(1755420692 - 10000, 1755420692 + 10000):
if b"CTF{" in xor_layer(ct, get_aes_key(i)):
print(xor_layer(ct, get_aes_key(i)))
print(get_aes_key(i))
if b"ctf{" in xor_layer(ct, get_aes_key(i)):
print(xor_layer(ct, get_aes_key(i)))
print(get_aes_key(i))

# ctf{85442935690be24eaa7278925fbb35368b8bb230516a530090c637f83b25f516}

Close-message [crypto] 48 solve

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
from Crypto.Util.number import *
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random

def getkey(x):
h = SHA256.new()
h.update(str(x).encode())
return h.digest()

testcases=10
nbit=256

Ns=[]
Cs=[]
Ms=[]
ms=[]

for T in range(testcases):
p=getPrime(nbit)
q=getPrime(nbit)
n=p*q

m=random.randint(0,n-1)
c=pow(m,2,n)

R=random.sample(list(range(nbit*2-1)),k=4)
eps=sum([pow(2,x) for x in R])

M=m^^eps
Ns.append(n)
Cs.append(c)
Ms.append(M)
ms.append(m)


key=getkey(ms)

cipher=AES.new(key,AES.MODE_ECB)

flag=b"CTF{????????????????????????????????????????????????????????????????}"

enc=cipher.encrypt(pad(flag,16)).hex()

print(f"{hex(m)=}")
print(f"{hex(n)=}")
print(f"{hex(c)=}")
print(f"{hex(M)=}")
print(f"{hex(enc)=}")

solve

$[1,nbit2-1] M=m \oplus \sum2^{R_i}$としています。特に最適化せずにも102**36個のBFで解けるなとなり、cppで並列化+全探索しました。

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
// g++ -std=c++17 -O2 part.cpp -o part
#include <bits/stdc++.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <sstream>

using boost::multiprecision::cpp_int;

int main(int argc, char *argv[]) {
constexpr int nbit = 256;
// const int N = 2 * nbit - 1; // Pythonの range(nbit*2-1) → [0, 2*nbit-2]
int i3;
i3 = std::stoi(argv[1]);

cpp_int n, M, c;
std::istringstream iss_N("681c8b4ce56dcc0246923d5a941a268a71f3e1a6eab5738492cdaa8c6869b5e43450145d12535290190ac9bac5c1595abd18c0c08d1599cdc884f0367c808fb3");iss_N >> std::hex >> n;
std::istringstream iss_M("73b142c40ef4af03f3628faa3e5e4e1686a22cf93e488b60fb4c2f67bd012e24539700a07c6db2768103a79290b434007ff8b6a3f2dda2bd0e1660a2d195f41b");iss_M >> std::hex >> M;
std::istringstream iss_C("49e561ddd73bcbd33bdfdb9c3fb11e23fb16b33c51e667b8976637913fdab3b11d00130634693c0dd12093b4e353de9015c9b338df6e32ef8bcdb7bbf2ff25a5");iss_C >> std::hex >> c;

cpp_int eps;

std::array<int, 4> R;

for(int i0= 0;i0<=512;i0++){
std::cout << i0 << "\n";
for(int i1= 0;i1<=512;i1++){
for(int i2= 0;i2<=512;i2++){
R = { i0, i1, i2, i3};
eps = 0;
for (int x : R) {
cpp_int term = cpp_int(1);
term <<= x; // 2**x
eps += term;
}
cpp_int _m = M ^ eps;
cpp_int _c = (_m * _m) % n;
if (c == _c) {
std::ofstream file(argv[1], std::ios::app);
file << "found = " << R[0] << ", "
<< R[1] << ", "
<< R[2] << ", "
<< R[3] << ", "
<< M << ", " << "\n" << std::endl;
}

}
}
}
return 0;
}

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
import re
from Crypto.Util.number import *
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


Ns = --omited--
Cs = --omited--
enc = --omited--'985c099ea39ae0c1cc86b002695cd60b4efd49a1eecdbcea8dba3229bead65a9a91c42c0694e28206059add3ce4034e964da904073c0c52e71d56af97e271fe0cefc2431cf1e2cdb3ae1629b1bbd8c25'


def getkey(x):
h = SHA256.new()
h.update(str(x).encode())
return h.digest()


ms = [0 for i in range(10)]

S = set()

for i in range(514):
try:
for line in open(f"./ctf/{i}").readlines():
nums = list(map(int, re.findall(r"\d+", line)))
if len(nums) != 5:
continue

R = nums[:4]
index = Ms.index(nums[-1])

M = Ms[index]
c = Cs[index]
n = Ns[index]
eps = sum([pow(2, x) for x in R])

m = M ^ eps
S.add(index)
print(index, m)
ms[index] = int(m)
assert pow(m, 2, n) == c

except FileNotFoundError:
pass

key = getkey(ms)

cipher = AES.new(key, AES.MODE_ECB)

print(cipher.decrypt(bytes.fromhex(enc)))
# b'CTF{5d2b2c1d469fe336fd530d2511abfc70be7a7e798112c85c1e6402dc0bcb40d8}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'

scroll [crypto] 19 solve

chall

以下のようなscrachで組んだアルゴリズムとそれを使った暗号文が与えられて平文を復号せよという問題。

image-20250918002026733

solve

とりあえず、maon: arr1,2の配列がAESのSboxとinv sboxであることからAESのsub bytes likeな関数もしくはAESそのものが実装されている予想が立ちます。

また、start部分を見ると、func15func14func16から構成されています。

image-20250918002428106

更に、func15に注目すると以下のようなfor文に該当する部分が見つかり、AESのsubbytes,shiftrows, mixcolums, addroundkeyに該当しそうと推測できます。

image-20250918002726047

実際には、以下のような対応が成り立ちます。これにより、startで実施している内容はencryptdecryptcustomという形になります。customは独自の実装を意味しています。

image-20250918003038856

customは通常のAESと同じように10 roundから構成されていますが、各ラウンドで使われる関数はバラバラです。このため一見解けないようにも見えますが、一部関数は打消しが発生します。一例として、func6→func7だと結果的に何もしていないと同義ということです。ここで、初めのAdd roundkeyから最後のAdd roundkeyまでにsubbytes関連の関数が使われていない=Affine変換として考えることができます。

ということは、初めのAdd roundkeyまでは関数通りに実行→Affine変換で計算→最後のAdd roundkeyからは関数通りに実行でよさそうです。

Affine変換で計算とは、関数内に非線形部分がないのでと表現することができます。
この時鍵はにのみ依存するので、の計算はlocalで可能です。これにより一つのpt,ctのペアがわかれば、も導出可能となり容易に復号できます。

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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#!/usr/bin/env python


"""
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.
"""

"""
This has been edited a bit from the original to update it to python3
..as well as some security improvements
"""

import time
import os
from ptrlib import xor
SboxOriginal = (
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,
)

# learnt from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c


def xtime(a):
return (((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 = int(text.hex(), 16)
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 int(text).to_bytes(16, byteorder='big')


def by2bi(arr):
return list(map(int, "".join(map(lambda x: f"{x:08b}", arr))))


def bi2by(arr):
return bytes([int("".join(map(str, arr[i:i + 8])), 2) for i in range(0, len(arr), 8)])


class AES:
def __init__(self, master_key, Sbox=SboxOriginal):
self.Sbox = Sbox
self.InvSbox = [0] * 256
for i in range(256):
self.InvSbox[self.Sbox[i]] = i
self.master_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] \
^^ self.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] \
^^ self.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.change_key(self.master_key)
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.change_key(self.master_key)

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] = self.Sbox[s[i][j]]

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

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

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

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)

# new code!

def do_call(self, calls, s, key):
s = text2matrix(s)

self.change_key(key)
func13i = 0
for name in ['func10'] + calls:
if name == "func10":
arr5 = self.round_keys
elif name == "func11":
self.__sub_bytes(s)
elif name == "func12":
self.__inv_sub_bytes(s)
elif name == "func9":
self.__inv_shift_rows(s)
elif name == "func8":
self.__shift_rows(s)
elif name == "func7":
self.__inv_mix_columns(s)
elif name == "func6":
self.__mix_columns(s)
elif name == "func13":
# print("func13i", func13i)
self.__add_round_key(s, self.round_keys[4 * func13i: 4 * (func13i + 1)])
func13i += 1
else:
print(name, "paased")
pass
return matrix2text(s)

def inv_do_call(self, calls, s, key):
s = text2matrix(s)

self.change_key(key)
func13i = 0
for name in ['func10'] + calls:
if name == "func10":
arr5 = self.round_keys
elif name == "func11":
self.__inv_sub_bytes(s)
elif name == "func12":
self.__sub_bytes(s)
elif name == "func9":
self.__shift_rows(s)
elif name == "func8":
self.__inv_shift_rows(s)
elif name == "func7":
self.__mix_columns(s)
elif name == "func6":
self.__inv_mix_columns(s)
elif name == "func13":
# print("func13i", func13i)
self.__add_round_key(s, self.round_keys[4 * func13i: 4 * (func13i + 1)])
func13i += 1
else:
print(name, "paased")
pass
return matrix2text(s)



def func16_pipeline(self, pt: bytes, ct: bytes, cts:list) -> bytes:
calls_before = ['func8', 'func7', 'func7', 'func8', 'func6', 'func8', 'func12', 'func6', 'func8', 'func8', 'func6', 'func11', 'func9', 'func7', 'func8']
calls_middle = ['func13', 'func8', 'func7', 'func13', 'func6', 'func9', 'func13', 'func9', 'func9', 'func7', 'func7', 'func13', 'func8', 'func8', 'func8', 'func6', 'func13', 'func8', 'func7', 'func13', 'func13', 'func6', 'func6', 'func6', 'func6', 'func13', 'func7', 'func8', 'func6', 'func6', 'func13', 'func9', 'func6', 'func13', 'func8', 'func7', 'func8', 'func7', 'func13']
calls_after = ['func9', 'func12', 'func7', 'func9', 'func11', 'func6', 'func11', 'func6', 'func12', 'func9', 'func6', 'func9', 'func11', 'func6', 'func12', 'func9', 'func12', 'func8', 'func8']
inv_calls_before = calls_before[::-1]
inv_calls_after = calls_after[::-1]

test_pt = self.do_call(calls_before, pt, self.master_key)
test_ct = self.inv_do_call(inv_calls_after, ct, self.master_key)

key = b"\xff" * 16
b = self.do_call(calls_middle, b"\x00"*16, key)
pt_mat = []
ct_mat = []

for i in range(128):
pt = [0 for _ in range(i)] + [1] + [0 for _ in range(127 - i)]
pt_mat.append(pt)
aesed = self.do_call(calls_middle, bi2by(pt), key)
ct_mat.append(by2bi(xor(b, aesed)))

pt_mat = matrix(GF(2),pt_mat).T
ct_mat = matrix(GF(2),ct_mat).T
print(pt_mat.dimensions())
print(ct_mat.dimensions())

M = ct_mat.solve_right(pt_mat)
b = vector(GF(2), by2bi(b))


rand_pt = b"\x3f"*16
assert self.do_call(calls_middle, rand_pt, key) == bi2by(M * vector(GF(2), by2bi(rand_pt)) + b)


collect_b = vector(GF(2), by2bi(test_ct)) - M * vector(GF(2), by2bi(test_pt))

ct = b""

for ctsi in cts:
ctsi = self.inv_do_call(inv_calls_after, ctsi, b"\x00"*16)
test_pt = bi2by((M^(-1)* (vector(GF(2), by2bi(ctsi)) - collect_b)))
ct += self.inv_do_call(inv_calls_before, test_pt, self.master_key)
print(ct)


aes = AES(b"\x00"*16)
cts = [b'\xb4{\xfeL\xa6\xf3\xcb\xcf\r\x95\xf1E\x8e\x1cD.',b'*\xbd\xe8\xc6\xcb\xb8\xcf\xbf\xfb\xf7\xdd\xccO\x8b\xa6\xfc',b')\xac\t\xeaS\x05\xc1\x81\x1a\xb8E\x9a\xc4\x84Q\xa2',b'\xdbn\xad\x16 \xd4\xe5\xcdoQ\xda|\x17\t\x8b\x04',b'\xb2!\xd4m\x9eY&\xd7:\xebu\x06\xbb^&\x1e']
aes.func16_pipeline(b'minipif sends <3' , b'\x93?X\x9e\x15\xfa\xd1g\xc8\x94\x05\x9cy\xb5S\r', cts)
b'CTF{c26e15467b875abc7fe6be20b796d6cd2b980f1fd8130de5c5cbc2206e46fc53}PADPADPADPA'