Home HTB SPG Writeup
Post
Cancel

HTB SPG Writeup

SPG HTB

The description of the challenge is as follows: After successfully joining the academy, there is a process where you have to log in to eclass in order to access notes in each class and get the current updates for the ongoing prank labs. When you attempt to log in, though, your browser crashes, and all your files get encrypted. This is yet another prank for the newcomers. The only thing provided is the password generator script. Can you crack it, unlock your files, and log in to the spooky platform?

Source

Below is the source provided by the challenge:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from hashlib import sha256
import string, random
from secret import MASTER_KEY, FLAG
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from base64 import b64encode

ALPHABET = string.ascii_letters + string.digits + '~!@#$%^&*'

def generate_password():
    master_key = int.from_bytes(MASTER_KEY, 'little')
    password = ''

    while master_key:
        bit = master_key & 1
        if bit:
            password += random.choice(ALPHABET[:len(ALPHABET)//2])
        else:
            password += random.choice(ALPHABET[len(ALPHABET)//2:])
        master_key >>= 1

    return password

def main():
    password = generate_password()
    encryption_key = sha256(MASTER_KEY).digest()
    cipher = AES.new(encryption_key, AES.MODE_ECB)
    ciphertext = cipher.encrypt(pad(FLAG, 16))

    with open('output.txt', 'w') as f:
        f.write(f'Your Password : {password}\nEncrypted Flag : {b64encode(ciphertext).decode()}')

if __name__ == '__main__':
    main()

The first few lines define the libraries that the program will use. One stands out in particular: from secret import MASTER_KEY, FLAG. This is not a standard library and is likely used to import the “secrets” that the challenge uses.

1
2
3
4
5
6
from hashlib import sha256
import string, random
from secret import MASTER_KEY, FLAG
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from base64 import b64encode

Next, the program defines ALPHABET, which is a string consisting of all ASCII letters, digits 0-9, and the special characters ~!@#$%^&*.

1
ALPHABET = string.ascii_letters + string.digits + '~!@#$%^&*'

The first real code block is generate_password(). It first defines master_key, which is an integer representation of the MASTER_KEY from the import above. This variable would look something like a big list of 1’s and 0’s, such as 01110001001. The function then loops through each bit of this binary representation. If the bit is 1, it picks a random character from the first half of ALPHABET; if the bit is 0, it picks a random character from the second half. Once it loops through all bits, it returns the generated password.

1
2
3
4
5
6
7
8
9
10
11
12
13
def generate_password():
    master_key = int.from_bytes(MASTER_KEY, 'little')
    password = ''

    while master_key:
        bit = master_key & 1
        if bit:
            password += random.choice(ALPHABET[:len(ALPHABET)//2])
        else:
            password += random.choice(ALPHABET[len(ALPHABET)//2:])
        master_key >>= 1

    return password

The final part of the code is the main() function, where everything comes together. It begins by generating a password using the previously defined function. Next, it creates an encryption key derived from the MASTER_KEY by hashing it with SHA-256. It then defines the AES cipher in ECB mode and encrypts the FLAG with the encryption key. Finally, it writes the generated password and the encrypted flag (encoded in Base64) to an output file.

1
2
3
4
5
6
7
8
9
10
11
def main():
    password = generate_password()
    encryption_key = sha256(MASTER_KEY).digest()
    cipher = AES.new(encryption_key, AES.MODE_ECB)
    ciphertext = cipher.encrypt(pad(FLAG, 16))

    with open('output.txt', 'w') as f:
        f.write(f'Your Password : {password}\nEncrypted Flag : {b64encode(ciphertext).decode()}')

if __name__ == '__main__':
    main()

The goal of this challenge is to find a weakness in the code and reverse the encrypted flag. We have three pieces of information: the source code, the generated password, and the encrypted flag.

By analyzing the main function, we see that the encrypted flag is produced using an encryption key that is the SHA-256 hash of the MASTER_KEY. The password generation does not directly affect the encryption process.

The vulnerability lies in the password generation function. The password is directly tied to the MASTER_KEY’s bits. If the first character of the password is in the first half of ALPHABET, the first bit of MASTER_KEY is 1. If the character is in the second half, the bit is 0. By analyzing the entire password, we can determine the bits of the MASTER_KEY.

For each character in the password:

  • If the character is in the first half of ALPHABET, set the corresponding bit in the master_key to 1.
  • If the character is in the second half of ALPHABET, the corresponding bit remains 0.

After processing all characters, we can reconstruct the MASTER_KEY. This reconstructed MASTER_KEY can then be hashed to produce the encryption key, which can be used to decrypt the encrypted flag.

In summary, by analyzing the characters in the password and their positions in ALPHABET, we can reconstruct the MASTER_KEY and decrypt the data.

Solution

This is my solution code to the challenge:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from hashlib import sha256
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import string

ALPHABET = string.ascii_letters + string.digits + '~!@#$%^&*'

def crack_masterKey(password):
    return sum(1 << i for i, p in enumerate(password) if p in ALPHABET[:len(ALPHABET) // 2]).to_bytes((7 + len(password)) // 8, 'little')

def main():
    password = 't*!zGnf#LKO~drVQc@n%oFFZyvhvGZq8zbfXKvE1#*R%uh*$M6c$zrxWedrAENFJB7xz0ps4zh94EwZOnVT9&h'
    ciphertext = 'GKLlVVw9uz/QzqKiBPAvdLA+QyRqyctsPJ/tx8Ac2hIUl8/kJaEvHthHUuwFDRCs'
    
    master_key = crack_masterKey(password)
    encryption_key = sha256(master_key).digest()
    cipher = AES.new(encryption_key, AES.MODE_ECB)

    print("Decrypted MASTER_KEY: " + master_key.decode())
    print("Flag: " + unpad(cipher.decrypt(b64decode(ciphertext)), AES.block_size).decode())

if __name__ == '__main__':
    main()

This post is licensed under CC BY 4.0 by the author.