3 perc
Multlock
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}
...