diff --git a/cryptography/cpa-prefix-pad/DESCRIPTION.md b/cryptography/cpa-prefix-pad/DESCRIPTION.md new file mode 100644 index 0000000..b67f911 --- /dev/null +++ b/cryptography/cpa-prefix-pad/DESCRIPTION.md @@ -0,0 +1,30 @@ +The previous challenge ignored something very important: [_padding_](https://en.wikipedia.org/wiki/Padding_(cryptography)#Byte_padding). +AES has a 128-bit (16 byte) block size. +This means that input to the algorithm _must_ be 16 bytes long, and any input shorter than that must be _padded_ to 16 bytes by having data added to the plaintext before encryption. +When the ciphertext is decrypted, the result must be _unpadded_ (e.g., the added padding bytes must be removed) to recover the original plaintext. + +_How_ to pad is an interesting question. +For example, you could pad with null bytes (`0x00`). +But what if your data has null bytes at the end? +They might be erroneously removed during unpadding, leaving you with a plaintext different than your original! +This would not be good. + +One padding standard (and likely the most popular) is PKCS7, which simply pads the input with bytes all containing a value equal to the number of bytes padded. +If one byte is added to a 15-byte input, it contains the value `0x01`, two bytes added to a 14-byte input would be `0x02 0x02`, and the 15 bytes added to a 1-byte input would all have a value `0x0f`. +During unpadding, PKCS7 looks at the value of the last byte of the block and removes that many bytes. +Simple! + +But wait... +What if exactly 16 bytes of plaintext are encrypted (e.g., no padding needed), but the plaintext byte has a value of `0x01`? +Left to its own devices, PKCS7 would chop off that byte during unpadding, leaving us with a corrupted plaintext. +The solution to this is slightly silly: if the last block of the plaintext is exactly 16 bytes, we add a block of _all_ padding (e.g., 16 padding bytes, each with a value of `0x10`). +PKCS7 removes the whole block during unpadding, and the sanctity of the plaintext is preserved at the expense of a bit more data. + +Anyways, the previous challenge explicitly disabled this last case, which would have the result of popping in a "decoy" ciphertext block full of padding as you tried to push the very first suffix byte to its own block. +This challenge pads properly. +Watch out for that "decoy" block, and go solve it! + +---- +**NOTE:** +The full-padding block will *only* appear when the last block of plaintext perfectly fills 16 bytes. +It'll vanish when one more byte is appended (replaced with the padded new block containing the last byte of plaintext), but will reappear when the new block reaches 16 bytes in length. diff --git a/cryptography/cpa-prefix-pad/run b/cryptography/cpa-prefix-pad/run new file mode 100755 index 0000000..a85e1a2 --- /dev/null +++ b/cryptography/cpa-prefix-pad/run @@ -0,0 +1,27 @@ +#!/opt/pwn.college/python + +from base64 import b64encode, b64decode +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad +from Crypto.Random import get_random_bytes + +flag = open("/flag", "rb").read().strip() + +key = get_random_bytes(16) +cipher = AES.new(key=key, mode=AES.MODE_ECB) + +for n in range(31337): + print("") + print("Choose an action?") + print("1. Encrypt chosen plaintext.") + print("2. Prepend something to the flag.") + if (choice := int(input("Choice? "))) == 1: + pt = input("Data? ").strip().encode() + elif choice == 2: + pt = input("Data? ").strip().encode() + flag + else: + break + + padded_pt = pad(pt, cipher.block_size) + ct = cipher.encrypt(padded_pt) + print(f"Result: {b64encode(ct).decode()}") diff --git a/cryptography/cpa-prefix/DESCRIPTION.md b/cryptography/cpa-prefix/DESCRIPTION.md index a2c7b01..99d1c5e 100644 --- a/cryptography/cpa-prefix/DESCRIPTION.md +++ b/cryptography/cpa-prefix/DESCRIPTION.md @@ -11,3 +11,7 @@ The core attack is the same as before, it just involves more data massaging. Keep in mind that a typical pwn.college flag is somewhere upwards of 50 bytes long. This is four blocks (three full and one partial), and the length can vary slightly. You will need to experiment with how many bytes you must prepend to push even one of the end characters to its own block. + +**HINT:** +Keep in mind that blocks are 16 bytes long! +After you leak the last 16 bytes, you'll be looking at the second-to-last block, and so on. diff --git a/cryptography/module.yml b/cryptography/module.yml index 6f98f61..ba5f608 100644 --- a/cryptography/module.yml +++ b/cryptography/module.yml @@ -25,6 +25,8 @@ challenges: name: AES-ECB-CPA-Suffix - id: cpa-prefix name: AES-ECB-CPA-Prefix +- id: cpa-prefix-pad + name: AES-ECB-CPA-Prefix-2 - id: level-5 name: level5 - id: level-6