Anaken21sec1 - author: anaken21sec1 - crypto

Writeup: bylal

Here’s a step-by-step write-up of how I approached and solved the challenge:


Challenge Overview

You’re given:

Your goal is to recover the original plaintext (the flag) and present it wrapped in byuctf{…}.


1. Understanding the Encryption

1.1 Block Size & Padding

1.2 Mapping Letters → 6×6 Matrix

1.3 Scrambling: Permute + Add Rounds

1.4 Re-encoding the Matrix

1.5 Columnar Transposition


2. Building the Decryption

To reverse the above:

  1. Reverse columnar transposition

    • Compute column lengths from ciphertext length and key length.
    • Slice off each column (in sorted-key order) into its original box.
    • Reassemble by taking one letter at a time from each box in box-order.
  2. Invert the 6×6 rounds

    • For each 12-letter block, rebuild the 6×6 matrix from two letters per row.
    • Loop backwards over the keyNums:

      1. Inverse add (subtract instead of add, same region choices)
      2. Inverse permute (apply the inverse mapping of the chosen permutation table)
  3. Reconstruct the plaintext letters

    • Read off row 0–2 columns for first 6 letters, then row 3–5 for next 6, converting base-3 back to 1–26→‘a’–‘z’, discarding any “0” placeholders.

3. Example Decryption Script (Excerpt)

def inverse_permute(blockM, count):
    inverseBlock = np.zeros((6,6))
    for i in range(6):
        for j in range(6):
            index = int(permutes[count][i,j] - 1)
            inverseBlock[index//6, index%6] = blockM[i,j]
    return inverseBlock

def inverse_add(blockM, count):
    if count == 0:
        for i in range(6):
            for j in range(6):
                if (i+j)%2 == 0:
                    blockM[i,j] -= 1
    elif count == 1:
        blockM[3:,3:] = blockM[3:,3:] - blockM[:3,:3]
    elif count == 2:
        blockM[:3,:3] = blockM[:3,:3] - blockM[3:,3:]
    elif count == 3:
        blockM[3:,:3] = blockM[3:,:3] - blockM[:3,3:]
    else:
        blockM[:3,3:] = blockM[:3,3:] - blockM[3:,:3]
    return np.mod(blockM, 3)

def reverse_columnar_transposition(text, key):
    keyNums = [ord(k)-97 for k in key]
    reducedKeyNums = []
    [reducedKeyNums.append(x) for x in keyNums if x not in reducedKeyNums]
    keyLen = len(reducedKeyNums)

    colLens = [len(text) // keyLen for _ in range(keyLen)]
    extra = len(text) % keyLen
    for i in range(extra):
        colLens[i] += 1

    sortedKey = sorted((val, idx) for idx, val in enumerate(reducedKeyNums))

    columns = [''] * keyLen
    pos = 0
    for val, origIdx in sortedKey:
        l = colLens[origIdx]
        columns[origIdx] = text[pos:pos+l]
        pos += l

    plaintext = ''
    for i in range(max(colLens)):
        for col in columns:
            if i < len(col):
                plaintext += col[i]
    return plaintext

def decrypt(ciphertext, key):
    reversedText = reverse_columnar_transposition(ciphertext, key)
    keyNums = [ord(k)-97 for k in key]

    blocks = [reversedText[12*i:12*(i+1)] for i in range(len(reversedText)//12)]
    decrypted = ""

    for block in blocks:
        blockM = np.zeros((6,6))
        for i in range(6):
            val = ord(block[i])-96
            blockM[i,0] = val // 9
            blockM[i,1] = (val % 9) // 3
            blockM[i,2] = val % 3
        for i in range(6):
            val = ord(block[i+6])-96
            blockM[i,3] = val // 9
            blockM[i,4] = (val % 9) // 3
            blockM[i,5] = val % 3

        for keyNum in reversed(keyNums):
            blockM = inverse_add(blockM, keyNum % 5)
            blockM = inverse_permute(blockM, (keyNum // 5) % 5)

        for i in range(6):
            val = int(9*blockM[0,i] + 3*blockM[1,i] + blockM[2,i])
            if val > 0:
                decrypted += chr(val + 96)
        for i in range(6):
            val = int(9*blockM[3,i] + 3*blockM[4,i] + blockM[5,i])
            if val > 0:
                decrypted += chr(val + 96)

    return decrypted

4. Recovering the Flag

Running the full decryption against

cipher = "cnpiaytjyzggnnnktjzcvuzjexxkvnrlfzectovhfswyphjt"
key    = "orygwktcjpb"

yields the plaintext:

revisreallythestartingpointformostcategoriesiydk

So the final flag is:

byuctf{revisreallythestartingpointformostcategoriesiydk}

Final Answer

Flag:

byuctf{revisreallythestartingpointformostcategoriesiydk}