Skip to content

Commit 9fec17c

Browse files
committed
fix: added spaes.py script needed for the full solution script
1 parent fd31f01 commit 9fec17c

File tree

1 file changed

+163
-0
lines changed
  • content/writeups/2025-09-30-ctrl-space-quals-spaes-1

1 file changed

+163
-0
lines changed

content/writeups/2025-09-30-ctrl-space-quals-spaes-1/index.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ print([k.hex() for k in solutions])
266266

267267
{{< details summary="Full Exploit Code" open=false class="big-detail">}}
268268

269+
#### solve.py
270+
269271
```py
270272
from spaes import *
271273
import operator
@@ -448,8 +450,169 @@ def pwn():
448450
pwn()
449451
```
450452

453+
Additionally, we need the implementation of the cipher that includes the decryption routine, which is provided below.
454+
455+
#### spaes.py
456+
457+
```py
458+
from Crypto.Util.Padding import pad, unpad
459+
460+
###
461+
462+
SBOX = [4, 14, 13, 5, 0, 9, 2, 15, 11, 8, 12, 3, 1, 6, 7, 10]
463+
464+
inv_sbox = [0] * 16
465+
for i, v in enumerate(SBOX):
466+
inv_sbox[v] = i
467+
468+
ROUNDS = 6
469+
470+
CONSTS = ['6d6ab780eb885a101263a3e2f73520c9', 'f71df57947881932a33a3a0b8732b912', '0aa5df6fadf91c843977d378cc721147', '4a8f29cf09b62619c596465a59fb9827', '29b408cfd4910c80866f5121c6b1cc77', '8589c67a30dbced873b34bd04f40b7cb', '6d64bc8485817ba330fc81b9d2899532', '46495adad2786761ae89e8c26ff1c769', '747470d62b219d12abf9a0816b950639', '4ed2d429061e5d13a2b2ad1df1e63110']
471+
472+
def rotr_128(x, n):
473+
return ((x >> n) | (x << (128 - n))) & ((1 << 128) - 1)
474+
475+
def rotl_4(x, n):
476+
return ((x << n) | (x >> (4 - n))) & ((1 << 4) - 1)
477+
478+
def to_matrix(bts):
479+
return [
480+
[bts[i] >> 4 for i in range(0, 16, 2)],
481+
[bts[i] & 0x0F for i in range(0, 16, 2)],
482+
[bts[i] >> 4 for i in range(1, 16, 2)],
483+
[bts[i] & 0x0F for i in range(1, 16, 2)],
484+
]
485+
486+
def from_matrix(state):
487+
return bytes([state[i][j] << 4 | state[i + 1][j] for j in range(8) for i in (0, 2)])
488+
489+
def shift_rows(state):
490+
return [
491+
state[0],
492+
state[1][1:] + state[1][:1],
493+
state[2][2:] + state[2][:2],
494+
state[3][3:] + state[3][:3]
495+
]
496+
497+
def inv_shift_rows(state):
498+
return [
499+
state[0],
500+
state[1][-1:] + state[1][:-1],
501+
state[2][-2:] + state[2][:-2],
502+
state[3][-3:] + state[3][:-3],
503+
]
504+
505+
def mix_columns(state):
506+
mixed = [[0 for i in range(8)] for j in range(4)]
507+
for i in range(8):
508+
mixed[0][i] = state[1][i] ^ rotl_4(state[2][i], 1) ^ rotl_4(state[3][i], 2)
509+
mixed[1][i] = state[2][i] ^ rotl_4(state[3][i], 1) ^ rotl_4(state[0][i], 2)
510+
mixed[2][i] = state[3][i] ^ rotl_4(state[0][i], 1) ^ rotl_4(state[1][i], 2)
511+
mixed[3][i] = state[0][i] ^ rotl_4(state[1][i], 1) ^ rotl_4(state[2][i], 2)
512+
return mixed
513+
514+
def inv_mix_columns(state):
515+
unmixed = [[0 for i in range(8)] for j in range(4)]
516+
for i in range(8):
517+
unmixed[0][i] = state[3][i] ^ rotl_4(state[1][i], 2) ^ rotl_4(state[2][i], 3)
518+
unmixed[1][i] = state[0][i] ^ rotl_4(state[2][i], 2) ^ rotl_4(state[3][i], 3)
519+
unmixed[2][i] = state[1][i] ^ rotl_4(state[3][i], 2) ^ rotl_4(state[0][i], 3)
520+
unmixed[3][i] = state[2][i] ^ rotl_4(state[0][i], 2) ^ rotl_4(state[1][i], 3)
521+
return unmixed
522+
523+
def enc(m, k, t):
524+
assert len(m) == 16
525+
assert len(k) == 16
526+
assert len(t) == 16
527+
528+
RCON = [bytes.fromhex(x) for x in CONSTS]
529+
530+
final_key = int.from_bytes(k, byteorder='big')
531+
final_key = rotr_128(final_key, 63) ^ (final_key >> 1)
532+
final_key = int.to_bytes(final_key, length=16, byteorder='big')
533+
534+
state = to_matrix(m)
535+
key_matrix = to_matrix(k)
536+
tweak_matrix = to_matrix(t)
537+
final_key_matrix = to_matrix(final_key)
538+
539+
state = [[state[i][j] ^ key_matrix[i][j] for j in range(8)] for i in range(4)]
540+
541+
for r in range(ROUNDS-1):
542+
state = [[SBOX[state[i][j]] for j in range(8)] for i in range(4)]
543+
state = shift_rows(state)
544+
state = mix_columns(state)
545+
round_const_matrix = to_matrix(RCON[r])
546+
state = [[state[i][j] ^ round_const_matrix[i][j] for j in range(8)] for i in range(4)]
547+
548+
if r % 2 == 0:
549+
state = [[state[i][j] ^ key_matrix[i][j] for j in range(8)] for i in range(4)]
550+
else:
551+
state = [[state[i][j] ^ tweak_matrix[i][j] for j in range(8)] for i in range(4)]
552+
553+
state = [[SBOX[state[i][j]] for j in range(8)] for i in range(4)]
554+
state = shift_rows(state)
555+
state = [[state[i][j] ^ final_key_matrix[i][j] for j in range(8)] for i in range(4)]
556+
557+
c = from_matrix(state)
558+
return c
559+
560+
def dec(c, k, t):
561+
assert len(c) == 16
562+
assert len(k) == 16
563+
assert len(t) == 16
564+
565+
RCON = [bytes.fromhex(x) for x in CONSTS]
566+
567+
final_key = int.from_bytes(k, byteorder='big')
568+
final_key = rotr_128(final_key, 63) ^ (final_key >> 1)
569+
final_key = int.to_bytes(final_key, length=16, byteorder='big')
570+
571+
key_matrix = to_matrix(k)
572+
tweak_matrix = to_matrix(t)
573+
final_key_matrix = to_matrix(final_key)
574+
state = to_matrix(c)
575+
576+
state = [[state[i][j] ^ final_key_matrix[i][j] for j in range(8)] for i in range(4)]
577+
state = inv_shift_rows(state)
578+
state = [[inv_sbox[state[i][j]] for j in range(8)] for i in range(4)]
579+
580+
for r in range(ROUNDS - 2, -1, -1):
581+
if r % 2 == 0:
582+
state = [[state[i][j] ^ key_matrix[i][j] for j in range(8)] for i in range(4)]
583+
else:
584+
state = [[state[i][j] ^ tweak_matrix[i][j] for j in range(8)] for i in range(4)]
585+
586+
round_const_matrix = to_matrix(RCON[r])
587+
state = [[state[i][j] ^ round_const_matrix[i][j] for j in range(8)] for i in range(4)]
588+
state = inv_mix_columns(state)
589+
state = inv_shift_rows(state)
590+
state = [[inv_sbox[state[i][j]] for j in range(8)] for i in range(4)]
591+
592+
state = [[state[i][j] ^ key_matrix[i][j] for j in range(8)] for i in range(4)]
593+
return from_matrix(state)
594+
595+
###
596+
597+
class spAES:
598+
def __init__(self, master_key):
599+
self.master_key = master_key
600+
self.tweak = b"\x00"*16
601+
602+
def encrypt_ecb(self, plaintext):
603+
plaintext = pad(plaintext, 16)
604+
blocks = [plaintext[i:i+16] for i in range(0, len(plaintext), 16)]
605+
return b"".join([enc(block, self.master_key, self.tweak) for block in blocks])
606+
607+
def decrypt_ecb(self, chipertext):
608+
blocks = [chipertext[i:i+16] for i in range(0, len(chipertext), 16)]
609+
return unpad(b"".join([dec(block, self.master_key, self.tweak) for block in blocks]), 16)
610+
```
611+
451612
{{</details>}}
452613

614+
615+
453616
Below the execution.
454617

455618
```

0 commit comments

Comments
 (0)