Esempio n. 1
0
def detect_mode(ciphertext):
    blocks = challenge_07.as_blocks(ciphertext, 16)

    if challenge_08.detect_aes_ecb(blocks)[1] > 1:
        return "ecb"
    else:
        return "cbc"
Esempio n. 2
0
def forge_padding_block(oracle):
    """
    Given an oracle, forges a block with all PKCS#7 padding (which occurs when
    the length of a plaintext is an integer multiple of the block size)
    """
    b_size, pt_size, padding = challenge_12.determine_block_stats(oracle)
    new_padding = b"A" * padding

    return challenge_07.as_blocks(oracle(new_padding), b_size)[-1]
Esempio n. 3
0
def recover_key(ciphertext, oracle):
    """
    Uses a decryption oracle to recover the IV, which in this case happens to
    also be the key.

    Let:

    C'₁ = C₁
    C'₂ = 0x00
    C'₃ = C₁

    And consider that:

    P'₃ = D(C'₃) ⊕ C'₂
    P'₃ = D(C₁)  ⊕ C'₂
    P'₃ = D(C₁)  ⊕ 0x00
    P'₃ = D(C₁)

    P'₁ = D(C₁) ⊕ IV

    Which implies that:

    P'₁ = P'₃ ⊕ IV
    IV  = P'₁ ⊕ P'₃
    """
    blocks = challenge_07.as_blocks(ciphertext, 16)
    key = b""
    error_msg = b""

    # Leak the exception containing the plaintext to the attacker.
    try:
        oracle(blocks[0] + (b"\x00" * 16) + blocks[0] + b"".join(blocks[1:5]))
    except Exception as e:
        error_msg = str(e)

    # Offset for actual error string is 17, which is all we really care about
    error_msg = error_msg[17:].encode('iso-8859-1')
    plaintext = challenge_07.as_blocks(error_msg, 16)

    # Recover the IV (the key)
    for i, c in enumerate(plaintext[0]):
        key += bytes([c ^ plaintext[2][i]])

    return key
Esempio n. 4
0
def forge_block(offset, plaintext, oracle):
    """
    Given an offset, plaintext, and oracle, forges a block with the proper
    padding.
    """
    b_size, _, _ = challenge_12.determine_block_stats(oracle)
    new_padding = b"A" * (b_size - offset)
    payload = new_padding + challenge_09.pkcs7(plaintext, b_size)
    ciphertext = oracle(payload)

    return challenge_07.as_blocks(ciphertext, b_size)[1]
Esempio n. 5
0
    def decrypt(self, ciphertext):
        if len(ciphertext) % 16 != 0:
            raise ValueError("Invalid length of ciphertext")

        # Break the ciphertext up into blocks
        blocks = challenge_07.as_blocks(ciphertext, 16)

        # All blocks are decrypted individually
        for i in range(len(blocks)):
            blocks[i] = self.cipher.decrypt(blocks[i])

        return challenge_09.remove_pkcs7(b''.join(blocks), 16)
Esempio n. 6
0
    def encrypt(self, plaintext):
        # Apply padding so all blocks end up as 16 bytes
        plaintext = challenge_09.pkcs7(plaintext, 16)

        # Break the plaintext up into blocks
        blocks = challenge_07.as_blocks(plaintext, 16)

        # All blocks are encrypted individually
        for i in range(len(blocks)):
            blocks[i] = self.cipher.encrypt(blocks[i])

        return b''.join(blocks)
Esempio n. 7
0
def determine_offset(oracle, block_size):
    """
    Determines the offset in which user input starts within the ciphertext by
    analyzing what injected padding causes a repeated block.
    """
    for i in range(1, 128):
        ciphertext = oracle(b"A" * i)
        blocks = challenge_07.as_blocks(ciphertext, block_size)
        last = None

        for idx, block in enumerate(blocks):
            if block == last:
                return idx * block_size - (i - block_size)
            last = block
Esempio n. 8
0
    def encrypt(self, plaintext, nonce):
        blocks = challenge_07.as_blocks(plaintext, 16)
        keystream = b""
        ciphertext = b""

        # Produce the blocks we need for our keystream
        for i in range(len(blocks)):
            nonce_padded = nonce + b"\x00" * (8 - len(nonce))
            counter = bytes([i])
            counter_padded = counter + b"\x00" * (8 - len(counter))
            keystream += self.cipher.encrypt(nonce_padded + counter_padded)

        for i, c in enumerate(plaintext):
            ciphertext += bytes([c ^ keystream[i]])

        return ciphertext
Esempio n. 9
0
    def encrypt(self, plaintext):
        # Apply padding so all blocks end up as 16 bytes
        plaintext = challenge_09.pkcs7(plaintext, 16)

        # Break the plaintext up into blocks
        blocks = challenge_07.as_blocks(plaintext, 16)

        # First block is the result of encrypt(blocks[0] ^ IV)
        blocks[0] = self.cipher.encrypt(
            challenge_02.fixed_xor(blocks[0], self.iv))

        # All other blocks are the result of encrypt(blocks[i] ^ blocks[i-1])
        for i in range(1, len(blocks)):
            blocks[i] = self.cipher.encrypt(
                challenge_02.fixed_xor(blocks[i], blocks[i - 1]))

        return b''.join(blocks)
Esempio n. 10
0
    def decrypt(self, ciphertext):
        if len(ciphertext) % 16 != 0:
            raise ValueError("Invalid length of ciphertext")

        # Break the ciphertext up into blocks
        blocks = challenge_07.as_blocks(ciphertext, 16)
        decrypted = blocks

        # All but the first decrypted block are the result of
        # decrypt(blocks[i]) ^ blocks[i-1]
        for i in range(len(decrypted) - 1, 0, -1):
            decrypted[i] = challenge_02.fixed_xor(
                self.cipher.decrypt(blocks[i]), blocks[i - 1])

        # First decrypted block is the result of decrypt(blocks[0]) ^ IV
        decrypted[0] = challenge_02.fixed_xor(self.cipher.decrypt(blocks[0]),
                                              self.iv)

        return challenge_09.remove_pkcs7(b''.join(decrypted), 16)
Esempio n. 11
0
def decrypt_with_padding_oracle(oracle):
    ciphertext, iv = get_ciphertext_and_iv()
    blocks = [bytes(iv)] + challenge_07.as_blocks(ciphertext, 16)
    plaintext = b""

    for i in range(len(blocks) - 1, 0, -1):
        block_p = b""  # Recovered plaintext of current block

        for j in range(15, -1, -1):
            cprime_k = b""  # Last k bytes of C′, the block we control
            pkcs = 16 - j  # PKCS#7 padding char (our P′ value)

            for k in range(15 - j):
                #    P′ᵢ[k] = Pᵢ[k]  ⊕ Cᵢ₋₁[k] ⊕ C′[k]
                # => C′[k]  = P′ᵢ[k] ⊕ Pᵢ[k]   ⊕ Cᵢ₋₁[k]
                cprime_k = bytes([pkcs ^ block_p[k] ^ blocks[i-1][15-k]]) + \
                    cprime_k

            for k in range(256):
                padding = b"0" * j
                guess = bytes([k])
                c_prime = padding + guess + cprime_k

                # If C′||Cᵢ has valid padding, we know C′[j]
                if oracle(c_prime + blocks[i]):
                    # We now have enough info to solve for Pᵢ[j] in
                    #
                    #    P′ᵢ[j] = Pᵢ[j]  ⊕ Cᵢ₋₁[j] ⊕ C′[j]
                    # => Pᵢ[j]  = P′ᵢ[j] ⊕ Cᵢ₋₁[j] ⊕ C′[j]
                    #
                    p_char = bytes([pkcs ^ blocks[i - 1][j] ^ ord(guess)])
                    block_p += p_char
                    plaintext = p_char + plaintext
                    break

    return challenge_09.remove_pkcs7(plaintext, 16)
Esempio n. 12
0
def sanitize_email(email):
    return email.translate({ord(c): None for c in '&='})


def parse_key_value(str1):
    return dict(item.split("=") for item in str1.split("&"))


if __name__ == '__main__':
    # Forge a block with an offset of 6 to compensate for "email="
    forgery = forge_block(6, b"admin", new_profile)

    # When email length is 13, the second block ends in "role="
    ciphertext = new_profile(b"*****@*****.**")
    b_size, _, _ = challenge_12.determine_block_stats(new_profile)
    blocks = challenge_07.as_blocks(ciphertext, b_size)

    # "Cut and paste" blocks to provide the desired ciphertext
    profile1 = get_profile(blocks[0] + blocks[1] + forgery)

    print(profile1)

    # Try another way of doing this. This version relies on generating a block
    # full of PKCS#7 padding and doing more block "cut and pasting"

    # When input length is 13, the second block ends in "role=". The last 3
    # chars will become part of the email
    ciphertext = new_profile(b"AAAAAAAAAAcom")
    role_block = challenge_07.as_blocks(ciphertext, b_size)[1]

    # When input length is 15, the second block starts with last 5 chars
Esempio n. 13
0
import base64
import challenge_07


def detect_aes_ecb(blocks):
    histogram = {}

    for block in blocks:
        histogram[block] = histogram.get(block, 0) + 1

    block = max(histogram, key=histogram.get)

    return block, histogram[block]


if __name__ == '__main__':
    max_score = 0
    block = b""

    for line in open("08.txt", "r"):
        ciphertext = base64.b64decode(line)
        blocks = challenge_07.as_blocks(ciphertext, 16)
        guessed_block, score = detect_aes_ecb(blocks)

        if score > max_score:
            max_score = score
            block = guessed_block

    print("Likely AES ECB Block: {}".format(block))
    print("Highest Score       : {}".format(max_score))