[crypto] janken vs yoshiking 2 (CakeCTF 2023) writeup

---

- Source: CakeCTF 2023
- Author: [Furutsuki](https://x.com/theoremoon)

---

じゃんけんで100連勝すればflagがもらえるサーバー。

<details><summary>server.sage</summary>

```py
import random
import signal
import os

HANDNAMES = {
    1: "Rock",
    2: "Scissors",
    3: "Paper"
}

def commit(M, m):
    while True:
        r = random.randint(2, 2**256)
        if r % 3 + 1 == m:
            break
    return M**r, r


signal.alarm(1000)

flag = os.environ.get("FLAG", "neko{old_yoshiking_never_die,simply_fade_away}")
p = 1719620105458406433483340568317543019584575635895742560438771105058321655238562613083979651479555788009994557822024565226932906295208262756822275663694111
M = random_matrix(GF(p), 5)
print("[yoshiking]: Hello! Let's play Janken(RPS)")
print("[yoshiking]: Here is p: {}, and M: {}".format(p, M.list()))

round = 0
wins = 0
while True:
    round += 1
    print("[system]: ROUND {}".format(round))

    yoshiking_hand = random.randint(1, 3)
    C, r = commit(M, yoshiking_hand)
    print("[yoshiking]: my commitment is={}".format(C.list()))

    hand = input("[system]: your hand(1-3): ")
    print("")
    try:
        hand = int(hand)
        if not (1 <= hand <= 3):
            raise ValueError()
    except ValueError:
        print("[yoshiking]: Ohhhhhhhhhhhhhhhh no! :(")
        exit()

    print("[yoshiking]: My hand is ... {}".format(HANDNAMES[yoshiking_hand]))
    print("[yoshiking]: Your hand is ... {}".format(HANDNAMES[hand]))
    result = (yoshiking_hand - hand + 3) % 3
    if result == 0:
        print("[yoshiking]: Draw, draw, draw!!!")
        print("[yoshiking]: I'm only respect to win!")
        print("[system]: you can check that yoshiking doesn't cheat")
        print("[system]: here's the secret value: {}".format(r))
        exit()
    elif result == 1:
        print("[yoshiking]: Yo! You win!!! Ho!")
        wins += 1
        print("[system]: wins: {}".format(wins))

        if wins >= 100:
            break
    elif result == 2:
        print("[yoshiking]: Ahahahaha! I'm the winnnnnnner!!!!")
        print("[yoshiking]: You, good loser!")
        print("[system]: you can check that yoshiking doesn't cheat")
        print("[system]: here's the secret value: {}".format(r))
        exit()

print("[yoshiking]: Wow! You are the king of roshambo!")
print("[yoshiking]: suge- flag ageru")
print(flag)
```

</details>

接続時に有限体上の行列$M$が与えられ、各じゃんけんの前に$M^r$を教えてもらえる。$r\ mod\ 3$が出す手となる。

よって、$M^r$から$r$を求めることができればじゃんけん勝ち放題。

$p$の値について検索してみるとこれはユークリッド素数というものらしいということが分かったので、$p-1$をFactorDBで素因数分解してみる。
![{643AAA87-C02E-4E8A-9F4C-8DDFCBDD9734}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982709/c5a23563-6f74-d652-d7d6-5db552d5ee71.png)

$p-1$が379-smoothということが分かった。よって、Pohlig-Hellman algorithmで小さな離散対数問題に分割することで効率的に$r$を求めることができそう。ちなみに今回は$mod\ 3$についてのみ分かれば良い。

しかし、今回は$M$が行列式であるためそのままは使えない。だが一般的に$det\ (M^r)=(det\ M)^r$が成り立つので、行列式で考えれば良い。

solverを書く。
```py
from pwn import *

conn = remote("34.170.146.252", 55205)

conn.recvuntil("p: ")
p = int(conn.recvuntil(",")[:-1].decode())
conn.recvuntil("M: ")
M = eval(conn.recvline().decode())
M = Matrix(GF(p), 5, 5, M)

for i in range(100):
  conn.recvuntil("my commitment is=")
  C = eval(conn.recvline().decode())
  C = Matrix(GF(p), 5, 5, C)
  r = C.det().log(M.det()) % 3
  conn.sendlineafter("your hand(1-3): ", str((r-1)%3 + 1))
  print("progress:", i+1)

conn.interactive()
```

これをsagemathで実行するとflagが得られた。
`CakeCTF{though_yoshiking_may_die_janken_will_never_perish}`