1. rész

Forrás

A feladathoz egy zip fájl és egy weboldal címe van megadva. A weboldalon felhasználó nevet és egy jelszót kér. A zip-ben egy bináris encrypted_message.aes és egy generator.py található. A python fájl tartalma:

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import binascii, string


with open('password.txt') as pwd_file:
    pwd = pwd_file.read().strip()

# check printability
assert all(k in string.ascii_letters + string.digits for k in pwd)

pwd = pwd.encode()

if len(pwd) not in [16, 24, 32]:
    raise ValueError("Invalid AES key length. Key must be 16, 24, or 32 bytes long.")

with open('message.txt', 'r') as message_file:
    message = message_file.read().strip()  

words = message.split()

ciphertext = b""
cipher = AES.new(pwd, AES.MODE_ECB)

for word in words:
    word_padded = pad(word.encode(), AES.block_size)
    encrypted_word = cipher.encrypt(word_padded)
    ciphertext += encrypted_word


with open('encrypted_message.aes', 'wb') as file:
    file.write(ciphertext)

print("Encryption complete. Ciphertext saved to 'encrypted_message.aes'.")

Magyarázat

A encrypted_message.aes fájl tartalmát a generator.py fájl hozta létre. A program egy password.txt fájlból beolvas egy jelszót, amiről kiderül, hogy csak (angol) alfanumerikus karaktereket tartalmaz és 16, 24 vagy 32 karakter hosszú. Beolvas ezen kívül egy message.txt fájlt is, amit szavanként feldarabol, és a jelszóval eltitkosítja AES-ECB titkosítással, majd a titkos blokkokat kiírja egy fájlba.
Tudni kell az AES-ről, hogy 16 bájt hosszúságú blokkokat fogad, és ugyan ilyen hosszú blokkokat ad vissza. Amikor a titkosítandó adat mérete nem pontosan osztható 16 bájtal, ki kell egészíteni. Ezt teszi a pad függvény. Az érdekesség, hogy a program az AES-t ECB módban használja, aminek egy nagy veszélye, hogy nem tesz entrópiát a titkosítandó adatba, azaz módosítás nélkül titkosít el minden blokkot. Ezzel az a baj, hogy az azonos blokkok mindig azonos eredményt adnak.

Cél

A zipben lévő üzenetet kell kititkosítani, abból pedig kiderül, mivel kell belépni az oldalra.

Megoldás

Ha kiszámoljuk a lehetséges jelszavak számát:

>>> 16**62 + 24**62 + 32**62
2085924877185928105442759926841284955304321885717825681976502561860327976404420042599490912256

kiderül, hogy nem lenne értelme elkezdeni bruteforceolni. Jobb ötlet, ha megnézünk inkább egy jelszólistát, hátha benne van. Ilyen például a rockyou.txt, amiből kigyűjtjük a megfelelő jelszavakat: grep -E '^[a-zA-Z0-9]{16}$|^[a-zA-Z0-9]{24}$|^[a-zA-Z0-9]{32}$' rockyou.txt > passwords.txt. Ha feldaraboljuk a fájl tartalmát 16 bájtonként, feltűnik, hogy a 8332988eb6fafe7212c52c0ec20495b6 blokk 7-szer, míg a c151fc2e0b9cec7d36616c458029a99e blokk 6-szor fordul elő. Azt tudjuk, hogy minden blokk egy szót jelöl (ha kisseb mint 16 karakter). Az angolban a két leggyakoribb szó a the és a be. Próbáljuk meg ezeket dekriptálni a kigyűjtött jelszavakkal, például a következő programmal:

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

with open('passwords.txt') as pw_file:
    pws = pw_file.read().splitlines()

encrypted_block1 = bytes.fromhex("c151fc2e0b9cec7d36616c458029a99e")
encrypted_block2 = bytes.fromhex("8332988eb6fafe7212c52c0ec20495b6")

for pw in pws:
    pw = pw.encode()

    cipher = AES.new(pw, AES.MODE_ECB)

    decrypted_block1 = cipher.decrypt(encrypted_block1)
    try:
        unpadded_text1 = unpad(decrypted_block1, AES.block_size)
    except ValueError:
        continue
    else:
        print(pw, unpadded_text1)
        text1 = unpadded_text1.decode()

    decrypted_block2 = cipher.decrypt(encrypted_block2)
    try:
        unpadded_text2 = unpad(decrypted_block2, AES.block_size)
    except ValueError:
        continue
    else:
        print(pw, unpadded_text2)
        text2 = unpadded_text2.decode()

    #ha a két halmaznak van közös eleme
    if {"the", "be"} & {text1.lower(), text2.lower()}:
        print(pw)

Kiderül, hogy rögtön a 6. jelszó megfelelő: avengedsevenfold. Már csak vissza kell titkosítani az egészet. Egy egyszerű megoldás, ha feltöltjük a fájlt Cyberchefre, mivel itt is van AES titkosító. A Cyberchef ugyan nem unpaddol minden egyes blokkot, csupán az egész üzenetet egyben, de a lényeg így is látszik belőle: a flag első fele HTB{Br3@k_Th3, valamint a belépési adatok Username: Hacktheboxadmin Password: G7!xR9$sT@2mL^bW4&uV

2. rész

Forrás

Az oldalra belépve egy chat beszélgetést látunk, amiben két fájl is fel lett töltve: encrypted.txt és encryptor.py

A Python fájl tartalma:

from math import gcd
import random

def encrypt(a, b):
    ct = []
    for ch in msg:
        if ch.isalpha():
            encrypted_char = chr(((a * (ord(ch) - 65 - b)) % 26) + 65)
            ct.append(encrypted_char)
        else :
            ct.append(ch)
    return ''.join(ct)


msg = open('secret_message.txt').read()

while True:
    a = random.randint(1, 26)
    b = random.randint(1, 26)
    if gcd(a, 26) == 1:
        break

with open('encrypted.txt', 'w') as f:
    f.write(encrypt(a,b))

print("Encrypted message saved to encrypted.txt")

Magyarázat

A program egy úgynevezett affin titkosítást használ. Ahol a titkosítás menete: \(E_{a,b}(x) = a \cdot x + b ~ mod ~ m\), a kititkosításé: \(D_{a,b}(y) = a^{-1} (a - b) ~ mod ~ m\), ahol \(a, b\) a kulcs, \(m\) pedig az ábécé mérete, itt \(m = 26\). Ehez véletlenszerűen generált kulcsot használ. Így titkosítja a secret_message.txt tartalmát az encrypted.txt fájlba (amit szintén megkaptunk). Ennek a titkosításnak nagy hátránya, hogy egy betűt eltitkosítva mindig ugyan azt az eredményt adja. Ezért frekvencia analízissel (a számok előfordulási arányát megállapítva) lehet egy jó tippünk a gyakori betűk titkosított megfelelőjére, amiből ki lehet számítani a kulcs értékeit.

Cél

Meghatározni a titkosításhoz használt kulcsot és kititkosítani a kapott fájlt.

Megoldás

A titkos üzenetben előforduló leggyakoribb betű a D (3.), valószínűleg ez rejti az E (4.) betűt. Ezt beírva a képletbe: \(3 = a \cdot 4 + b ~ mod ~ 26\). Tudjuk, hogy \(a\) és \(b\) is \([1,26]\) tartományban vannak, valamint \(gcd(a, 26) = 1\) (relatív prímek). Ezek alapján a 676 kulcsot leszűkíthetjük a következő 12 kulcsra: (1 25), (3 17), (5 9), (7 1), (9 19), (11 11), (15 21), (17 13), (19 5), (21 23), (23 15), (25 7). Lehetne a második leggyakoribb karakterre is kiszámítani, amivel tovább szűkülnének a lehetséges kulcsok számai, de ez is megfelelően kevés. A kulcsokat próbálgatva a (7, 1) kulcsra egy értelmes szöveg áll elő, benne a keresett flag második részével: _BL0CK_P@TT3RN} Íme a program, ami visszatitkosítja az üzenetet (vagy használható a Cyberchef is):

from math import gcd

def decrypt(a, b):
    a_inv = pow(a, -1, 26)

    ct = []
    for ch in msg:
        if ch.isalpha():
            encrypted_char = chr(((a_inv * (ord(ch) - 65 - b)) % 26) + 65)
            ct.append(encrypted_char)
        else :
            ct.append(ch)
    return ''.join(ct)

msg = open('encrypted.txt').read()

key= (7, 1)

with open('secret_message.txt', 'w') as f:
    f.write(decrypt(*key))

print("Decrypted message saved to secret_message.txt")