Tags: crypto 

Rating: 5.0

# waRSAw (711 points, 18 solves)

We are given a program encrypt.py and the address of a server running the program.
Upon connection, the program generates a new RSA key and uses it to encrypt the flag, then sends this and the public modulus back to us. It then presents two options: [1] encrypt and return a client-provided value using the current key, or [2] decrypt a user provided value and return the parity (LSB) of the plaintext.

```Welcome to RSA encryption oracle!
Here take your flag (in hex): 3bca234bc42e7bad6ddfb34087379d61a641b3d479ab81a1596dd0039d494a2f2dac34a84ea164d4d32c71b4c1c4f0fc8578a2351d56835ca5e29c10c7159aa2a06fccb04f6a78b2bad9f6ae6a5a2d28d517e2db1b4afa32c1ad7069ac911efe0c613f549feddd999654564962efca072eafc00513fb787accaaa669ea43cab3
Here take modulus: 92401869837306977294650062841998549939040887700291715753752856697480103414693811412477653726273704373726166706255039340501057500321883555397456175481567491772576064447048367968512697492739285702706678102637780437395436063586692324627652276064622882449368388496256736344111756633249154600280404636361369082973
RSA service
[1] Encrypt
[2] Decrypt
Enter your choice: 2
Enter the ciphertext you want to decrypt (in hex): 3bca234bc42e7bad6ddfb34087379d61a641b3d479ab81a1596dd0039d494a2f2dac34a84ea164d4d32c71b4c1c4f0fc8578a2351d56835ca5e29c10c7159aa2a06fccb04f6a78b2bad9f6ae6a5a2d28d517e2db1b4afa32c1ad7069ac911efe0c613f549feddd999654564962efca072eafc00513fb787accaaa669ea43cab3
Here take your plaintext (in hex): 00
```

There exists a well-known LSB Oracle attack for RSA, explained in detail [here](https://github.com/ashutosh1206/Crypton/tree/master/RSA-encryption/Attack-LSBit-Oracle).

In short, for modulus `n=p*q`, exponent `e`, secret plaintext `P` and ciphertext `C`, i.e. `P^e = C (mod n)`, we know `e`, `C` and `n`. Thus we can compute `2^e * C = 2^e * P^e = 2P^e (mod n)`, ask the Oracle (server) to decrypt this and return the parity of `2P`. We know that
1. `P < n`, otherwise encryption would be lossy due to mod
2. `n` is odd, it is the product of two big primes
3. `2P` is even

So `2P (mod n)` is even if and only if `P < n/2`, narrowing the possible values of `P` by half.

Repeating the process, decrypting `2^e * 2^e * C (mod n)` we can get the parity of `4P (mod n)` which has another factor of two and hence will be even if and only if `0 < P < n/4` or `3n/4 < P < n` (I am not being careful with inclusive bounds). Combined with the information from the first step we have reduced the range of possible values of `P` by a factor of 4. Continuing (on step `i` computing `2^ie * C (mod n)`), we may iteratively half the possible values of `P` until after about `log_2(n)` requests we recover the plaintext.

The twist in this challenge comes from the fact that a new key is generated for every connection, and we may only request one decrypt per connection. Therefore we must generalise the solution.

Convince yourself that, on step `i+1` above, the LSB oracle gives us the ability to divide the range `[0, n]` evenly into `2^i` sub-ranges and ask whether `P` lies in the lower half or upper half of any of those sub-ranges. Now, since we have a different `n` on each conection, our sub-ranges don't line up nicely with the ranges from the previous step. But that's fine.

We simply maintain a running lower and upper bound on `P`, called `L` and `U` resp. In each iteration, given an `n`, we compute `i` such that both these bounds lie inside one of the `2^i` even partitions of `[0, n]` and that the halfway point inside this partition lies between `L` and `U`. Then we request the parity of the decryption of `2^((i+1)*e) * c (mod n)`, telling us whether `P` lies above or below the midpoint, and update either our upper or lower bound accordingly. Thus each step we further restrict the range of possible values of `P` by half on average (well maybe not exactly due to how moduli are distributed?). The relevant part of one step in the attack code is shown below.

```
factor = 2
midpoint = modulus / factor
while not (midpoint > lower_limit and midpoint < upper_limit):
factor *= 2
if midpoint < lower_limit:
midpoint += modulus / factor
elif midpoint > upper_limit:
midpoint -= modulus / factor

# (ask server for parity)
...

if odd:
lower_limit = int(midpoint)
else:
upper_limit = int(midpoint)
```

The flag was padded at the start and end to make the search a bit more interesting, but the end-padding in particular actually helped because deducing the final few bits of the plaintext would have required much more careful handling of bounds than I used. The exploit script prints a value within the range of possiblities as bytes every step, so we can see when enough of the flag has emerged to finish.

The full attack script is waRSAaw.py

Original writeup (https://github.com/jndean/CTF-writeups/tree/master/inCTF-2019/waRSAw).