#!/usr/bin/python3 -u
# requirements: PyCryptodome
# taken from https://gist.github.com/nil0x42/8bb48b337d64971fb296b8b9b6e89a0d
# and modified to handle the first block and use python only
import base64
from Crypto.Util.strxor import strxor
from Crypto.Util.Padding import pad
### variables to set
PLAINTEXT = b"id=12345678;name=myname;is_admin=false;mail=mymail@mail.com"
CIPHERTEXT = base64.b64decode("RlDfIOgTrnUsIZJE802+wNr0jll/3ZiM4BGHH7xMO8TF0QBkebuuychCaeDBhUP2kOJnerZm3kQoe3h9Sv12oA==")
BLOCK_SIZE = 16 # AES
PADDING_TYPE = "pkcs7" # pkcs7, x923 for ANSI_X923, iso7816 for ISO/IEC 7816-4
OLD_STR = b"false" # string to flip
NEW_STR = b"true;" # string that will replace OLD_STR
## Optional IV if the string to flip is in the first block
IV = None
print("\n[+] Infos:")
print("OLD_STR = %s" % OLD_STR)
print("NEW_STR = %s" % NEW_STR)
print("\n[+] Plaintext (%d bytes):" % len(PLAINTEXT))
print(" %s" % PLAINTEXT.hex())
if len(PLAINTEXT) != len(CIPHERTEXT):
PLAINTEXT = pad(PLAINTEXT, block_size=BLOCK_SIZE, style=PADDING_TYPE)
print("\n[+] Plaintext [Padded with %s] (%d bytes):" % (PADDING_TYPE, len(PLAINTEXT)))
print(" %s" % PLAINTEXT.hex())
print("\n[+] Ciphertext (%d bytes):" % len(CIPHERTEXT))
print(" %s" % CIPHERTEXT.hex())
# sanity checks on inputs
assert len(PLAINTEXT) == len(CIPHERTEXT)
assert len(CIPHERTEXT) % BLOCK_SIZE == 0
assert OLD_STR in PLAINTEXT
assert len(OLD_STR) == len(NEW_STR)
# Find the first block where the string to flip is located
blocks = [PLAINTEXT[i:i + BLOCK_SIZE] for i in range(0, len(PLAINTEXT), BLOCK_SIZE)]
block_offset = 0
in_block = -1
for block_id, block in enumerate(blocks):
if OLD_STR in block:
in_block = block_id
block_offset = block.find(OLD_STR)
break
flipped_ciphertext = None
if in_block == -1:
raise Exception("String to flip must be contained in one single block")
elif in_block == 0:
# If the string to flip is in the first block, flip the bits in the IV
if IV is None:
raise Exception("IV is required to flip the first block")
flipped_iv = IV[:block_offset]
flipped_iv += strxor( strxor(OLD_STR,NEW_STR), IV[block_offset:block_offset+len(OLD_STR)] )
flipped_iv += IV[block_offset+len(OLD_STR):]
print("\033[32m\n[+] Flipped IV: (%d bytes)" % len(flipped_iv))
print(" %s" % flipped_iv.hex())
else:
# If the string to flip is in a regular CBC block, flip the bits in the previous block
# pos = same block offset, in previous block
pos = (in_block - 1) * BLOCK_SIZE + block_offset
end_pos = pos + len(OLD_STR)
# here the magic happens...
flipped_ciphertext = CIPHERTEXT[:pos]
flipped_ciphertext += strxor( strxor(OLD_STR,NEW_STR), CIPHERTEXT[pos:end_pos] )
flipped_ciphertext += CIPHERTEXT[end_pos:]
print("\033[32m\n[+] Flipped ciphertext: (%d bytes)" % len(flipped_ciphertext))
print(" %s" % flipped_ciphertext.hex())
print("\n[+] Flipped ciphertext [BASE64]:")
print(" " + base64.b64encode(flipped_ciphertext).decode() + "\033[0m")