//Dynastic (Crypto)
Challenge: Dynastic
Flag format: HTB{...}
>Summary
This writeup explains how I solved the "Dynastic" crypto challenge. The repository provided a ZIP containing source.py and output.txt. The source.py contained an encryption function that shifts letters by their index (a variant of a Trithemius autokey-like cipher). The output.txt contained the ciphertext produced by encrypting the secret FLAG with that function. By reversing the shift (subtracting the index modulo 26), we can recover the original flag body and wrap it with the HTB format.
I include all relevant code used for analysis and decryption. The decryption script is numbered as requested (001_decrypt.py) and the writeup as 001_writeup.md.
>Files provided
-
crypto_dynastic/source.py(from the challenge ZIP) -
crypto_dynastic/output.txt(from the challenge ZIP) — contains the ciphertext
Contents of crypto_dynastic/source.py (verbatim):
from secret import FLAG
from random import randint
def to_identity_map(a):
return ord(a) - 0x41
def from_identity_map(a):
return chr(a % 26 + 0x41)
def encrypt(m):
c = ''
for i in range(len(m)):
ch = m[i]
if not ch.isalpha():
ech = ch
else:
chi = to_identity_map(ch)
ech = from_identity_map(chi + i)
c += ech
return c
with open('output.txt', 'w') as f:
f.write('Make sure you wrap the decrypted text with the HTB flag format :-]\n')
f.write(encrypt(FLAG))
Notes:
-
The
encrypt(m)function maps uppercase ASCII letters (A-Z) to 0..25 viato_identity_map, then adds the indexi, and maps back into uppercase letters. -
Non-alphabetic characters are passed through unchanged.
-
This is a position-dependent Caesar shift. Each character is Caesar-shifted by its position index
i.
Contents of crypto_dynastic/output.txt (verbatim):
Make sure you wrap the decrypted text with the HTB flag format :-]
DJF_CTA_SWYH_NPDKK_MBZ_QPHTIGPMZY_KRZSQE?!_ZL_CN_PGLIMCU_YU_KJODME_RYGZXL
The second line is the ciphertext — the result of encrypt(FLAG).
>Analysis & attack
-
Recognize the cipher: the code reveals a per-position shift (Trithemius/autoshift). The shift for byte at position
iis+imodulo 26. -
To decrypt, for each alphabetic character at index
i(0-based), subtractimodulo 26. Non-alpha characters remain intact. -
The ciphertext is entirely uppercase letters and underscores ("_") for separators. So we implement a simple inverse function.
Decryption algorithm (contract)
-
Input: ciphertext string (uppercase A-Z, underscores, punctuation)
-
Output: plaintext: the original FLAG body
-
Operation: For each character at index i:
- if not alphabetic: leave as-is
- else: convert letter to 0..25 -> subtract i -> mod 26 -> convert back to letter
Edge cases:
-
Non-alphabetic characters must be preserved as-is (spaces, punctuation, underscores)
-
Indexing is 0-based as shown in
source.py
>Decryption script
I wrote 001_decrypt.py to automate the decryption and pick the ciphertext line from crypto_dynastic/output.txt.
Path: /home/noigel/HTB/Crypto/Dynastic/001_decrypt.py
# 001_decrypt.py
# Decrypts the ciphertext in crypto_dynastic/output.txt produced by source.py
# The encryption shifts each uppercase letter by its index (i) modulo 26.
def decrypt_autoshift(ct):
pt = ''
for i, ch in enumerate(ct):
if not ch.isalpha():
pt += ch
else:
# Assume uppercase A-Z as used in source.py
base = ord('A')
chi = ord(ch) - base
pi = (chi - i) % 26
pt += chr(pi + base)
return pt
if __name__ == '__main__':
import os
path = os.path.join('crypto_dynastic', 'output.txt')
print('🔎 Reading ciphertext from:', path)
with open(path, 'r') as f:
lines = [l.rstrip('\n') for l in f.readlines()]
# find the line that looks like the ciphertext: choose the longest line with many uppercase letters
candidate_lines = []
for l in lines:
upper_count = sum(1 for ch in l if ch.isalpha() and ch.isupper())
candidate_lines.append((upper_count, len(l), l))
candidate_lines.sort(reverse=True)
# pick the line with the most uppercase letters (and length)
cipher = None
for upper_count, length, l in candidate_lines:
if upper_count >= 10:
cipher = l
break
if cipher is None:
print('❌ Could not find ciphertext line in output.txt')
raise SystemExit(1)
print('✉️ Ciphertext:')
print('---')
print(cipher)
print('---')
print('🔐 Decrypting...')
plaintext = decrypt_autoshift(cipher)
print('\n🧾 Plaintext:')
print('---')
print(plaintext)
print('---')
# Try to locate HTB{...} or otherwise show candidate flag-like token
import re
m = re.search(r'HTB\{.*?\}', plaintext)
if m:
print('\n🎯 Flag found: ' + m.group(0))
else:
# If not found, show candidate uppercase words that might be wrapped
words = [w for w in plaintext.split('_') if len(w)>=3]
print('\nℹ️ No HTB{...} found inside plaintext. Candidate segments (underscore separated):')
print(', '.join(words))
print('\n➡️ If the plaintext is the flag body, wrap it as HTB{...} to submit.')
How to run
I ran the script inside a Python virtual environment (numbered 002_venv as requested). Example commands used (from the challenge directory):
python3 -m venv 002_venv
. 002_venv/bin/activate
python3 001_decrypt.py
The script prints the ciphertext and the decrypted plaintext.
>Decryption result
The script produced the plaintext:
DID_YOU_KNOW_ABOUT_THE_TRITHEMIUS_CIPHER?!_IT_IS_SIMILAR_TO_CAESAR_CIPHER
This is the decrypted FLAG body. The challenge's instruction line in output.txt explicitly told us to wrap the decrypted text in the HTB flag format, so the correct submitted flag is exactly that plaintext wrapped in HTB{...}.
Final flag
HTB{DID_YOU_KNOW_ABOUT_THE_TRITHEMIUS_CIPHER?!_IT_IS_SIMILAR_TO_CAESAR_CIPHER}
Note: Some CTF platforms restrict punctuation inside flags. However, the challenge explicitly told us to "wrap the decrypted text with the HTB flag format" — so we retain punctuation exactly as decrypted.
>Verification
If you want a one-liner to print the flag from the file using the same method in the shell (without the Python script), you can use Python in a one-liner from the challenge directory:
python3 - <<'PY'
ct = open('crypto_dynastic/output.txt').read().splitlines()[1]
pt = ''.join((chr(((ord(c)-65 - i) % 26)+65) if c.isalpha() else c) for i,c in enumerate(ct))
print('HTB{' + pt + '}')
PY
This prints the wrapped flag.
>Notes & takeaways
-
The cipher used is position-based shifting (Trithemius-style). Recognizing the pattern in code is usually simpler than attempting classical frequency analysis.
-
Always read the provided
source.pyor server code in CTF crypto challenges — often the encryption method is fully revealed. -
Keep original punctuation and separations unless the challenge specifically states otherwise.