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)
import sys import threading from datetime import datetime from utils import encoder, bitwise, padding from crypter import aes from attack import cpa_guess_mode, cpa_retrieve_secret, cca2_forge_data from oracle.oracle_server import OracleServer # Chall 9 print('[+] Chall 9 (implement pkcs7 padding) ...', end='') data_utf8 = 'YELLOW SUBMARINE' padded_expected = b'YELLOW SUBMARINE\x04\x04\x04\x04' data_bytes = encoder.utf8_to_bytes(data_utf8) padded_bytes = padding.pad_pkcs7(data_bytes, 20) assert padded_bytes == padded_expected, 'Failed 9: Expected {}, got {}'.format( padded_expected, padded_bytes) print(' ok') # Chall 10 print('\n[+] Chall 10 (implement AES-CBC) ...', end='') cipher_b64 = '' with open('./data/set2_chall10.txt', mode='r') as f: lines = f.read().splitlines() cipher_b64 = ''.join(lines) cipher_bytes = encoder.b64_to_bytes(cipher_b64) iv = bytes([0]) * 16 key_utf8 = 'YELLOW SUBMARINE' key_bytes = encoder.utf8_to_bytes(key_utf8) plain_bytes = aes.decrypt_cbc_homemade(cipher_bytes, key_bytes, iv) encrypted_bytes = aes.encrypt_cbc_homemade(plain_bytes, key_bytes, iv) assert encrypted_bytes[
'score': best_score }) index += 1 line = f.readline() plains.sort(key=lambda elt: elt['score'], reverse=True) print(' ok: found xor cipher at index={}'.format(plains[0]['index'])) print(' | where cipher={}, frequency attack gave key={}, plain=0x{}, score={}'. format(encoder.bytes_to_hex(plains[0]['cipher']), encoder.bytes_to_hex(plains[0]['key']), encoder.bytes_to_utf8(plains[0]['plain']).strip('\n'), plains[0]['score'])) # Chall5 print('\n[+] Chall 5 (implement repeating key xor cipher) ...', end='') plain_utf8 = "Burning 'em, if you ain't quick and nimble\nI go crazy when I hear a cymbal" plain_bytes = encoder.utf8_to_bytes(plain_utf8) key_utf8 = 'ICE' key_bytes = encoder.utf8_to_bytes(key_utf8) cipher_bytes = xor.encrypt(plain_bytes, key_bytes) cipher_hex_observed = encoder.bytes_to_hex(cipher_bytes) cipher_hex_expected = '0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a2622632427276527' cipher_hex_expected += '2a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f' assert cipher_hex_observed == cipher_hex_expected, 'Failed 5: Expected {}, got {}'.format( cipher_hex_expected, cipher_hex_observed) print(' ok') # Chall6 print('\n[+] Chall 6 (break repeating key xor)') cipher_b64 = '' with open('./data/set1_chall6.txt', mode='r') as f: lines = f.readlines()
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
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