Forrás

A feladathoz egy zip fájl van megadva, benne egy source.py és egy output.txt fájl.

A python fájl tartalma:

import random
import string
import base64
import time
from secret import FLAG

def generate_key(seed, length=16):
    random.seed(seed)
    key = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
    return key

def polyalphabetic_encrypt(plaintext, key):
    key_length = len(key)
    ciphertext = []
    for i, char in enumerate(plaintext):
        key_char = key[i % key_length]
        encrypted_char = chr((ord(char) + ord(key_char)) % 256)
        ciphertext.append(encrypted_char)
    return base64.b64encode(''.join(ciphertext).encode()).decode()

def xor_cipher(text, key):
    return bytes([ord(c) ^ key for c in text])

def get_timestamp_based_keys():
    timestamp = int(time.time())
    if timestamp % 2 == 0:
        key_seed = random.randint(1, 1000)
        xor_key = 42
    else:
        key_seed = 42
        xor_key = random.randint(1, 255)
    return key_seed, xor_key

def main():
    # Split the flag
    flag_half1 = FLAG[:len(FLAG)//2]
    flag_half2 = FLAG[len(FLAG)//2:]
    
    encrypted_flags = []
    
    for _ in range(2):
        key_seed, xor_key = get_timestamp_based_keys()
        key = generate_key(key_seed)
        encrypted_half = polyalphabetic_encrypt(flag_half1 if len(encrypted_flags) == 0 else flag_half2, key)
        encrypted_half = xor_cipher(encrypted_half, xor_key)
        encrypted_flags.append(encrypted_half.hex())
        time.sleep(1)
    
    # Save encrypted flags to output.txt
    with open('output.txt', 'w') as f:
        f.write(f"{encrypted_flags[0]}\n{encrypted_flags[1]}\n")


if __name__ == "__main__":
    main()

Magyarázat

A program fog egy flag értéket és kettészedi a felénél. Mindkét félhez külön kulcsokat generál, de azonos módon titkosítja őket. A részeket előbb egy Vigenère-rejtjelhez hasonló módszerrel titkosítja (itt az egész bájtot veszi, nem csak az ábécét), base64-el bekódolja, majd XOR-oz minden bájtot egy kulcs bájttal. A kulcsok generálása időhöz van kötve. Páros másodpercnyi időben [1,1000] tartományból választ seedet a Vigenère kulcs generálásához (ami mindig 16 hosszú), az XOR bájt ilyenkor 42. Páratlan másodperc szám esetén a Vigenère kulcs generátor seedje lesz 42 és az XOR bájt véletlenszerű. Mindkét fél más paritású másodpercben titkosítódott le, mivel a titkosítások között vár 1 másodpercig.

Cél

A titkosításhoz használt kulcsok kiszámítása és a flag visszaállítása.

Megoldás

A kódolt flagrészeket beillesztve a Cyberchefbe rá lehet jönni, hogy a 2. fél páros másodpercben titkosítódott, mivel a 42 XOR bájtra egy base64 szöveget ad vissza. Tehát az 1. fél páratlanban, míg a 2. párosban jött létre. A Cyberchef XOR bruteforce funkcióját használva kiderül, hogy az 1. fél XOR bájtja 119, mivel ez ad csak értelmes base64 kódot. Ez azt is jelenti, hogy a kulcs generálásához használt seed a 42-es szám. A generált kulcs pedig: OhbVrpoiVgRV5IfL.

A program által használt titkosítás inverze:

def polyalphabetic_decrypt(ciphertext: str, key: str) -> str:
    ciphertext = base64.b64decode(ciphertext.encode()).decode()
    key_length = len(key)
    plaintext = []
    for i, char in enumerate(ciphertext):
        key_char = key[i % key_length]
        decrypted_char = chr((ord(char) - ord(key_char)) % 256)
        plaintext.append(decrypted_char)
    return ''.join(plaintext)

Az első fél visszafejtése a kulccsal:

>>> polyalphabetic_decrypt("wpfCvMKkw5HDpsOZw5zDjsOJw5vCs8ODwqXCqMOIwq3DgsONw4bCtcOXw57DksObw4/Dl8OG", 'OhbVrpoiVgRV5IfL')
'HTB{timestamp_based_encrypt'

A 2. félnél már tudjuk, hogy 42-vel kell XOR-ozni. A seed megtalálásához pedig csak 1000 lehetőséget kell végigpróbálni, ami nem olyan sok.

def polyalphabetic_bruteforce(value: str) -> None:
    for key_seed in range(1, 1000+1):
        key = generate_key(key_seed)
        plaintext = polyalphabetic_decrypt(value, key)
        if (plaintext[-1] == '}'):
            print(plaintext)

A néhány kijött eredmény között felbukkan egy értelmes szöveg is:

>>> polyalphabetic_bruteforce("w5bDgcK0w4TDjMK5wq3DqsKjw5PCq8KdwrzDh8Oawq3DjMK7wqXDlcOVwrXCu8OgwqfDmcK1")
...
ion_is_so_secure_i_promise}
...