Tags: pwntools aes-cbc aes crypto 

Rating:

(The writeup looks better [in github](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/tree/main/tag-series-2))
# Crypto: tag-series-2
solver: [N04M1st3r](https://github.com/N04M1st3r)
writeup-writer: [L3d](https://github.com/imL3d)

![Challege image](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/blob/main/tag-series-2/_images/challengeimage.png?raw=true)

**files (copy):** [chal.py](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/blob/main/tag-series-2/files/chal.py)

## Solution

### Preview

*It is recommended to read the [first challenge](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/tree/main/tag-series-1) in this series before reading this one.*

This challenge is very similar to the first one; we need to input a plaintext string and a guess to the last block of it's AES (CBC) encryption 4 times in a row - if one of those guesses match we get the flag.

The differences from the first challenge:
1. AES encryption with mode CBC is used, instead of ECB.
2. We get 4 tries instead of 3.
3. To each plaintext given it's length is being added to the end before the encryption.
4. The plaintext to get the flag needs to start with a shorter string: `"GET: flag.txt"`

And again, before we will showcase the solution, an understanding of how the `AES CBC` mode works is needed, so we can properly try and exploit this algorithm and it's usecase.

### AES (CBC) mode

The CBC mode fixes some of the problems with the lack of *cryptographic diffusion* that EBC mode had. Instead of each plaintext block being enciphered on its own, it is being XORed with the previous ciphertext, and only then being encrypted (the first block is being XORed with a random Initialization Vector), as shown in the image below:

![illu1](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/blob/main/tag-series-2/_images/cbc.png?raw=true)

Again, for our purposes in the tag-series challenges, we don't really need to know much about the [Block Cipher algorithm](https://en.wikipedia.org/wiki/Block_cipher), apart from it being a *deterministic algorithm*, meaning that if we give it the same key and the same input, it will always give us the same output.

The change from `ECB` mode to `CBC` mode fixes the exploit we used at the previous challenge - because each part affects the following one it seems as though we cannot have two different plaintexts produce the same result.

### The exploit

At first glance, this challenge seems very discouraging - we need to find two differnet plaintext inputs that will give us the same last block of ciphertext, but every little change in the previous blocks affects the outcome!
After looking further at the `CBC` mode of encryption and the given restrictions, we can come to 2 subtle but important realizations:
1. The same 2 inputs to the XOR and then the Block Encryption, will yield the same ciphertext.
2. It **is** possible to get the same last block of ciphertext from two different plaintexts, we just have to make sure the two inputs to that "part of the chain" are the same (take a look at the illustartaion below).

![illu1](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/raw/main/tag-series-2/_images/illu1.png?raw=true)

Because the last block will always contain the legth, our goal is to try and create the same ciphertext of the block that came before it (it's the other part of the XOR), with two different plaintext inputs.
For this, we have to do some sort of computation. XOR is commutative operation, and we can leverage this fact to our advantage.
As shown in the illustartion below, both the blocks of ciphertexts generated by `1)` and `2)` will be the same.

![illu2](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/raw/main/tag-series-2/_images/illu2.png?raw=true)

So, the only thing left to do is just find a way to get `A` and `B`, and to put the pieces together!

As it happens, the both parts is really easy (at this point xD) - we have four tries.
Lets go over each try, and what information we receive from it:
**1, 2.** The plaintext that will be encrypted is `our_input + ITS_LENGTH`. We do this in order to find A and B - they are the last block of a ciphertext that we now know. This part is necessary because we can't evaluate them ourselves, we don't have the key for the Block Encryption (nor the Initializing Vector). But because these factors are the same for each differenty try, it doesn't matter that we don't know them.

**3.** The response from this step will be the same result that we should get at the next one. We need to **recreate the terms** the allowed B (or A) to be formed, then append the other ciphertext. This will create the same input to the rest of the encryption, thus allowing us to achive the same result in 2 way (see the illustration above).

**4.** For the last time we want to do the same, but swapped - recreate the terms that allowed the other part to be formed, and the send the other one. This will be evaluated to the same last block of ciphertext we recieved at the previous try.

NOTE: for this to work both the inputs that created A and B **must** to have the same length - the two final payloads will contain these inputs, and the lengths of those payloads need to be the same for us to get the same result.
This is an illustartion of the final payload:

![illu3](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/raw/main/tag-series-2/_images/illu3.png?raw=true)

The implementation of the exploit we devised in code:
```python
from pwn import *

INPUT1 = b"GET: flag.txt" + b"pad" # The last plaintext we send, is obligated start with this string to get the flag
INPUT2 = b"The-C0d3Bre4k3rs" # Second input, needs to be the same length as the first - 16 bytes
LEN16 = b'\x00' * 15 + b'\x10' # The length of our inputs

con = connect('tagseries2.wolvctf.io', 1337)

con.recvuntil(b'disabled ==')
con.recvline()

con.sendline(INPUT1)
con.sendline(b'irrelevant')
A = con.recvline()[:-1] # the output of INPUT1 + ITS_LEN

con.sendline(INPUT2)
con.sendline(b'irrelevant')
B = con.recvline()[:-1] # the output of INPUT2 + ITS_LEN

# The parts that make B then A
con.sendline(INPUT2 + LEN16 + A)
con.sendline(b'irrelevant')
result = con.recvline()[:-1]

# The parts that make A then B
con.sendline(INPUT1 + LEN16 + B)
con.sendline(result) # This has the same result as the one we performed above

flag = con.recvline()[:-1]
print(flag)

con.close()
```

We get the flag?: `wctf{W0w_1_w4s_ev3n_u51ng_CBC}`

To the [next one](https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/tree/main/tag-series-3) ➡️

Original writeup (https://github.com/C0d3-Bre4k3rs/WolvCTF2024-Writeups/tree/main/tag-series-2).