Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
CryptoMedium

Dynastic

CTF writeup for Dynastic from HTB CTF TRY OUT

//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):

python

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 via to_identity_map, then adds the index i, 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

  1. Recognize the cipher: the code reveals a per-position shift (Trithemius/autoshift). The shift for byte at position i is +i modulo 26.

  2. To decrypt, for each alphabetic character at index i (0-based), subtract i modulo 26. Non-alpha characters remain intact.

  3. 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

python

# 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):

bash

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:

bash

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.py or server code in CTF crypto challenges — often the encryption method is fully revealed.

  • Keep original punctuation and separations unless the challenge specifically states otherwise.