Tags: crypto 

Rating: 5.0

# Simple ECDSA — Writeup

## Challenge
```
18 Solves
Simple ECDSA
449
I optimised ECDSA. My experiments confirm that it is still a correct signature scheme.

nc 52.59.124.14 5050
```

We were given a service that claimed to provide an "optimized" version of ECDSA. Our task was to either sign a challenge or recover the flag by forging a valid signature.

---

## Provided Code
Two files were given:

- `ec.py` — basic elliptic curve arithmetic (point addition, doubling, scalar multiplication).
- `chall.py` — the server script implementing the signature scheme.

The relevant part of the server’s signing/verification:

```python
def sign(m, d, k):
e = H(m)
r = (k*G).x % n
s = inverse(k, n) * (e + d) % n
return r, s

def verify(m, r, s, Q):
e = H(m)
R = (e * inverse(s, n)) * G + inverse(s, n) * Q
return r == R.x % n
```

---

## Vulnerability
This is **not standard ECDSA**.
In real ECDSA:
```
s = k^{-1}( e + d·r ) mod n
```
Here, the multiplier `r` was **dropped** — they used:
```
s = k^{-1}( e + d ) mod n
```

Verification becomes:
```
R = (e/s)·G + (1/s)·Q
check r == x(R) mod n
```

That means we can freely choose `s`, compute `r` accordingly, and produce valid signatures **without the private key**.

---

## Exploit Strategy
1. Connect to the service, parse the public key `Q = d·G`.
2. Ask for a challenge string, hash it with **MD5** (as in the code).
3. Forge a signature:
- Pick `s = 1`.
- Compute `R = H(m)·G + Q`.
- Set `r = R.x mod n`.
4. Send `(r, s)` back to the server.

This is always accepted, since verification holds by construction.

---

## Exploit Script

```python
from pwn import remote
import re, hashlib

# --- elliptic curve implementation omitted for brevity ---
# (standard P-256 curve, same as ec.py)

# connect
io = remote("52.59.124.14", 5050)

# parse public key
line = io.recvuntil(b"Choose").decode()
m = re.search(r"Point\((\d+),\s*(\d+)\)", line)
Qx, Qy = int(m.group(1)), int(m.group(2))
Q = P(C, Qx, Qy)

# choose option 2
io.sendline(b"2")
line = io.recvline().decode()
chal_hex = re.search(r"([0-9a-f]{64})", line).group(1)
chal = bytes.fromhex(chal_hex)

# forge
e = int(hashlib.md5(chal).hexdigest(),16) % n
R = G*e + Q
r = R.x % n
s = 1

# send
io.sendline(f"{r},{s}".encode())
print(io.recvall().decode())
```

---

## Result
Running the solver yields:

```
ENO{1her3_i5_4_lack_0f_r2dund4ncy}
```

---

## Lessons
- Removing the `r` factor from ECDSA breaks its security completely.
- Always stick to standard, reviewed implementations of cryptographic primitives.
- Deterministic nonces (RFC 6979) or secure randomness are essential for ECDSA, but even more critical is **not modifying the math**.

---

**Flag:** `ENO{1her3_i5_4_lack_0f_r2dund4ncy}`