Esempio n. 1
0
def guess_ecb_cbc(nb_repeating_blocks, nb_runs, url):
    result = {
        'total_oracle_calls': 0,
        'step1': {},
        'step2': {},
        'step3': {},
    }

    # step 1: detect block size
    block_byte_size, nb_oracle_calls = query_oracle.get_block_size(url)
    result['step1'] = {
        'nb_oracle_calls': nb_oracle_calls,
        'block_byte_size': block_byte_size,
    }
    result['total_oracle_calls'] += nb_oracle_calls

    # step 2: create a repeating plain
    base_bytes = bytes([00])
    base_bytes += b'A' * (block_byte_size - 1)
    plain_bytes = base_bytes * nb_repeating_blocks
    result['step2'] = {
        'nb_repeating_blocks': nb_repeating_blocks,
        'base_plain': encoder.bytes_to_hex(base_bytes),
    }

    # step 3: query the oracle with that special plain and analyze the cipher
    result['step3'] = {
        'nb_oracle_calls': 0,
        'attempts': [],
    }
    for i in range(0, nb_runs):
        attempt = {
            'sent': encoder.bytes_to_hex(plain_bytes),
            'received': None,
            'guessed': None,
        }
        cipher_bytes = query_oracle.get_response(url, plain_bytes)
        attempt['received'] = encoder.bytes_to_hex(cipher_bytes)
        result['step3']['nb_oracle_calls'] += 1

        blocks = [
            cipher_bytes[i:i + block_byte_size]
            for i in range(0, len(cipher_bytes), block_byte_size)
        ]
        duplicates = list(
            set(block for block in blocks if blocks.count(block) > 1))

        # plain with identical blocks and cipher too: it is ecb
        if len(duplicates) > 0:
            attempt['guessed'] = 'ecb'
        else:
            attempt['guessed'] = 'cbc'

        result['step3']['attempts'].append(attempt)

    result['total_oracle_calls'] += result['step3']['nb_oracle_calls']
    return result
Esempio n. 2
0
        def do_GET(self):
            data = self._parse_query_string().get('data', None)
            user_hex = ''
            if data is not None:
                user_hex = data[0]

            user_bytes = self._convert_or_raise_input(user_hex)

            # generate key and iv
            key = randomness.get_bytes(self.__KEY_LENGTH)
            iv = randomness.get_bytes(16)

            # pick ECB or CBC chaining mode randomly
            flip = bool(randomness.get_bits(1))

            # encrypt
            cipher_bytes = None
            if flip is True:
                cipher_bytes = aes.encrypt(user_bytes, key, 'ecb')
            else:
                cipher_bytes = aes.encrypt(user_bytes, key, 'cbc', iv)

                # remove iv
                cipher_bytes = cipher_bytes[16:]

            cipher_hex = encoder.bytes_to_hex(cipher_bytes)

            # send response
            self._send_response(cipher_hex)
        def do_POST(self):
            content = self._parse_body()
            data = content.get(b'data', None)
            if data is None:
                self.send_error(400, 'received empty encrypted comment')

            encrypted_comment = encoder.hex_to_bytes(data[0].decode('utf-8'))
            iv = encrypted_comment[:16]
            try:
                encoded_bytes = aes.decrypt(encrypted_comment[16:], self.__KEY,
                                            'cbc', iv)
            except Exception as err:
                self.send_error(400, str(err))

            encoded_hex = encoder.bytes_to_hex(encoded_bytes)
            self._send_response(encoded_hex)
Esempio n. 4
0
        def do_GET(self):
            data = self._parse_query_string().get('data', None)
            user_hex = ''
            if data is not None:
                user_hex = data[0]

            user_bytes = self._convert_or_raise_input(user_hex)

            # append secret
            plain_bytes = before + user_bytes + self.__SECRET

            # encrypt
            cipher_bytes = aes.encrypt(plain_bytes, self.__KEY, 'ecb')
            cipher_hex = encoder.bytes_to_hex(cipher_bytes)

            # send response
            self._send_response(cipher_hex)
Esempio n. 5
0
def get_response(url, data: bytes, verb='GET'):
    data_hex = encoder.bytes_to_hex(data)

    response = None
    if verb == 'GET':
        response = requests.get(url, params={'data': data_hex})

    if verb == 'POST':
        response = requests.post(url, data={'data': data_hex})
    response.encoding = 'utf-8'

    result = b''
    if response.status_code == '200':
        result = encoder.hex_to_bytes(response.text)
    else:
        result = encoder.utf8_to_bytes(response.reason)

    return encoder.hex_to_bytes(response.text)
        def do_GET(self):
            data = self._parse_query_string().get('data', None)
            data_hex = ''
            if data is not None:
                data_hex = data[0]
            data_utf8 = encoder.hex_to_utf8(data_hex)

            # create comment, encode then encrypt
            comment = self.__create_comment(data_utf8)
            encoded = self.__encode_comment(comment)
            encoded_bytes = encoder.utf8_to_bytes(encoded)

            iv = randomness.get_bytes(16)
            cipher_bytes = aes.encrypt(encoded_bytes, self.__KEY, 'cbc', iv)

            # remove iv
            cipher_hex = encoder.bytes_to_hex(cipher_bytes)
            self._send_response(cipher_hex)
Esempio n. 7
0
def attack_ecb(url):
    result = {
        'total_oracle_calls': 0,
        'step1': {},
        'step2': {},
        'step3': {},
        'step4': {},
    }

    # step 1: detect block size
    block_byte_size, nb_oracle_calls = query_oracle.get_block_size(url)
    result['step1'] = {
        'nb_oracle_calls': nb_oracle_calls,
        'block_byte_size': block_byte_size,
    }
    result['total_oracle_calls'] += nb_oracle_calls

    # step 2: verify chaining mode is ECB
    is_ecb, nb_oracle_calls = query_oracle.is_ecb_mode(url, block_byte_size)
    if is_ecb is False:
        raise RuntimeError('The oracle is not chaining blocks in ECB mode. Aborting.')
    result['step2'] = {
        'nb_oracle_calls': nb_oracle_calls,
        'verified_ecb': True,
    }
    result['total_oracle_calls'] += nb_oracle_calls

    # step 3: detect the offset from which we fully control encrypted blocks
    first_controlled_block_index, prefix_length, nb_oracle_calls = query_oracle.get_offset_first_controlled_block(url, block_byte_size)
    prefix_bytes = bytes([0]) * prefix_length
    result['step3'] = {
        'nb_oracle_calls': nb_oracle_calls,
        'first_controlled_block_index': first_controlled_block_index,
        'chosen_plaintext_prefix': encoder.bytes_to_hex(prefix_bytes),
    }
    result['total_oracle_calls'] += nb_oracle_calls

    # step 4: extract secret byte per byte
    result['step4'], nb_oracle_calls = query_oracle.bruteforce_ecb(url, block_byte_size, first_controlled_block_index, prefix_bytes)
    result['step4']['nb_oracle_calls'] = nb_oracle_calls
    result['total_oracle_calls'] += nb_oracle_calls

    return result
Esempio n. 8
0
def bruteforce_ecb(url, block_byte_size: int,
                   first_controlled_block_index: int, prefix: bytes):
    nb_oracle_calls = 0
    result = {
        'witnesses': {
            'collected': False,
            'nb_oracle_calls': 0,
        },
        'bruteforce': [],
        'secret': {
            'length': 0,
            'nb_oracle_calls': 0,
            'value': None,
        },
    }

    # store encryptions of [s(0)/s(b)/s(2b)/..., s(1)/s(b+1)/s(2b+1)/..., ...]
    witnesses = []
    for offset in range(0, block_byte_size):
        base_block = bytes([0]) * (block_byte_size - (offset + 1))
        base_block = prefix + base_block
        witness = get_response(url, base_block)
        result['witnesses']['nb_oracle_calls'] += 1
        witnesses.append(witness)
    result['witnesses']['collected'] = True
    nb_oracle_calls += result['witnesses']['nb_oracle_calls']

    # get length of the secret
    # thanks to pkcs7: full block of padding is added if plaintext is a multiple of block_byte_size
    initial_length = len(get_response(url, prefix))
    result['secret']['nb_oracle_calls'] += 1
    for i in range(1, block_byte_size):
        plain_bytes = prefix + bytes([0]) * i
        length = len(get_response(url, plain_bytes))
        result['secret']['nb_oracle_calls'] += 1
        if length > initial_length:
            result['secret'][
                'length'] = initial_length - block_byte_size * first_controlled_block_index - i
            break
    nb_oracle_calls += result['secret']['nb_oracle_calls']

    # attack the secret block per block (s(0)..s(b-1), then s(b)..s(2b-1), etc)
    secret = b''
    block_index = first_controlled_block_index

    while len(secret) < result['secret']['length']:
        block_start = block_byte_size * block_index
        block_end = block_start + block_byte_size

        # bruteforce s(offset) inside the current block
        for offset in range(0, block_byte_size):
            attempt = {
                'block_index': block_index,
                'witness': None,
                'sent': None,
                'received': None,
                'nb_oracle_calls': 0,
            }

            # get witness block
            block_witness = witnesses[offset][block_start:block_end]
            attempt['witness'] = encoder.bytes_to_hex(block_witness)

            # build base block
            base_block = bytes([0]) * (block_byte_size - (offset + 1))
            base_block = prefix + base_block + secret

            # bruteforce s(offset): query oracle for all possible values until getting the witness
            for guess in range(0, 256):
                plain_bytes = base_block + encoder.int_to_bytes(guess)
                cipher_bytes = get_response(url, plain_bytes)
                attempt['nb_oracle_calls'] += 1
                block = cipher_bytes[block_start:block_end]
                if block == block_witness:
                    secret += encoder.int_to_bytes(guess)
                    attempt['sent'] = encoder.bytes_to_hex(plain_bytes)
                    attempt['received'] = encoder.bytes_to_hex(cipher_bytes)
                    result['bruteforce'].append(attempt)
                    nb_oracle_calls += attempt['nb_oracle_calls']
                    print('*', end='', flush=True)
                    break

            if len(secret) == result['secret']['length']:
                break
        block_index += 1

    print('')
    result['secret']['value'] = secret
    return result, nb_oracle_calls
Esempio n. 9
0
str_hex = '49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d'
str_b64_expected = 'SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t'
str_b64_observed = encoder.hex_to_b64(str_hex)
assert str_b64_observed == str_b64_expected, 'Failed 1: Expected {}, got {}'.format(
    str_b64_expected, str_b64_observed)
print(' ok')

# Chall2
print('\n[+] Chall 2 (xor operation) ...', end='')
a_hex = '1c0111001f010100061a024b53535009181c'
b_hex = '686974207468652062756c6c277320657965'
a_bytes = encoder.hex_to_bytes(a_hex)
b_bytes = encoder.hex_to_bytes(b_hex)
xor_bytes = bitwise.xor(a_bytes, b_bytes)
xor_hex_expected = '746865206b696420646f6e277420706c6179'
xor_hex_observed = encoder.bytes_to_hex(xor_bytes)
assert xor_hex_observed == xor_hex_expected, 'Failed 2: Expected {}, got {}'.format(
    xor_hex_expected, xor_hex_observed)
print(' ok')

# Chall3
print('\n[+] Chall 3 (single byte xor cipher, frequency attack) ...', end='')
cipher_hex = '1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736'
cipher_bytes = encoder.hex_to_bytes(cipher_hex)
best_key, best_plain, _ = xor_frequency.attack_single_byte(
    cipher_bytes, 'english')
print(' ok: found key=0x{}, plain={}'.format(
    encoder.bytes_to_hex(best_key), encoder.bytes_to_utf8(best_plain)))

# Chall4
print('\n[+] Chall 4 (detect single xor cipher, frequency attack) ...', end='')
Esempio n. 10
0
def create_admin_profile_ecb(url, role_user: str, role_target: str):
    result = {
        'total_oracle_calls': 0,
        'step1': {},
        'step2': {},
        'step3': {},
    }

    # step 1: detect block size
    block_byte_size, nb_oracle_calls = query_oracle.get_block_size(url)
    result['step1'] = {
        'nb_oracle_calls': nb_oracle_calls,
        'block_byte_size': block_byte_size,
    }
    result['total_oracle_calls'] += nb_oracle_calls

    # step 2: verify chaining mode is ECB
    is_ecb, nb_oracle_calls = query_oracle.is_ecb_mode(url, block_byte_size)
    if is_ecb is False:
        raise RuntimeError(
            'The oracle is not chaining blocks in ECB mode. Aborting.')
    result['step2'] = {
        'nb_oracle_calls': nb_oracle_calls,
        'verified_ecb': True,
    }
    result['total_oracle_calls'] += nb_oracle_calls

    # step 3: forge an admin profile
    result['step3'] = {
        'nb_oracle_calls': 0,
    }
    # request a decryption example to know the structure
    email_bytes = b'*****@*****.**'
    profile_encrypted = query_oracle.get_response(url, email_bytes)
    profile_bytes = query_oracle.get_response(url, profile_encrypted, 'POST')
    result['step3']['nb_oracle_calls'] += 2
    result['step3']['sample'] = {
        'email': encoder.bytes_to_utf8(email_bytes),
        'profile': encoder.bytes_to_utf8(profile_bytes),
        'encrypted': encoder.bytes_to_hex(profile_encrypted),
    }

    # choose an email so that low_privileged role (eg. 'user') get encrypted in a new block
    # let profile = before_email|email|before_role|user
    # block0              |block1              |block2
    # before_email|emaiiii|iiiiiiil|before_role|user
    before_email, after_email = profile_bytes.split(email_bytes, 1)
    before_role = after_email.split(encoder.utf8_to_bytes(role_user), 1)[0]
    email_length = block_byte_size - (len(before_email) +
                                      len(before_role)) % block_byte_size

    domain = b'@example.com'
    while email_length < min(len(domain), block_byte_size):
        email_length += block_byte_size
    username = b'a' * (email_length - len(domain))
    email_bytes = username + domain
    nb_blocks_to_keep = (len(before_email) + len(email_bytes) +
                         len(before_role)) // block_byte_size

    profile_encrypted = query_oracle.get_response(url, email_bytes)
    result['step3']['nb_oracle_calls'] += 1
    forged = profile_encrypted[:block_byte_size * nb_blocks_to_keep]
    result['step3']['pushed_user_out'] = {
        'email': encoder.bytes_to_utf8(email_bytes),
        'encrypted': encoder.bytes_to_hex(profile_encrypted),
        'keeping': encoder.bytes_to_hex(forged),
    }

    # leveraging pkcs7 padding, get encryption of 'admin' alone
    # block0              |block1              |block2
    # before_email|prefix |admin               |after_email
    prefix = b'a' * (block_byte_size - len(before_email) % block_byte_size)
    email_bytes = padding.pad_pkcs7(encoder.utf8_to_bytes(role_target),
                                    block_byte_size)
    email_bytes = prefix + email_bytes
    profile_encrypted = query_oracle.get_response(url, email_bytes)
    result['step3']['nb_oracle_calls'] += 1
    nb_block_to_skip = (len(before_email) + len(prefix)) // block_byte_size
    block_start = block_byte_size * nb_block_to_skip
    block_end = block_start + block_byte_size
    forged += profile_encrypted[block_start:block_end]
    result['step3']['admin_encryption'] = {
        'prefix':
        encoder.bytes_to_utf8(prefix),
        'admin_block_index':
        nb_block_to_skip,
        'email':
        email_bytes,
        'encrypted':
        encoder.bytes_to_hex(profile_encrypted),
        'keeping':
        ' ' * block_byte_size * nb_block_to_skip * 2 +
        encoder.bytes_to_hex(profile_encrypted[block_start:block_end]),
    }

    # verify the forged encrypted profile
    profile = query_oracle.get_response(url, forged, 'POST')
    result['step3']['nb_oracle_calls'] += 1
    result['step3']['forged'] = {
        'encrypted': encoder.bytes_to_hex(forged),
        'profile': encoder.bytes_to_utf8(profile),
    }
    result['total_oracle_calls'] += result['step3']['nb_oracle_calls']

    return result
Esempio n. 11
0
def add_admin_cbc(url, target_data: str):
    result = {
        'total_oracle_calls': 0,
        'step1': {},
        'step2': {},
        'step3': {},
    }

    # step 1: detect block size
    block_byte_size, nb_oracle_calls = query_oracle.get_block_size(url)
    result['step1'] = {
        'nb_oracle_calls': nb_oracle_calls,
        'block_byte_size': block_byte_size,
    }
    result['total_oracle_calls'] += nb_oracle_calls

    if len(target_data) > block_byte_size:
        raise ValueError(
            'Target data too long. Should be at most {} bytes'.format(
                block_byte_size))

    # step 2: get a decryption sample to know the structure
    result['step2'] = {
        'nb_oracle_calls': 0,
    }

    # encrypt/decrypt a sample
    sample_bytes = b'foobar' + encoder.utf8_to_bytes(target_data)
    comment_encrypted = query_oracle.get_response(url, sample_bytes)
    result['step2']['nb_oracle_calls'] += 1

    comment_bytes = query_oracle.get_response(url, comment_encrypted, 'POST')
    result['step2']['nb_oracle_calls'] += 1
    result['step2'].update({
        'sample':
        encoder.bytes_to_utf8(sample_bytes),
        'comment':
        encoder.bytes_to_utf8(comment_bytes),
        'encrypted':
        encoder.bytes_to_hex(comment_encrypted[16:]),
    })
    result['total_oracle_calls'] += result['step2']['nb_oracle_calls']

    # find prefix to ensure we know where we will inject our target data
    # +1 because of iv
    sample_quoted = quote(encoder.bytes_to_utf8(sample_bytes))
    before_user_input = comment_bytes.split(
        encoder.utf8_to_bytes(sample_quoted), 1)[0]
    prefix = b''
    if len(before_user_input) % block_byte_size != 0:
        prefix = b'x' * (block_byte_size -
                         len(before_user_input) % block_byte_size)
    first_controlled_block_index = (len(before_user_input) +
                                    len(prefix)) // block_byte_size + 1
    result['step2'].update({
        'chosen_plaintext_prefix':
        encoder.bytes_to_hex(prefix),
        'first_controlled_block_index':
        first_controlled_block_index,
    })

    # step 3: inject data to add data (eg. 'admin=true' or alike)
    result['step3'] = {
        'nb_oracle_calls': 0,
    }

    # get a cipher for a string as long as the target we want
    witness = b'a' * len(target_data)
    ref_bytes = prefix + witness
    comment_encrypted = query_oracle.get_response(url, ref_bytes)
    comment_bytes = query_oracle.get_response(url, comment_encrypted, 'POST')
    result['step3']['nb_oracle_calls'] += 2
    result['step3']['reference'] = {
        'sent': encoder.bytes_to_utf8(ref_bytes),
        'received': encoder.bytes_to_hex(comment_encrypted),
        'comment': encoder.bytes_to_utf8(comment_bytes),
    }

    # leverage CBC chaining mode
    # starting from the global bloc matching cipher/plain:
    #       |cipher0                 |cipher1                 |cipher2
    #       |before_user_input|prefix|000000000           |after_user_input

    # when decrypting:
    #       |before_user_input|prefix|000000000           |after_user_input
    # iv    |plain0                  |plain1                  |plain2
    #       |dec(cipher0) xor iv     |dec(cipher1) xor cipher0|dec(cipher2) xor cipher1

    # because xor is a bitwise operation, wisely tampering cipher0 (cipher0') will force the oracle to decrypt plain1 to the thing we want (plain1')
    # of course, plain0 will become gribbish
    # we know: cipher0, plain1
    # plain1  = 00000000000 | after_user_input
    # plain1' = target_data | after_user_input
    # plain1  = dec(cipher1) xor cipher0   <=> dec(cipher1) = plain1 xor cipher0
    # plain1' = dec(cipher1) xor cipher0'  <=> cipher0' = plain1' xor dec(cipher1)
    # => cipher0' = plain1' xor plain1 xor cipher0

    # note: because after_user_input xor after_user_input = 0, we will just pad plain1 and plain1' with 0x00 directly
    block_start = block_byte_size * (first_controlled_block_index - 1)
    block_end = block_start + block_byte_size
    cipher0 = comment_encrypted[block_start:block_end]

    plain1 = witness + bytes([0]) * (block_byte_size - len(target_data))
    plain1prime = encoder.utf8_to_bytes(target_data) + bytes(
        [0]) * (block_byte_size - len(target_data))

    cipher0prime = bitwise.xor(plain1, plain1prime)
    cipher0prime = bitwise.xor(cipher0prime, cipher0)
    forged = comment_encrypted[:block_start] + cipher0prime + comment_encrypted[
        block_end:]

    comment_bytes = query_oracle.get_response(url, forged, 'POST')
    result['step3']['forging'] = {
        'plain_block_target_index': first_controlled_block_index,
        'cipher_block_to_tamper': encoder.bytes_to_hex(cipher0),
        'plain_current': encoder.bytes_to_hex(plain1),
        'plain_target': encoder.bytes_to_hex(plain1prime),
        'cipher_block_tampered': encoder.bytes_to_hex(cipher0prime),
        'sent': encoder.bytes_to_hex(forged),
        'comment': comment_bytes,
    }
    result['step3']['nb_oracle_calls'] += 1
    result['total_oracle_calls'] += result['step3']['nb_oracle_calls']

    return result