Level Description

Can you get sense of this code file and write the function that will decode the given encrypted file content. Find the encrypted file here flag_info and code file might be good to analyze and get the flag.

Given Data

enc_flag :

a = 94
b = 21
cipher is: [131553, 993956, 964722, 1359381, 43851, 1169360, 950105, 321574, 1081658, 613914, 0, 1213211, 306957, 73085, 993956, 0, 321574, 1257062, 14617, 906254, 350808, 394659, 87702, 87702, 248489, 87702, 380042, 745467, 467744, 716233, 380042, 102319, 175404, 248489]

custom_encryption.py :

from random import randint
import sys


def generator(g, x, p):
    return pow(g, x) % p


def encrypt(plaintext, key):
    cipher = []
    for char in plaintext:
        cipher.append(((ord(char) * key*311)))
    return cipher


def is_prime(p):
    v = 0
    for i in range(2, p + 1):
        if p % i == 0:
            v = v + 1
    if v > 1:
        return False
    else:
        return True


def dynamic_xor_encrypt(plaintext, text_key):
    cipher_text = ""
    key_length = len(text_key)
    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char
    return cipher_text


def test(plain_text, text_key):
    p = 97
    g = 31
    if not is_prime(p) and not is_prime(g):
        print("Enter prime numbers")
        return
    a = randint(p-10, p)
    b = randint(g-10, g)
    print(f"a = {a}")
    print(f"b = {b}")
    u = generator(g, a, p)
    v = generator(g, b, p)
    key = generator(v, a, p)
    b_key = generator(u, b, p)
    shared_key = None
    if key == b_key:
        shared_key = key
    else:
        print("Invalid key")
        return
    semi_cipher = dynamic_xor_encrypt(plain_text, text_key)
    cipher = encrypt(semi_cipher, shared_key)
    print(f'cipher is: {cipher}')


if __name__ == "__main__":
    message = sys.argv[1]
    test(message, "trudeau")

Solution :

After looking for way to long at the code and worrying about the fact that some of the values were randomly generated and didn`t know how I could reverse engineer that, I catted enc_flag and saw that I have the actual values used for the specific given encryption. The process from now on is pretty straight forward, we just need to define what we know, reverse each function and call the sequence for the given output.

What do we know?

# given cipher
cipher = [131553, 993956, 964722, 1359381, 43851, 1169360, 950105, 321574, 1081658, 613914, 0, 1213211, 306957, 73085, 993956, 0, 321574, 1257062, 14617, 906254, 350808, 394659, 87702, 87702, 248489, 87702, 380042, 745467, 467744, 716233, 380042, 102319, 175404, 248489]
# values
p = 97
g = 31
a = 94
b = 21
#generator function which we can reuse cause it doesn`t really matter the logic all that much
def generator(g, x, p):
    return pow(g, x) % p
#text_key
text_key="trudeau"

Encrypt function :

def encrypt(plaintext, key):
    cipher = []
    for char in plaintext:
        cipher.append(((ord(char) * key*311)))
    return cipher

Decrypt function :

def decrypt(given_str, key):
    plaintext = ""
    for val in given_str:
        val = val // (key * 311)
        plaintext += chr(decrypted_value)  # just the reverse of ord(char)*key*311
    return plaintext

Dynamic XOR encrypt :

def dynamic_xor_encrypt(plaintext, text_key):
    cipher_text = ""
    key_length = len(text_key)
    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char
    return cipher_text

Dynamic XOR decrypt :

def dynamic_xor_decrypt(plaintext, text_key):
    # Initialize an empty string to hold the cipher text after each round of XOR
    cipher_text = ""
    # Calculate the length of the key to loop through it in a cyclical manner
    key_length = len(text_key)

    # First round of XOR decryption (reverse the string and XOR each character with the corresponding key character)
    for i, char in enumerate(plaintext[::-1]):  # Enumerate over the reversed plaintext
        key_char = text_key[i % key_length]     # Cycle through the key using modulus
        encrypted_char = chr(ord(char) ^ ord(key_char))  # XOR the ASCII values of the current char and key char
        cipher_text += encrypted_char           # Append the decrypted character to the result

    # Update plaintext to be the newly decrypted text from the first round
    plaintext = cipher_text
    # Reset cipher_text for the next round of decryption
    cipher_text = ""

    # Second round of XOR decryption (same process, but using the result from the first round)
    for i, char in enumerate(plaintext[::-1]):  # Reversing the text again
        key_char = text_key[i % key_length]     # Cycle through the key again
        encrypted_char = chr(ord(char) ^ ord(key_char))  # Perform XOR decryption again
        cipher_text += encrypted_char           # Append the decrypted character

    # Update plaintext to be the newly decrypted text from the second round
    plaintext = cipher_text
    # Reset cipher_text for the third and final round
    cipher_text = ""

    # Third round of XOR decryption (repeat the same process)
    for i, char in enumerate(plaintext[::-1]):  # Reverse again
        key_char = text_key[i % key_length]     # Cycle through the key
        encrypted_char = chr(ord(char) ^ ord(key_char))  # XOR decryption
        cipher_text += encrypted_char           # Append the decrypted character

    # Return the final decrypted result after three rounds of XOR decryption
    return cipher_text

Now all there`s left to do is calling the test function with the reversed functions and given values and watch the output.

Result

11:16:49 archie@Archie pico → python3 decrypt.py
picoCTF{custom_d2cr0pt6d_8b41f976}