Rating:

# Nibelung (crypto, 525p, 22 solved)

A very interesting crypto challenge.
We get [server code](https://raw.githubusercontent.com/TFNS/writeups/master/2020-03-07-zer0ptsCTF/nibelung/server.py) and a [library](https://raw.githubusercontent.com/TFNS/writeups/master/2020-03-07-zer0ptsCTF/nibelung/fglg.py) presumably implementing a FiniteGeneralLinearGroup.
It seems to provide a way to perform operations on Matrices mod p (default p is random 512 bit prime).

The server turns flag into a matrix, then generates a random matrix `U`, encrypts the flag and sends the encryption result to us.
Encryption and decryption is:

```python
def encrypt(U, X):
return U * X * U ** -1

def decrypt(U, X):
return U ** -1 * X * U
```

The results of those operations are elements of `fglg` so matrices mod p.

The server allows us to encrypt and decrypt data at will, but there is a small twist -> we can only provide data using:

```python
def bytes2gl(b, n, p=None):
assert len(b) <= n * n
X = FiniteGeneralLinearGroup(n, p)
padlen = n * n - len(b)
b = bytes([padlen]) * padlen + b
for i in range(n):
for j in range(n):
X.set_at((j, i), b[i*n + j])
return X

def recv_message(n, p):
print("Data: ", end="", flush=True)
b = base64.b64decode(input())
return bytes2gl(b, n, p)
```

This means we can only encrypt or decrypt matrix with elements `0..255`, so we can't simply send the encrypted flag for decryption, because the matrix elements are much bigger than that.

The key observation here is to notice that the name of the library is a lie.
While it claims to implement a `Group`, in reality what we get is a `Ring` instead.
This is also suggested by the task name -> https://en.wikipedia.org/wiki/Der_Ring_des_Nibelungen

As everyone, I'm sure, remembers from Algebra a Group has only `additive` operation defined, while a Ring has also a `multiplicative` operation available.
It's clearly the case here -> we can both add and multiply matrices provided by the library.

Going back to Algebra, we know that there are some properties which hold:

```
(x+y)+z = x+(y+z)
x*y = y*x
(x+y)*z = x*z + y*z
```

Those properties hold just as well with matrices we have.
This means the encryption and decryption process is homomorphic, for example: `encrypt(A+B) == encrypt(A)+encrypt(B)`.

This means we can split the encrypted flag into parts, decrypt each one of them separately, and then combine them back! It's a classic example of `blinding attack`.

We don't even have to work with whole matrix, we can focus on single cell!
Notice that:

```
[A B] = [A 0] + [0 B] + [0 0] + [0 0]
[C D] [0 0] [0 0] [C 0] [0 D]
```

In order to get decrypted value for element `A` we need to perform two decryptions, one for `255` and another for `A%255`.
Once we do that we can simply do `dec_matrix(255)*A/255 + dec_matrix(A%255)` to recover the original value.

We effectively split the value into `x*255 + y`.

We proceed like that for every cell of the encrypted flag and combine the results:

```python
def solver(res, p, dec_oracle):
n = len(res)
recovered = FiniteGeneralLinearGroup(n, p)
for i in range(n):
for j in range(n):
print('recovered', i, j)
val = res[i][j]
k = val / 255
remainder = val % 255
payload = list('\0' * n * n)
payload[i * n + j] = chr(255)
result_255 = dec_oracle("".join(payload))
X = create_from_matrix(result_255, n, p)
recovered += X * k
payload[i * n + j] = chr(remainder)
result_remainder = dec_oracle("".join(payload))
X = create_from_matrix(result_remainder, n, p)
recovered += X
return recovered
```

And once we run this we get `zer0pts{r1ng_h0m0m0rph1sm_1s_c00l}`

Complete solver, including some sanity checks, [here](https://raw.githubusercontent.com/TFNS/writeups/master/2020-03-07-zer0ptsCTF/nibelung/solver.py)

Original writeup (https://github.com/TFNS/writeups/blob/master/2020-03-07-zer0ptsCTF/nibelung/README.md).