Here’s a step-by-step write-up of how I approached and solved the challenge:
You’re given:
orygwktcjpb
Encrypted flag:
cnpiaytjyzggnnnktjzcvuzjexxkvnrlfzectovhfswyphjt
A Python implementation of the encryption routine, consisting of two main stages:
Your goal is to recover the original plaintext (the flag) and present it wrapped in byuctf{…}
.
Each block is laid out into a 6×6 matrix in two halves:
Row 0–2 hold the first 6 letters, with each letter number (1–26) broken into base-3 “digits”:
Row 3–5 do the same for the last 6 letters.
For each keyNum
in sequence:
(keyNum//5) % 5
.keyNum % 5
.After all rounds, each row of 6 cells yields two letters:
To reverse the above:
Reverse columnar transposition
Invert the 6×6 rounds
Loop backwards over the keyNums:
Reconstruct the plaintext letters
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
Running the full decryption against
cipher = "cnpiaytjyzggnnnktjzcvuzjexxkvnrlfzectovhfswyphjt"
key = "orygwktcjpb"
yields the plaintext:
revisreallythestartingpointformostcategoriesiydk
So the final flag is:
byuctf{revisreallythestartingpointformostcategoriesiydk}
Flag:
byuctf{revisreallythestartingpointformostcategoriesiydk}