← Back to blog

Living with Elegance

Challenge

  • CTF: HTB Business CTF 2024: The Vault of Hope
  • Name: Living with Elegance
  • Category: Crypto
  • Difficulty: Easy
  • Points: 350
  • Description: With injuries and illnesses escalating, the priority is clear: human lives take precedence. Before seeking hidden treasures, it is imperative to first treat the wounded ones. The resolute survivors learn through rumors about a hidden medical research facility known as the “BioMed Research Institute” reputed for its advanced treatments. They plan to locate and infiltrate the institute, intent on securing vital medications and medical equipment necessary to save the lives of their injured comrades. However, such a feat will not come easily. The facility is safeguarded by state-of-the-art security mechanisms known only to the government. The team must navigate several layers of doors to access the heart of the facility. Can you identify any vulnerability or hidden backdoor in this enigmatic security system?

Writeup

server.py:

from secrets import token_bytes, randbelow
from Crypto.Util.number import bytes_to_long as b2l

class ElegantCryptosystem:
    def __init__(self):
        self.d = 16
        self.n = 256
        self.S = token_bytes(self.d)

    def noise_prod(self):
        return randbelow(2*self.n//3) - self.n//2

    def get_encryption(self, bit):
        A = token_bytes(self.d)
        b = self.punc_prod(A, self.S) % self.n
        e = self.noise_prod()
        if bit == 1:
            return A, b + e
        else:
            return A, randbelow(self.n)

    def punc_prod(self, x, y):
        return sum(_x * _y for _x, _y in zip(x, y))

def main():
    FLAGBIN = bin(b2l(open('flag.txt', 'rb').read()))[2:]
    crypto = ElegantCryptosystem()

    while True:
        idx = input('Specify the index of the bit you want to get an encryption for : ')
        if not idx.isnumeric():
            print('The index must be an integer.')
            continue
        idx = int(idx)
        if idx < 0 or idx >= len(FLAGBIN):
            print(f'The index must lie in the interval [0, {len(FLAGBIN)-1}]')
            continue

        bit = int(FLAGBIN[idx])
        A, b = crypto.get_encryption(bit)
        print('Here is your ciphertext: ')
        print(f'A = {b2l(A)}')
        print(f'b = {b}')

if __name__ == '__main__':
    main()

To obtain the flag from the given Python code, we need to analyze and exploit the behavior of the ElegantCryptosystem class and its get_encryption method. Here are the steps and key points to understand:

Initialization: self.d = 16: Byte size of secret S. self.n = 256: Modulus used in encryption. self.S = token_bytes(self.d): A 16-byte secret randomly generated.

Noise Production: noise_prod() generates a noise value within a certain range around zero.

Encryption Function: For a bit value (bit), the function generates a random byte array A of size 16. Computes b = self.punc_prod(A, self.S) % self.n, where punc_prod computes a dot product of A and S. Adds a noise e if bit == 1. If bit == 0, returns A and a random value less than n.

Obtaining the Flag: The main function reads the flag from a file, converts it to a binary string, and allows user interaction to get the encryption of specific bits.

Exploitation

Exploit Randomness:

  • For bit == 0, b is a random value from 0 -> 255.
  • For bit == 1, b is close to self.punc_prod(A, S) % self.n due to the addition of a small noise. This value can be from -255 to 255.

Interactive Decryption:

  • By specifying different indices and observing the output b, we can determine if the bit was 0 or 1 based on the randomness and proximity to the dot product modulo n.

Analysis: The script collects multiple samples for each index and uses statistical analysis to determine the bit. Reconstruction: The collected binary string is converted back to the original flag.

This approach leverages the difference in the randomness introduced for bit 0 and the more deterministic value for bit 1 due to the dot product, allowing us to reconstruct the binary flag. Adjust the host, port, and flag length as necessary based on the actual challenge setup.

flag_bits=100100001010100010000100111101101100100011010010111001101110100011100100110100101100010011101010111010001100101011001000101111101100101011100100111001001101111011100100101111101101110011011110111010001011111001110000011011101100001011001000011100000110101010000110011010001100110001110010110000100100110001100100011000001100110001100000011010000110111011001000110000000110111001100010010000001100001001110000110010101100010011001010110001001100011001100000110011001111101, flag=HTB{distributed_error_not_87ad85C4f9a&20f047d`71 a8ebebc0f}

flag_bits=100100001010100010000100111101101100100011010010111001101110100011100100110100101100010011101010111010001100101011001000101111101100101011100100111001001101111011100100101111101101110011011110111010001011111001110000011011101100001011001000011100000110101011000110011010101100110001110010110000101100110001100100011000001100110001100010011010000110111011001000110001000110111001100010011000001100001001110000110010101100011011001010110001001100011001100110110011001111101, flag=HTB{distributed_error_not_87ad85c5f9af20f147db710a8ecebc3f}

flag_bits=100100001010100010000100101101101100100011010010111001101110100011100100110100101100010011101010111010001100101011001000101111101100100011100100111001001101111011100100100111101101110011011110111010001011111001110000110010100110001001100000011011000111000001100010011000001100110011001100110001100110001001100010011010100110000001110000011001101100101011001000110001000110001001101110011001101100101011000010011001000011000001100000011010001100010011000110011010001111101, flag=HTB[distributed_drrorOnot_8e106810ffc115083edb173ea2\x1804bc4}