Example #1
0
def known_plaintexts(pairs, ciphertext, block_size=16):
    """Given enough pairs plaintext-ciphertext, we can assign ciphertexts blocks to plaintexts blocks,
    then we can possibly decrypt ciphertext

    Args:
        pairs(list): list of dict, [{'cipher': 'aaa', 'plain': 'bbb'}, {'cipher': 'xxx', 'plain': 'pwa'}]
                     plaintexts have to be correctly padded (len(cipher) == len(plain))
        ciphertext(string): ciphertext to decrypt
        block_size(int)

    Returns
        tuple: ([decrypted_ciphertext_blocks], {'ciphertext_block': 'plaintext_block', ...})
        decrypted_ciphertext_blocks may contain not-decrypted blocks from ciphertext
    """
    result_mapping = {}
    for pair in pairs:
        ciphertext_blocks = chunks(pair['cipher'], block_size)
        plaintext_blocks = chunks(pair['plain'], block_size)
        if len(ciphertext_blocks) != len(plaintext_blocks):
            print(pair)
            print(ciphertext_blocks, plaintext_blocks)
            print(len(ciphertext_blocks), len(plaintext_blocks))
            assert 0
        for cipher_block_no in range(len(ciphertext_blocks)):
            result_mapping[ciphertext_blocks[cipher_block_no]] = plaintext_blocks[cipher_block_no]

    target_ciphertext_blocks = chunks(ciphertext, block_size)
    for cipher_block_no in range(len(target_ciphertext_blocks)):
        if target_ciphertext_blocks[cipher_block_no] in list(result_mapping.keys()):
            target_ciphertext_blocks[cipher_block_no] = result_mapping[target_ciphertext_blocks[cipher_block_no]]

    return target_ciphertext_blocks, result_mapping
Example #2
0
def fake_ciphertext(new_plaintext,
                    padding_oracle=None,
                    decryption_oracle=None,
                    block_size=16):
    """Make ciphertext that will decrypt to given plaintext
    Give padding_oracle or decryption_oracle (or both)

    Args:
        new_plaintext(string): with padding
        padding_oracle(function/None)
        decryption_oracle(function/None): maximum one block to decrypt
        block_size(int)

    Returns:
        fake_ciphertext(string): fake ciphertext that will decrypt to new_plaintext
    """
    _check_oracles(padding_oracle=padding_oracle,
                   decryption_oracle=decryption_oracle,
                   block_size=block_size)

    if block_size % 8 != 0:
        log.critical_error("Incorrect block size: {}".format(block_size))

    log.info("Start fake ciphertext")
    ciphertext = bytes(b'A' * (len(new_plaintext) + block_size))

    # prepare blocks
    blocks = chunks(ciphertext, block_size)
    new_pl_blocks = chunks(new_plaintext, block_size)
    if len(new_pl_blocks) != len(blocks) - 1:
        log.critical_error(
            "Wrong new plaintext length({}), should be {}".format(
                len(new_plaintext), block_size * (len(blocks) - 1)))
    new_ct_blocks = list(blocks)

    # add known plaintext

    for count_block in range(len(blocks) - 1, 0, -1):
        """ Every block, modify block[count_block-1] to set block[count_block] """
        log.info("Block no. {}".format(count_block))

        ciphertext_to_decrypt = bytes(b''.join(new_ct_blocks[:count_block +
                                                             1]))
        original_plaintext = decrypt(ciphertext_to_decrypt,
                                     padding_oracle=padding_oracle,
                                     decryption_oracle=decryption_oracle,
                                     block_size=block_size,
                                     amount=1,
                                     is_correct=False)
        log.info("Set block no. {}".format(count_block))
        new_ct_blocks[count_block - 1] = xor(blocks[count_block - 1],
                                             original_plaintext,
                                             new_pl_blocks[count_block - 1])

    fake_ciphertext_res = bytes(b''.join(new_ct_blocks))
    log.success("Fake ciphertext(hex): {}".format(b2h(fake_ciphertext_res)))
    return fake_ciphertext_res
Example #3
0
def find_prefix_suffix_size(encryption_oracle, block_size=16):
    """Determine prefix and suffix sizes if ecb mode, sizes must be constant
    Rarely may fail (if random data that are send unhappily matches prefix/suffix)

    Args:
        encryption_oracle(callable)
        block_size(int)

    Returns:
        tuple(int,int): prefix_size, suffix_size
    """
    blocks_to_send = 5
    payload = random_bytes(1) * (blocks_to_send * block_size)
    enc_chunks = chunks(encryption_oracle(payload), block_size)
    log.debug("Encryption of length {}".format(blocks_to_send * block_size))
    log.debug(print_chunks(enc_chunks))

    for position_start in range(len(enc_chunks) - 1):
        if enc_chunks[position_start] == enc_chunks[position_start + 1]:
            for y in range(2, blocks_to_send - 1):
                if enc_chunks[position_start] != enc_chunks[position_start + y]:
                    break
            else:
                log.success("Controlled payload start at chunk {}".format(position_start))
                break
    else:
        log.critical_error("Position of controlled chunks not found")

    log.info('Finding prefix')
    changed_char = bytes([(payload[0] - 1)%256])
    for aligned_bytes in range(block_size):
        payload_new = payload[:aligned_bytes] + changed_char + payload[aligned_bytes+1:]
        enc_chunks_new = chunks(encryption_oracle(payload_new), block_size)
        log.debug(print_chunks(chunks(payload_new, block_size)))
        log.debug(print_chunks(enc_chunks_new))
        if enc_chunks_new[position_start] != enc_chunks[position_start]:
            prefix_size = position_start*block_size - aligned_bytes
            log.success("Prefix size: {}".format(prefix_size))
            break
    else:
        log.critical_error("Size of prefix not found")

    log.info('Finding suffix')
    payload = random_bytes(1) * (block_size - (prefix_size % block_size))  # align to block_size
    encrypted = encryption_oracle(payload)
    suffix_size = len(encrypted) - len(payload) - prefix_size
    while True:
        payload += random_bytes(1)
        suffix_size -= 1
        if len(encryption_oracle(payload)) > len(encrypted):
            log.success("Suffix size: {}".format(suffix_size))
            break
    else:
        log.critical_error("Size of suffix not found")

    return prefix_size, suffix_size
Example #4
0
def length_extension(old_hash, size, new_message, type='sha1'):
    """Length extension attack: given hash(secret) and len(secret),
    compute new_hash and old_padding so that hash(secret+old_padding+new_message) == new_hash

    Args:
        old_hash(string): hash of secret value
        size(int): length of secret (in bytes)
        new_message(string)
        type(string): sha1 or md4

    Returns:
        tuple: (new_hash, old_padding+new_message)
    """
    implemented_functions = ['sha1', 'md4']
    if type == 'sha1':
        endian = 'big'
        hash_function = sha1
    elif type == 'md4':
        endian = 'little'
        hash_function = md4
    else:
        log.critical_error("Not implemented, type must be one of {}".format(
            implemented_functions))
        return None

    old_padding = add_md_padding(bytes(b'a' * size), endian=endian)[size:]
    new_data_size = len(new_message) + len(old_padding) + size
    new_padding = add_md_padding(bytes(b'a' * new_data_size),
                                 endian=endian)[new_data_size:]
    h = [b2i(x, endian=endian) for x in chunks(old_hash, 4)]
    return hash_function(new_message, h,
                         new_padding), old_padding + new_message
def break_reuse_key(ciphertexts, lang='English', no_of_comparisons=5, alphabet=None,
                    key_space=None, reliability=100.0):
    """Sentences xored with the same key

    Args:
        ciphertexts(list): texts xored with the same key
        lang(string): key in frequencies dict
        no_of_comparisons(int): used during comparing by frequencies
        alphabet(string/None): plaintext space
        key_space(string/None): key space
        reliability(float): between 0 and 100, used during comparing by frequencies

    Returns:
        list: sorted (by frequencies) list of tuples (key, list(plaintexts))
    """
    if len(ciphertexts) < 2:
        log.critical_error("Too less ciphertexts")

    min_size = min(list(map(len, ciphertexts)))
    ciphertexts = [one[:min_size] for one in ciphertexts]
    log.info("Ciphertexts shrinked to {} bytes".format(min_size))

    pairs = break_repeated_key(''.join(ciphertexts), lang=lang, no_of_comparisons=no_of_comparisons, key_size=min_size,
                               alphabet=alphabet, key_space=key_space, reliability=reliability)

    res = [(pair[0], chunks(pair[1], min_size)) for pair in pairs]
    return res
Example #6
0
def iv_as_key(ciphertext,
              plaintext,
              padding_oracle=None,
              decryption_oracle=None,
              block_size=16):
    """If iv is used as key, we can recover it using decryption oracle, padding oracle or
    known plaintext (if first ciphertext block is repeated)

    Args:
        ciphertext(string): first block must be AES.encrypt(iv xor plaintext[0])
        plaintext(string): with padding
        padding_oracle(function/None)
        decryption_oracle(function/None)
        block_size(int)

    Returns:
        string: key (== iv)
    """
    key = None
    ciphertext = chunks(ciphertext, block_size)
    plaintext = chunks(plaintext, block_size)

    try:
        position_second = ciphertext.index(ciphertext[0], 1)
        log.debug("Position of the same block as the first is {}".format(
            position_second))
        key = xor(plaintext[0], ciphertext[position_second - 1],
                  plaintext[position_second])
    except ValueError:
        log.debug(
            "first ciphertext block is not repeated, will use decryption/padding oracle"
        )

    if key is None:
        iv = bytes(b'A' * block_size)
        iv_xor_plaintext0 = decrypt(ciphertext[0],
                                    padding_oracle=padding_oracle,
                                    decryption_oracle=decryption_oracle,
                                    iv=iv,
                                    block_size=block_size,
                                    is_correct=False,
                                    amount=1)
        iv_xor_plaintext0 = xor(iv_xor_plaintext0, iv)
        key = xor(iv_xor_plaintext0, plaintext[0])
    log.success("Key(hex): {}".format(b2h(key)))
    return key
Example #7
0
def is_ecb(cipher, block_size=16):
    """Check if there are repeated blocks in ciphertext

    Args:
        cipher(string)
        block_size(int)

    Returns:
        bool: True if there are repeated blocks (so it's probably ECB mode)
    """
    cipher_blocks = chunks(cipher, block_size)
    unique_blocks = set(cipher_blocks)
    if len(unique_blocks) < len(cipher_blocks):
        return True
    return False
Example #8
0
def compression_function_sha1(chunk, state):
    """Sha1 compression function

    Args:
        chunk(string): len(chunk) == 64
        state(list of ints): len(state) == 5

    Returns:
        list of ints: compressed state
    """
    state = state[:]
    w = chunks(chunk, 4)
    w = list(map(b2i, w))
    for i in range(16, 80):
        w.append(
            _left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1, 32))

    a = state[0]
    b = state[1]
    c = state[2]
    d = state[3]
    e = state[4]

    for i in range(80):
        if i // 20 == 0:
            f = d ^ (b & (c ^ d))
            k = 0x5A827999
        elif i // 20 == 1:
            f = b ^ c ^ d
            k = 0x6ED9EBA1
        elif i // 20 == 2:
            f = (b & c) | (b & d) | (c & d)
            k = 0x8F1BBCDC
        elif i // 20 == 3:
            f = b ^ c ^ d
            k = 0xCA62C1D6

        a, b, c, d, e = (_left_rotate(a, 5, 32) + f + e + k +
                         w[i]) & 0xffffffff, a, _left_rotate(b, 30, 32), c, d

    state[0] = (state[0] + a) & 0xffffffff
    state[1] = (state[1] + b) & 0xffffffff
    state[2] = (state[2] + c) & 0xffffffff
    state[3] = (state[3] + d) & 0xffffffff
    state[4] = (state[4] + e) & 0xffffffff
    return state
Example #9
0
def merkle_damgard(data, initial_state, compression_function):
    """Merkle-Damgard construction

    Args:
        data(string)
        initial_state(list of ints)
        compression_function(function)

    Returns:
        final state(string)
    """
    state = initial_state[:]
    data_chunks = chunks(data, 64)
    log.debug("Start merkle-damgard, chunks are: {}".format(data_chunks))
    for chunk in data_chunks:
        log.debug("Process chunk: {} with state: {}".format(b2h(chunk), state))
        state = compression_function(chunk, state)
    return state
Example #10
0
def find_block_size(encryption_oracle, constant=True):
    """Determine block size if ecb mode

    Args:
        encryption_oracle(callable)
        constant(bool): True if prefix and suffix have constant length

    Returns:
        int
    """
    if constant:
        log.debug("constant == True")
        payload = bytes(b'A')
        size = len(encryption_oracle(payload))
        while True:
            payload += bytes(b'A')
            new_size = len(encryption_oracle(payload))
            if new_size > size:
                log.info("block_size={}".format(new_size - size))
                return new_size - size
    else:
        log.debug("constant == False")
        payload = bytes(b'A')
        max_size = len(encryption_oracle(payload))
        possible_sizes = factors(max_size)
        possible_sizes.add(max_size)
        blocks_to_send = 5

        for block_size in sorted(possible_sizes):
            """send payload of length x, so at least x-1 blocks should be identical"""
            payload = random_bytes(1) * (blocks_to_send*block_size)
            enc_chunks = chunks(encryption_oracle(payload), block_size)
            for x in range(len(enc_chunks)-1):
                if enc_chunks[x] == enc_chunks[x+1]:
                    log.debug("Found two identical blocks at {}: {}".format(x, print_chunks(enc_chunks)))
                    for y in range(2, blocks_to_send-1):
                        if enc_chunks[x] != enc_chunks[x+y]:
                            break
                    else:
                        log.info("block_size={}".format(block_size))
                        return block_size
Example #11
0
def decrypt(encryption_oracle, constant=True, block_size=16, prefix_size=None, secret_size=None,
            alphabet=None):
    """Given encryption oracle which produce ecb(prefix || our_input || secret), find secret
    
    Args:
        encryption_oracle(callable)
        constant(bool): True if prefix have constant length (secret must have constant length)
        block_size(int/None)
        prefix_size(int/None)
        secret_size(int/None)
        alphabet(string): plaintext space
    
    Returns:
        secret(string)
    """
    log.debug("Start decrypt function")
    if not alphabet:
        alphabet = bytes(string.printable.encode())

    if not block_size:
        block_size = find_block_size(encryption_oracle, constant)

    if constant:
        log.debug("constant == True")
        if not prefix_size or not secret_size:
            prefix_size, secret_size = find_prefix_suffix_size(encryption_oracle, block_size)

        """Start decrypt"""
        secret = bytes(b'')
        aligned_bytes = random_bytes(1) * (block_size - (prefix_size % block_size))
        if len(aligned_bytes) == block_size:
            aligned_bytes = bytes(b'')

        aligned_bytes_suffix = random_bytes(1) * (block_size - (secret_size % block_size))
        if len(aligned_bytes_suffix) == block_size:
            aligned_bytes_suffix = bytes(b'')

        block_to_find_position = -1
        controlled_block_position = (prefix_size+len(aligned_bytes)) // block_size

        while len(secret) < secret_size:
            if (len(secret)+1) % block_size == 0:
                block_to_find_position -= 1
            payload = aligned_bytes + aligned_bytes_suffix + random_bytes(1) + secret
            enc_chunks = chunks(encryption_oracle(payload), block_size)
            block_to_find = enc_chunks[block_to_find_position]

            log.debug("To guess at position {}:".format(block_to_find_position))
            log.debug("Plain: " + print_chunks(chunks(bytes(b'P'*prefix_size) + payload + bytes(b'S'*secret_size), block_size)))
            log.debug("Encry: " + print_chunks(enc_chunks)+"\n")

            for guessed_char in range(256):
                guessed_char = bytes([guessed_char])
                payload = aligned_bytes + add_padding(guessed_char + secret, block_size)
                enc_chunks = chunks(encryption_oracle(payload), block_size)

                log.debug("Plain: " + print_chunks(chunks(bytes(b'P'*prefix_size) + payload + bytes(b'S'*secret_size), block_size)))
                log.debug("Encry: " + print_chunks(enc_chunks)+"\n")
                if block_to_find == enc_chunks[controlled_block_position]:
                    secret = guessed_char + secret
                    log.debug("Found char, secret={}".format(repr(secret)))
                    break
            else:
                log.critical_error("Char not found, try change alphabet. Secret so far: {}".format(repr(secret)))
        log.success("Secret(hex): {}".format(b2h(secret)))
        return secret
    else:
        log.debug("constant == False")
Example #12
0
def compression_function_md4(chunk, state):
    """MD4 compression function, taken from: https://gist.github.com/tristanwietsma/5937448

    Args:
        chunk(string): len(chunk) == 64
        state(list of ints): len(state) == 4

    Returns:
        list of ints: compressed state
    """
    def _f(x, y, z):
        return x & y | ~x & z

    def _g(x, y, z):
        return x & y | x & z | y & z

    def _h(x, y, z):
        return x ^ y ^ z

    def _f1(a, b, c, d, k, s, X):
        return _left_rotate(a + _f(b, c, d) + X[k], s)

    def _f2(a, b, c, d, k, s, X):
        return _left_rotate(a + _g(b, c, d) + X[k] + 0x5a827999, s)

    def _f3(a, b, c, d, k, s, X):
        return _left_rotate(a + _h(b, c, d) + X[k] + 0x6ed9eba1, s)

    state = state[:]
    x = chunks(chunk, 4)
    x = [b2i(one_x, endian='little') for one_x in x]
    a, b, c, d = state

    a = _f1(a, b, c, d, 0, 3, x)
    d = _f1(d, a, b, c, 1, 7, x)
    c = _f1(c, d, a, b, 2, 11, x)
    b = _f1(b, c, d, a, 3, 19, x)
    a = _f1(a, b, c, d, 4, 3, x)
    d = _f1(d, a, b, c, 5, 7, x)
    c = _f1(c, d, a, b, 6, 11, x)
    b = _f1(b, c, d, a, 7, 19, x)
    a = _f1(a, b, c, d, 8, 3, x)
    d = _f1(d, a, b, c, 9, 7, x)
    c = _f1(c, d, a, b, 10, 11, x)
    b = _f1(b, c, d, a, 11, 19, x)
    a = _f1(a, b, c, d, 12, 3, x)
    d = _f1(d, a, b, c, 13, 7, x)
    c = _f1(c, d, a, b, 14, 11, x)
    b = _f1(b, c, d, a, 15, 19, x)

    a = _f2(a, b, c, d, 0, 3, x)
    d = _f2(d, a, b, c, 4, 5, x)
    c = _f2(c, d, a, b, 8, 9, x)
    b = _f2(b, c, d, a, 12, 13, x)
    a = _f2(a, b, c, d, 1, 3, x)
    d = _f2(d, a, b, c, 5, 5, x)
    c = _f2(c, d, a, b, 9, 9, x)
    b = _f2(b, c, d, a, 13, 13, x)
    a = _f2(a, b, c, d, 2, 3, x)
    d = _f2(d, a, b, c, 6, 5, x)
    c = _f2(c, d, a, b, 10, 9, x)
    b = _f2(b, c, d, a, 14, 13, x)
    a = _f2(a, b, c, d, 3, 3, x)
    d = _f2(d, a, b, c, 7, 5, x)
    c = _f2(c, d, a, b, 11, 9, x)
    b = _f2(b, c, d, a, 15, 13, x)

    a = _f3(a, b, c, d, 0, 3, x)
    d = _f3(d, a, b, c, 8, 9, x)
    c = _f3(c, d, a, b, 4, 11, x)
    b = _f3(b, c, d, a, 12, 15, x)
    a = _f3(a, b, c, d, 2, 3, x)
    d = _f3(d, a, b, c, 10, 9, x)
    c = _f3(c, d, a, b, 6, 11, x)
    b = _f3(b, c, d, a, 14, 15, x)
    a = _f3(a, b, c, d, 1, 3, x)
    d = _f3(d, a, b, c, 9, 9, x)
    c = _f3(c, d, a, b, 5, 11, x)
    b = _f3(b, c, d, a, 13, 15, x)
    a = _f3(a, b, c, d, 3, 3, x)
    d = _f3(d, a, b, c, 11, 9, x)
    c = _f3(c, d, a, b, 7, 11, x)
    b = _f3(b, c, d, a, 15, 15, x)

    state[0] = (state[0] + a) & 0xffffffff
    state[1] = (state[1] + b) & 0xffffffff
    state[2] = (state[2] + c) & 0xffffffff
    state[3] = (state[3] + d) & 0xffffffff
    return state
Example #13
0
def decrypt(ciphertext,
            padding_oracle=None,
            decryption_oracle=None,
            iv=None,
            block_size=16,
            is_correct=True,
            amount=0,
            known_plaintext=None,
            async_calls=False):
    """Decrypt ciphertext
    Give padding_oracle or decryption_oracle (or both)

    Args:
        ciphertext(string): to decrypt
        padding_oracle(function/None)
        decryption_oracle(function/None)
        iv(string): if not specified, first block of ciphertext is treated as iv
        block_size(int)
        is_correct(bool): set if ciphertext will decrypt to something with correct padding
        amount(int): how much blocks decrypt (counting from last), zero (default) means all
        known_plaintext(string): with padding, from end (aligned to end of ciphertext)
        async_calls(bool): make asynchronous calls to oracle (not implemented yet)

    Returns:
        plaintext(string): with padding
    """
    _check_oracles(padding_oracle=padding_oracle,
                   decryption_oracle=decryption_oracle,
                   block_size=block_size)

    if block_size % 8 != 0:
        log.critical_error("Incorrect block size: {}".format(block_size))

    if len(ciphertext) % block_size != 0:
        log.critical_error("Incorrect ciphertext length: {}".format(
            len(ciphertext)))

    if decryption_oracle:
        if iv:
            ciphertext = iv + ciphertext
        blocks = chunks(ciphertext, block_size)
        plaintext = bytes(b'')
        for position in range(len(blocks) - 1, 0, -1):
            plaintext = xor(decryption_oracle(blocks[position]),
                            blocks[position - 1]) + plaintext
            log.info("Plaintext(hex): {}".format(b2h(plaintext)))
            if amount != 0 and len(plaintext) == amount * block_size:
                break
        log.success("Decrypted(hex): {}".format(b2h(plaintext)))
        return plaintext

    log.info("Start cbc padding oracle")
    log.debug(print_chunks(chunks(ciphertext, block_size)))

    # prepare blocks
    blocks = chunks(ciphertext, block_size)
    if iv:
        if len(iv) % block_size != 0:
            log.critical_error("Incorrect iv length: {}".format(len(iv)))
        log.info("Set iv")
        blocks.insert(0, iv)

    if amount != 0:
        amount = len(blocks) - amount - 1
    if amount < 0 or amount >= len(blocks):
        log.critical_error(
            "Incorrect amount of blocks to decrypt: {} (have to be in [0,{}]".
            format(amount,
                   len(blocks) - 1))
    log.info("Will decrypt {} block(s)".format(len(blocks) - 1 - amount))

    # add known plaintext
    plaintext = bytes(b'')
    position_known = 0
    chars_decoded = 0
    if known_plaintext:
        is_correct = False
        plaintext = known_plaintext
        blocks_decoded = len(plaintext) // block_size
        chars_decoded = len(plaintext) % block_size

        if blocks_decoded == len(blocks) - 1:
            log.debug("Nothing decrypted, known plaintext long enough")
            return plaintext
        if blocks_decoded > len(blocks) - 1:
            log.critical_error(
                "Too long known plaintext ({} blocks)".format(blocks_decoded))

        if blocks_decoded != 0:
            blocks = blocks[:-blocks_decoded]

        position_known = chars_decoded
        log.info("Have known plaintext, skip {} block(s) and {} bytes".format(
            blocks_decoded, chars_decoded))

    # start decryption
    for count_block in range(len(blocks) - 1, amount, -1):
        """ Blocks from the last to the second (all except iv) """
        log.info("Block no. {}".format(count_block))

        payload_prefix = bytes(b''.join(blocks[:count_block - 1]))
        payload_modify = blocks[count_block - 1]
        payload_decrypt = blocks[count_block]

        if chars_decoded != 0:
            # we know some chars, so modify previous block
            payload_modify = payload_modify[:-chars_decoded] +\
                             xor(plaintext[:chars_decoded], payload_modify[-chars_decoded:], bytes([chars_decoded + 1]))
            chars_decoded = 0

        position = block_size - 1 - position_known
        position_known = 0
        while position >= 0:
            """ Every position in block, from the end """
            log.debug("Position: {}".format(position))

            found_correct_char = False
            for guess_char in range(256):
                modified = payload_modify[:position] + bytes(
                    [guess_char]) + payload_modify[position + 1:]
                payload = bytes(b''.join(
                    [payload_prefix, modified, payload_decrypt]))

                iv = payload[:block_size]
                payload = payload[block_size:]
                log.debug(print_chunks(chunks(iv + payload, block_size)))

                correct = padding_oracle(payload=payload, iv=iv)
                if correct:
                    """ oracle returns True """
                    padding = block_size - position  # sent ciphertext decoded to that padding
                    decrypted_char = bytes(
                        [payload_modify[position] ^ guess_char ^ padding])

                    if is_correct:
                        """ If we didn't send original ciphertext, then we have found original padding value.
                            Otherwise keep searching and if won't find any other correct char - padding is \x01
                        """
                        if guess_char == blocks[-2][-1]:
                            log.debug(
                                "Skip this guess char ({})".format(guess_char))
                            continue

                        dc = int(decrypted_char[0])
                        log.info(
                            "Found padding value for correct ciphertext: {}".
                            format(dc))
                        if dc == 0 or dc > block_size:
                            log.critical_error(
                                "Found bad padding value (given ciphertext may not be correct)"
                            )

                        plaintext = decrypted_char * dc
                        payload_modify = payload_modify[:-dc] + xor(
                            payload_modify[-dc:], decrypted_char,
                            bytes([dc + 1]))
                        position = position - dc + 1
                        is_correct = False
                    else:
                        """ abcd efgh ijkl o|guess_char|xy || 1234 5678 9tre qwer - ciphertext
                            what ever itma ybex            || xyzw rtua lopo k|\x03|\x03\x03 - plaintext
                            abcd efgh ijkl |guess_char|wxy || 1234 5678 9tre qwer - next round ciphertext
                            some thin gels eheh            || xyzw rtua lopo guessing|\x04\x04\x04 - next round plaintext
                        """
                        if position == block_size - 1:
                            """ if we decrypt first byte, check if we didn't hit other padding than \x01 """
                            payload = iv + payload
                            payload = payload[:-block_size - 2] + bytes(
                                b'A') + payload[-block_size - 1:]
                            iv = payload[:block_size]
                            payload = payload[block_size:]
                            correct = padding_oracle(payload=payload, iv=iv)
                            if not correct:
                                log.debug("Hit false positive, guess char({})".
                                          format(guess_char))
                                continue

                        payload_modify = payload_modify[:position] + xor(
                            bytes([guess_char]) +
                            payload_modify[position + 1:], bytes([padding]),
                            bytes([padding + 1]))
                        plaintext = decrypted_char + plaintext

                    found_correct_char = True
                    log.debug(
                        "Guessed char(\\x{:02x}), decrypted char(\\x{:02x})".
                        format(guess_char, decrypted_char[0]))
                    log.debug("Plaintext: {}".format(plaintext))
                    log.info("Plaintext(hex): {}".format(b2h(plaintext)))
                    break
            position -= 1
            if found_correct_char is False:
                if is_correct:
                    padding = 0x01
                    payload_modify = payload_modify[:position + 1] + xor(
                        payload_modify[position + 1:], bytes([padding]),
                        bytes([padding + 1]))
                    plaintext = bytes(b"\x01")
                    is_correct = False
                else:
                    log.critical_error(
                        "Can't find correct padding (oracle function return False 256 times)"
                    )
    log.success("Decrypted(hex): {}".format(b2h(plaintext)))
    return plaintext