---
# System prepended metadata

title: Simple Crypto - 0x09(2023 Lab - signature)
tags: [Crypto, eductf, CTF]

---

# Simple Crypto - 0x09(2023 Lab - signature)
## Background
[ [edu-ctf 2023] week03 - crypto2 - ECDSA](https://www.youtube.com/live/u4ZVc8PuJC0?si=ychlqdZnGVfFYRAV&t=4075)
>![](https://hackmd.io/_uploads/ryVbmdMWp.png)
>
>![](https://hackmd.io/_uploads/HkJMXOG-T.png)

## Source code
:::spoiler Source Code
```python=
from random import randint
from Crypto.Util.number import *
from hashlib import sha256
from ecdsa import SECP256k1
from ecdsa.ecdsa import Public_key, Private_key, Signature
from secret import FLAG

E = SECP256k1
G, n = E.generator, E.order

d = randint(1, n)
k = randint(1, n)
pubkey = Public_key(G, d*G)
prikey = Private_key(pubkey, d)
print(f'P = ({pubkey.point.x()}, {pubkey.point.y()})')

for _ in range(3):
    print('''
1) Request for Signature
2) Check the Permission
3) exit''')
    option = input()
    if option == '1':
        msg = input('What do you want? ')
        if msg == 'Give me the FLAG.':
                print('No way!')
        else:
            h = sha256(msg.encode()).digest()
            # k = k * 1337 % n
            sig = prikey.sign(bytes_to_long(h), k)
            print(f'sig = ({sig.r}, {sig.s})')

    elif option == '2':
        msg = 'Give me the FLAG.'
        r = input('r: ')
        s = input('s: ')
        h = bytes_to_long(sha256(msg.encode()).digest())
        verified = pubkey.verifies(h, Signature(int(r), int(s)))
        if verified:
            print(FLAG)
        else:
            print('Bad signature')
    else:
        print("bye~")
        break


```
:::
## Recon
這一題主要就是利用上課提到的nonce $k$不隨機的問題，因為$k$只能用一次，也就代表他需要夠隨機，如果像LCG這樣的psudo random generator產生的話，一但被compromise，就會被推導出private key $d$，而這個lab就是有這樣的問題
1. 觀察source code會發現不同的nonce $k$之間會產生一個1337倍數的關係，然後如果request `Give me the FLAG.`的signature會被拒絕，所以只能自己產生`Give me the FLAG.`的signature再丟給server檢查，如果過了就可以拿到flag，但重點是要怎麼偽造signature假裝是server簽的?就是要想辦法拿到server產生的private key $d$，可以詳細看一下source code中提到，通常public key都一樣，所以重點是$d$才能產生private key，然後用private key簽署message
    ```python
    E = SECP256k1
    G, n = E.generator, E.order
    d = randint(1, n)
    pubkey = Public_key(G, d*G)
    prikey = Private_key(pubkey, d)
    ↓
    sig = prikey.sign(bytes_to_long(h), k)
    ```
2. 已知(題目給的部分)
    只要我們給兩次要簽章的message，總共可以得到以下資訊
    $$
    coordinate\ (x_0,\ y_0),\\
    hash\ H_1,\ hash\ H_2,\\
    signature\ (s_1,\ r_1),\ (s_2,\ r_2)
    $$
3. 推導
    假設$msg=b'a'$
    $$
    H_1 = H_2 = sha256(msg)\\
    \begin{aligned}
    k_1 &= {s_1}^{-1} \cdot (H_1 + d\cdot r_1)={s_1}^{-1} \cdot H_1 + d\cdot r_1 \cdot {s_1}^{-1}\\
    k_2 &= {s_2}^{-1} \cdot (H_2 + d\cdot r_2) = 1337\times k_1=\\
    &= {s_2}^{-1} \cdot H_2 + {s_2}^{-1}\cdot d\cdot r_2\\
    &= 1337 \cdot {s_1}^{-1} \cdot H_1 + 1337 \cdot d\cdot r_1 \cdot {s_1}^{-1}
    \end{aligned}\\
    \downarrow \\
    d\cdot (H_2\cdot {s_2}^{-1} - 1337\cdot H_1\cdot {s_1}^{-1})=1337\cdot r_1\cdot {s_1}^{-1}-r_2\cdot {s_2}^{-1}\\
    \hookrightarrow d = {1337\cdot r_1\cdot {s_1}^{-1}-r_2\cdot {s_2}^{-1} \over H_2\cdot {s_2}^{-1} - 1337\cdot H_1\cdot {s_1}^{-1}}
    $$
4. 得到原本的private key $d$之後就可以直接選一個亂數nonce $k$，然後重新自己簽署`Give me the FLAG.`的signature
## Exploit
:::spoiler Whole Exploit
```python=
from pwn import *
from Crypto.Util.number import *
from hashlib import sha256
from ecdsa import SECP256k1
from ecdsa.ecdsa import Public_key, Private_key, Signature

# r = process(["python", "./signature_416666d57b34123f.py"])
r = remote('10.113.184.121', 10033)

# Receive Some Info from Server
msg = 'a'
E = SECP256k1
G, n = E.generator, E.order
r.recvuntil(b'P = (')
x, y = r.recvline().decode().strip().rstrip(')').split(', ')
r.recvlines(3)
r.sendline(b'1')
r.sendlineafter(b'What do you want?', msg.encode())
r.recvuntil(b'sig = (')
r1, s1 = r.recvline().decode().strip().rstrip(')').split(', ')
r.recvlines(3)
r.sendline(b'1')
r.sendlineafter(b'What do you want?', msg.encode())
r.recvuntil(b'sig = (')
r2, s2 = r.recvline().decode().strip().rstrip(')').split(', ')

log.info(f'x = {x}\ny = {y}')
log.info(f'r1 = {r1}\ns1 = {s1}')
log.info(f'r2 = {r2}\ns2 = {s2}')

# Calculte Private Key - d
hash_msg = sha256(msg.encode()).digest()
inv_s1 = inverse(int(s1), n)
inv_s2 = inverse(int(s2), n)
hash_msg_decimal = bytes_to_long(hash_msg)
r1 = int(r1)
r2 = int(r2)
d = inverse(1337 * r1 * inv_s1 - r2 * inv_s2, n) * (hash_msg_decimal * inv_s2 - 1337 * hash_msg_decimal * inv_s1)
k1 = inv_s1 * (hash_msg_decimal + d * r1)
k2 = inv_s2 * (hash_msg_decimal + d * r2)
assert k2 % n == k1 * 1337 % n

# Forgery Signature & Send it
k = randint(1, n)
pubkey = Public_key(G, d*G)
prikey = Private_key(pubkey, d)
flag_msg = 'Give me the FLAG.'
flag_msg_h = sha256(flag_msg.encode()).digest()
sig = prikey.sign(bytes_to_long(flag_msg_h), k)
r.recvlines(3)
r.sendline(b'2')
r.sendlineafter(b'r: ', sig.r.digits().encode())
r.sendlineafter(b's: ', sig.s.digits().encode())
flag = r.recvline().strip().decode()

log.info(f'Flag: {flag}')

r.close()
:::

```bash
$ python exp.py
[+] Opening connection to 10.113.184.121 on port 10033: Done
[*] x = 80833128996081892656118221427167942614367970190999112028100047868271602908158
    y = 7692760766381285656486680270900861598977131934640663688795645395086394523342
[*] r1 = 57205296794452689467192257573140114834242454684651993799259557149551452463654
    s1 = 46076932900642565773729561332617152693574412598169577544559584675273278539735
[*] r2 = 32274988576741840972950688950377038880296385056439434547263507357520953909449
    s2 = 38964710627625045025023640822136515580011444306594995093726779755542228691436
[*] Flag: b'FLAG{EphemeralKeyShouldBeRandom}'
[*] Closed connection to 10.113.184.121 port 10033
```

Flag: `FLAG{EphemeralKeyShouldBeRandom}`