arrow_backUS Cyber Games 2026
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

  1. 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, so ct[30:32] is plaintext and reads t} directly.
  2. The flag starts SVIUSCG{, giving known blocks SVI, USC, G{?. Encrypt a known block forward and compare against the ciphertext rather than inverting anything. The full crack.c recovers the key in three brute-forced chunks (top 8 bits from USC, bottom 13 from G{?, middle 11 pinned by SVI) 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;
}
  1. Brute all 2^32 keys. The rotate by th&7 is not bijective, so two known blocks leave many candidate keys; adding the third known block (G{?) pins a single key:
    KEY = cc035499
    
  2. 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.
    $ gcc -O3 -march=native -o crack crack.c && ./crack
    enc    = 4aae4b7aa9f8ab7b44ec79a3479673759e7eee5ccbdd5c10cfaffb7b4316747d
    target = 4aae4b7aa9f8ab7b44ec79a3479673759e7eee5ccbdd5c10cfaffb7b4316747d
    MATCH  = True
    
    Verified independently in Python: re-encrypting the flag under key cc035499 reproduces every ciphertext block and the trailing t}.

Flag: SVIUSCG{m4dryg4_1s_s3cur3_r1ght}