Cryptomedium
A Secure Cipher
by Pavel Knespl (capyplivl)
A homemade block cipher that is really Madryga (8 rounds, 3-byte blocks, data-dependent rotate, byte XOR with a rotating key) keyed by secrets.token_bytes(4). The key is only 32 bits, so the whole keyspace is brute-forceable on a laptop.
Solve
- Read the encryptor. Each round transforms every 3-byte block in place, independently, so each block is its own 8-round mini-cipher keyed only by the rotating 64-bit key state derived from the 4-byte key. There is no diffusion between blocks. The loop
range(0, len-2, 3)over a 32-byte flag stops at index 27, soct[30:32]is plaintext and readst}directly. - The flag starts
SVIUSCG{, giving known blocksSVI,USC,G{?. Encrypt a known block forward and compare against the ciphertext rather than inverting anything. The fullcrack.crecovers the key in three brute-forced chunks (top 8 bits fromUSC, bottom 13 fromG{?, middle 11 pinned bySVI) then peels each remaining block:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
static uint32_t eb(const uint8_t *p, uint64_t k, int ki){
uint32_t a = p[0], b = p[1], c = p[2];
for (int n = 0; n < 8; n++){
uint8_t kk = (k >> (ki * 8)) & 0xff;
uint32_t w = (a << 16) | (b << 8) | (c ^ kk);
int r = w & 7;
w = ((w >> r) | (w << (24 - r))) & 0xffffff;
a = (w >> 16) & 0xff; b = (w >> 8) & 0xff; c = w & 0xff;
k = (k >> 3) | (k << 61);
}
return (a << 16) | (b << 8) | c;
}
int main(void){
uint8_t ct[] = {
0x4a,0xae,0x4b,0x7a,0xa9,0xf8,0xab,0x7b,0x44,0xec,0x79,0xa3,
0x47,0x96,0x73,0x75,0x9e,0x7e,0xee,0x5c,0xcb,0xdd,0x5c,0x10,
0xcf,0xaf,0xfb,0x7b,0x43,0x16,0x74,0x7d
};
size_t n = sizeof(ct);
uint32_t cb0 = (ct[0] << 16) | (ct[1] << 8) | ct[2];
uint32_t cb1 = (ct[3] << 16) | (ct[4] << 8) | ct[5];
uint32_t cb2 = (ct[6] << 16) | (ct[7] << 8) | ct[8];
int tops[256], nt = 0;
for (int t = 0; t < 256; t++)
if (eb((const uint8_t*)"USC", (uint64_t)t << 24, 3) == cb1) tops[nt++] = t;
int bots[8192], nb = 0;
for (int bk = 0; bk < 8192; bk++)
for (int p2 = 32; p2 < 127; p2++){
uint8_t blk[3] = {'G', '{', (uint8_t)p2};
if (eb(blk, bk, 6) == cb2){ bots[nb++] = bk; break; }
}
uint64_t key = 0; int found = 0;
for (int i = 0; i < nt && !found; i++)
for (int j = 0; j < nb && !found; j++)
for (int mid = 0; mid < 2048; mid++){
uint64_t cand = ((uint64_t)tops[i] << 24) | ((uint64_t)mid << 13) | bots[j];
if (eb((const uint8_t*)"SVI", cand, 0) == cb0){ key = cand; found = 1; break; }
}
printf("KEY = %08llx\n", (unsigned long long)key);
const char *cs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_}";
uint8_t flag[64]; memcpy(flag, ct, n);
for (size_t idx = 0; idx + 2 < n; idx += 3){
int ki = idx % 8;
uint32_t cblk = (ct[idx] << 16) | (ct[idx+1] << 8) | ct[idx+2];
if (idx == 0){ memcpy(flag, "SVI", 3); continue; }
if (idx == 3){ memcpy(flag + 3, "USC", 3); continue; }
const char *s0 = cs, *s1 = cs, *s2 = cs;
if (idx == 6){ s0 = "G"; s1 = "{"; }
for (const char *a = s0; *a; a++)
for (const char *b = s1; *b; b++)
for (const char *c = s2; *c; c++){
uint8_t pt[3] = {(uint8_t)*a, (uint8_t)*b, (uint8_t)*c};
if (eb(pt, key, ki) == cblk){
flag[idx] = *a; flag[idx+1] = *b; flag[idx+2] = *c;
goto next;
}
}
next:;
}
flag[n] = 0;
printf("%s\n", (char*)flag);
return 0;
}
- Brute all
2^32keys. The rotate byth&7is not bijective, so two known blocks leave many candidate keys; adding the third known block (G{?) pins a single key:KEY = cc035499 - For that key, brute the 3 bytes of each remaining block, keeping flag-charset preimages, then re-encrypt the full guess and confirm it matches the ciphertext exactly.
Verified independently in Python: re-encrypting the flag under key$ gcc -O3 -march=native -o crack crack.c && ./crack enc = 4aae4b7aa9f8ab7b44ec79a3479673759e7eee5ccbdd5c10cfaffb7b4316747d target = 4aae4b7aa9f8ab7b44ec79a3479673759e7eee5ccbdd5c10cfaffb7b4316747d MATCH = Truecc035499reproduces every ciphertext block and the trailingt}.
Flag: SVIUSCG{m4dryg4_1s_s3cur3_r1ght}