flagTJCTF

Pretty easy ctf good for beginners like me!

Name
Category

bacon-bits

Cryptography

alchemist-recipe

Cryptography

theartofwar

Cryptography

seeds

Cryptography

close-secrets

Cryptography

double-trouble

Cryptography

dotdotdotv2

Cryptography

pseudo-secure

Cryptography

1

bacon-bits

follow the trail of bacon bits...

flag is all lowercase, with the format of tjctf{...}

starting of the challenge we were given 2 files

 ls
enc.py  out.txt

lets check the enc.py to understand better of the challenge logic

with open('flag.txt') as f: flag = f.read().strip()
with open('text.txt') as t: text = t.read().strip()

baconian = {
'a': '00000',   'b': '00001',
'c': '00010',   'd': '00011',
'e': '00100',   'f': '00101',
'g': '00110',   'h': '00111',
'i': '01000',    'j': '01000',
'k': '01001',    'l': '01010',
'm': '01011',    'n': '01100',
'o': '01101',    'p': '01110',
'q': '01111',    'r': '10000',
's': '10001',    't': '10010',
'u': '10011',    'v': '10011',
'w': '10100',   'x': '10101',
'y': '10110',   'z': '10111'}

text = [*text]
ciphertext = ""
for i,l in enumerate(flag):
    if not l.isalpha(): continue
    change = baconian[l]
    ciphertext += "".join([ts for ix, lt in enumerate(text[i*5:(i+1)*5]) if int(change[ix]) and (ts:=lt.upper()) or (ts:=lt.lower())]) #python lazy boolean evaluation + walrus operator

with open('out.txt', 'w') as e:
    e.write(''.join([chr(ord(i)-13) for i in ciphertext]))

So basicly, the core logic is :

  1. it will take a char and turn it to a 5 bit binary

  2. those bits will determine if the text will be uppercase of lowercase

  3. the output of that will be caesar shifted -13

baconian = {
    'a': '00000',   'b': '00001',
    'c': '00010',   'd': '00011',
    'e': '00100',   'f': '00101',
    'g': '00110',   'h': '00111',
    'i': '01000',    'j': '01000',
    'k': '01001',    'l': '01010',
    'm': '01011',    'n': '01100',
    'o': '01101',    'p': '01110',
    'q': '01111',    'r': '10000',
    's': '10001',    't': '10010',
    'u': '10011',    'v': '10011',
    'w': '10100',   'x': '10101',
    'y': '10110',   'z': '10111'
}

reverse_baconian = {}
for key, value in baconian.items():
    if value not in reverse_baconian:
        reverse_baconian[value] = key

with open('out.txt', 'r') as f:
    out_txt_full = f.read().strip()

truncated_len = (len(out_txt_full) // 5) * 5
out_txt_content = out_txt_full[:truncated_len]

intermediate_ciphertext = "".join([chr(ord(c) + 13) for c in out_txt_content])

binary_flag_string = ""
for char_val in intermediate_ciphertext:
    if 'a' <= char_val <= 'z':
        binary_flag_string += '0'
    elif 'A' <= char_val <= 'Z':
        binary_flag_string += '1'
    else:
        binary_flag_string += '?'

recovered_chars = []
for i in range(0, len(binary_flag_string), 5):
    chunk = binary_flag_string[i:i+5]
    if len(chunk) == 5:
        recovered_chars.append(reverse_baconian.get(chunk, f"[?{chunk}?]"))
    else:
        recovered_chars.append(f"[SHORT:{chunk}]")

recovered_content = "".join(recovered_chars)
print("tjctf{" + recovered_content[5:] + "}")

So we can reverse the provess by first caesar shifting +13 to get the original ciphertext, then we can use the corresponding text to determine if its a bit 0 or bit 1.

After that we can decode this string using the baconian table to get the original text and get the flag!

 python3 solve.py
tjctf{oinkooinkoooinkooooink}
2

alchemist-recipe

an alchemist claims to have a recipe to transform lead into gold. however, he accidentally encrypted it with a peculiar process of his own. he left behind his notes on the encryption method and an encrypted sample. unfortunately, he spilled some magic ink on the notes, making them weirdly formatted. the notes include comments showing how he encrypted his recipe. can you find his "golden" secret?

We were given 2 files in the ctf

 ls
encrypted.txt  chall.py

Lets see the contents of chall.py

import hashlib

SNEEZE_FORK = "AurumPotabileEtChymicumSecretum"
WUMBLE_BAG = 8 

def glorbulate_sprockets_for_bamboozle(blorbo):
    zing = {}
    yarp = hashlib.sha256(blorbo.encode()).digest() 
    zing['flibber'] = list(yarp[:WUMBLE_BAG])
    zing['twizzle'] = list(yarp[WUMBLE_BAG:WUMBLE_BAG+16])
    glimbo = list(yarp[WUMBLE_BAG+16:])
    snorb = list(range(256))
    sploop = 0
    for _ in range(256): 
        for z in glimbo:
            wob = (sploop + z) % 256
            snorb[sploop], snorb[wob] = snorb[wob], snorb[sploop]
            sploop = (sploop + 1) % 256
    zing['drizzle'] = snorb
    return zing

def scrungle_crank(dingus, sprockets):
    if len(dingus) != WUMBLE_BAG:
        raise ValueError(f"Must be {WUMBLE_BAG} wumps for crankshaft.")
    zonked = bytes([sprockets['drizzle'][x] for x in dingus])
    quix = sprockets['twizzle']
    splatted = bytes([zonked[i] ^ quix[i % len(quix)] for i in range(WUMBLE_BAG)])
    wiggle = sprockets['flibber'] 
    waggly = sorted([(wiggle[i], i) for i in range(WUMBLE_BAG)])
    zort = [oof for _, oof in waggly]
    plunk = [0] * WUMBLE_BAG
    for y in range(WUMBLE_BAG):
        x = zort[y]
        plunk[y] = splatted[x]
    return bytes(plunk)

def snizzle_bytegum(bubbles, jellybean):
    fuzz = WUMBLE_BAG - (len(bubbles) % WUMBLE_BAG)
    if fuzz == 0: 
        fuzz = WUMBLE_BAG
    bubbles += bytes([fuzz] * fuzz)
    glomp = b""
    for b in range(0, len(bubbles), WUMBLE_BAG):
        splinter = bubbles[b:b+WUMBLE_BAG]
        zap = scrungle_crank(splinter, jellybean)
        glomp += zap
    return glomp

def main():
    try:
        with open("flag.txt", "rb") as f:
            flag_content = f.read().strip()
    except FileNotFoundError:
        print("Error: flag.txt not found. Create it with the flag content.")
        return

    if not flag_content:
        print("Error: flag.txt is empty.")
        return

    print(f"Original Recipe (for generation only): {flag_content.decode(errors='ignore')}")

    jellybean = glorbulate_sprockets_for_bamboozle(SNEEZE_FORK)
    encrypted_recipe = snizzle_bytegum(flag_content, jellybean)

    with open("encrypted.txt", "w") as f_out:
        f_out.write(encrypted_recipe.hex())

    print(f"\nEncrypted recipe written to encrypted.txt:")
    print(encrypted_recipe.hex())

if __name__ == "__main__":
    main()

So this challenge is pretty kind because we were given a hardcoded key thats AurumPotabileEtChymicumSecretum and the variable WUMBLE_BAG is the block size of the ciphertext.

Generation for jellybean in the function glorbulate_sprockets_for_bamboozle :

  1. it will take the sha256 hash of the hardcoded key

  2. flibber -> the first 8 bytes of the hash

  3. twizzle -> the next 16 bytes of the hash used as a xor key

  4. glimbo -> the remaining bytes as key to shuffle an S-box

  5. drizzle -> S-box

Then it will perform the encryption in snizzle_bytegum this function takes the key and the ciphertext and pads it then each block called splinter will be encrypted using scrungle_crank.

import hashlib

SNEEZE_FORK = "AurumPotabileEtChymicumSecretum"
WUMBLE_BAG = 8

def glorbulate_sprockets_for_bamboozle(blorbo):
    zing = {}
    yarp = hashlib.sha256(blorbo.encode()).digest()
    zing['flibber'] = list(yarp[:WUMBLE_BAG])
    zing['twizzle'] = list(yarp[WUMBLE_BAG:WUMBLE_BAG+16])
    glimbo = list(yarp[WUMBLE_BAG+16:])
    snorb = list(range(256))
    sploop = 0
    for _ in range(256):
        for z in glimbo:
            wob = (sploop + z) % 256
            snorb[sploop], snorb[wob] = snorb[wob], snorb[sploop]
            sploop = (sploop + 1) % 256
    zing['drizzle'] = snorb
    return zing

def prepare_decryption_sprockets(sprockets):
    inv_drizzle = [0] * 256
    for i, val in enumerate(sprockets['drizzle']):
        inv_drizzle[val] = i
    sprockets['inv_drizzle'] = inv_drizzle

    wiggle = sprockets['flibber']
    waggly = sorted([(wiggle[i], i) for i in range(WUMBLE_BAG)])
    zort = [oof for _, oof in waggly]
    sprockets['zort'] = zort
    return sprockets

def unscrungle_crank(plunk_block, sprockets):
    zort = sprockets['zort']
    splatted_recovered_list = [0] * WUMBLE_BAG
    for y in range(WUMBLE_BAG):
        splatted_recovered_list[zort[y]] = plunk_block[y]
    splatted_recovered = bytes(splatted_recovered_list)

    quix = sprockets['twizzle']
    zonked_recovered_list = [splatted_recovered[i] ^ quix[i % len(quix)] for i in range(WUMBLE_BAG)]
    zonked_recovered = bytes(zonked_recovered_list)

    inv_drizzle = sprockets['inv_drizzle']
    dingus_recovered_list = [inv_drizzle[x] for x in zonked_recovered]
    dingus_recovered = bytes(dingus_recovered_list)

    return dingus_recovered

def unsnizzle_bytegum(encrypted_glomp, jellybean):
    decrypted_padded_bytes = b""
    for b_idx in range(0, len(encrypted_glomp), WUMBLE_BAG):
        cipher_block = encrypted_glomp[b_idx : b_idx + WUMBLE_BAG]
        plain_block = unscrungle_crank(cipher_block, jellybean)
        decrypted_padded_bytes += plain_block

    pad_val = decrypted_padded_bytes[-1]
    return decrypted_padded_bytes[:-pad_val]

def main_decrypt():
    with open("encrypted.txt", "r") as f_in:
        hex_encrypted_recipe = f_in.read().strip()

    encrypted_recipe_bytes = bytes.fromhex(hex_encrypted_recipe)

    print(f"Encrypted recipe (hex): {hex_encrypted_recipe}")

    jellybean = glorbulate_sprockets_for_bamboozle(SNEEZE_FORK)
    jellybean_for_decryption = prepare_decryption_sprockets(jellybean)

    decrypted_flag_content = unsnizzle_bytegum(encrypted_recipe_bytes, jellybean_for_decryption)
    print(f"\nDecrypted content (bytes): {decrypted_flag_content}")
    print(f"Decrypted content (utf-8): {decrypted_flag_content.decode(errors='replace')}")

if __name__ == "__main__":
    main_decrypt()
 python3 solve.py
Encrypted recipe (hex): b80854d7b5920901192ea91ccd9f588686d69684ec70583abe46f6747e940c027bdeaa848ecb316e11d9a99c7e87b09e

Decrypted content (bytes): b'tjctf{thank_you_for_making_me_normal_again_yay}'
Decrypted content (utf-8): tjctf{thank_you_for_making_me_normal_again_yay}
3

theartofwar

"In the midst of chaos, there is also opportunity"

  • Sun Tzu, The Art of War

4

seeds

You can't grow crops without planting the seeds.

(Server is hosted in the US eastern time zone)

5

close-secrets

I tried to make my Diffie-Hellman implementation a bit more interesting, but maybe I went too far?

Can you make sense of this custom cryptography and decode the encrypted flag?

6

double-trouble

Twice the encryption, half the security.

7

dotdotdotv2

might wanna find out what np . dot does first...

8

pseudo-secure

Can you predict the future? What about the past?

Last updated