# CBC Bitflipping Attacks

Cryptopals Set 2 Challenge 16 // code repo // demo

### Step 1: Flipping Bits, Wearing Knits

In CBC decryption, the plaintext of each block’s post-decryption is XOR’d with the previous block’s ciphertext. Altering ciphertext block `N-1`

(Cn-1) thus effects the `N`

th block of resulting plaintext (Pn). CBC cares not whether a message has been tampered with. It has no MAC. We can XOR arbitrary bytes into the ciphertext and have them appear in the plaintext under two conditions:

- We know the plaintext
- We know the ciphertext

### Step 2: But How Can We Know the Plaintext?

Well, if there’s a user component, the plaintext can be whatever we’d like it to be. Lots of `A`

s.

The challenge has us write some basic one-liners to escape and de-escape the special characters `;`

and `=`

(to prevent the trivial solution of just entering these directly into the user-supplied data).

```
const enc = { ";": "%3B", "=": "%3D" };
const decr = { "%3B": ";", "%3D": "=" };
const quoteOut = (txt) => txt.replace(/(\;|\=)/g, ($1) => enc[$1]);
const quoteIn = (esc) => esc.replace(/(\%3B|\%3D)/g, ($1) => decr[$1]);
```

We write a random encryption function that prepends the escaped string `comment1=cooking%20MCs;userdata=`

to user-supplied data, appends the escaped `;comment2=%20like%20a%20pound%20of%20bacon`

, PKCS#7 pads, and encrypts under a random key and IV using CBC.

```
function randomEncrypt(pText) {
pText = txtToHex(quoteOut(pText)).map((e) => hexPad(e));
let input = PKCS7(pre.concat(pText, post), 16);
return aesEncryptCBC(input, randomKey, randomIV, 16);
}
```

We write a function to decrypt and unescape the special characters.

```
const randomDecrypt = (c) =>
quoteIn(aesDecryptCBC(c, randomKey, randomIV, 16).map(hexToText).join(""));
```

We’re then challenged to manipulate the ciphertext results of the encryption function to produce a ciphertext that decrypts to contain the string `;admin=true;`

.

### Step 3: Twiddles and Bits (Theory)

You can write to the plaintext in contiguous blocks of `blockSize`

characters. Writing to block Pn+1 requires modifying block Cn-1 and creates gibberish in Pn, since the error introduced into Cn-1 propagates one block forward to Cn. If you’d like to write more than a `blockSize`

worth, write to every other plaintext block, so long as you do not care about intervening gibberish plaintext blocks.

What and how to write? Think about the decryption process (`n==block, i==block byte index`

):

Cn-1[i] XOR Cn[i] (post-decryption) = Pn[i].

We want to change Pn[i] to a given value, and we need to know what to change Cn-1[i] to in order to do this. So just solve for it.

Cn-1[i] = Pn[i] XOR Cn[i] (post-decryption)

- Pn[i] is just the current plaintext value of the block we wish to modify
- Cn[i] (post-decryption) is the value we wish the plaintext to have
- Cn-1[i] is the byte we flip (XOR-ing in (Pn[i] XOR Cn[i])) in the ciphertext

Note the special case when `n=1`

, and the previous ciphertext block is the CBC initialization vector.

Here’s one more visual, in case ASCII does it for you better:

```
Plaintext
|comment1%3Dcooki||ng%20MCs%3Buserd|
Block 1 Block 2
Ciphertext
|----------------||----------------|
Block 1 Block 2
```

Let’s say I want to flip ‘n’ in Block 2 to ‘Z’.

- Pn[0] = ‘n’ (0x6e)
- Cn[0] (post-decryption) is the value we wish the plaintext to have Z (0x5A)
- Cn-1[0] is the byte we need to flip in the ciphertext

0x63 XOR 0x5A = 0x39. Then just set byte 1 of block 1 of the ciphertext to ^=(0x39). The first block of plaintext in the decryption output is jumbled, but the second block reads `Zg%20MCs%3Buserd`

. Zing!

### Step 4: Twiddles and Bits (Praxis)

We know that the blocks of plaintext (chunked 16 byte sections) look like this when userdata is `''`

:

```
comment1%3Dcooki
ng%20MCs%3Buserd
ata%3D%3Bcomment
2%3D%20like%20a%
20pound%20of%20b
acon
```

We know that we want to have a `%3B`

at the end of our string. But if we put it in ourselves, we’ll be over 16 characters, which is the limitation for contiguous character writing with `blockSize==16`

. So I’ll use the existing `%3B`

, before ‘comment’, padding out `userdata`

with 25 `0`

s.

`randomEncrypt('0000000000000000000000000')`

gives us this:

```
comment1%3Dcooki
ng%20MCs%3Buserd
ata%3D0000000000
000000000000000%
3Bcomment2%3D%20
like%20a%20pound
%20of%20bacon
```

When we decrypt, we map the escaped characters `;`

and `=`

back from their escaped forms, so we should really make the replacement of `%3Badmin%3Dtrue%3B`

. I’ll calculate my array of replacements and XOR each one with our padding character (‘0’ == 0x30).

```
'%3Badmin%3Dtrue'.split("").map(e=>parseInt(e.charCodeAt(),10).toString(16)).map(e=>hexXOR(e,'30'));
=> ["15", "3", "72", "51", "54", "5d", "59", "5e", "15", "3", "74", "44", "42", "45", "55"]
```

We can XOR in our 15 hex characters with ciphertext block 3, and the result will end up in plaintext block 4, followed by the `%3B`

we borrowed.

```
for (var i = 0; i < theReplacements.length; i++) {
e[32 + i] = hexXOR(e[32 + i], theReplacements[i]);
}
randomDecrypt(e);
```

`comment1=cooking%20MCs;userdo|=¦1^<ÉTv¤Ï;admin=true;comment2=%20like%20a%20pound%20of%20bacon`

This replacement occurs without any direct knowledge of the encryption key or the initialization vector, simply a manipulation of the ciphertext output. What have we learned today? Use a MAC!

The challenge to write a function that lets you XOR arbitrary lengths of text into the ciphertext is left as an exercise for the reader. Though perhaps not a valuable one, what with the interleaved mangled plaintext block every `blockLength`

chars.